Skip to content

Add support for hexadecimal numbers #101

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,15 @@ func (il *IntegerLiteral) expressionNode() {}
func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal }
func (il *IntegerLiteral) String() string { return il.Token.Literal }

type HexadecimalLiteral struct {
Token token.Token
Value int64
}

func (hl *HexadecimalLiteral) expressionNode() {}
func (hl *HexadecimalLiteral) TokenLiteral() string { return hl.Token.Literal }
func (hl *HexadecimalLiteral) String() string { return hl.Token.Literal }

type PrefixExpression struct {
Token token.Token
Operator string
Expand Down
3 changes: 3 additions & 0 deletions evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
case *ast.IntegerLiteral:
return &object.Integer{Value: node.Value}

case *ast.HexadecimalLiteral:
return &object.Hexadecimal{Value: node.Value}

case *ast.FloatLiteral:
return &object.Float{Value: node.Value}

Expand Down
166 changes: 114 additions & 52 deletions evaluator/infix.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package evaluator

import (
"fmt"
"math"
"strings"

Expand Down Expand Up @@ -68,17 +69,17 @@ func evalInfixExpression(operator string, left, right object.Object, line int) o
rightVal := right.(*object.String).Value
return &object.String{Value: strings.Repeat(rightVal, int(leftVal))}

case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ:
return evalIntegerInfixExpression(operator, left, right, line)
case isNumber(left) && isNumber(right):
return evalNumberInfixExpression(operator, left, right, line)

case left.Type() == object.FLOAT_OBJ && right.Type() == object.FLOAT_OBJ:
return evalFloatInfixExpression(operator, left, right, line)

case left.Type() == object.INTEGER_OBJ && right.Type() == object.FLOAT_OBJ:
return evalFloatIntegerInfixExpression(operator, left, right, line)
case isNumber(left) && right.Type() == object.FLOAT_OBJ:
return evalFloatInfixExpr(operator, left, right, line)

case left.Type() == object.FLOAT_OBJ && right.Type() == object.INTEGER_OBJ:
return evalFloatIntegerInfixExpression(operator, left, right, line)
case left.Type() == object.FLOAT_OBJ && isNumber(right):
return evalFloatInfixExpr(operator, left, right, line)

case operator == "==":
return nativeBoolToBooleanObject(left == right)
Expand All @@ -98,14 +99,94 @@ func evalInfixExpression(operator string, left, right object.Object, line int) o
}
}

