Skip to content

Commit

Permalink
feat(core): add float value parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
ahmedsaheed committed May 15, 2024
1 parent d5f8449 commit c89d64b
Show file tree
Hide file tree
Showing 11 changed files with 220 additions and 20 deletions.
14 changes: 14 additions & 0 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
138 changes: 135 additions & 3 deletions evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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 == "+":
Expand Down Expand Up @@ -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}
Expand All @@ -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)
Expand All @@ -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 "!":
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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...)}
}
Expand Down
8 changes: 4 additions & 4 deletions evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
20 changes: 16 additions & 4 deletions lexer/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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 == '_'
Expand Down
11 changes: 11 additions & 0 deletions object/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
11 changes: 10 additions & 1 deletion object/string_obj.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
13 changes: 13 additions & 0 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
20 changes: 14 additions & 6 deletions repl/repl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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)
}
}
}
Expand All @@ -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()
Expand All @@ -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)
}

}
}
}
Expand Down
1 change: 0 additions & 1 deletion second-test.eso → second.eso
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
let Hi = "Hello World";
skhfbsdj := "sum_bag"
let Sum = fn(a, b) {
return a + b;
};
3 changes: 2 additions & 1 deletion test.eso
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
let sec = import("second-test")
let sec = import("second")
let fizzbuzz = fn(n, arr) {
let i = 0
when(i < n) {
Expand Down Expand Up @@ -45,5 +45,6 @@ name := p::name
println(name)
println(p::occupation)
println(sec::Sum(50, 10))
println(2.5 + 2.5)

hello()
Loading

0 comments on commit c89d64b

Please sign in to comment.