From 6b17892228726547234e1a5b67560f333f26253b Mon Sep 17 00:00:00 2001 From: Gekko Wrld Date: Tue, 11 Mar 2025 16:24:33 +0300 Subject: [PATCH 1/4] feat: Add support for hexadecimal numbers Add the ability for parsing hexadecimal numbers. During parsing, the value decays down to either Integer or Float. This simplifies the parsing process. This partially fixes: https://github.com/NuruProgramming/Nuru/issues/63 Signed-off-by: Gekko Wrld --- ast/ast.go | 9 +++ evaluator/evaluator.go | 3 + evaluator/infix.go | 151 +++++++++++++++++++++++++++++++++++++++-- lexer/lexer.go | 29 ++++++++ object/hexadecimal.go | 14 ++++ object/object.go | 1 + parser/hexadecimal.go | 37 ++++++++++ parser/parser.go | 1 + token/token.go | 9 +-- 9 files changed, 244 insertions(+), 10 deletions(-) create mode 100644 object/hexadecimal.go create mode 100644 parser/hexadecimal.go diff --git a/ast/ast.go b/ast/ast.go index c42241f..48f58c1 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -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 diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 5fd219d..25fb60f 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -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} diff --git a/evaluator/infix.go b/evaluator/infix.go index 2e51052..f27cc9e 100644 --- a/evaluator/infix.go +++ b/evaluator/infix.go @@ -1,6 +1,7 @@ package evaluator import ( + "fmt" "math" "strings" @@ -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 (left.Type() == object.INTEGER_OBJ || left.Type() == object.HEX_OBJ) && (right.Type() == object.INTEGER_OBJ || right.Type() == object.HEX_OBJ): + 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 (left.Type() == object.INTEGER_OBJ || left.Type() == object.HEX_OBJ) && 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 && (right.Type() == object.INTEGER_OBJ || right.Type() == object.HEX_OBJ): + return evalFloatInfixExpr(operator, left, right, line) case operator == "==": return nativeBoolToBooleanObject(left == right) @@ -98,6 +99,144 @@ func evalInfixExpression(operator string, left, right object.Object, line int) o } } +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, 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 { + _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 + switch operator { + case "+": + val = leftVal + rightVal + case "-": + val = leftVal - rightVal + case "*": + val = leftVal * rightVal + case "**": + val = math.Pow(float64(leftVal), float64(rightVal)) + case "/": + val = leftVal / rightVal + case "%": + val = math.Mod(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()) + } + + if math.Mod(val, 1) == 0 { + return &object.Integer{Value: int64(val)} + } else { + return &object.Float{Value: val} + } +} + +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 evalFloatIntegerInfixExpression(operator string, left, right object.Object, line int) object.Object { var leftVal, rightVal float64 if left.Type() == object.FLOAT_OBJ { diff --git a/lexer/lexer.go b/lexer/lexer.go index 7d83728..d112bcc 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -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) @@ -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} } diff --git a/object/hexadecimal.go b/object/hexadecimal.go new file mode 100644 index 0000000..e6fa237 --- /dev/null +++ b/object/hexadecimal.go @@ -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)} +} diff --git a/object/object.go b/object/object.go index 46de338..6594f27 100644 --- a/object/object.go +++ b/object/object.go @@ -8,6 +8,7 @@ type ObjectType string const ( INTEGER_OBJ = "NAMBA" + HEX_OBJ = "HEXADESIMALI" FLOAT_OBJ = "DESIMALI" BOOLEAN_OBJ = "BOOLEAN" NULL_OBJ = "TUPU" diff --git a/parser/hexadecimal.go b/parser/hexadecimal.go new file mode 100644 index 0000000..a38a74a --- /dev/null +++ b/parser/hexadecimal.go @@ -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' +} diff --git a/parser/parser.go b/parser/parser.go index 0bcf5f8..25e4bce 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -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) diff --git a/token/token.go b/token/token.go index 18c4f6a..277429a 100644 --- a/token/token.go +++ b/token/token.go @@ -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 = "=" From e50daf6c390d384aa77be7d32797f9b8d63162fe Mon Sep 17 00:00:00 2001 From: Gekko Wrld Date: Wed, 12 Mar 2025 08:23:00 +0300 Subject: [PATCH 2/4] fix: remove dead code introduced in previous commit Signed-off-by: Gekko Wrld --- evaluator/infix.go | 88 ---------------------------------------------- 1 file changed, 88 deletions(-) diff --git a/evaluator/infix.go b/evaluator/infix.go index f27cc9e..7929fe4 100644 --- a/evaluator/infix.go +++ b/evaluator/infix.go @@ -237,54 +237,6 @@ func getFloatValue(val object.Object) (float64, error) { return 0.0, fmt.Errorf("Si desimali") } -func evalFloatIntegerInfixExpression(operator string, left, right object.Object, line int) object.Object { - var leftVal, rightVal float64 - if left.Type() == object.FLOAT_OBJ { - leftVal = left.(*object.Float).Value - rightVal = float64(right.(*object.Integer).Value) - } else { - leftVal = float64(left.(*object.Integer).Value) - rightVal = right.(*object.Float).Value - } - - var val float64 - switch operator { - case "+": - val = leftVal + rightVal - case "-": - val = leftVal - rightVal - case "*": - val = leftVal * rightVal - case "**": - val = math.Pow(float64(leftVal), float64(rightVal)) - case "/": - val = leftVal / rightVal - case "%": - val = math.Mod(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()) - } - - if math.Mod(val, 1) == 0 { - return &object.Integer{Value: int64(val)} - } else { - return &object.Float{Value: val} - } -} - func evalStringInfixExpression(operator string, left, right object.Object, line int) object.Object { leftVal := left.(*object.String).Value @@ -348,43 +300,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()) - } -} From c211148afcc7d2952c5d7307dcbe9e835ae67827 Mon Sep 17 00:00:00 2001 From: Gekko Wrld Date: Wed, 12 Mar 2025 08:41:26 +0300 Subject: [PATCH 3/4] chore: add helper function for number(s) Signed-off-by: Gekko Wrld --- evaluator/infix.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/evaluator/infix.go b/evaluator/infix.go index 7929fe4..bbb4526 100644 --- a/evaluator/infix.go +++ b/evaluator/infix.go @@ -69,16 +69,16 @@ 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 || left.Type() == object.HEX_OBJ) && (right.Type() == object.INTEGER_OBJ || right.Type() == object.HEX_OBJ): + 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 || left.Type() == object.HEX_OBJ) && right.Type() == object.FLOAT_OBJ: + 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 || right.Type() == object.HEX_OBJ): + case left.Type() == object.FLOAT_OBJ && isNumber(right): return evalFloatInfixExpr(operator, left, right, line) case operator == "==": @@ -99,6 +99,18 @@ func evalInfixExpression(operator string, left, right object.Object, line int) o } } +// 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: + default: + return false + } + + return true +} + func evalNumberInfixExpression(operator string, left, right object.Object, line int) object.Object { leftVal, err := getNumberValue(left) if err != nil { From bff105916cc4ec49e610a2987339ad413589af5e Mon Sep 17 00:00:00 2001 From: Gekko Wrld Date: Wed, 12 Mar 2025 18:12:49 +0300 Subject: [PATCH 4/4] fix: correctly identify nnumbers instead of making a broad assumption Signed-off-by: Gekko Wrld --- evaluator/infix.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/evaluator/infix.go b/evaluator/infix.go index bbb4526..743b605 100644 --- a/evaluator/infix.go +++ b/evaluator/infix.go @@ -104,8 +104,7 @@ func evalInfixExpression(operator string, left, right object.Object, line int) o func isNumber(number object.Object) bool { switch number.(type) { case *object.Integer, *object.Hexadecimal: - default: - return false + return true } return true