func evalFloatIntegerInfixExpression(operator string, left, right object.Object, line int) object.Object {
var leftVal, rightVal float64
// This are types that can be categorized as a number.
// Hexadecimal, Integer and other types except Float can be consided number
func isNumber(number object.Object) bool {
switch number.(type) {
case *object.Integer, *object.Hexadecimal:
return true
}

return true
}

func evalNumberInfixExpression(operator string, left, right object.Object, line int) object.Object {
leftVal, err := getNumberValue(left)
if err != nil {
return newError("Mstari %d: Operesheni Haieleweki: %s %s %s",
line, left.Type(), operator, right.Type())
}

rightVal, err := getNumberValue(right)
if err != nil {
return newError("Mstari %d: Operesheni Haieleweki: %s %s %s",
line, left.Type(), operator, right.Type())
}

switch operator {
case "+":
return &object.Integer{Value: leftVal + rightVal}
case "-":
return &object.Integer{Value: leftVal - rightVal}
case "*":
return &object.Integer{Value: leftVal * rightVal}
case "**":
return &object.Float{Value: float64(math.Pow(float64(leftVal), float64(rightVal)))}
case "/":
x := float64(leftVal) / float64(rightVal)
if math.Mod(x, 1) == 0 {
return &object.Integer{Value: int64(x)}
} else {
return &object.Float{Value: x}
}
case "%":
return &object.Integer{Value: leftVal % rightVal}
case "<":
return nativeBoolToBooleanObject(leftVal < rightVal)
case "<=":
return nativeBoolToBooleanObject(leftVal <= rightVal)
case ">":
return nativeBoolToBooleanObject(leftVal > rightVal)
case ">=":
return nativeBoolToBooleanObject(leftVal >= rightVal)
case "==":
return nativeBoolToBooleanObject(leftVal == rightVal)
case "!=":
return nativeBoolToBooleanObject(leftVal != rightVal)
default:
return newError("Mstari %d: Operesheni Haieleweki: %s %s %s",
line, left.Type(), operator, right.Type())
}

}

func evalFloatInfixExpr(operator string, left, right object.Object, line int) object.Object {
var rightVal float64
var leftVal float64
var _tempv int64
var err error

if left.Type() == object.FLOAT_OBJ {
leftVal = left.(*object.Float).Value
rightVal = float64(right.(*object.Integer).Value)
leftVal, err = getFloatValue(left)
} else {
_tempv, err = getNumberValue(left)
leftVal = float64(_tempv)
}
if err != nil {
return newError("Mstari %d: Operesheni Haieleweki: %s %s %s",
line, left.Type(), operator, right.Type())
}

if right.Type() == object.FLOAT_OBJ {
rightVal, err = getFloatValue(right)
} else {
leftVal = float64(left.(*object.Integer).Value)
rightVal = right.(*object.Float).Value
_tempv, err = getNumberValue(right)
rightVal = float64(_tempv)
}

if err != nil {
return newError("Mstari %d: Operesheni Haieleweki: %s %s %s",
line, left.Type(), operator, right.Type())
}

var val float64
Expand Down Expand Up @@ -146,6 +227,27 @@ func evalFloatIntegerInfixExpression(operator string, left, right object.Object,
}
}

func getNumberValue(val object.Object) (int64, error) {
switch val.(type) {
case *object.Hexadecimal:
return val.(*object.Hexadecimal).Value, nil
case *object.Integer:
return val.(*object.Integer).Value, nil
}

return 0, fmt.Errorf("expected integer, got %T", val)
}

func getFloatValue(val object.Object) (float64, error) {

switch val.(type) {
case *object.Float:
return val.(*object.Float).Value, nil
}

return 0.0, fmt.Errorf("Si desimali")
}

func evalStringInfixExpression(operator string, left, right object.Object, line int) object.Object {

leftVal := left.(*object.String).Value
Expand Down Expand Up @@ -209,43 +311,3 @@ func evalFloatInfixExpression(operator string, left, right object.Object, line i
line, left.Type(), operator, right.Type())
}
}

