diff --git a/ast/ast.go b/ast/ast.go index 72373a2..77112ee 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -141,6 +141,20 @@ func (il *IntegerLiteral) expressionNode() {} func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal } func (il *IntegerLiteral) String() string { return il.Token.Literal } +// FloatLiteral holds a floating-point number +// For example, `5.0`. +type FloatLiteral struct { + Token token.Token + + Value float64 +} + +func (fl *FloatLiteral) expressionNode() {} + +func (fl *FloatLiteral) TokenLiteral() string { return fl.Token.Literal } + +func (fl *FloatLiteral) String() string { return fl.Token.Literal } + /* PrefixExpression represents a prefix expression. For example, the `-` in `-5`. diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 22ff024..673b2ae 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -40,6 +40,9 @@ func Eval(node ast.Node, env *object.Environment) object.Object { case *ast.IntegerLiteral: return &object.Integer{Value: node.Value} + case *ast.FloatLiteral: + return &object.Float{Value: node.Value} + case *ast.Boolean: return nativeBoolToBooleanObject(node.Value) @@ -300,6 +303,12 @@ func evalInfixExpression(node *ast.InfixExpression, leftOperand, rightOperand ob switch { case leftOperand.Type() == object.INTEGER_OBJ && rightOperand.Type() == object.INTEGER_OBJ: return evalIntegerInfixExpression(node, operator, leftOperand, rightOperand) + case leftOperand.Type() == object.FLOAT_OBJ && rightOperand.Type() == object.FLOAT_OBJ: + return evalFloatInfixExpression(node, operator, leftOperand, rightOperand) + case leftOperand.Type() == object.FLOAT_OBJ && rightOperand.Type() == object.INTEGER_OBJ: + return evalFloatIntegerInfixExpression(node, operator, leftOperand, rightOperand) + case leftOperand.Type() == object.INTEGER_OBJ && rightOperand.Type() == object.FLOAT_OBJ: + return evalIntegerFloatInfixExpression(node, operator, leftOperand, rightOperand) case leftOperand.Type() == object.STRING_OBJ && (rightOperand.Type() == object.STRING_OBJ || rightOperand.Type() == object.INTEGER_OBJ): return evalStringInfixExpression(node, leftOperand, rightOperand) case leftOperand.Type() == object.ARRAY_OBJ && rightOperand.Type() == object.ARRAY_OBJ && operator == "+": @@ -355,7 +364,6 @@ func evalStringInfixExpression(node *ast.InfixExpression, leftOperand, rightOper func evalIntegerInfixExpression(node *ast.InfixExpression, operator string, leftOperand, rightOperand object.Object) object.Object { leftValue := leftOperand.(*object.Integer).Value rightValue := rightOperand.(*object.Integer).Value - switch operator { case "+": return &object.Integer{Value: leftValue + rightValue} @@ -364,8 +372,14 @@ func evalIntegerInfixExpression(node *ast.InfixExpression, operator string, left case "*": return &object.Integer{Value: leftValue * rightValue} case "/": + if rightValue == 0 { + return newError(node.Token.FileName, node.Token.Line, node.Token.Column, "Can't divide by zero") + } return &object.Integer{Value: leftValue / rightValue} case "%": + if rightValue == 0 { + return newError(node.Token.FileName, node.Token.Line, node.Token.Column, "Can't divide by zero") + } return &object.Integer{Value: leftValue % rightValue} case "<": return nativeBoolToBooleanObject(leftValue < rightValue) @@ -380,6 +394,123 @@ func evalIntegerInfixExpression(node *ast.InfixExpression, operator string, left } } +func evalFloatInfixExpression(node *ast.InfixExpression, operator string, leftOperand, rightOperand object.Object) object.Object { + leftValue := leftOperand.(*object.Float).Value + rightValue := rightOperand.(*object.Float).Value + switch operator { + case "+": + return &object.Float{Value: leftValue + rightValue} + case "-": + return &object.Float{Value: leftValue - rightValue} + case "*": + return &object.Float{Value: leftValue * rightValue} + case "/": + if rightValue == 0 { + return newError(node.Token.FileName, node.Token.Line, node.Token.Column, "Can't divide by zero") + } + return &object.Float{Value: leftValue / rightValue} + case "<": + return nativeBoolToBooleanObject(leftValue < rightValue) + case ">": + return nativeBoolToBooleanObject(leftValue > rightValue) + case "==": + return nativeBoolToBooleanObject(leftValue == rightValue) + case "!=": + return nativeBoolToBooleanObject(leftValue != rightValue) + default: + return newError(node.Token.FileName, node.Token.Line, node.Token.Column, "unknown operator: %s %s %s", leftOperand.Type(), operator, rightOperand.Type()) + } +} + +func evalFloatIntegerInfixExpression(node *ast.InfixExpression, operator string, left, right object.Object) object.Object { + leftVal := left.(*object.Float).Value + rightVal := float64(right.(*object.Integer).Value) + switch operator { + case "+": + return &object.Float{Value: leftVal + rightVal} + // case "+=": + // return &object.Float{Value: leftVal + rightVal} + case "-": + return &object.Float{Value: leftVal - rightVal} + // case "-=": + // return &object.Float{Value: leftVal - rightVal} + case "*": + return &object.Float{Value: leftVal * rightVal} + // case "*=": + // return &object.Float{Value: leftVal * rightVal} + // case "**": + // return &object.Float{Value: math.Pow(leftVal, rightVal)} + case "/": + if rightVal == 0 { + return newError(node.Token.FileName, node.Token.Line, node.Token.Column, "Can't divide by zero") + } + return &object.Float{Value: leftVal / rightVal} + // case "/=": + // return &object.Float{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(node.Token.FileName, node.Token.Line, node.Token.Column, "unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } +} + +func evalIntegerFloatInfixExpression(node *ast.InfixExpression, operator string, left, right object.Object) object.Object { + leftVal := float64(left.(*object.Integer).Value) + rightVal := right.(*object.Float).Value + switch operator { + case "+": + return &object.Float{Value: leftVal + rightVal} + case "-": + return &object.Float{Value: leftVal - rightVal} + case "*": + return &object.Float{Value: leftVal * rightVal} + + case "/": + if rightVal == 0 { + return newError(node.Token.FileName, node.Token.Line, node.Token.Column, "divide by zero") + } + return &object.Float{Value: leftVal / rightVal} + case "<": + return nativeBoolToBooleanObject(leftVal < rightVal) + case ">": + return nativeBoolToBooleanObject(leftVal > rightVal) + + case "==": + return nativeBoolToBooleanObject(leftVal == rightVal) + case "!=": + return nativeBoolToBooleanObject(leftVal != rightVal) + // TODO: Implement the following operators + // case "-=": + // return &object.Float{Value: leftVal - rightVal} + // case "*=": + // return &object.Float{Value: leftVal * rightVal} + // case "**": + // return &object.Float{Value: math.Pow(leftVal, rightVal)} + // case "+=": + // return &object.Float{Value: leftVal + rightVal} + // case "/=": + // return &object.Float{Value: leftVal / rightVal + // case "<=": + // return nativeBoolToBooleanObject(leftVal <= rightVal) + // case ">=": + // return nativeBoolToBooleanObject(leftVal >= rightVal) + default: + return newError(node.Token.FileName, node.Token.Line, node.Token.Column, "unknown operator: %s %s %s", + left.Type(), operator, right.Type()) + } +} + func evalPrefixExpression(node *ast.PrefixExpression, operator string, right object.Object) object.Object { switch operator { case "!": @@ -488,7 +619,8 @@ func evalObjectCallExpression(call *ast.ObjectCallExpression, env *object.Enviro } } // TODO: check if the object has the method implemented in esolang - return newError(call.Token.FileName, call.Token.Line, call.Token.Column, "object has no method %s", call.Call.String()) + + return newError(call.Token.FileName, call.Token.Line, call.Token.Column, "value of type `%s` has no member `%s`", objectValue.Type(), call.Call.String()) } func evalProgram(program *ast.Program, env *object.Environment) object.Object { @@ -573,7 +705,7 @@ func extendFunctionEnv(function *object.Function, args []object.Object) *object. } func newError(fileName string, line, column int, format string, a ...interface{}) *object.Error { - linesAndCol := fmt.Sprintf("%s: Error at line %d, column %d:", fileName, line, column) + linesAndCol := fmt.Sprintf("%s:%d:%d:", fileName, line, column) format = fmt.Sprintf("%s %s", linesAndCol, format) return &object.Error{Message: fmt.Sprintf(format, a...)} } diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index 3246e3c..0ad36d8 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -405,10 +405,10 @@ func TestErrorHandling(t *testing.T) { input string expectedMessage string }{ - {"10 + true;", FILE + ": " + "Error at line 1, column 5: type mismatch: INTEGER + BOOLEAN"}, - {"10 + true; 10;", FILE + ": " + "Error at line 1, column 5: type mismatch: INTEGER + BOOLEAN"}, - {"-true", FILE + ": " + "Error at line 1, column 2: unknown operator: -BOOLEAN"}, - {"true + false;", FILE + ": " + "Error at line 1, column 7: unknown operator: BOOLEAN + BOOLEAN"}, + {"10 + true;", FILE + ":1:5:" + " type mismatch: INTEGER + BOOLEAN"}, + {"10 + true; 10;", FILE + ":1:5:" + " type mismatch: INTEGER + BOOLEAN"}, + {"-true", FILE + ":1:2:" + " unknown operator: -BOOLEAN"}, + {"true + false;", FILE + ":1:7:" + " unknown operator: BOOLEAN + BOOLEAN"}, } for _, test := range tests { diff --git a/lexer/lexer.go b/lexer/lexer.go index 12c2519..28b326f 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -81,10 +81,11 @@ func (L *Lexer) NextToken() token.Token { tok.FileName = L.fileName return tok } else if isDigit(L.char) { - tok.Type = token.INT - tok.Literal = L.readNumber() - tok.Line = L.line - tok.Column = L.column + // tok.Type = token.INT + // tok.Literal = L.readNumber() + // tok.Line = L.line + // tok.Column = L.column + tok = L.readDecimal() return tok } else { tok = token.Token{Type: token.ILLEGAL, Literal: string(L.char), Line: L.line, Column: L.column, FileName: L.fileName} @@ -282,6 +283,17 @@ func (L *Lexer) readNumber() string { return L.input[position:L.position] } +// readDecimal reads the float number in the input and returns it. +func (L *Lexer) readDecimal() token.Token { + integer := L.readNumber() + if rune(L.char) == rune('.') && isDigit(L.peekChar()) { + L.readChar() + fraction := L.readNumber() + return token.Token{Type: token.FLOAT, Literal: integer + "." + fraction, Line: L.line, Column: L.column, FileName: L.fileName} + } + return token.Token{Type: token.INT, Literal: integer, Line: L.line, Column: L.column, FileName: L.fileName} +} + // isLetter returns true if the given character is a letter. func isLetter(char byte) bool { return 'a' <= char && char <= 'z' || 'A' <= char && char <= 'Z' || char == '_' diff --git a/object/object.go b/object/object.go index 2817d8f..b09c1be 100644 --- a/object/object.go +++ b/object/object.go @@ -13,6 +13,7 @@ type ObjectType string const ( STRING_OBJ = "STRING" INTEGER_OBJ = "INTEGER" + FLOAT_OBJ = "FLOAT" BOOLEAN_OBJ = "BOOLEAN" NULL_OBJ = "NULL" RETURN_VALUE_OBJ = "RETURN_VALUE" @@ -58,6 +59,16 @@ func (i *Integer) InvokeMethod(method string, env Environment, args ...Object) O return intInvokables(method, i) } +type Float struct { + Value float64 +} + +func (f *Float) Type() ObjectType { return FLOAT_OBJ } +func (f *Float) Inspect() string { return fmt.Sprintf("%f", f.Value) } +func (f *Float) InvokeMethod(method string, env Environment, args ...Object) Object { + return nil +} + // Boolean wraps a single value to a boolean. type Boolean struct { Value bool diff --git a/object/string_obj.go b/object/string_obj.go index 660dbc7..01f6c47 100644 --- a/object/string_obj.go +++ b/object/string_obj.go @@ -27,13 +27,22 @@ func stringInvokables(method string, s *String, args ...Object) Object { if err := _noArgsExpected(name, args...); err != nil { return newErrorFromTypings(err.Error()) } - runes := []rune(s.Value) for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { runes[i], runes[j] = runes[j], runes[i] } return &String{Value: string(runes)} + case "equals": + if err := CheckTypings( + name, args, + ExactArgsLength(1), + WithTypes(STRING_OBJ), + ); err != nil { + return newErrorFromTypings(err.Error()) + } + return &Boolean{Value: s.Value == args[0].(*String).Value} + case "empty": if err := _noArgsExpected(name, args...); err != nil { return newErrorFromTypings(err.Error()) diff --git a/parser/parser.go b/parser/parser.go index b729be9..2c6495f 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -84,6 +84,7 @@ func New(L *lexer.Lexer) *Parser { p.prefixParseFns = make(map[token.TokenType]prefixParseFn) p.registerPrefix(token.IDENT, p.parseIdentifier) p.registerPrefix(token.INT, p.parseIntegerLiteral) + p.registerPrefix(token.FLOAT, p.parseFloatLiteral) p.registerPrefix(token.BANG, p.parsePrefixExpression) p.registerPrefix(token.DEF_FN, p.parseFunctionDefinition) p.registerPrefix(token.MINUS, p.parsePrefixExpression) @@ -722,3 +723,15 @@ func (p *Parser) parseFunctionDefParameter() (map[string]ast.Expression, []*ast. return m, identifiers } + +func (p *Parser) parseFloatLiteral() ast.Expression { + flo := &ast.FloatLiteral{Token: p.currentToken} + value, err := strconv.ParseFloat(p.currentToken.Literal, 64) + if err != nil { + msg := fmt.Sprintf("could not parse %q as float around line", p.currentToken.Literal) + p.errors = append(p.errors, msg) + return nil + } + flo.Value = value + return flo +} diff --git a/repl/repl.go b/repl/repl.go index 75178fe..77ba704 100644 --- a/repl/repl.go +++ b/repl/repl.go @@ -66,7 +66,7 @@ func Start(in io.Reader, out io.Writer, stdLib string) { // If the line ends with a semicolon or is empty, evaluate the input if strings.HasSuffix(line, ";") { - evaluteInput("REPL", inputBuffer.String(), logger, environmnet, stdLib) + evaluteInput("repl.eso", inputBuffer.String(), logger, environmnet, stdLib) // Clear the input buffer inputBuffer.Reset() inputBuffer.WriteString("\n") @@ -75,7 +75,7 @@ func Start(in io.Reader, out io.Writer, stdLib string) { if line[0] == '.' { evaluateReplCommand(line[1:]) } else { - evaluteInput("REPL", line, logger, environmnet, stdLib) + evaluteInput("repl.eso", line, logger, environmnet, stdLib) } } } @@ -92,9 +92,9 @@ func evaluteInput(sourceName, input string, log *log.Logger, environmnet *object return } - if REPL_MODE { - fmt.Println(syntaxHiglight(input)) - } + // if REPL_MODE { + // fmt.Println(syntaxHiglight(input)) + // } libLexer := lexer.New("STDLIB", stdLib) libParser := parser.New(libLexer) libProgram := libParser.ParseProgram() @@ -109,7 +109,15 @@ func evaluteInput(sourceName, input string, log *log.Logger, environmnet *object } else { var DONT_PRINT = "flag=noshow" if !strings.HasSuffix(output, DONT_PRINT) { - log.Info(output) + if REPL_MODE { + cyan := "\033[36m" //Cyan color + reset := "\033[0m" // Reset color + // Print cyan-colored text + fmt.Println(cyan + output + reset) + } else { + fmt.Println(output) + } + } } } diff --git a/second-test.eso b/second.eso similarity index 74% rename from second-test.eso rename to second.eso index 547a853..f871672 100644 --- a/second-test.eso +++ b/second.eso @@ -1,5 +1,4 @@ let Hi = "Hello World"; -skhfbsdj := "sum_bag" let Sum = fn(a, b) { return a + b; }; diff --git a/test.eso b/test.eso index 84890c9..092fa2e 100644 --- a/test.eso +++ b/test.eso @@ -1,4 +1,4 @@ -let sec = import("second-test") +let sec = import("second") let fizzbuzz = fn(n, arr) { let i = 0 when(i < n) { @@ -45,5 +45,6 @@ name := p::name println(name) println(p::occupation) println(sec::Sum(50, 10)) +println(2.5 + 2.5) hello() diff --git a/token/token.go b/token/token.go index ef4a859..37d9739 100644 --- a/token/token.go +++ b/token/token.go @@ -19,6 +19,7 @@ const ( EOF = "EOF" IDENT = "IDENT" INT = "INT" + FLOAT = "FLOAT" ASSIGN = "=" PLUS = "+" COMMA = ","