func evalIntegerInfixExpression(operator string, left, right object.Object, line int) object.Object {
leftVal := left.(*object.Integer).Value
rightVal := right.(*object.Integer).Value

switch operator {
case "+":
return &object.Integer{Value: leftVal + rightVal}
case "-":
return &object.Integer{Value: leftVal - rightVal}
case "*":
return &object.Integer{Value: leftVal * rightVal}
case "**":
return &object.Float{Value: float64(math.Pow(float64(leftVal), float64(rightVal)))}
case "/":
x := float64(leftVal) / float64(rightVal)
if math.Mod(x, 1) == 0 {
return &object.Integer{Value: int64(x)}
} else {
return &object.Float{Value: x}
}
case "%":
return &object.Integer{Value: leftVal % rightVal}
case "<":
return nativeBoolToBooleanObject(leftVal < rightVal)
case "<=":
return nativeBoolToBooleanObject(leftVal <= rightVal)
case ">":
return nativeBoolToBooleanObject(leftVal > rightVal)
case ">=":
return nativeBoolToBooleanObject(leftVal >= rightVal)
case "==":
return nativeBoolToBooleanObject(leftVal == rightVal)
case "!=":
return nativeBoolToBooleanObject(leftVal != rightVal)
default:
return newError("Mstari %d: Operesheni Haieleweki: %s %s %s",
line, left.Type(), operator, right.Type())
}
}
29 changes: 29 additions & 0 deletions lexer/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ func (l *Lexer) NextToken() token.Token {
tok.Type = token.LookupIdent(tok.Literal)
tok.Line = l.line
return tok
} else if isDigit(l.ch) && l.ch == '0' && isLetter(l.peekChar()) {
return l.readOtherSystems()
} else if isDigit(l.ch) && isLetter(l.peekChar()) {
tok.Literal = l.readIdentifier()
tok.Type = token.LookupIdent(tok.Literal)
Expand All @@ -200,6 +202,33 @@ func (l *Lexer) NextToken() token.Token {
return tok
}

func (l *Lexer) readOtherSystems() token.Token {
var sys_token token.Token

switch l.peekChar() {
case rune('x'), rune('X'):
sys_token = l.readHexadecimal()
default:
l.readChar()
sys_token = newToken(token.ILLEGAL, l.line, l.ch)
}

return sys_token
}

func (l *Lexer) readHexadecimal() token.Token {
l.readChar() // 0
l.readChar() // x

position := l.position

for isDigit(l.ch) || isLetter(l.ch) {
l.readChar()
}

return token.Token{Type: token.HEXADESIMALI, Literal: string(l.input[position:l.position]), Line: l.line}
}

func newToken(tokenType token.TokenType, line int, ch rune) token.Token {
return token.Token{Type: tokenType, Literal: string(ch), Line: line}
}
Expand Down
14 changes: 14 additions & 0 deletions object/hexadecimal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package object

import "fmt"

type Hexadecimal struct {
Value int64
}

func (i *Hexadecimal) Inspect() string { return fmt.Sprintf("%d", i.Value) }
func (i *Hexadecimal) Type() ObjectType { return INTEGER_OBJ }

func (i *Hexadecimal) HashKey() HashKey {
return HashKey{Type: i.Type(), Value: uint64(i.Value)}
}
1 change: 1 addition & 0 deletions object/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type ObjectType string

const (
INTEGER_OBJ = "NAMBA"
HEX_OBJ = "HEXADESIMALI"
FLOAT_OBJ = "DESIMALI"
BOOLEAN_OBJ = "BOOLEAN"
NULL_OBJ = "TUPU"
Expand Down
37 changes: 37 additions & 0 deletions parser/hexadecimal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package parser

import (
"fmt"
"math/big"

"github.com/NuruProgramming/Nuru/ast"
)

func (p *Parser) parseHexadecimalLiteral() ast.Expression {
lit := &ast.HexadecimalLiteral{Token: p.curToken}
tlit := p.curToken.Literal

for _, x := range tlit {
if !(isDigit(x) || isLetter(x)) {
msg := fmt.Sprintf("Mstari %d: Hatuwezi kuparse %q kama hexa desimali", p.curToken.Line, tlit)
p.errors = append(p.errors, msg)
return nil

}
}

value := new(big.Int)
value.SetString(tlit, 16)

lit.Value = value.Int64()

return lit
}

func isDigit(ch rune) bool {
return '0' <= ch && ch <= '9'
}

func isLetter(ch rune) bool {
return 'a' <= ch && ch <= 'f' || 'A' <= ch && ch <= 'F'
}
1 change: 1 addition & 0 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ func New(l *lexer.Lexer) *Parser {
p.registerPrefix(token.STRING, p.parseStringLiteral)
p.registerPrefix(token.IDENT, p.parseIdentifier)
p.registerPrefix(token.INT, p.parseIntegerLiteral)
p.registerPrefix(token.HEXADESIMALI, p.parseHexadecimalLiteral)
p.registerPrefix(token.FLOAT, p.parseFloatLiteral)
p.registerPrefix(token.BANG, p.parsePrefixExpression)
p.registerPrefix(token.MINUS, p.parsePrefixExpression)
Expand Down
9 changes: 5 additions & 4 deletions token/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ const (
EOF = "MWISHO"

// Identifiers + literals
IDENT = "KITAMBULISHI"
INT = "NAMBA"
STRING = "NENO"
FLOAT = "DESIMALI"
IDENT = "KITAMBULISHI"
INT = "NAMBA"
STRING = "NENO"
FLOAT = "DESIMALI"
HEXADESIMALI = "HEXADESIMALI"

// Operators
ASSIGN = "="
Expand Down