From 793c8354d6d9a25fdad11f025b91d03284b7bd60 Mon Sep 17 00:00:00 2001 From: lymo <30964644+peterlymo@users.noreply.github.com> Date: Wed, 14 Dec 2022 11:09:26 +0300 Subject: [PATCH 01/59] Adding nuru binary direct to /usr/local/bin command ```cp nuru $HOME/bin``` will actually change nuru binary name to bin name instead of copying nuru binary to /$HOME/bin/ folder so will look like /$HOME/bin/nuru, since /$HOME/bin is not in path yet also will make things complicate so to solve this you can direct extract nuru binary to /usr/local/bin by using a command below ``` sudo tar -C /usr/local/bin -xzvf nuru_linux_amd64_v0.1.0.tar.gz ``` --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e3f6bba..17047ac 100644 --- a/README.md +++ b/README.md @@ -22,16 +22,12 @@ instructions for your device below: curl -O -L https://github.com/AvicennaJr/Nuru/releases/download/v0.1.0/nuru_linux_amd64_v0.1.0.tar.gz ``` - - Extract the file: + - Extract the file to make global available: ``` -tar -xzvf nuru_linux_amd64_v0.1.0.tar.gz +sudo tar -C /usr/local/bin -xzvf nuru_linux_amd64_v0.1.0.tar.gz ``` - - Add it to your $PATH: -``` -cp nuru $HOME/bin -``` - Confirm installation with: ``` From 38adde1f3c4dbb7c8932b69bf72c67ddb4c53c00 Mon Sep 17 00:00:00 2001 From: Avicenna <87450618+AvicennaJr@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:25:12 +0300 Subject: [PATCH 02/59] Add $PATH command --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 17047ac..d8c9066 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,14 @@ instructions for your device below: curl -O -L https://github.com/AvicennaJr/Nuru/releases/download/v0.1.0/nuru_linux_amd64_v0.1.0.tar.gz ``` - - Extract the file to make global available: + - Extract the file to `$HOME/bin`: ``` -sudo tar -C /usr/local/bin -xzvf nuru_linux_amd64_v0.1.0.tar.gz +sudo tar -C $HOME/bin -xzvf nuru_linux_amd64_v0.1.0.tar.gz +``` + - Add $HOME/bin to path: +``` +export PATH="${HOME}/bin:${PATH}" ``` - Confirm installation with: From dea8550c2f1722c18005ec7304825e154a258a9e Mon Sep 17 00:00:00 2001 From: Avicenna <87450618+AvicennaJr@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:32:22 +0300 Subject: [PATCH 03/59] Fixed $PATH command on Linux --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d8c9066..1da9796 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ sudo tar -C $HOME/bin -xzvf nuru_linux_amd64_v0.1.0.tar.gz ``` - Add $HOME/bin to path: ``` -export PATH="${HOME}/bin:${PATH}" +echo 'export PATH="${HOME}/bin:${PATH}"' >> ~/.bashrc ``` - Confirm installation with: From a42ebe3af5149914c00ef096cfffe1e822789d3e Mon Sep 17 00:00:00 2001 From: Avicenna <87450618+AvicennaJr@users.noreply.github.com> Date: Wed, 14 Dec 2022 13:47:00 +0300 Subject: [PATCH 04/59] Update Linux directory to /usr/local/bin --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1da9796..23e53a9 100644 --- a/README.md +++ b/README.md @@ -22,14 +22,10 @@ instructions for your device below: curl -O -L https://github.com/AvicennaJr/Nuru/releases/download/v0.1.0/nuru_linux_amd64_v0.1.0.tar.gz ``` - - Extract the file to `$HOME/bin`: + - Extract the file to make global available: ``` -sudo tar -C $HOME/bin -xzvf nuru_linux_amd64_v0.1.0.tar.gz -``` - - Add $HOME/bin to path: -``` -echo 'export PATH="${HOME}/bin:${PATH}"' >> ~/.bashrc +sudo tar -C /usr/local/bin -xzvf nuru_linux_amd64_v0.1.0.tar.gz ``` - Confirm installation with: From a176bbbd12f490429370e12d97da5effc352bcdd Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Fri, 16 Dec 2022 02:03:39 +0300 Subject: [PATCH 05/59] Fixed unexpected return behaviour & added null object --- ast/ast.go | 8 ++++++++ evaluator/evaluator.go | 9 +++++++-- parser/parser.go | 13 ++++++++++++- repl/repl.go | 6 ++++-- token/token.go | 2 ++ 5 files changed, 33 insertions(+), 5 deletions(-) diff --git a/ast/ast.go b/ast/ast.go index 241a169..3307fa2 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -367,3 +367,11 @@ func (we *WhileExpression) String() string { return out.String() } + +type Null struct { + Token token.Token +} + +func (n *Null) expressionNode() {} +func (n *Null) TokenLiteral() string { return n.Token.Literal } +func (n *Null) String() string { return n.Token.Literal } diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 9b2726b..c0f72ac 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -108,6 +108,8 @@ func Eval(node ast.Node, env *object.Environment) object.Object { return evalDictLiteral(node, env) case *ast.WhileExpression: return evalWhileExpression(node, env) + case *ast.Null: + return NULL case *ast.AssignmentExpression: left := Eval(node.Left, env) if isError(left) { @@ -417,9 +419,12 @@ func applyFunction(fn object.Object, args []object.Object) object.Object { evaluated := Eval(fn.Body, extendedEnv) return unwrapReturnValue(evaluated) case *object.Builtin: - return fn.Fn(args...) + if result := fn.Fn(args...); result != nil { + return result + } + return NULL default: - return newError("sio function: %s", fn.Type()) + return newError("Hii sio function: %s", fn.Type()) } } diff --git a/parser/parser.go b/parser/parser.go index 1018ace..e8da477 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -75,6 +75,7 @@ func New(l *lexer.Lexer) *Parser { p.registerPrefix(token.LBRACKET, p.parseArrayLiteral) p.registerPrefix(token.LBRACE, p.parseDictLiteral) p.registerPrefix(token.WHILE, p.parseWhileExpression) + p.registerPrefix(token.NULL, p.parseNull) p.infixParseFns = make(map[token.TokenType]infixParseFn) p.registerInfix(token.PLUS, p.parseInfixExpression) @@ -146,6 +147,10 @@ func (p *Parser) parseLetStatment() *ast.LetStatement { return stmt } +func (p *Parser) parseNull() ast.Expression { + return &ast.Null{Token: p.curToken} +} + func (p *Parser) parseAssignmentExpression(exp ast.Expression) ast.Expression { switch node := exp.(type) { case *ast.Identifier, *ast.IndexExpression: @@ -229,6 +234,11 @@ func (p *Parser) noPrefixParseFnError(t token.TokenType) { p.errors = append(p.errors, msg) } +func (p *Parser) noInfixParseFnError(t token.TokenType) { + msg := fmt.Sprintf("Tumeshindwa kuparse %s", t) + p.errors = append(p.errors, msg) +} + func (p *Parser) parseExpression(precedence int) ast.Expression { prefix := p.prefixParseFns[p.curToken.Type] if prefix == nil { @@ -240,7 +250,8 @@ func (p *Parser) parseExpression(precedence int) ast.Expression { for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() { infix := p.infixParseFns[p.peekToken.Type] if infix == nil { - return leftExp + p.noInfixParseFnError(p.peekToken.Type) + return nil } p.nextToken() diff --git a/repl/repl.go b/repl/repl.go index bd99864..e3c1b7c 100644 --- a/repl/repl.go +++ b/repl/repl.go @@ -92,8 +92,10 @@ func Start(in io.Reader, out io.Writer) { } evaluated := evaluator.Eval(program, env) if evaluated != nil { - io.WriteString(out, colorfy(evaluated.Inspect(), 32)) - io.WriteString(out, "\n") + if evaluated.Type() != object.NULL_OBJ { + io.WriteString(out, colorfy(evaluated.Inspect(), 32)) + io.WriteString(out, "\n") + } } } } diff --git a/token/token.go b/token/token.go index e9f006d..79a3ad2 100644 --- a/token/token.go +++ b/token/token.go @@ -48,6 +48,7 @@ const ( ELSE = "SIVYO" RETURN = "RUDISHA" WHILE = "WAKATI" + NULL = "TUPU" ) var keywords = map[string]TokenType{ @@ -60,6 +61,7 @@ var keywords = map[string]TokenType{ "sivyo": ELSE, "wakati": WHILE, "rudisha": RETURN, + "tupu": NULL, } func LookupIdent(ident string) TokenType { From 46483b51ce3f81db92bf16cfb1f1f8c96cb78f15 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Fri, 16 Dec 2022 02:46:20 +0300 Subject: [PATCH 06/59] Added ability to parse single quotes --- lexer/lexer.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lexer/lexer.go b/lexer/lexer.go index 3df1a45..e419280 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -84,6 +84,8 @@ func (l *Lexer) NextToken() token.Token { case '"': tok.Type = token.STRING tok.Literal = l.readString() + case '\'': + tok = token.Token{Type: token.STRING, Literal: l.readSingleQuoteString()} case '[': tok = newToken(token.LBRACKET, l.ch) case ']': @@ -191,3 +193,17 @@ func (l *Lexer) readString() string { return l.input[position:l.position] } + +func (l *Lexer) readSingleQuoteString() string { + var str string + for { + l.readChar() + if l.ch == '\'' || l.ch == 0 { + break + } else if l.ch == '\\' && l.peekChar() == '\'' { + l.readChar() + } + str += string(l.ch) + } + return str +} From 58f28ab2bf962b1a167f76883cec01c7f14eb157 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Fri, 16 Dec 2022 04:03:49 +0300 Subject: [PATCH 07/59] Added line numbers for better error reporting --- evaluator/evaluator.go | 68 +++++++++++++++++++----------------------- lexer/lexer.go | 58 +++++++++++++++++++---------------- parser/parser.go | 8 ++--- repl/repl.go | 2 +- token/token.go | 1 + 5 files changed, 70 insertions(+), 67 deletions(-) diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index c0f72ac..8e9bcb7 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -33,7 +33,7 @@ func Eval(node ast.Node, env *object.Environment) object.Object { if isError(right) { return right } - return evalPrefixExpression(node.Operator, right) + return evalPrefixExpression(node.Operator, right, node.Token.Line) case *ast.InfixExpression: left := Eval(node.Left, env) @@ -44,7 +44,7 @@ func Eval(node ast.Node, env *object.Environment) object.Object { if isError(right) { return right } - return evalInfixExpression(node.Operator, left, right) + return evalInfixExpression(node.Operator, left, right, node.Token.Line) case *ast.BlockStatement: return evalBlockStatement(node, env) @@ -84,7 +84,7 @@ func Eval(node ast.Node, env *object.Environment) object.Object { if len(args) == 1 && isError(args[0]) { return args[0] } - return applyFunction(function, args) + return applyFunction(function, args, node.Token.Line) case *ast.StringLiteral: return &object.String{Value: node.Value} @@ -103,7 +103,7 @@ func Eval(node ast.Node, env *object.Environment) object.Object { if isError(index) { return index } - return evalIndexExpression(left, index) + return evalIndexExpression(left, index, node.Token.Line) case *ast.DictLiteral: return evalDictLiteral(node, env) case *ast.WhileExpression: @@ -189,14 +189,14 @@ func nativeBoolToBooleanObject(input bool) *object.Boolean { return FALSE } -func evalPrefixExpression(operator string, right object.Object) object.Object { +func evalPrefixExpression(operator string, right object.Object, line int) object.Object { switch operator { case "!": return evalBangOperatorExpression(right) case "-": - return evalMinusPrefixOperatorExpression(right) + return evalMinusPrefixOperatorExpression(right, line) default: - return newError("operesheni haieleweki: %s%s", operator, right.Type()) + return newError("Mstari %d: peresheni haieleweki: %s%s", line, operator, right.Type()) } } @@ -213,19 +213,16 @@ func evalBangOperatorExpression(right object.Object) object.Object { } } -func evalMinusPrefixOperatorExpression(right object.Object) object.Object { +func evalMinusPrefixOperatorExpression(right object.Object, line int) object.Object { if right.Type() != object.INTEGER_OBJ { - return newError("Operesheni Haielweki: -%s", right.Type()) + return newError("Mstari %d: Operesheni Haielweki: -%s", line, right.Type()) } value := right.(*object.Integer).Value return &object.Integer{Value: -value} } -func evalInfixExpression( - operator string, - left, right object.Object, -) object.Object { +func evalInfixExpression(operator string, left, right object.Object, line int) object.Object { switch { case operator == "+" && left.Type() == object.DICT_OBJ && right.Type() == object.DICT_OBJ: @@ -276,7 +273,7 @@ func evalInfixExpression( 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) + return evalIntegerInfixExpression(operator, left, right, line) case operator == "==": return nativeBoolToBooleanObject(left == right) @@ -285,22 +282,19 @@ func evalInfixExpression( return nativeBoolToBooleanObject(left != right) case left.Type() != right.Type(): - return newError("Aina Hazilingani: %s %s %s", - left.Type(), operator, right.Type()) + return newError("Mstari %d: Aina Hazilingani: %s %s %s", + line, left.Type(), operator, right.Type()) case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: - return evalStringInfixExpression(operator, left, right) + return evalStringInfixExpression(operator, left, right, line) default: - return newError("Operesheni Haielweki: %s %s %s", - left.Type(), operator, right.Type()) + return newError("Mstari %d: Operesheni Haielweki: %s %s %s", + line, left.Type(), operator, right.Type()) } } -func evalIntegerInfixExpression( - operator string, - left, right object.Object, -) object.Object { +func evalIntegerInfixExpression(operator string, left, right object.Object, line int) object.Object { leftVal := left.(*object.Integer).Value rightVal := right.(*object.Integer).Value @@ -322,8 +316,8 @@ func evalIntegerInfixExpression( case "!=": return nativeBoolToBooleanObject(leftVal != rightVal) default: - return newError("Operesheni Haielweki: %s %s %s", - left.Type(), operator, right.Type()) + return newError("Mstari %d: Operesheni Haielweki: %s %s %s", + line, left.Type(), operator, right.Type()) } } @@ -394,7 +388,7 @@ func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object return builtin } - return newError("Neno Halifahamiki: " + node.Value) + return newError("Mstari %d: Neno Halifahamiki: %s", node.Token.Line, node.Value) } func evalExpressions(exps []ast.Expression, env *object.Environment) []object.Object { @@ -412,7 +406,7 @@ func evalExpressions(exps []ast.Expression, env *object.Environment) []object.Ob return result } -func applyFunction(fn object.Object, args []object.Object) object.Object { +func applyFunction(fn object.Object, args []object.Object, line int) object.Object { switch fn := fn.(type) { case *object.Function: extendedEnv := extendedFunctionEnv(fn, args) @@ -424,7 +418,7 @@ func applyFunction(fn object.Object, args []object.Object) object.Object { } return NULL default: - return newError("Hii sio function: %s", fn.Type()) + return newError("Mstari %d: Hii sio function: %s", line, fn.Type()) } } @@ -448,9 +442,9 @@ func unwrapReturnValue(obj object.Object) object.Object { return obj } -func evalStringInfixExpression(operator string, left, right object.Object) object.Object { +func evalStringInfixExpression(operator string, left, right object.Object, line int) object.Object { if operator != "+" { - return newError("Operesheni Haielweki: %s %s %s", left.Type(), operator, right.Type()) + return newError("Mstari %d: Operesheni Haielweki: %s %s %s", line, left.Type(), operator, right.Type()) } leftVal := left.(*object.String).Value @@ -459,16 +453,16 @@ func evalStringInfixExpression(operator string, left, right object.Object) objec return &object.String{Value: leftVal + rightVal} } -func evalIndexExpression(left, index object.Object) object.Object { +func evalIndexExpression(left, index object.Object, line int) object.Object { switch { case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: return evalArrayIndexExpression(left, index) case left.Type() == object.ARRAY_OBJ && index.Type() != object.INTEGER_OBJ: - return newError("Tafadhali tumia number, sio: %s", index.Type()) + return newError("Mstari %d: Tafadhali tumia number, sio: %s", line, index.Type()) case left.Type() == object.DICT_OBJ: - return evalDictIndexExpression(left, index) + return evalDictIndexExpression(left, index, line) default: - return newError("Operesheni hii haiwezekani kwa: %s", left.Type()) + return newError("Mstari %d: Operesheni hii haiwezekani kwa: %s", line, left.Type()) } } @@ -495,7 +489,7 @@ func evalDictLiteral(node *ast.DictLiteral, env *object.Environment) object.Obje hashKey, ok := key.(object.Hashable) if !ok { - return newError("Hashing imeshindikana: %s", key.Type()) + return newError("Mstari %d: Hashing imeshindikana: %s", node.Token.Line, key.Type()) } value := Eval(valueNode, env) @@ -510,12 +504,12 @@ func evalDictLiteral(node *ast.DictLiteral, env *object.Environment) object.Obje return &object.Dict{Pairs: pairs} } -func evalDictIndexExpression(dict, index object.Object) object.Object { +func evalDictIndexExpression(dict, index object.Object, line int) object.Object { dictObject := dict.(*object.Dict) key, ok := index.(object.Hashable) if !ok { - return newError("Samahani, %s haitumiki kama key", index.Type()) + return newError("Mstari %d: Samahani, %s haitumiki kama key", line, index.Type()) } pair, ok := dictObject.Pairs[key.HashKey()] diff --git a/lexer/lexer.go b/lexer/lexer.go index e419280..4d7daa7 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -9,6 +9,7 @@ type Lexer struct { position int readPosition int ch byte // make this a rune too + line int } func New(input string) *Lexer { @@ -45,67 +46,71 @@ func (l *Lexer) NextToken() token.Token { if l.peekChar() == '=' { ch := l.ch l.readChar() - tok = token.Token{Type: token.EQ, Literal: string(ch) + string(l.ch)} + tok = token.Token{Type: token.EQ, Literal: string(ch) + string(l.ch), Line: l.line} } else { - tok = newToken(token.ASSIGN, l.ch) + tok = newToken(token.ASSIGN, l.line, l.ch) } case ';': - tok = newToken(token.SEMICOLON, l.ch) + tok = newToken(token.SEMICOLON, l.line, l.ch) case '(': - tok = newToken(token.LPAREN, l.ch) + tok = newToken(token.LPAREN, l.line, l.ch) case ')': - tok = newToken(token.RPAREN, l.ch) + tok = newToken(token.RPAREN, l.line, l.ch) case '{': - tok = newToken(token.LBRACE, l.ch) + tok = newToken(token.LBRACE, l.line, l.ch) case '}': - tok = newToken(token.RBRACE, l.ch) + tok = newToken(token.RBRACE, l.line, l.ch) case ',': - tok = newToken(token.COMMA, l.ch) + tok = newToken(token.COMMA, l.line, l.ch) case '+': - tok = newToken(token.PLUS, l.ch) + tok = newToken(token.PLUS, l.line, l.ch) case '-': - tok = newToken(token.MINUS, l.ch) + tok = newToken(token.MINUS, l.line, l.ch) case '!': if l.peekChar() == '=' { ch := l.ch l.readChar() - tok = token.Token{Type: token.NOT_EQ, Literal: string(ch) + string(l.ch)} + tok = token.Token{Type: token.NOT_EQ, Literal: string(ch) + string(l.ch), Line: l.line} } else { - tok = newToken(token.BANG, l.ch) + tok = newToken(token.BANG, l.line, l.ch) } case '/': - tok = newToken(token.SLASH, l.ch) + tok = newToken(token.SLASH, l.line, l.ch) case '*': - tok = newToken(token.ASTERISK, l.ch) + tok = newToken(token.ASTERISK, l.line, l.ch) case '<': - tok = newToken(token.LT, l.ch) + tok = newToken(token.LT, l.line, l.ch) case '>': - tok = newToken(token.GT, l.ch) + tok = newToken(token.GT, l.line, l.ch) case '"': tok.Type = token.STRING tok.Literal = l.readString() + tok.Line = l.line case '\'': - tok = token.Token{Type: token.STRING, Literal: l.readSingleQuoteString()} + tok = token.Token{Type: token.STRING, Literal: l.readSingleQuoteString(), Line: l.line} case '[': - tok = newToken(token.LBRACKET, l.ch) + tok = newToken(token.LBRACKET, l.line, l.ch) case ']': - tok = newToken(token.RBRACKET, l.ch) + tok = newToken(token.RBRACKET, l.line, l.ch) case ':': - tok = newToken(token.COLON, l.ch) + tok = newToken(token.COLON, l.line, l.ch) case 0: tok.Literal = "" tok.Type = token.EOF + tok.Line = l.line default: if isLetter(l.ch) { tok.Literal = l.readIdentifier() tok.Type = token.LookupIdent(tok.Literal) + tok.Line = l.line return tok } else if isDigit(l.ch) { tok.Type = token.INT tok.Literal = l.readNumber() + tok.Line = l.line return tok } else { - tok = newToken(token.ILLEGAL, l.ch) + tok = newToken(token.ILLEGAL, l.line, l.ch) } } @@ -113,8 +118,8 @@ func (l *Lexer) NextToken() token.Token { return tok } -func newToken(tokenType token.TokenType, ch byte) token.Token { - return token.Token{Type: tokenType, Literal: string(ch)} +func newToken(tokenType token.TokenType, line int, ch byte) token.Token { + return token.Token{Type: tokenType, Literal: string(ch), Line: line} } func (l *Lexer) readIdentifier() string { @@ -127,11 +132,14 @@ func (l *Lexer) readIdentifier() string { } func isLetter(ch byte) bool { - return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch == '?' || ch == '&' + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' } func (l *Lexer) skipWhitespace() { for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' { + if l.ch == '\n' { + l.line++ + } l.readChar() } } @@ -177,9 +185,9 @@ func (l *Lexer) skipMultiLineComment() { } l.readChar() + l.skipWhitespace() } - l.skipWhitespace() } func (l *Lexer) readString() string { diff --git a/parser/parser.go b/parser/parser.go index e8da477..2890e59 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -192,7 +192,7 @@ func (p *Parser) Errors() []string { } func (p *Parser) peekError(t token.TokenType) { - msg := fmt.Sprintf("Tulitegemea kupata %s, badala yake tumepata %s", t, p.peekToken.Type) + msg := fmt.Sprintf("Mstari %d: Tulitegemea kupata %s, badala yake tumepata %s", p.curToken.Line, t, p.peekToken.Type) p.errors = append(p.errors, msg) } @@ -230,12 +230,12 @@ func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { } func (p *Parser) noPrefixParseFnError(t token.TokenType) { - msg := fmt.Sprintf("Tumeshindwa kuparse %s", t) + msg := fmt.Sprintf("Mstari %d: Tumeshindwa kuparse %s", p.curToken.Line, t) p.errors = append(p.errors, msg) } func (p *Parser) noInfixParseFnError(t token.TokenType) { - msg := fmt.Sprintf("Tumeshindwa kuparse %s", t) + msg := fmt.Sprintf("Mstari %d: Tumeshindwa kuparse %s", p.curToken.Line, t) p.errors = append(p.errors, msg) } @@ -270,7 +270,7 @@ func (p *Parser) parseIntegerLiteral() ast.Expression { value, err := strconv.ParseInt(p.curToken.Literal, 0, 64) if err != nil { - msg := fmt.Sprintf("Hatuwezi kuparse %q kama namba", p.curToken.Literal) + msg := fmt.Sprintf("Mstari %d: Hatuwezi kuparse %q kama namba", p.curToken.Line, p.curToken.Literal) p.errors = append(p.errors, msg) return nil } diff --git a/repl/repl.go b/repl/repl.go index e3c1b7c..3043704 100644 --- a/repl/repl.go +++ b/repl/repl.go @@ -49,7 +49,7 @@ func Read(contents string) { program := p.ParseProgram() if len(p.Errors()) != 0 { - fmt.Println(colorfy(ERROR_FACE, 31)) + // fmt.Println(colorfy(ERROR_FACE, 31)) fmt.Println("Kuna Errors Zifuatazo:") for _, msg := range p.Errors() { diff --git a/token/token.go b/token/token.go index 79a3ad2..7d6446a 100644 --- a/token/token.go +++ b/token/token.go @@ -5,6 +5,7 @@ type TokenType string type Token struct { Type TokenType Literal string + Line int } const ( From 9b7bc13d7e071a88de4df6a6093ed797482fbfe5 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Fri, 16 Dec 2022 04:36:55 +0300 Subject: [PATCH 08/59] Fixed reading from file return bug --- evaluator/evaluator.go | 15 +++++++++++---- repl/repl.go | 6 ++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 8e9bcb7..2a3185b 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -443,14 +443,21 @@ func unwrapReturnValue(obj object.Object) object.Object { } func evalStringInfixExpression(operator string, left, right object.Object, line int) object.Object { - if operator != "+" { - return newError("Mstari %d: Operesheni Haielweki: %s %s %s", line, left.Type(), operator, right.Type()) - } leftVal := left.(*object.String).Value rightVal := right.(*object.String).Value - return &object.String{Value: leftVal + rightVal} + switch operator { + case "+": + return &object.String{Value: leftVal + rightVal} + // doesn't work for some reason, maybe cause its cause its 4am + // case "==": + // return nativeBoolToBooleanObject(leftVal == rightVal) + // case "!=": + // return nativeBoolToBooleanObject(leftVal != rightVal) + default: + return newError("Mstari %d: Operesheni Haielweki: %s %s %s", line, left.Type(), operator, right.Type()) + } } func evalIndexExpression(left, index object.Object, line int) object.Object { diff --git a/repl/repl.go b/repl/repl.go index 3043704..9700152 100644 --- a/repl/repl.go +++ b/repl/repl.go @@ -49,7 +49,7 @@ func Read(contents string) { program := p.ParseProgram() if len(p.Errors()) != 0 { - // fmt.Println(colorfy(ERROR_FACE, 31)) + fmt.Println(colorfy(ERROR_FACE, 31)) fmt.Println("Kuna Errors Zifuatazo:") for _, msg := range p.Errors() { @@ -59,7 +59,9 @@ func Read(contents string) { } evaluated := evaluator.Eval(program, env) if evaluated != nil { - fmt.Println(colorfy(evaluated.Inspect(), 32)) + if evaluated.Type() != object.NULL_OBJ { + fmt.Println(colorfy(evaluated.Inspect(), 32)) + } } } From 2a95148217b91733479c98fd44b97c860ef7df84 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Fri, 16 Dec 2022 05:33:46 +0300 Subject: [PATCH 09/59] Add ability to compare strings --- evaluator/evaluator.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 2a3185b..2bff0f7 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -224,6 +224,8 @@ func evalMinusPrefixOperatorExpression(right object.Object, line int) object.Obj func evalInfixExpression(operator string, left, right object.Object, line int) object.Object { switch { + case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: + return evalStringInfixExpression(operator, left, right, line) case operator == "+" && left.Type() == object.DICT_OBJ && right.Type() == object.DICT_OBJ: leftVal := left.(*object.Dict).Pairs @@ -285,9 +287,6 @@ func evalInfixExpression(operator string, left, right object.Object, line int) o return newError("Mstari %d: Aina Hazilingani: %s %s %s", line, left.Type(), operator, right.Type()) - case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: - return evalStringInfixExpression(operator, left, right, line) - default: return newError("Mstari %d: Operesheni Haielweki: %s %s %s", line, left.Type(), operator, right.Type()) @@ -450,11 +449,10 @@ func evalStringInfixExpression(operator string, left, right object.Object, line switch operator { case "+": return &object.String{Value: leftVal + rightVal} - // doesn't work for some reason, maybe cause its cause its 4am - // 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 Haielweki: %s %s %s", line, left.Type(), operator, right.Type()) } From 7fa4e115912629365a5005f77a37f210c97e5763 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Fri, 16 Dec 2022 07:19:02 +0300 Subject: [PATCH 10/59] Everything math -> Add logical operators || and && -> Add >= and <= -> Pow (**) and Modulus (%) -> Initialize variables with fanya or acha -> Define functions with fn() or unda() --- evaluator/evaluator.go | 24 ++++++++++++++++++++++++ lexer/lexer.go | 38 +++++++++++++++++++++++++++++++++++--- parser/parser.go | 18 +++++++++++++++++- token/token.go | 12 ++++++++++++ 4 files changed, 88 insertions(+), 4 deletions(-) diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 2bff0f7..60a6b1d 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -2,6 +2,7 @@ package evaluator import ( "fmt" + "math" "strings" "github.com/AvicennaJr/Nuru/ast" @@ -282,6 +283,8 @@ func evalInfixExpression(operator string, left, right object.Object, line int) o case operator == "!=": return nativeBoolToBooleanObject(left != right) + case left.Type() == object.BOOLEAN_OBJ && right.Type() == object.BOOLEAN_OBJ: + return evalBooleanInfixExpression(operator, left, right, line) case left.Type() != right.Type(): return newError("Mstari %d: Aina Hazilingani: %s %s %s", @@ -304,12 +307,20 @@ func evalIntegerInfixExpression(operator string, left, right object.Object, line return &object.Integer{Value: leftVal - rightVal} case "*": return &object.Integer{Value: leftVal * rightVal} + case "**": + return &object.Integer{Value: int64(math.Pow(float64(leftVal), float64(rightVal)))} case "/": return &object.Integer{Value: leftVal / rightVal} + 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 "!=": @@ -320,6 +331,19 @@ func evalIntegerInfixExpression(operator string, left, right object.Object, line } } +func evalBooleanInfixExpression(operator string, left, right object.Object, line int) object.Object { + leftVal := left.(*object.Boolean).Value + rightVal := right.(*object.Boolean).Value + + switch operator { + case "&&": + return nativeBoolToBooleanObject(leftVal && rightVal) + case "||": + return nativeBoolToBooleanObject(leftVal || rightVal) + default: + return newError("Mstarid %d: Opereresheni Haielweki: %s %s %s", line, left.Type(), operator, right.Type()) + } +} func evalIfExpression(ie *ast.IfExpression, env *object.Environment) object.Object { condition := Eval(ie.Condition, env) diff --git a/lexer/lexer.go b/lexer/lexer.go index 4d7daa7..5377cd9 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -77,11 +77,29 @@ func (l *Lexer) NextToken() token.Token { case '/': tok = newToken(token.SLASH, l.line, l.ch) case '*': - tok = newToken(token.ASTERISK, l.line, l.ch) + if l.peekChar() == '*' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.POW, Literal: string(ch) + string(l.ch), Line: l.line} + } else { + tok = newToken(token.ASTERISK, l.line, l.ch) + } case '<': - tok = newToken(token.LT, l.line, l.ch) + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.LTE, Literal: string(ch) + string(l.ch), Line: l.line} + } else { + tok = newToken(token.LT, l.line, l.ch) + } case '>': - tok = newToken(token.GT, l.line, l.ch) + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.GTE, Literal: string(ch) + string(l.ch), Line: l.line} + } else { + tok = newToken(token.GT, l.line, l.ch) + } case '"': tok.Type = token.STRING tok.Literal = l.readString() @@ -94,6 +112,20 @@ func (l *Lexer) NextToken() token.Token { tok = newToken(token.RBRACKET, l.line, l.ch) case ':': tok = newToken(token.COLON, l.line, l.ch) + case '&': + if l.peekChar() == '&' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.AND, Literal: string(ch) + string(l.ch), Line: l.line} + } + case '|': + if l.peekChar() == '|' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.OR, Literal: string(ch) + string(l.ch), Line: l.line} + } + case '%': + tok = newToken(token.MODULUS, l.line, l.ch) case 0: tok.Literal = "" tok.Type = token.EOF diff --git a/parser/parser.go b/parser/parser.go index 2890e59..3224aa9 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -13,26 +13,36 @@ const ( // Think of BODMAS _ int = iota LOWEST + COND // OR or AND ASSIGN // = EQUALS // == LESSGREATER // > OR < SUM // + PRODUCT // * + POWER // ** we got the power XD + MODULUS // % PREFIX // -X OR !X CALL // myFunction(X) INDEX // Arrays ) var precedences = map[token.TokenType]int{ - token.ASSIGN: ASSIGN, // Lowest priority + token.AND: COND, + token.OR: COND, + token.ASSIGN: ASSIGN, token.EQ: EQUALS, token.NOT_EQ: EQUALS, token.LT: LESSGREATER, + token.LTE: LESSGREATER, token.GT: LESSGREATER, + token.GTE: LESSGREATER, token.PLUS: SUM, token.MINUS: SUM, token.SLASH: PRODUCT, token.ASTERISK: PRODUCT, + token.POW: POWER, + token.MODULUS: MODULUS, + // token.BANG: PREFIX, token.LPAREN: CALL, token.LBRACKET: INDEX, // Highest priority } @@ -78,14 +88,20 @@ func New(l *lexer.Lexer) *Parser { p.registerPrefix(token.NULL, p.parseNull) p.infixParseFns = make(map[token.TokenType]infixParseFn) + p.registerInfix(token.AND, p.parseInfixExpression) + p.registerInfix(token.OR, p.parseInfixExpression) p.registerInfix(token.PLUS, p.parseInfixExpression) p.registerInfix(token.MINUS, p.parseInfixExpression) p.registerInfix(token.SLASH, p.parseInfixExpression) p.registerInfix(token.ASTERISK, p.parseInfixExpression) + p.registerInfix(token.POW, p.parseInfixExpression) + p.registerInfix(token.MODULUS, p.parseInfixExpression) p.registerInfix(token.EQ, p.parseInfixExpression) p.registerInfix(token.NOT_EQ, p.parseInfixExpression) p.registerInfix(token.LT, p.parseInfixExpression) + p.registerInfix(token.LTE, p.parseInfixExpression) p.registerInfix(token.GT, p.parseInfixExpression) + p.registerInfix(token.GTE, p.parseInfixExpression) p.registerInfix(token.LPAREN, p.parseCallExpression) p.registerInfix(token.LBRACKET, p.parseIndexExpression) p.registerInfix(token.ASSIGN, p.parseAssignmentExpression) diff --git a/token/token.go b/token/token.go index 7d6446a..f9f269e 100644 --- a/token/token.go +++ b/token/token.go @@ -23,11 +23,17 @@ const ( MINUS = "-" BANG = "!" ASTERISK = "*" + POW = "**" SLASH = "/" + MODULUS = "%" LT = "<" + LTE = "<=" GT = ">" + GTE = ">=" EQ = "==" NOT_EQ = "!=" + AND = "&&" + OR = "||" //Delimiters COMMA = "," @@ -50,11 +56,15 @@ const ( RETURN = "RUDISHA" WHILE = "WAKATI" NULL = "TUPU" + BREAK = "SUSA" + CONTINUE = "ENDELEA" ) var keywords = map[string]TokenType{ "fn": FUNCTION, + "unda": FUNCTION, "acha": LET, + "fanya": LET, "kweli": TRUE, "sikweli": FALSE, "kama": IF, @@ -62,6 +72,8 @@ var keywords = map[string]TokenType{ "sivyo": ELSE, "wakati": WHILE, "rudisha": RETURN, + "susa": BREAK, + "endelea": CONTINUE, "tupu": NULL, } From f08bb63f7e1b528d0e5aa2894194eccc2ea92d29 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Fri, 16 Dec 2022 08:08:49 +0300 Subject: [PATCH 11/59] Add Break and Continue --- ast/ast.go | 18 ++++++++++++++++ evaluator/evaluator.go | 48 +++++++++++++++++++++++++----------------- object/object.go | 12 +++++++++++ parser/parser.go | 20 ++++++++++++++++++ 4 files changed, 79 insertions(+), 19 deletions(-) diff --git a/ast/ast.go b/ast/ast.go index 3307fa2..78100b4 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -375,3 +375,21 @@ type Null struct { func (n *Null) expressionNode() {} func (n *Null) TokenLiteral() string { return n.Token.Literal } func (n *Null) String() string { return n.Token.Literal } + +type Break struct { + Statement + Token token.Token // the 'break' token +} + +func (b *Break) expressionNode() {} +func (b *Break) TokenLiteral() string { return b.Token.Literal } +func (b *Break) String() string { return b.Token.Literal } + +type Continue struct { + Statement + Token token.Token // the 'continue' token +} + +func (c *Continue) expressionNode() {} +func (c *Continue) TokenLiteral() string { return c.Token.Literal } +func (c *Continue) String() string { return c.Token.Literal } diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 60a6b1d..0b93827 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -10,9 +10,11 @@ import ( ) var ( - NULL = &object.Null{} - TRUE = &object.Boolean{Value: true} - FALSE = &object.Boolean{Value: false} + NULL = &object.Null{} + TRUE = &object.Boolean{Value: true} + FALSE = &object.Boolean{Value: false} + BREAK = &object.Break{} + CONTINUE = &object.Continue{} ) func Eval(node ast.Node, env *object.Environment) object.Object { @@ -109,6 +111,10 @@ func Eval(node ast.Node, env *object.Environment) object.Object { return evalDictLiteral(node, env) case *ast.WhileExpression: return evalWhileExpression(node, env) + case *ast.Break: + return evalBreak(node) + case *ast.Continue: + return evalContinue(node) case *ast.Null: return NULL case *ast.AssignmentExpression: @@ -381,7 +387,7 @@ func evalBlockStatement(block *ast.BlockStatement, env *object.Environment) obje if result != nil { rt := result.Type() - if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ { + if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ || rt == object.CONTINUE_OBJ || rt == object.BREAK_OBJ { return result } } @@ -550,23 +556,27 @@ func evalDictIndexExpression(dict, index object.Object, line int) object.Object } func evalWhileExpression(we *ast.WhileExpression, env *object.Environment) object.Object { - var result object.Object - - for { - condition := Eval(we.Condition, env) - if isError(condition) { - return condition + condition := Eval(we.Condition, env) + if isError(condition) { + return condition + } + if isTruthy(condition) { + evaluated := Eval(we.Consequence, env) + if isError(evaluated) { + return evaluated } - - if isTruthy(condition) { - result = Eval(we.Consequence, env) - } else { - break + if evaluated.Type() == object.BREAK_OBJ { + return evaluated } + evalWhileExpression(we, env) } + return NULL +} - if result != nil { - return result - } - return nil +func evalBreak(node *ast.Break) object.Object { + return BREAK +} + +func evalContinue(node *ast.Continue) object.Object { + return CONTINUE } diff --git a/object/object.go b/object/object.go index fba6726..e31d719 100644 --- a/object/object.go +++ b/object/object.go @@ -22,6 +22,8 @@ const ( BUILTIN_OBJ = "YA_NDANI" ARRAY_OBJ = "ARRAY" DICT_OBJ = "KAMUSI" + CONTINUE_OBJ = "ENDELEA" + BREAK_OBJ = "SUSA" ) type Object interface { @@ -189,3 +191,13 @@ func (d *Dict) Inspect() string { type Hashable interface { HashKey() HashKey } + +type Continue struct{} + +func (c *Continue) Type() ObjectType { return CONTINUE_OBJ } +func (c *Continue) Inspect() string { return "continue" } + +type Break struct{} + +func (b *Break) Type() ObjectType { return BREAK_OBJ } +func (b *Break) Inspect() string { return "break" } diff --git a/parser/parser.go b/parser/parser.go index 3224aa9..e1400b5 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -134,6 +134,10 @@ func (p *Parser) parseStatement() ast.Statement { return p.parseLetStatment() case token.RETURN: return p.parseReturnStatement() + case token.BREAK: + return p.parseBreak() + case token.CONTINUE: + return p.parseContinue() default: return p.parseExpressionStatement() } @@ -560,3 +564,19 @@ func (p *Parser) parseWhileExpression() ast.Expression { return expression } + +func (p *Parser) parseBreak() *ast.Break { + stmt := &ast.Break{Token: p.curToken} + for p.curTokenIs(token.SEMICOLON) { + p.nextToken() + } + return stmt +} + +func (p *Parser) parseContinue() *ast.Continue { + stmt := &ast.Continue{Token: p.curToken} + for p.curTokenIs(token.SEMICOLON) { + p.nextToken() + } + return stmt +} From ee36e9dd800ac1594578fa60fa6ca1cb1364c48b Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Fri, 16 Dec 2022 08:52:01 +0300 Subject: [PATCH 12/59] Fixed while loop bug --- evaluator/evaluator.go | 2 +- lexer/lexer.go | 44 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 0b93827..0a175cc 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -565,7 +565,7 @@ func evalWhileExpression(we *ast.WhileExpression, env *object.Environment) objec if isError(evaluated) { return evaluated } - if evaluated.Type() == object.BREAK_OBJ { + if evaluated != nil && evaluated.Type() == object.BREAK_OBJ { return evaluated } evalWhileExpression(we, env) diff --git a/lexer/lexer.go b/lexer/lexer.go index 5377cd9..cea2bd2 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -223,15 +223,33 @@ func (l *Lexer) skipMultiLineComment() { } func (l *Lexer) readString() string { - position := l.position + 1 + var str string for { l.readChar() if l.ch == '"' || l.ch == 0 { break + } else if l.ch == '\\' { + switch l.peekChar() { + case 'n': + l.readChar() + l.ch = '\n' + case 'r': + l.readChar() + l.ch = '\r' + case 't': + l.readChar() + l.ch = '\t' + case '"': + l.readChar() + l.ch = '"' + case '\\': + l.readChar() + l.ch = '\\' + } } + str += string(l.ch) } - - return l.input[position:l.position] + return str } func (l *Lexer) readSingleQuoteString() string { @@ -240,8 +258,24 @@ func (l *Lexer) readSingleQuoteString() string { l.readChar() if l.ch == '\'' || l.ch == 0 { break - } else if l.ch == '\\' && l.peekChar() == '\'' { - l.readChar() + } else if l.ch == '\\' { + switch l.peekChar() { + case 'n': + l.readChar() + l.ch = '\n' + case 'r': + l.readChar() + l.ch = '\r' + case 't': + l.readChar() + l.ch = '\t' + case '"': + l.readChar() + l.ch = '"' + case '\\': + l.readChar() + l.ch = '\\' + } } str += string(l.ch) } From ad165dd6ab09d73f7bb2d414c75721f12710d7b2 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Fri, 16 Dec 2022 10:08:48 +0300 Subject: [PATCH 13/59] Updated download links --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 23e53a9..ae524f1 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,13 @@ instructions for your device below: - Download the binary: ``` -curl -O -L https://github.com/AvicennaJr/Nuru/releases/download/v0.1.0/nuru_linux_amd64_v0.1.0.tar.gz +curl -O -L https://github.com/AvicennaJr/Nuru/releases/download/v0.1.5/nuru_linux_amd64_v0.1.5.tar.gz ``` - Extract the file to make global available: ``` -sudo tar -C /usr/local/bin -xzvf nuru_linux_amd64_v0.1.0.tar.gz +sudo tar -C /usr/local/bin -xzvf nuru_linux_amd64_v0.1.5.tar.gz ``` - Confirm installation with: @@ -40,12 +40,12 @@ nuru -v - Download the binary with this command: ``` -curl -O -L https://github.com/AvicennaJr/Nuru/releases/download/v0.1.0/nuru_android_arm64_v0.1.0.tar.gz +curl -O -L https://github.com/AvicennaJr/Nuru/releases/download/v0.1.5/nuru_android_arm64_v0.1.5.tar.gz ``` - Extract the file: ``` -tar -xzvf nuru_android_arm64_v0.1.0.tar.gz +tar -xzvf nuru_android_arm64_v0.1.5.tar.gz ``` - Add it to path: @@ -65,8 +65,8 @@ nuru -v ``` mkdir C:\bin ``` - - Download the Nuru Program [Here](https://github.com/AvicennaJr/Nuru/releases/download/v0.1.0/nuru_windows_amd64_v0.1.0.exe) - - Rename the downloaded program from `nuru_windows_amd64_v0.1.0.exe` to `nuru.exe` + - Download the Nuru Program [Here](https://github.com/AvicennaJr/Nuru/releases/download/v0.1.5/nuru_windows_amd64_v0.1.5.exe) + - Rename the downloaded program from `nuru_windows_amd64_v0.1.5.exe` to `nuru.exe` - Move the file `nuru.exe` to the folder `C:\bin` - Add the bin folder to Path with this command: From 5c487273d9c7592e82dfcec2fb7f8ac42975c21c Mon Sep 17 00:00:00 2001 From: lymo <30964644+peterlymo@users.noreply.github.com> Date: Fri, 16 Dec 2022 11:29:11 +0300 Subject: [PATCH 14/59] update version number and fix typo --- main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 54ef51a..001130b 100644 --- a/main.go +++ b/main.go @@ -17,7 +17,7 @@ const ( | Authored by Avicenna | ` - VERSION = "v0.1.0" + VERSION = "v0.1.5" ) func main() { @@ -52,7 +52,7 @@ func main() { } else { fmt.Println(fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, "Error: Opereshen imeshindikana boss.")) - fmt.Println(fmt.Sprintf("\x1b[%dm%s\x1b[0m", 32, "\nTumia Command: 'nuru' kutmia program AU\nTumia Command: 'nuru' ikifuatwa na program file.\n\n\tMfano:\tnuru fileYangu.nr\n")) + fmt.Println(fmt.Sprintf("\x1b[%dm%s\x1b[0m", 32, "\nTumia Command: 'nuru' kutumia program AU\nTumia Command: 'nuru' ikifuatwa na program file.\n\n\tMfano:\tnuru fileYangu.nr\n")) os.Exit(0) } } From ba26f7e457f9bd88930ac794b6951f658b4de878 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Wed, 21 Dec 2022 02:40:37 +0300 Subject: [PATCH 15/59] Added '++' and '--' --- ast/ast.go | 16 +++++++++ evaluator/builtins.go | 4 ++- evaluator/evaluator.go | 33 +++++++++++++++++- evaluator/evaluator_test.go | 22 ++++++------ examples/sorting_algorithm.nr | 63 +++++++++++++++++++++++++++++++++++ lexer/lexer.go | 46 ++++++++++++++++++++++--- parser/parser.go | 34 ++++++++++++++++--- token/token.go | 39 +++++++++++++--------- 8 files changed, 218 insertions(+), 39 deletions(-) create mode 100644 examples/sorting_algorithm.nr diff --git a/ast/ast.go b/ast/ast.go index 78100b4..adc360a 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -393,3 +393,19 @@ type Continue struct { func (c *Continue) expressionNode() {} func (c *Continue) TokenLiteral() string { return c.Token.Literal } func (c *Continue) String() string { return c.Token.Literal } + +type PostfixExpression struct { + Token token.Token + Operator string +} + +func (pe *PostfixExpression) expressionNode() {} +func (pe *PostfixExpression) TokenLiteral() string { return pe.Token.Literal } +func (pe *PostfixExpression) String() string { + var out bytes.Buffer + out.WriteString("(") + out.WriteString(pe.Token.Literal) + out.WriteString(pe.Operator) + out.WriteString(")") + return out.String() +} diff --git a/evaluator/builtins.go b/evaluator/builtins.go index f07138c..639254a 100644 --- a/evaluator/builtins.go +++ b/evaluator/builtins.go @@ -94,7 +94,9 @@ var builtins = map[string]*object.Builtin{ fmt.Println("") } else { for _, arg := range args { - + if arg == nil { + return newError("Hauwezi kufanya operesheni hii") + } fmt.Println(arg.Inspect()) } } diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 0a175cc..ef81804 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -48,6 +48,8 @@ func Eval(node ast.Node, env *object.Environment) object.Object { return right } return evalInfixExpression(node.Operator, left, right, node.Token.Line) + case *ast.PostfixExpression: + return evalPostfixExpression(env, node.Operator, node) case *ast.BlockStatement: return evalBlockStatement(node, env) @@ -347,9 +349,38 @@ func evalBooleanInfixExpression(operator string, left, right object.Object, line case "||": return nativeBoolToBooleanObject(leftVal || rightVal) default: - return newError("Mstarid %d: Opereresheni Haielweki: %s %s %s", line, left.Type(), operator, right.Type()) + return newError("Mstari %d: Operesheni Haielweki: %s %s %s", line, left.Type(), operator, right.Type()) + } +} + +func evalPostfixExpression(env *object.Environment, operator string, node *ast.PostfixExpression) object.Object { + val, ok := env.Get(node.Token.Literal) + if !ok { + return newError("Tumia kitambulishi cha namba, sio %s", node.Token.Type) + } + switch operator { + case "++": + switch arg := val.(type) { + case *object.Integer: + v := arg.Value + 1 + return env.Set(node.Token.Literal, &object.Integer{Value: v}) + default: + return newError("%s sio kitambulishi cha namba. Tumia '++' na kitambulishi cha namba tu.\nMfano:\tacha i = 2; i++", node.Token.Literal) + + } + case "--": + switch arg := val.(type) { + case *object.Integer: + v := arg.Value - 1 + return env.Set(node.Token.Literal, &object.Integer{Value: v}) + default: + return newError("%s sio kitambulishi cha namba. Tumia '--' na kitambulishi cha namba tu.\nMfano:\tacha i = 2; i++", node.Token.Literal) + } + default: + return newError("Haifahamiki: %s", operator) } } + func evalIfExpression(ie *ast.IfExpression, env *object.Environment) object.Object { condition := Eval(ie.Condition, env) diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index e2d9771..731e451 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -174,27 +174,27 @@ func TestErrorHandling(t *testing.T) { }{ { "5 + kweli", - "Aina Hazilingani: NAMBA + BOOLEAN", + "Mstari 0: Aina Hazilingani: NAMBA + BOOLEAN", }, { "5 + kweli; 5;", - "Aina Hazilingani: NAMBA + BOOLEAN", + "Mstari 0: Aina Hazilingani: NAMBA + BOOLEAN", }, { "-kweli", - "Operesheni Haielweki: -BOOLEAN", + "Mstari 0: Operesheni Haielweki: -BOOLEAN", }, { "kweli + sikweli", - "Operesheni Haielweki: BOOLEAN + BOOLEAN", + "Mstari 0: Operesheni Haielweki: BOOLEAN + BOOLEAN", }, { "5; kweli + sikweli; 5", - "Operesheni Haielweki: BOOLEAN + BOOLEAN", + "Mstari 0: Operesheni Haielweki: BOOLEAN + BOOLEAN", }, { "kama (10 > 1) { kweli + sikweli;}", - "Operesheni Haielweki: BOOLEAN + BOOLEAN", + "Mstari 0: Operesheni Haielweki: BOOLEAN + BOOLEAN", }, { ` @@ -206,19 +206,19 @@ kama (10 > 1) { rudisha 1; } `, - "Operesheni Haielweki: BOOLEAN + BOOLEAN", + "Mstari 3: Operesheni Haielweki: BOOLEAN + BOOLEAN", }, { "bangi", - "Neno Halifahamiki: bangi", + "Mstari 0: Neno Halifahamiki: bangi", }, { `"Habari" - "Habari"`, - "Operesheni Haielweki: NENO - NENO", + "Mstari 0: Operesheni Haielweki: NENO - NENO", }, { `{"jina": "Avi"}[fn(x) {x}];`, - "Samahani, FUNCTION haitumiki kama key", + "Mstari 0: Samahani, FUNCTION haitumiki kama key", }, } @@ -232,7 +232,7 @@ kama (10 > 1) { } if errObj.Message != fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, tt.expectedMessage) { - t.Errorf("wrong error message, expected=%q, got=%q", tt.expectedMessage, errObj.Message) + t.Errorf("wrong error message, expected=%q, got=%q", fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, tt.expectedMessage), errObj.Message) } } } diff --git a/examples/sorting_algorithm.nr b/examples/sorting_algorithm.nr new file mode 100644 index 0000000..5241c31 --- /dev/null +++ b/examples/sorting_algorithm.nr @@ -0,0 +1,63 @@ +* +############ Sorting Algorithm ############## + + By @VictorKariuki + + https://github.com/VictorKariuki + +############################################# +*/ + +acha slice = fn(arr,start, end) { + acha result = [] + wakati (start < end) { + result = result + [arr[start]] + start = start + 1 + } + rudisha result +} + +acha merge = fn(left, right) { + acha result = [] + acha lLen = idadi(left) + acha rLen = idadi(right) + acha l = 0 + acha r = 0 + wakati (l < lLen && r < rLen) { + kama (left[l] < right[r]) { + result = result + [left[l]] + l = l + 1 + } sivyo { + result = result + [right[r]] + r = r + 1 + } + } + andika(result) +} + + +acha mergeSort = fn(arr){ + acha len = idadi(arr) + andika("arr is ", arr," of length ", len) + kama (len < 2) { + rudisha arr + } + andika("len is greater than or == to 2", len > 1) + + acha mid = (len / 2) + andika("arr has a mid point of ", mid) + + acha left = slice(arr, 0, mid) + acha right = slice(arr, mid, len) + andika("left slice is ", left) + andika("right slice is ", right) + acha sortedLeft = mergeSort(left) + acha sortedRight = mergeSort(right) + andika("sortedLeft is ", sortedLeft) + andika("sortedRight is ", sortedRight) + rudisha merge(sortedLeft, sortedRight) +} + +acha arr = [6, 5, 3, 1, 8, 7, 2, 4] +acha sortedArray = mergeSort(arr) +andika(sortedArray) \ No newline at end of file diff --git a/lexer/lexer.go b/lexer/lexer.go index cea2bd2..6cb7ec1 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -63,9 +63,29 @@ func (l *Lexer) NextToken() token.Token { case ',': tok = newToken(token.COMMA, l.line, l.ch) case '+': - tok = newToken(token.PLUS, l.line, l.ch) + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.PLUS_ASSIGN, Line: l.line, Literal: string(ch) + string(l.ch)} + } else if l.peekChar() == '+' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.PLUS_PLUS, Literal: string(ch) + string(l.ch), Line: l.line} + } else { + tok = newToken(token.PLUS, l.line, l.ch) + } case '-': - tok = newToken(token.MINUS, l.line, l.ch) + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.MINUS_ASSIGN, Line: l.line, Literal: string(ch) + string(l.ch)} + } else if l.peekChar() == '-' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.MINUS_MINUS, Literal: string(ch) + string(l.ch), Line: l.line} + } else { + tok = newToken(token.MINUS, l.line, l.ch) + } case '!': if l.peekChar() == '=' { ch := l.ch @@ -75,9 +95,19 @@ func (l *Lexer) NextToken() token.Token { tok = newToken(token.BANG, l.line, l.ch) } case '/': - tok = newToken(token.SLASH, l.line, l.ch) + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.SLASH_ASSIGN, Line: l.line, Literal: string(ch) + string(l.ch)} + } else { + tok = newToken(token.SLASH, l.line, l.ch) + } case '*': - if l.peekChar() == '*' { + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.ASTERISK_ASSIGN, Line: l.line, Literal: string(ch) + string(l.ch)} + } else if l.peekChar() == '*' { ch := l.ch l.readChar() tok = token.Token{Type: token.POW, Literal: string(ch) + string(l.ch), Line: l.line} @@ -125,7 +155,13 @@ func (l *Lexer) NextToken() token.Token { tok = token.Token{Type: token.OR, Literal: string(ch) + string(l.ch), Line: l.line} } case '%': - tok = newToken(token.MODULUS, l.line, l.ch) + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.MODULUS_ASSIGN, Line: l.line, Literal: string(ch) + string(l.ch)} + } else { + tok = newToken(token.MODULUS, l.line, l.ch) + } case 0: tok.Literal = "" tok.Type = token.EOF diff --git a/parser/parser.go b/parser/parser.go index e1400b5..6be6ff5 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -48,8 +48,9 @@ var precedences = map[token.TokenType]int{ } type ( - prefixParseFn func() ast.Expression - infixParseFn func(ast.Expression) ast.Expression + prefixParseFn func() ast.Expression + infixParseFn func(ast.Expression) ast.Expression + postfixParseFn func() ast.Expression ) type Parser struct { @@ -57,11 +58,13 @@ type Parser struct { curToken token.Token peekToken token.Token + prevToken token.Token errors []string - prefixParseFns map[token.TokenType]prefixParseFn - infixParseFns map[token.TokenType]infixParseFn + prefixParseFns map[token.TokenType]prefixParseFn + infixParseFns map[token.TokenType]infixParseFn + postfixParseFns map[token.TokenType]postfixParseFn } func New(l *lexer.Lexer) *Parser { @@ -105,11 +108,16 @@ func New(l *lexer.Lexer) *Parser { p.registerInfix(token.LPAREN, p.parseCallExpression) p.registerInfix(token.LBRACKET, p.parseIndexExpression) p.registerInfix(token.ASSIGN, p.parseAssignmentExpression) + + p.postfixParseFns = make(map[token.TokenType]postfixParseFn) + p.registerPostfix(token.PLUS_PLUS, p.parsePostfixExpression) + p.registerPostfix(token.MINUS_MINUS, p.parsePostfixExpression) + return p } func (p *Parser) nextToken() { - // only missing shuffle to make it a music player XD + p.prevToken = p.curToken p.curToken = p.peekToken p.peekToken = p.l.NextToken() } @@ -237,6 +245,10 @@ func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) { p.infixParseFns[tokenType] = fn } +func (p *Parser) registerPostfix(tokenType token.TokenType, fn postfixParseFn) { + p.postfixParseFns[tokenType] = fn +} + func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { stmt := &ast.ExpressionStatement{Token: p.curToken} @@ -260,6 +272,10 @@ func (p *Parser) noInfixParseFnError(t token.TokenType) { } func (p *Parser) parseExpression(precedence int) ast.Expression { + postfix := p.postfixParseFns[p.curToken.Type] + if postfix != nil { + return (postfix()) + } prefix := p.prefixParseFns[p.curToken.Type] if prefix == nil { p.noPrefixParseFnError(p.curToken.Type) @@ -356,6 +372,14 @@ func (p *Parser) parseGroupedExpression() ast.Expression { return exp } +func (p *Parser) parsePostfixExpression() ast.Expression { + expression := &ast.PostfixExpression{ + Token: p.prevToken, + Operator: p.curToken.Literal, + } + return expression +} + func (p *Parser) parseIfExpression() ast.Expression { expression := &ast.IfExpression{Token: p.curToken} diff --git a/token/token.go b/token/token.go index f9f269e..8a09670 100644 --- a/token/token.go +++ b/token/token.go @@ -18,22 +18,29 @@ const ( STRING = "NENO" // Operators - ASSIGN = "=" - PLUS = "+" - MINUS = "-" - BANG = "!" - ASTERISK = "*" - POW = "**" - SLASH = "/" - MODULUS = "%" - LT = "<" - LTE = "<=" - GT = ">" - GTE = ">=" - EQ = "==" - NOT_EQ = "!=" - AND = "&&" - OR = "||" + ASSIGN = "=" + PLUS = "+" + MINUS = "-" + BANG = "!" + ASTERISK = "*" + POW = "**" + SLASH = "/" + MODULUS = "%" + LT = "<" + LTE = "<=" + GT = ">" + GTE = ">=" + EQ = "==" + NOT_EQ = "!=" + AND = "&&" + OR = "||" + PLUS_ASSIGN = "+=" + PLUS_PLUS = "++" + MINUS_ASSIGN = "-=" + MINUS_MINUS = "--" + ASTERISK_ASSIGN = "*=" + SLASH_ASSIGN = "/=" + MODULUS_ASSIGN = "%=" //Delimiters COMMA = "," From 6ec8c3d3232aee4169899e106db70d06432f2188 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Wed, 21 Dec 2022 12:51:32 +0300 Subject: [PATCH 16/59] Add ability to parse floating points --- .gitignore | 54 +++++++++++++++++++++ ast/ast.go | 9 ++++ evaluator/evaluator.go | 104 +++++++++++++++++++++++++++++++++++++++-- lexer/lexer.go | 18 +++++-- object/object.go | 15 ++++++ parser/parser.go | 13 ++++++ token/token.go | 1 + 7 files changed, 204 insertions(+), 10 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..55c0ff1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +.DS_Store +*.[56789ao] +*.a[56789o] +*.so +*.pyc +._* +.nfs.* +[56789a].out +*~ +*.orig +*.rej +*.exe +.*.swp +core +*.cgo*.go +*.cgo*.c +_cgo_* +_obj +_test +_testmain.go +/VERSION.cache +/bin/ +/build.out +/doc/articles/wiki/*.bin +/goinstall.log +/last-change +/misc/cgo/life/run.out +/misc/cgo/stdio/run.out +/misc/cgo/testso/main +/pkg/ +/src/*.*/ +/src/cmd/cgo/zdefaultcc.go +/src/cmd/dist/dist +/src/cmd/go/internal/cfg/zdefaultcc.go +/src/cmd/go/internal/cfg/zosarch.go +/src/cmd/internal/objabi/zbootstrap.go +/src/go/build/zcgo.go +/src/go/doc/headscan +/src/runtime/internal/sys/zversion.go +/src/unicode/maketables +/test.out +/test/garbage/*.out +/test/pass.out +/test/run.out +/test/times.out + +#Personal + +testbinaries +tests_random +parser/tracingZeAnnoyingParser.go +testTracing.go +Notes.md + diff --git a/ast/ast.go b/ast/ast.go index adc360a..0dd1acd 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -409,3 +409,12 @@ func (pe *PostfixExpression) String() string { out.WriteString(")") return out.String() } + +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 } diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index ef81804..d97e2c0 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -28,6 +28,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) @@ -223,12 +226,17 @@ func evalBangOperatorExpression(right object.Object) object.Object { } func evalMinusPrefixOperatorExpression(right object.Object, line int) object.Object { - if right.Type() != object.INTEGER_OBJ { + switch obj := right.(type) { + + case *object.Integer: + return &object.Integer{Value: -obj.Value} + + case *object.Float: + return &object.Float{Value: -obj.Value} + + default: return newError("Mstari %d: Operesheni Haielweki: -%s", line, right.Type()) } - - value := right.(*object.Integer).Value - return &object.Integer{Value: -value} } func evalInfixExpression(operator string, left, right object.Object, line int) object.Object { @@ -286,6 +294,15 @@ func evalInfixExpression(operator string, left, right object.Object, line int) o case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ: return evalIntegerInfixExpression(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.FLOAT_OBJ && right.Type() == object.INTEGER_OBJ: + return evalFloatIntegerInfixExpression(operator, left, right, line) + case operator == "==": return nativeBoolToBooleanObject(left == right) @@ -318,7 +335,13 @@ func evalIntegerInfixExpression(operator string, left, right object.Object, line case "**": return &object.Integer{Value: int64(math.Pow(float64(leftVal), float64(rightVal)))} case "/": - return &object.Integer{Value: leftVal / rightVal} + 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 "<": @@ -339,6 +362,77 @@ func evalIntegerInfixExpression(operator string, left, right object.Object, line } } +func evalFloatInfixExpression(operator string, left, right object.Object, line int) object.Object { + leftVal := left.(*object.Float).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 "**": + return &object.Float{Value: math.Pow(float64(leftVal), float64(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("Mstari %d: Operesheni Haielweki: %s %s %s", + line, left.Type(), operator, right.Type()) + } +} + +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 + } + 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: math.Pow(float64(leftVal), float64(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("Mstari %d: Operesheni Haielweki: %s %s %s", + line, left.Type(), operator, right.Type()) + } +} + func evalBooleanInfixExpression(operator string, left, right object.Object, line int) object.Object { leftVal := left.(*object.Boolean).Value rightVal := right.(*object.Boolean).Value diff --git a/lexer/lexer.go b/lexer/lexer.go index 6cb7ec1..5634ff6 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -173,9 +173,7 @@ func (l *Lexer) NextToken() token.Token { tok.Line = l.line return tok } else if isDigit(l.ch) { - tok.Type = token.INT - tok.Literal = l.readNumber() - tok.Line = l.line + tok = l.readDecimal() return tok } else { tok = newToken(token.ILLEGAL, l.line, l.ch) @@ -212,6 +210,10 @@ func (l *Lexer) skipWhitespace() { } } +func isDigit(ch byte) bool { + return '0' <= ch && ch <= '9' +} + func (l *Lexer) readNumber() string { position := l.position for isDigit(l.ch) { @@ -220,8 +222,14 @@ func (l *Lexer) readNumber() string { return l.input[position:l.position] } -func isDigit(ch byte) bool { - return '0' <= ch && ch <= '9' +func (l *Lexer) readDecimal() token.Token { + integer := l.readNumber() + if l.ch == '.' && isDigit(l.peekChar()) { + l.readChar() + fraction := l.readNumber() + return token.Token{Type: token.FLOAT, Literal: integer + "." + fraction, Line: l.line} + } + return token.Token{Type: token.INT, Literal: integer, Line: l.line} } func (l *Lexer) peekChar() byte { diff --git a/object/object.go b/object/object.go index e31d719..5bc8bf4 100644 --- a/object/object.go +++ b/object/object.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "hash/fnv" + "strconv" "strings" "github.com/AvicennaJr/Nuru/ast" @@ -13,6 +14,7 @@ type ObjectType string const ( INTEGER_OBJ = "NAMBA" + FLOAT_OBJ = "DESIMALI" BOOLEAN_OBJ = "BOOLEAN" NULL_OBJ = "NULL" RETURN_VALUE_OBJ = "RUDISHA" @@ -38,6 +40,13 @@ type Integer struct { func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) } func (i *Integer) Type() ObjectType { return INTEGER_OBJ } +type Float struct { + Value float64 +} + +func (f *Float) Inspect() string { return strconv.FormatFloat(f.Value, 'f', -1, 64) } +func (f *Float) Type() ObjectType { return FLOAT_OBJ } + type Boolean struct { Value bool } @@ -155,6 +164,12 @@ func (i *Integer) HashKey() HashKey { return HashKey{Type: i.Type(), Value: uint64(i.Value)} } +func (f *Float) HashKey() HashKey { + h := fnv.New64a() + h.Write([]byte(f.Inspect())) + return HashKey{Type: f.Type(), Value: h.Sum64()} +} + func (s *String) HashKey() HashKey { h := fnv.New64a() h.Write([]byte(s.Value)) diff --git a/parser/parser.go b/parser/parser.go index 6be6ff5..b87cfb5 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -78,6 +78,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.FLOAT, p.parseFloatLiteral) p.registerPrefix(token.BANG, p.parsePrefixExpression) p.registerPrefix(token.MINUS, p.parsePrefixExpression) p.registerPrefix(token.TRUE, p.parseBoolean) @@ -315,6 +316,18 @@ func (p *Parser) parseIntegerLiteral() ast.Expression { return lit } +func (p *Parser) parseFloatLiteral() ast.Expression { + fl := &ast.FloatLiteral{Token: p.curToken} + value, err := strconv.ParseFloat(p.curToken.Literal, 64) + if err != nil { + msg := fmt.Sprintf("Mstari %d: Hatuwezi kuparse %q kama desimali", p.curToken.Line, p.curToken.Literal) + p.errors = append(p.errors, msg) + return nil + } + fl.Value = value + return fl +} + func (p *Parser) parsePrefixExpression() ast.Expression { expression := &ast.PrefixExpression{ Token: p.curToken, diff --git a/token/token.go b/token/token.go index 8a09670..97240fd 100644 --- a/token/token.go +++ b/token/token.go @@ -16,6 +16,7 @@ const ( IDENT = "KITAMBULISHI" INT = "NAMBA" STRING = "NENO" + FLOAT = "DESIMALI" // Operators ASSIGN = "=" From af42434dc9aa7eaa8bbbd5490497f63f1e4c62f5 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Wed, 21 Dec 2022 18:15:55 +0300 Subject: [PATCH 17/59] Add '+=', '*=' etc --- evaluator/evaluator.go | 13 +++++++++++++ parser/parser.go | 40 +++++++++++++++++++++++++--------------- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index d97e2c0..05ba89b 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -133,6 +133,19 @@ func Eval(node ast.Node, env *object.Environment) object.Object { return value } + // This is a very smart way to assign operators like +=, -= etc + // I'm surprised it work at the first try lol + // basically divide the += to + and =, take the + only and + // then perform the operation as normal + op := node.Token.Literal + if len(op) >= 2 { + op = op[:len(op)-1] + value = evalInfixExpression(op, left, value, node.Token.Line) + if isError(value) { + return value + } + } + if ident, ok := node.Left.(*ast.Identifier); ok { env.Set(ident.Value, value) } else if ie, ok := node.Left.(*ast.IndexExpression); ok { diff --git a/parser/parser.go b/parser/parser.go index b87cfb5..fff67d9 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -27,21 +27,26 @@ const ( ) var precedences = map[token.TokenType]int{ - token.AND: COND, - token.OR: COND, - token.ASSIGN: ASSIGN, - token.EQ: EQUALS, - token.NOT_EQ: EQUALS, - token.LT: LESSGREATER, - token.LTE: LESSGREATER, - token.GT: LESSGREATER, - token.GTE: LESSGREATER, - token.PLUS: SUM, - token.MINUS: SUM, - token.SLASH: PRODUCT, - token.ASTERISK: PRODUCT, - token.POW: POWER, - token.MODULUS: MODULUS, + token.AND: COND, + token.OR: COND, + token.ASSIGN: ASSIGN, + token.EQ: EQUALS, + token.NOT_EQ: EQUALS, + token.LT: LESSGREATER, + token.LTE: LESSGREATER, + token.GT: LESSGREATER, + token.GTE: LESSGREATER, + token.PLUS: SUM, + token.PLUS_ASSIGN: SUM, + token.MINUS: SUM, + token.MINUS_ASSIGN: SUM, + token.SLASH: PRODUCT, + token.SLASH_ASSIGN: PRODUCT, + token.ASTERISK: PRODUCT, + token.ASTERISK_ASSIGN: PRODUCT, + token.POW: POWER, + token.MODULUS: MODULUS, + token.MODULUS_ASSIGN: MODULUS, // token.BANG: PREFIX, token.LPAREN: CALL, token.LBRACKET: INDEX, // Highest priority @@ -95,11 +100,16 @@ func New(l *lexer.Lexer) *Parser { p.registerInfix(token.AND, p.parseInfixExpression) p.registerInfix(token.OR, p.parseInfixExpression) p.registerInfix(token.PLUS, p.parseInfixExpression) + p.registerInfix(token.PLUS_ASSIGN, p.parseAssignmentExpression) p.registerInfix(token.MINUS, p.parseInfixExpression) + p.registerInfix(token.MINUS_ASSIGN, p.parseAssignmentExpression) p.registerInfix(token.SLASH, p.parseInfixExpression) + p.registerInfix(token.SLASH_ASSIGN, p.parseAssignmentExpression) p.registerInfix(token.ASTERISK, p.parseInfixExpression) + p.registerInfix(token.ASTERISK_ASSIGN, p.parseAssignmentExpression) p.registerInfix(token.POW, p.parseInfixExpression) p.registerInfix(token.MODULUS, p.parseInfixExpression) + p.registerInfix(token.MODULUS_ASSIGN, p.parseAssignmentExpression) p.registerInfix(token.EQ, p.parseInfixExpression) p.registerInfix(token.NOT_EQ, p.parseInfixExpression) p.registerInfix(token.LT, p.parseInfixExpression) From 86c950b5cbff25ed377b538b1ff5510e3c072770 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Wed, 21 Dec 2022 18:41:24 +0300 Subject: [PATCH 18/59] Add ability to check types --- evaluator/builtins.go | 30 ++++++++++++++++++++++++++++++ object/object.go | 6 +++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/evaluator/builtins.go b/evaluator/builtins.go index 639254a..b08347b 100644 --- a/evaluator/builtins.go +++ b/evaluator/builtins.go @@ -103,4 +103,34 @@ var builtins = map[string]*object.Builtin{ return nil }, }, + "aina": { + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("Samahani, tunahitaji Hoja 1, wewe umeweka %d", len(args)) + } + + return &object.String{Value: string(args[0].Type())} + // switch args[0].(type) { + // case *object.String: + // return &object.String{Value: "NENO (STRING)"} + // case *object.Boolean: + // return &object.String{Value: "BOOLEAN (KWELI AU SIKWELI)"} + // case *object.Builtin: + // return &object.String{Value: "YA_NDANI (Builtin Function)"} + // case *object.Array: + // return &object.String{Value: "ORODHA (Array)"} + // case *object.Function: + // return &object.String{Value: "UNDO (Function)"} + // case *object.Integer: + // return &object.String{Value: "NAMBA (Integer)"} + // case *object.Float: + // return &object.String{Value: "DESIMALI (Float)"} + // case *object.Dict: + // return &object.String{Value: "KAMUSI (Dict)"} + // default: + // return newError("argument to `type` not supported, got=%s", + // args[0].Type()) + // } + }, + }, } diff --git a/object/object.go b/object/object.go index 5bc8bf4..d1c6523 100644 --- a/object/object.go +++ b/object/object.go @@ -16,13 +16,13 @@ const ( INTEGER_OBJ = "NAMBA" FLOAT_OBJ = "DESIMALI" BOOLEAN_OBJ = "BOOLEAN" - NULL_OBJ = "NULL" + NULL_OBJ = "TUPU" RETURN_VALUE_OBJ = "RUDISHA" ERROR_OBJ = "KOSA" - FUNCTION_OBJ = "FUNCTION" + FUNCTION_OBJ = "UNDO (FUNCTION)" STRING_OBJ = "NENO" BUILTIN_OBJ = "YA_NDANI" - ARRAY_OBJ = "ARRAY" + ARRAY_OBJ = "ORODHA" DICT_OBJ = "KAMUSI" CONTINUE_OBJ = "ENDELEA" BREAK_OBJ = "SUSA" From 9c52eed95717fb8073a5a1924d5185bc855b12c4 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Wed, 21 Dec 2022 18:59:36 +0300 Subject: [PATCH 19/59] Fixed bug in identifying floats/ints --- evaluator/evaluator.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 05ba89b..09d9919 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -354,7 +354,6 @@ func evalIntegerInfixExpression(operator string, left, right object.Object, line } else { return &object.Float{Value: x} } - case "%": return &object.Integer{Value: leftVal % rightVal} case "<": @@ -417,17 +416,19 @@ func evalFloatIntegerInfixExpression(operator string, left, right object.Object, leftVal = float64(left.(*object.Integer).Value) rightVal = right.(*object.Float).Value } + + var val float64 switch operator { case "+": - return &object.Float{Value: leftVal + rightVal} + val = leftVal + rightVal case "-": - return &object.Float{Value: leftVal - rightVal} + val = leftVal - rightVal case "*": - return &object.Float{Value: leftVal * rightVal} + val = leftVal * rightVal case "**": - return &object.Float{Value: math.Pow(float64(leftVal), float64(rightVal))} + val = math.Pow(float64(leftVal), float64(rightVal)) case "/": - return &object.Float{Value: leftVal / rightVal} + val = leftVal / rightVal case "<": return nativeBoolToBooleanObject(leftVal < rightVal) case "<=": @@ -444,6 +445,12 @@ func evalFloatIntegerInfixExpression(operator string, left, right object.Object, return newError("Mstari %d: Operesheni Haielweki: %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 evalBooleanInfixExpression(operator string, left, right object.Object, line int) object.Object { From e832da39e1163b6257e3897639bace916a810b61 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Wed, 21 Dec 2022 19:14:20 +0300 Subject: [PATCH 20/59] Improved andika() output --- evaluator/builtins.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/evaluator/builtins.go b/evaluator/builtins.go index b08347b..67a2a22 100644 --- a/evaluator/builtins.go +++ b/evaluator/builtins.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "os" + "strings" "github.com/AvicennaJr/Nuru/object" ) @@ -93,12 +94,15 @@ var builtins = map[string]*object.Builtin{ if len(args) == 0 { fmt.Println("") } else { + var arr []string for _, arg := range args { if arg == nil { return newError("Hauwezi kufanya operesheni hii") } - fmt.Println(arg.Inspect()) + arr = append(arr, arg.Inspect()) } + str := strings.Join(arr, " ") + print(str + "\n") } return nil }, From 429d6a9585e5a2e5f61b3143f4fbc2623004004a Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Wed, 21 Dec 2022 23:49:40 +0300 Subject: [PATCH 21/59] Add 'ktk' keyword to check if item is in an object --- evaluator/evaluator.go | 77 ++++++++++++++++++++++++++++++++++++++++++ parser/parser.go | 2 ++ token/token.go | 2 ++ 3 files changed, 81 insertions(+) diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 09d9919..5cf0df6 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -316,6 +316,9 @@ func evalInfixExpression(operator string, left, right object.Object, line int) o case left.Type() == object.FLOAT_OBJ && right.Type() == object.INTEGER_OBJ: return evalFloatIntegerInfixExpression(operator, left, right, line) + case operator == "ktk": + return evalInExpression(left, right, line) + case operator == "==": return nativeBoolToBooleanObject(left == right) @@ -725,3 +728,77 @@ func evalBreak(node *ast.Break) object.Object { func evalContinue(node *ast.Continue) object.Object { return CONTINUE } + +func evalInExpression(left, right object.Object, line int) object.Object { + switch right.(type) { + case *object.String: + return evalInStringExpression(left, right) + case *object.Array: + return evalInArrayExpression(left, right) + case *object.Dict: + return evalInDictExpression(left, right, line) + default: + return FALSE + } +} + +func evalInStringExpression(left, right object.Object) object.Object { + if left.Type() != object.STRING_OBJ { + return FALSE + } + leftVal := left.(*object.String) + rightVal := right.(*object.String) + found := strings.Contains(rightVal.Value, leftVal.Value) + return nativeBoolToBooleanObject(found) +} + +func evalInDictExpression(left, right object.Object, line int) object.Object { + leftVal, ok := left.(object.Hashable) + if !ok { + return newError("Huwezi kutumia kama 'key': %s", left.Type()) + } + key := leftVal.HashKey() + rightVal := right.(*object.Dict).Pairs + _, ok = rightVal[key] + return nativeBoolToBooleanObject(ok) +} + +func evalInArrayExpression(left, right object.Object) object.Object { + rightVal := right.(*object.Array) + switch leftVal := left.(type) { + case *object.Null: + for _, v := range rightVal.Elements { + if v.Type() == object.NULL_OBJ { + return TRUE + } + } + case *object.String: + for _, v := range rightVal.Elements { + if v.Type() == object.STRING_OBJ { + elem := v.(*object.String) + if elem.Value == leftVal.Value { + return TRUE + } + } + } + case *object.Integer: + for _, v := range rightVal.Elements { + if v.Type() == object.INTEGER_OBJ { + elem := v.(*object.Integer) + if elem.Value == leftVal.Value { + return TRUE + } + } + } + case *object.Float: + for _, v := range rightVal.Elements { + if v.Type() == object.FLOAT_OBJ { + elem := v.(*object.Float) + if elem.Value == leftVal.Value { + return TRUE + } + } + } + } + return FALSE +} diff --git a/parser/parser.go b/parser/parser.go index fff67d9..891a079 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -29,6 +29,7 @@ const ( var precedences = map[token.TokenType]int{ token.AND: COND, token.OR: COND, + token.IN: COND, token.ASSIGN: ASSIGN, token.EQ: EQUALS, token.NOT_EQ: EQUALS, @@ -119,6 +120,7 @@ func New(l *lexer.Lexer) *Parser { p.registerInfix(token.LPAREN, p.parseCallExpression) p.registerInfix(token.LBRACKET, p.parseIndexExpression) p.registerInfix(token.ASSIGN, p.parseAssignmentExpression) + p.registerInfix(token.IN, p.parseInfixExpression) p.postfixParseFns = make(map[token.TokenType]postfixParseFn) p.registerPostfix(token.PLUS_PLUS, p.parsePostfixExpression) diff --git a/token/token.go b/token/token.go index 97240fd..f54d3ed 100644 --- a/token/token.go +++ b/token/token.go @@ -66,6 +66,7 @@ const ( NULL = "TUPU" BREAK = "SUSA" CONTINUE = "ENDELEA" + IN = "KTK" ) var keywords = map[string]TokenType{ @@ -83,6 +84,7 @@ var keywords = map[string]TokenType{ "susa": BREAK, "endelea": CONTINUE, "tupu": NULL, + "ktk": IN, } func LookupIdent(ident string) TokenType { From 861366912482aebe1b04c52154708747ed185b1b Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Thu, 22 Dec 2022 15:31:30 +0300 Subject: [PATCH 22/59] Add For loop for iterables (string, lists and dicts) --- ast/ast.go | 20 +++++++ evaluator/evaluator.go | 118 +++++++++++++++++++++++++++++++++++++++-- object/object.go | 63 +++++++++++++++++++++- parser/parser.go | 87 ++++++++++++++++++++++++++++++ token/token.go | 2 + 5 files changed, 283 insertions(+), 7 deletions(-) diff --git a/ast/ast.go b/ast/ast.go index 0dd1acd..333c00c 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -418,3 +418,23 @@ type FloatLiteral struct { func (fl *FloatLiteral) expressionNode() {} func (fl *FloatLiteral) TokenLiteral() string { return fl.Token.Literal } func (fl *FloatLiteral) String() string { return fl.Token.Literal } + +type For struct { + Expression + Token token.Token + Identifier string // "i" + StarterName *Identifier // i = 0 + StarterValue Expression + Closer Expression // i++ + Condition Expression // i < 1 + Block *BlockStatement +} + +type ForIn struct { + Expression + Token token.Token + Key string + Value string + Iterable Expression + Block *BlockStatement +} diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 5cf0df6..2481502 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -122,6 +122,10 @@ func Eval(node ast.Node, env *object.Environment) object.Object { return evalContinue(node) case *ast.Null: return NULL + // case *ast.For: + // return evalForExpression(node, env) + case *ast.ForIn: + return evalForInExpression(node, env) case *ast.AssignmentExpression: left := Eval(node.Left, env) if isError(left) { @@ -133,9 +137,9 @@ func Eval(node ast.Node, env *object.Environment) object.Object { return value } - // This is a very smart way to assign operators like +=, -= etc + // This is an easy way to assign operators like +=, -= etc // I'm surprised it work at the first try lol - // basically divide the += to + and =, take the + only and + // basically separate the += to + and =, take the + only and // then perform the operation as normal op := node.Token.Literal if len(op) >= 2 { @@ -473,7 +477,7 @@ func evalBooleanInfixExpression(operator string, left, right object.Object, line func evalPostfixExpression(env *object.Environment, operator string, node *ast.PostfixExpression) object.Object { val, ok := env.Get(node.Token.Literal) if !ok { - return newError("Tumia kitambulishi cha namba, sio %s", node.Token.Type) + return newError("Tumia KITAMBULISHI CHA NAMBA AU DESIMALI, sio %s", node.Token.Type) } switch operator { case "++": @@ -481,8 +485,11 @@ func evalPostfixExpression(env *object.Environment, operator string, node *ast.P case *object.Integer: v := arg.Value + 1 return env.Set(node.Token.Literal, &object.Integer{Value: v}) + case *object.Float: + v := arg.Value + 1 + return env.Set(node.Token.Literal, &object.Float{Value: v}) default: - return newError("%s sio kitambulishi cha namba. Tumia '++' na kitambulishi cha namba tu.\nMfano:\tacha i = 2; i++", node.Token.Literal) + return newError("%s sio kitambulishi cha namba. Tumia '++' na kitambulishi cha namba au desimali.\nMfano:\tacha i = 2; i++", node.Token.Literal) } case "--": @@ -490,8 +497,11 @@ func evalPostfixExpression(env *object.Environment, operator string, node *ast.P case *object.Integer: v := arg.Value - 1 return env.Set(node.Token.Literal, &object.Integer{Value: v}) + case *object.Float: + v := arg.Value - 1 + return env.Set(node.Token.Literal, &object.Float{Value: v}) default: - return newError("%s sio kitambulishi cha namba. Tumia '--' na kitambulishi cha namba tu.\nMfano:\tacha i = 2; i++", node.Token.Literal) + return newError("%s sio kitambulishi cha namba. Tumia '--' na kitambulishi cha namba au desimali.\nMfano:\tacha i = 2; i++", node.Token.Literal) } default: return newError("Haifahamiki: %s", operator) @@ -802,3 +812,101 @@ func evalInArrayExpression(left, right object.Object) object.Object { } return FALSE } + +// func evalForExpression(fe *ast.For, env *object.Environment) object.Object { +// obj, ok := env.Get(fe.Identifier) +// defer func() { // stay safe and not reassign an existing variable +// if ok { +// env.Set(fe.Identifier, obj) +// } +// }() +// val := Eval(fe.StarterValue, env) +// if isError(val) { +// return val +// } + +// env.Set(fe.StarterName.Value, val) + +// // err := Eval(fe.Starter, env) +// // if isError(err) { +// // return err +// // } +// for { +// evaluated := Eval(fe.Condition, env) +// if isError(evaluated) { +// return evaluated +// } +// if !isTruthy(evaluated) { +// break +// } +// res := Eval(fe.Block, env) +// if isError(res) { +// return res +// } +// if res.Type() == object.BREAK_OBJ { +// break +// } +// if res.Type() == object.CONTINUE_OBJ { +// err := Eval(fe.Closer, env) +// if isError(err) { +// return err +// } +// continue +// } +// if res.Type() == object.RETURN_VALUE_OBJ { +// return res +// } +// err := Eval(fe.Closer, env) +// if isError(err) { +// return err +// } +// } +// return NULL +// } + +func evalForInExpression(fie *ast.ForIn, env *object.Environment) object.Object { + iterable := Eval(fie.Iterable, env) + existingKeyIdentifier, okk := env.Get(fie.Key) // again, stay safe + existingValueIdentifier, okv := env.Get(fie.Value) + defer func() { // restore them later on + if okk { + env.Set(fie.Key, existingKeyIdentifier) + } + if okv { + env.Set(fie.Value, existingValueIdentifier) + } + }() + switch i := iterable.(type) { + case object.Iterable: + defer func() { + i.Reset() + }() + return loopIterable(i.Next, env, fie) + default: + return newError("%s is a %s, not an iterable, cannot be used in for loop", i.Inspect(), i.Type()) + } +} + +func loopIterable(next func() (object.Object, object.Object), env *object.Environment, fi *ast.ForIn) object.Object { + k, v := next() + for k != nil && v != nil { + env.Set(fi.Key, k) + env.Set(fi.Value, v) + res := Eval(fi.Block, env) + if isError(res) { + return res + } + if res.Type() == object.BREAK_OBJ { + break + } + if res.Type() == object.CONTINUE_OBJ { + k, v = next() + continue + } + if res.Type() == object.RETURN_VALUE_OBJ { + return res + } + k, v = next() + } + return NULL +} diff --git a/object/object.go b/object/object.go index d1c6523..305bfb9 100644 --- a/object/object.go +++ b/object/object.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "hash/fnv" + "sort" "strconv" "strings" @@ -108,11 +109,23 @@ func (f *Function) Inspect() string { } type String struct { - Value string + Value string + offset int } func (s *String) Inspect() string { return s.Value } func (s *String) Type() ObjectType { return STRING_OBJ } +func (s *String) Next() (Object, Object) { + offset := s.offset + if len(s.Value) > offset { + s.offset = offset + 1 + return &Integer{Value: int64(offset)}, &String{Value: string(s.Value[offset])} + } + return nil, nil +} +func (s *String) Reset() { + s.offset = 0 +} type BuiltinFunction func(args ...Object) Object @@ -125,6 +138,7 @@ func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ } type Array struct { Elements []Object + offset int } func (ao *Array) Type() ObjectType { return ARRAY_OBJ } @@ -143,6 +157,19 @@ func (ao *Array) Inspect() string { return out.String() } +func (ao *Array) Next() (Object, Object) { + idx := ao.offset + if len(ao.Elements) > idx { + ao.offset = idx + 1 + return &Integer{Value: int64(idx)}, ao.Elements[idx] + } + return nil, nil +} + +func (ao *Array) Reset() { + ao.offset = 0 +} + type HashKey struct { Type ObjectType Value uint64 @@ -183,7 +210,8 @@ type DictPair struct { } type Dict struct { - Pairs map[HashKey]DictPair + Pairs map[HashKey]DictPair + offset int } func (d *Dict) Type() ObjectType { return DICT_OBJ } @@ -203,6 +231,31 @@ func (d *Dict) Inspect() string { return out.String() } +func (d *Dict) Next() (Object, Object) { + idx := 0 + dict := make(map[string]DictPair) + var keys []string + for _, v := range d.Pairs { + dict[v.Key.Inspect()] = v + keys = append(keys, v.Key.Inspect()) + } + + sort.Strings(keys) + + for _, k := range keys { + if d.offset == idx { + d.offset += 1 + return dict[k].Key, dict[k].Value + } + idx += 1 + } + return nil, nil +} + +func (d *Dict) Resest() { + d.offset = 0 +} + type Hashable interface { HashKey() HashKey } @@ -216,3 +269,9 @@ type Break struct{} func (b *Break) Type() ObjectType { return BREAK_OBJ } func (b *Break) Inspect() string { return "break" } + +// Iterable interface for dicts, strings and arrays +type Iterable interface { + Next() (Object, Object) + Reset() +} diff --git a/parser/parser.go b/parser/parser.go index 891a079..82a134a 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -96,6 +96,7 @@ func New(l *lexer.Lexer) *Parser { p.registerPrefix(token.LBRACE, p.parseDictLiteral) p.registerPrefix(token.WHILE, p.parseWhileExpression) p.registerPrefix(token.NULL, p.parseNull) + p.registerPrefix(token.FOR, p.parseForExpression) p.infixParseFns = make(map[token.TokenType]infixParseFn) p.registerInfix(token.AND, p.parseInfixExpression) @@ -629,3 +630,89 @@ func (p *Parser) parseContinue() *ast.Continue { } return stmt } + +func (p *Parser) parseForExpression() ast.Expression { + expression := &ast.For{Token: p.curToken} + p.nextToken() + if !p.curTokenIs(token.IDENT) { + return nil + } + if !p.peekTokenIs(token.ASSIGN) { + return p.parseForInExpression(expression) + } + + // In future will allow: kwa i = 0; i<10; i++ {andika(i)} + // expression.Identifier = p.curToken.Literal + // expression.StarterName = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + // if expression.StarterName == nil { + // return nil + // } + // if !p.expectPeek(token.ASSIGN) { + // return nil + // } + + // p.nextToken() + + // expression.StarterValue = p.parseExpression(LOWEST) + // // expression.Starter = p.parseExpression(LOWEST) + // if expression.StarterValue == nil { + // return nil + // } + // p.nextToken() + // for p.curTokenIs(token.SEMICOLON) { + // p.nextToken() + // } + // expression.Condition = p.parseExpression(LOWEST) + // if expression.Condition == nil { + // return nil + // } + // p.nextToken() + // for p.curTokenIs(token.SEMICOLON) { + // p.nextToken() + // } + // expression.Closer = p.parseExpression(LOWEST) + // if expression.Closer == nil { + // return nil + // } + // p.nextToken() + // for p.curTokenIs(token.SEMICOLON) { + // p.nextToken() + // } + // if !p.curTokenIs(token.LBRACE) { + // return nil + // } + // expression.Block = p.parseBlockStatement() + // return expression + return nil +} + +func (p *Parser) parseForInExpression(initialExpression *ast.For) ast.Expression { + expression := &ast.ForIn{Token: initialExpression.Token} + if !p.curTokenIs(token.IDENT) { + return nil + } + val := p.curToken.Literal + var key string + p.nextToken() + if p.curTokenIs(token.COMMA) { + p.nextToken() + if !p.curTokenIs(token.IDENT) { + return nil + } + key = val + val = p.curToken.Literal + p.nextToken() + } + expression.Key = key + expression.Value = val + if !p.curTokenIs(token.IN) { + return nil + } + p.nextToken() + expression.Iterable = p.parseExpression(LOWEST) + if !p.expectPeek(token.LBRACE) { + return nil + } + expression.Block = p.parseBlockStatement() + return expression +} diff --git a/token/token.go b/token/token.go index f54d3ed..4bdb2f5 100644 --- a/token/token.go +++ b/token/token.go @@ -67,6 +67,7 @@ const ( BREAK = "SUSA" CONTINUE = "ENDELEA" IN = "KTK" + FOR = "KWA" ) var keywords = map[string]TokenType{ @@ -85,6 +86,7 @@ var keywords = map[string]TokenType{ "endelea": CONTINUE, "tupu": NULL, "ktk": IN, + "kwa": FOR, } func LookupIdent(ident string) TokenType { From 9f96e029ffd14389cdfb7b899882b697ecde2c33 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Thu, 22 Dec 2022 15:56:55 +0300 Subject: [PATCH 23/59] Enforcing strict nomenclature 'acha' is permanently replaced with 'fanya' 'fn' is permanently replaced with 'unda' 'susa' is permanently replaced with 'vunja' --- token/token.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/token/token.go b/token/token.go index 4bdb2f5..e890931 100644 --- a/token/token.go +++ b/token/token.go @@ -71,9 +71,7 @@ const ( ) var keywords = map[string]TokenType{ - "fn": FUNCTION, "unda": FUNCTION, - "acha": LET, "fanya": LET, "kweli": TRUE, "sikweli": FALSE, @@ -82,7 +80,7 @@ var keywords = map[string]TokenType{ "sivyo": ELSE, "wakati": WHILE, "rudisha": RETURN, - "susa": BREAK, + "vunja": BREAK, "endelea": CONTINUE, "tupu": NULL, "ktk": IN, From 9ae4eefcae890bf43814db1a9082afcf23446555 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Thu, 22 Dec 2022 15:58:56 +0300 Subject: [PATCH 24/59] Updated README to reflect new nomenclature --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index ae524f1..57b4ddc 100644 --- a/README.md +++ b/README.md @@ -100,11 +100,11 @@ Nuru, although still in its early stage, intends to be a fully functional progra ### Defining A Variable -To initiliaze a variable use the `acha` keyword: +To initiliaze a variable use the `fanya` keyword: ``` -acha x = 2; -acha y = 3; +fanya x = 2; +fanya y = 3; andika(x*y) // output is 6 ``` @@ -161,7 +161,7 @@ DICT | `{} {"a": 3, 1: "moja", kweli: 2}` | Keys can be int, string This is how you define a function in Nuru: ``` -acha jumlisha = fn(x, y) { +fanya jumlisha = unda(x, y) { rudisha x + y } @@ -171,7 +171,7 @@ andika(jumlisha(3,4)) Nuru also supports recursion: ``` -acha fibo = fn(x) { +fanya fibo = unda(x) { kama (x == 0) { rudisha 0; } au kama (x == 1) { @@ -201,7 +201,7 @@ kama (2<1) { Nuru's while loop syntax is as follows: ``` -acha i = 10 +fanya i = 10 wakati (i > 0) { andika(i) @@ -213,7 +213,7 @@ wakati (i > 0) { This is how you initiliaze and perform other array operations in Nuru: ``` -acha arr = [] +fanya arr = [] // To add elements @@ -221,9 +221,9 @@ sukuma(arr, 2) andika(arr) // output = [2] // Add two Arrays -acha arr2 = [1,2,3,4] +fanya arr2 = [1,2,3,4] -acha arr3 = arr1 + arr2 +fanya arr3 = arr1 + arr2 andika(arr3) // output = [2,1,2,3,4] @@ -242,7 +242,7 @@ andika(arr[3]) // output = 3 Nuru also supports dictionaris and you can do a lot with them as follows: ``` -acha mtu = {"jina": "Mojo", "kabila": "Mnyakusa"} +fanya mtu = {"jina": "Mojo", "kabila": "Mnyakusa"} // get value from key andika(mtu["jina"]) // output = Mojo @@ -263,7 +263,7 @@ andika(mtu) // output = {"jina": "Avicenna", "kabila": "Mnyakusa", "anapoishi": // You can also add two Dictionaries -acha kazi = {"kazi": "jambazi"} +fanya kazi = {"kazi": "jambazi"} mtu = mtu + kazi @@ -274,7 +274,7 @@ andika(mtu) // output = {"jina": "Avicenna", "kabila": "Mnyakusa", "anapoishi": In Nuru you can get input from users using the `jaza()` keyword as follows: ``` -acha jina = jaza("Unaitwa nani? ") // will prompt for input +fanya jina = jaza("Unaitwa nani? ") // will prompt for input andika("Habari yako " + jina) ``` From f79647e5bce586619b84b05237e5bfc05069fd48 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Thu, 22 Dec 2022 22:43:56 +0300 Subject: [PATCH 25/59] Added switch statements --- ast/ast.go | 51 +++++++++++++++++++++ evaluator/evaluator.go | 32 +++++++++++-- evaluator/evaluator_test.go | 54 +++++++++++----------- object/object.go | 2 +- parser/parser.go | 89 ++++++++++++++++++++++++++++++++++++- parser/parser_test.go | 18 ++++---- token/token.go | 8 +++- 7 files changed, 212 insertions(+), 42 deletions(-) diff --git a/ast/ast.go b/ast/ast.go index 333c00c..810ccfd 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -438,3 +438,54 @@ type ForIn struct { Iterable Expression Block *BlockStatement } + +type CaseExpression struct { + Token token.Token + Default bool + Expr []Expression + Block *BlockStatement +} + +func (ce *CaseExpression) expressionNode() {} +func (ce *CaseExpression) TokenLiteral() string { return ce.Token.Literal } +func (ce *CaseExpression) String() string { + var out bytes.Buffer + + if ce.Default { + out.WriteString("default ") + } else { + out.WriteString("case ") + + tmp := []string{} + for _, exp := range ce.Expr { + tmp = append(tmp, exp.String()) + } + out.WriteString(strings.Join(tmp, ",")) + } + out.WriteString(ce.Block.String()) + return out.String() +} + +type SwitchExpression struct { + Token token.Token + Value Expression + Choices []*CaseExpression +} + +func (se *SwitchExpression) expressionNode() {} +func (se *SwitchExpression) TokenLiteral() string { return se.Token.Literal } +func (se *SwitchExpression) String() string { + var out bytes.Buffer + out.WriteString("\nswitch (") + out.WriteString(se.Value.String()) + out.WriteString(")\n{\n") + + for _, tmp := range se.Choices { + if tmp != nil { + out.WriteString(tmp.String()) + } + } + out.WriteString("}\n") + + return out.String() +} diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 2481502..4445eb6 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -120,12 +120,14 @@ func Eval(node ast.Node, env *object.Environment) object.Object { return evalBreak(node) case *ast.Continue: return evalContinue(node) + case *ast.SwitchExpression: + return evalSwitchStatement(node, env) case *ast.Null: return NULL // case *ast.For: // return evalForExpression(node, env) case *ast.ForIn: - return evalForInExpression(node, env) + return evalForInExpression(node, env, node.Token.Line) case *ast.AssignmentExpression: left := Eval(node.Left, env) if isError(left) { @@ -864,7 +866,7 @@ func evalInArrayExpression(left, right object.Object) object.Object { // return NULL // } -func evalForInExpression(fie *ast.ForIn, env *object.Environment) object.Object { +func evalForInExpression(fie *ast.ForIn, env *object.Environment, line int) object.Object { iterable := Eval(fie.Iterable, env) existingKeyIdentifier, okk := env.Get(fie.Key) // again, stay safe existingValueIdentifier, okv := env.Get(fie.Value) @@ -883,7 +885,7 @@ func evalForInExpression(fie *ast.ForIn, env *object.Environment) object.Object }() return loopIterable(i.Next, env, fie) default: - return newError("%s is a %s, not an iterable, cannot be used in for loop", i.Inspect(), i.Type()) + return newError("Mstari %d: Huwezi kufanya operesheni hii na %s", line, i.Type()) } } @@ -910,3 +912,27 @@ func loopIterable(next func() (object.Object, object.Object), env *object.Enviro } return NULL } + +func evalSwitchStatement(se *ast.SwitchExpression, env *object.Environment) object.Object { + obj := Eval(se.Value, env) + for _, opt := range se.Choices { + + if opt.Default { + continue + } + for _, val := range opt.Expr { + out := Eval(val, env) + if obj.Type() == out.Type() && obj.Inspect() == out.Inspect() { + blockOut := evalBlockStatement(opt.Block, env) + return blockOut + } + } + } + for _, opt := range se.Choices { + if opt.Default { + out := evalBlockStatement(opt.Block, env) + return out + } + } + return nil +} diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index 731e451..8fe8458 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -217,8 +217,8 @@ kama (10 > 1) { "Mstari 0: Operesheni Haielweki: NENO - NENO", }, { - `{"jina": "Avi"}[fn(x) {x}];`, - "Mstari 0: Samahani, FUNCTION haitumiki kama key", + `{"jina": "Avi"}[unda(x) {x}];`, + "Mstari 0: Samahani, UNDO (FUNCTION) haitumiki kama key", }, } @@ -242,10 +242,10 @@ func TestLetStatement(t *testing.T) { input string expected int64 }{ - {"acha a = 5; a;", 5}, - {"acha a = 5 * 5; a;", 25}, - {"acha a = 5; acha b = a; b;", 5}, - {"acha a = 5; acha b = a; acha c = a + b + 5; c;", 15}, + {"fanya a = 5; a;", 5}, + {"fanya a = 5 * 5; a;", 25}, + {"fanya a = 5; fanya b = a; b;", 5}, + {"fanya a = 5; fanya b = a; fanya c = a + b + 5; c;", 15}, } for _, tt := range tests { @@ -254,26 +254,26 @@ func TestLetStatement(t *testing.T) { } func TestFunctionObject(t *testing.T) { - input := "fn(x) { x + 2 ;};" + input := "unda(x) { x + 2 ;};" evaluated := testEval(input) - fn, ok := evaluated.(*object.Function) + unda, ok := evaluated.(*object.Function) if !ok { t.Fatalf("object is not a Function, got=%T(%+v)", evaluated, evaluated) } - if len(fn.Parameters) != 1 { - t.Fatalf("function haas wrong paramters,Parameters=%+v", fn.Parameters) + if len(unda.Parameters) != 1 { + t.Fatalf("function haas wrong paramters,Parameters=%+v", unda.Parameters) } - if fn.Parameters[0].String() != "x" { - t.Fatalf("parameter is not x, got=%q", fn.Parameters[0]) + if unda.Parameters[0].String() != "x" { + t.Fatalf("parameter is not x, got=%q", unda.Parameters[0]) } expectedBody := "(x + 2)" - if fn.Body.String() != expectedBody { - t.Fatalf("body is not %q, got=%q", expectedBody, fn.Body.String()) + if unda.Body.String() != expectedBody { + t.Fatalf("body is not %q, got=%q", expectedBody, unda.Body.String()) } } @@ -282,12 +282,12 @@ func TestFunctionApplication(t *testing.T) { input string expected int64 }{ - {"acha mfano = fn(x) {x;}; mfano(5);", 5}, - {"acha mfano = fn(x) {rudisha x;}; mfano(5);", 5}, - {"acha double = fn(x) { x * 2;}; double(5);", 10}, - {"acha add = fn(x, y) {x + y;}; add(5,5);", 10}, - {"acha add = fn(x, y) {x + y;}; add(5 + 5, add(5, 5));", 20}, - {"fn(x) {x;}(5)", 5}, + {"fanya mfano = unda(x) {x;}; mfano(5);", 5}, + {"fanya mfano = unda(x) {rudisha x;}; mfano(5);", 5}, + {"fanya double = unda(x) { x * 2;}; double(5);", 10}, + {"fanya add = unda(x, y) {x + y;}; add(5,5);", 10}, + {"fanya add = unda(x, y) {x + y;}; add(5 + 5, add(5, 5));", 20}, + {"unda(x) {x;}(5)", 5}, } for _, tt := range tests { @@ -297,11 +297,11 @@ func TestFunctionApplication(t *testing.T) { func TestClosures(t *testing.T) { input := ` -acha newAdder = fn(x) { - fn(y) { x + y}; +fanya newAdder = unda(x) { + unda(y) { x + y}; }; -acha addTwo = newAdder(2); +fanya addTwo = newAdder(2); addTwo(2); ` testIntegerObject(t, testEval(input), 4) @@ -403,11 +403,11 @@ func TestArrayIndexExpressions(t *testing.T) { 3, }, { - "acha i = 0; [1][i];", + "fanya i = 0; [1][i];", 1, }, { - "acha myArr = [1, 2, 3]; myArr[2];", + "fanya myArr = [1, 2, 3]; myArr[2];", 3, }, { @@ -432,7 +432,7 @@ func TestArrayIndexExpressions(t *testing.T) { } func TestDictLiterals(t *testing.T) { - input := `acha two = "two"; + input := `fanya two = "two"; { "one": 10 - 9, two: 1 +1, @@ -485,7 +485,7 @@ func TestDictIndexExpression(t *testing.T) { nil, }, { - `acha key = "foo"; {"foo": 5}[key]`, + `fanya key = "foo"; {"foo": 5}[key]`, 5, }, { diff --git a/object/object.go b/object/object.go index 305bfb9..3090165 100644 --- a/object/object.go +++ b/object/object.go @@ -26,7 +26,7 @@ const ( ARRAY_OBJ = "ORODHA" DICT_OBJ = "KAMUSI" CONTINUE_OBJ = "ENDELEA" - BREAK_OBJ = "SUSA" + BREAK_OBJ = "VUNJA" ) type Object interface { diff --git a/parser/parser.go b/parser/parser.go index 82a134a..3d418a8 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -97,6 +97,7 @@ func New(l *lexer.Lexer) *Parser { p.registerPrefix(token.WHILE, p.parseWhileExpression) p.registerPrefix(token.NULL, p.parseNull) p.registerPrefix(token.FOR, p.parseForExpression) + p.registerPrefix(token.SWITCH, p.parseSwitchStatement) p.infixParseFns = make(map[token.TokenType]infixParseFn) p.registerInfix(token.AND, p.parseInfixExpression) @@ -456,7 +457,12 @@ func (p *Parser) parseBlockStatement() *ast.BlockStatement { p.nextToken() - for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) { + for !p.curTokenIs(token.RBRACE) { + if p.curTokenIs(token.EOF) { + msg := fmt.Sprintf("Mstari %d: Hukufunga Mabano '}'", p.curToken.Line) + p.errors = append(p.errors, msg) + return nil + } stmt := p.parseStatement() block.Statements = append(block.Statements, stmt) p.nextToken() @@ -716,3 +722,84 @@ func (p *Parser) parseForInExpression(initialExpression *ast.For) ast.Expression expression.Block = p.parseBlockStatement() return expression } + +func (p *Parser) parseSwitchStatement() ast.Expression { + expression := &ast.SwitchExpression{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + p.nextToken() + expression.Value = p.parseExpression(LOWEST) + + if expression.Value == nil { + return nil + } + + if !p.expectPeek(token.RPAREN) { + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + p.nextToken() + + for !p.curTokenIs(token.RBRACE) { + + if p.curTokenIs(token.EOF) { + msg := fmt.Sprintf("Mstari %d: Haukufunga ENDAPO (SWITCH)", p.curToken.Line) + p.errors = append(p.errors, msg) + return nil + } + tmp := &ast.CaseExpression{Token: p.curToken} + + if p.curTokenIs(token.DEFAULT) { + + tmp.Default = true + + } else if p.curTokenIs(token.CASE) { + + p.nextToken() + + if p.curTokenIs(token.DEFAULT) { + tmp.Default = true + } else { + tmp.Expr = append(tmp.Expr, p.parseExpression(LOWEST)) + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + tmp.Expr = append(tmp.Expr, p.parseExpression(LOWEST)) + } + } + } else { + msg := fmt.Sprintf("Mstari %d: Tulitegemea Kauli IKIWA (CASE) au KAWAIDA (DEFAULT) lakini tumepewa: %s", p.curToken.Line, p.curToken.Type) + p.errors = append(p.errors, msg) + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + tmp.Block = p.parseBlockStatement() + p.nextToken() + expression.Choices = append(expression.Choices, tmp) + } + + count := 0 + for _, c := range expression.Choices { + if c.Default { + count++ + } + } + if count > 1 { + msg := fmt.Sprintf("Kauli ENDAPO (SWITCH) hua na kauli 'KAWAIDA' (DEFAULT) moja tu! Wewe umeweka %d", count) + p.errors = append(p.errors, msg) + return nil + + } + return expression + +} diff --git a/parser/parser_test.go b/parser/parser_test.go index 19bd04e..e205a4d 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -14,9 +14,9 @@ func TestLetStatements(t *testing.T) { expectedIdentifier string expectedValue interface{} }{ - {"acha x = 5;", "x", 5}, - {"acha y = x;", "y", "x"}, - {"acha bangi = y;", "bangi", "y"}, + {"fanya x = 5;", "x", 5}, + {"fanya y = x;", "y", "x"}, + {"fanya bangi = y;", "bangi", "y"}, } for _, tt := range tests { @@ -43,8 +43,8 @@ func TestLetStatements(t *testing.T) { } func testLetStatement(t *testing.T, s ast.Statement, name string) bool { - if s.TokenLiteral() != "acha" { - t.Errorf("s.TokenLiteral not 'acha', got = %q", s.TokenLiteral()) + if s.TokenLiteral() != "fanya" { + t.Errorf("s.TokenLiteral not 'fanya', got = %q", s.TokenLiteral()) return false } @@ -633,7 +633,7 @@ func TestIfElseExpression(t *testing.T) { } func TestFunctionLiteralParsing(t *testing.T) { - input := `fn(x, y) {x + y}` + input := `unda(x, y) {x + y}` l := lexer.New(input) p := New(l) @@ -678,9 +678,9 @@ func TestFunctionParameterParsing(t *testing.T) { input string expectedParams []string }{ - {input: "fn() {};", expectedParams: []string{}}, - {input: "fn(x) {};", expectedParams: []string{"x"}}, - {input: "fn(x, y, z) {};", expectedParams: []string{"x", "y", "z"}}, + {input: "unda() {};", expectedParams: []string{}}, + {input: "unda(x) {};", expectedParams: []string{"x"}}, + {input: "unda(x, y, z) {};", expectedParams: []string{"x", "y", "z"}}, } for _, tt := range tests { diff --git a/token/token.go b/token/token.go index e890931..39b0895 100644 --- a/token/token.go +++ b/token/token.go @@ -64,10 +64,13 @@ const ( RETURN = "RUDISHA" WHILE = "WAKATI" NULL = "TUPU" - BREAK = "SUSA" + BREAK = "VUNJA" CONTINUE = "ENDELEA" IN = "KTK" FOR = "KWA" + SWITCH = "ENDAPO" + CASE = "IKIWA" + DEFAULT = "KAWAIDA" ) var keywords = map[string]TokenType{ @@ -85,6 +88,9 @@ var keywords = map[string]TokenType{ "tupu": NULL, "ktk": IN, "kwa": FOR, + "endapo": SWITCH, + "ikiwa": CASE, + "kawaida": DEFAULT, } func LookupIdent(ident string) TokenType { From 96a7f3fa6cf3da3eb1f41e3cfc55885aa28ae183 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Fri, 23 Dec 2022 00:26:56 +0300 Subject: [PATCH 26/59] Fixed null pointer error on iterables --- evaluator/evaluator.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 4445eb6..a2a1251 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -898,15 +898,17 @@ func loopIterable(next func() (object.Object, object.Object), env *object.Enviro if isError(res) { return res } - if res.Type() == object.BREAK_OBJ { - break - } - if res.Type() == object.CONTINUE_OBJ { - k, v = next() - continue - } - if res.Type() == object.RETURN_VALUE_OBJ { - return res + if res != nil { + if res.Type() == object.BREAK_OBJ { + break + } + if res.Type() == object.CONTINUE_OBJ { + k, v = next() + continue + } + if res.Type() == object.RETURN_VALUE_OBJ { + return res + } } k, v = next() } From 4400076e05ad25d0ce543b3876aa3a89b835548e Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Fri, 23 Dec 2022 16:08:36 +0300 Subject: [PATCH 27/59] Add extension validator Only files ending with .sw or .nr are valid --- evaluator/evaluator.go | 5 +- examples/sorting_algorithm.nr | 36 ++++++------ examples/sudoku_solver.nr | 101 ++++++++++++++++++++++++++++++++++ main.go | 57 ++++++++++++------- 4 files changed, 160 insertions(+), 39 deletions(-) create mode 100644 examples/sudoku_solver.nr diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index a2a1251..9dec937 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -227,7 +227,7 @@ func evalPrefixExpression(operator string, right object.Object, line int) object case "-": return evalMinusPrefixOperatorExpression(right, line) default: - return newError("Mstari %d: peresheni haieleweki: %s%s", line, operator, right.Type()) + return newError("Mstari %d: Operesheni haieleweki: %s%s", line, operator, right.Type()) } } @@ -259,6 +259,9 @@ func evalMinusPrefixOperatorExpression(right object.Object, line int) object.Obj } func evalInfixExpression(operator string, left, right object.Object, line int) object.Object { + if left == nil { + return newError("Mstari %d: Umekosea hapa", line) + } switch { case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: return evalStringInfixExpression(operator, left, right, line) diff --git a/examples/sorting_algorithm.nr b/examples/sorting_algorithm.nr index 5241c31..10d6131 100644 --- a/examples/sorting_algorithm.nr +++ b/examples/sorting_algorithm.nr @@ -1,4 +1,4 @@ -* +/* ############ Sorting Algorithm ############## By @VictorKariuki @@ -8,8 +8,8 @@ ############################################# */ -acha slice = fn(arr,start, end) { - acha result = [] +fanya slice = unda(arr,start, end) { + fanya result = [] wakati (start < end) { result = result + [arr[start]] start = start + 1 @@ -17,12 +17,12 @@ acha slice = fn(arr,start, end) { rudisha result } -acha merge = fn(left, right) { - acha result = [] - acha lLen = idadi(left) - acha rLen = idadi(right) - acha l = 0 - acha r = 0 +fanya merge = unda(left, right) { + fanya result = [] + fanya lLen = idadi(left) + fanya rLen = idadi(right) + fanya l = 0 + fanya r = 0 wakati (l < lLen && r < rLen) { kama (left[l] < right[r]) { result = result + [left[l]] @@ -36,28 +36,28 @@ acha merge = fn(left, right) { } -acha mergeSort = fn(arr){ - acha len = idadi(arr) +fanya mergeSort = unda(arr){ + fanya len = idadi(arr) andika("arr is ", arr," of length ", len) kama (len < 2) { rudisha arr } andika("len is greater than or == to 2", len > 1) - acha mid = (len / 2) + fanya mid = (len / 2) andika("arr has a mid point of ", mid) - acha left = slice(arr, 0, mid) - acha right = slice(arr, mid, len) + fanya left = slice(arr, 0, mid) + fanya right = slice(arr, mid, len) andika("left slice is ", left) andika("right slice is ", right) - acha sortedLeft = mergeSort(left) - acha sortedRight = mergeSort(right) + fanya sortedLeft = mergeSort(left) + fanya sortedRight = mergeSort(right) andika("sortedLeft is ", sortedLeft) andika("sortedRight is ", sortedRight) rudisha merge(sortedLeft, sortedRight) } -acha arr = [6, 5, 3, 1, 8, 7, 2, 4] -acha sortedArray = mergeSort(arr) +fanya arr = [6, 5, 3, 1, 8, 7, 2, 4] +fanya sortedArray = mergeSort(arr) andika(sortedArray) \ No newline at end of file diff --git a/examples/sudoku_solver.nr b/examples/sudoku_solver.nr new file mode 100644 index 0000000..de5d3a6 --- /dev/null +++ b/examples/sudoku_solver.nr @@ -0,0 +1,101 @@ +/*########### Backtracking Algorithm ############## + + By @VictorKariuki + + https://github.com/VictorKariuki + +NURU program to solve Sudoku using Backtracking Algorithm + +The sudoku puzzle is represented as a 2D array. The empty +cells are represented by 0. The algorithm works by trying +out all possible numbers for an empty cell. If the number +is valid, it is placed in the cell. If the number is invalid, +the algorithm backtracks to the previous cell and tries +another number. The algorithm terminates when all cells +are filled. The algorithm is implemented in the solveSudoku +function. The isValid function checks kama a number is +valid in a given cell. The printSudoku function prints +the sudoku puzzle. The solveSudoku function solves the +sudoku puzzle. The main function initializes the sudoku +puzzle and calls the solveSudoku function. + +#################################################*/ + + +fanya printing = unda(sudoku) { + fanya row = 0 + wakati (row < 9){ + andika(sudoku[row]) + row++ + } +} + +fanya sudoku = [[3, 0, 6, 5, 0, 8, 4, 0, 0],[5, 2, 0, 0, 0, 0, 0, 0, 0],[0, 8, 7, 0, 0, 0, 0, 3, 1],[0, 0, 3, 0, 1, 0, 0, 8, 0],[9, 0, 0, 8, 6, 3, 0, 0, 5],[0, 5, 0, 0, 9, 0, 6, 0, 0],[1, 3, 0, 0, 0, 0, 2, 5, 0],[0, 0, 0, 0, 0, 0, 0, 7, 4],[0, 0, 5, 2, 0, 6, 3, 0, 0]] + + + +fanya isSafe = unda(grid, row, col, num) { + kwa x ktk [0,1,2,3,4,5,6,7,8] { + kama (grid[row][x] == num) { + rudisha sikweli + } + } + + kwa x ktk [0,1,2,3,4,5,6,7,8] { + kama (grid[x][col] == num) { + rudisha sikweli + } + } + + fanya startRow = row - row % 3 + fanya startCol = col - col % 3 + + kwa i ktk [0, 1, 2] { + kwa j ktk [0, 1, 2] { + kama (grid[i + startRow][j + startCol] == num) { + rudisha sikweli + } + } + } + + rudisha kweli +} + +fanya solveSudoku = unda(grid, row, col) { + kama (row == 8 && col == 9) { + rudisha kweli + } + + kama (col == 9) { + row += 1 + col = 0 + } + + kama (grid[row][col] > 0) { + rudisha solveSudoku(grid, row, col + 1) + } + + kwa num ktk [1,2,3,4,5,6,7,8,9] { + kama (isSafe(grid, row, col, num)) { + grid[row][col] = num + kama (solveSudoku(grid, row, col + 1)) { + rudisha kweli + } + } + + grid[row][col] = 0 + } + + rudisha sikweli +} +andika() +andika("----- PUZZLE TO SOLVE -----") +printing(sudoku) +kama (solveSudoku(sudoku, 0, 0)){ + andika() + andika("--------- SOLUTION --------") + printing(sudoku) + andika() +} sivyo { + andika("imeshindikana") +} \ No newline at end of file diff --git a/main.go b/main.go index 001130b..f9ba7a9 100644 --- a/main.go +++ b/main.go @@ -1,10 +1,10 @@ package main import ( - "flag" "fmt" "io/ioutil" "os" + "strings" "github.com/AvicennaJr/Nuru/repl" ) @@ -15,44 +15,61 @@ const ( █░░ █░█ █▀▀ █░█ ▄▀█   █▄█ ▄▀█   █▄░█ █░█ █▀█ █░█ █▄▄ █▄█ █▄█ █▀█ █▀█   ░█░ █▀█   █░▀█ █▄█ █▀▄ █▄█ - | Authored by Avicenna | + | Authored by Avicenna | v0.2.0 | ` - VERSION = "v0.1.5" ) func main() { - version := flag.Bool("v", false, "Onyesha version namba ya program") - flag.Parse() + // version := flag.Bool("v", false, "Onyesha version namba ya program") + // msaada := flag.Bool + // flag.Parse() - if *version { - fmt.Println(fmt.Sprintf("\x1b[%dm%s%s\x1b[0m", 32, "Nuru Programming Language || Version: ", VERSION)) - os.Exit(0) - } - args := flag.Args() + // if *version { + // fmt.Printf("\x1b[%dm%s%s\x1b[0m", 32, "Nuru Programming Language || Version: ", VERSION) + // os.Exit(0) + // } + args := os.Args + coloredLogo := fmt.Sprintf("\x1b[%dm%s\x1b[0m", 34, LOGO) - if len(args) < 1 { + if len(args) < 2 { - coloredLogo := fmt.Sprintf("\x1b[%dm%s\x1b[0m", 34, LOGO) fmt.Println(coloredLogo) fmt.Println("𝑯𝒂𝒃𝒂𝒓𝒊, 𝒌𝒂𝒓𝒊𝒃𝒖 𝒖𝒕𝒖𝒎𝒊𝒆 𝒍𝒖𝒈𝒉𝒂 𝒚𝒂 𝑵𝒖𝒓𝒖 ✨") fmt.Println("\nTumia exit() au toka() kuondoka") repl.Start(os.Stdin, os.Stdout) - } else if len(args) == 1 { + } - file := args[0] - contents, err := ioutil.ReadFile(file) - if err != nil { - fmt.Println(fmt.Sprintf("\x1b[%dm%s%s\x1b[0m", 31, "Error: Nimeshindwa kusoma file: ", args[0])) + if len(args) == 2 { + + switch args[1] { + case "msaada", "--msaada", "help", "--help", "-h": + fmt.Printf("\x1b[%dm%s\x1b[0m\n", 32, "\nTumia 'nuru' kuanza program\n\nAU\n\nTumia 'nuru' ikifuatiwa na jina la file.\n\n\tMfano:\tnuru fileYangu.nr") + os.Exit(0) + case "version", "--version", "-v", "v": + fmt.Println(coloredLogo) os.Exit(0) } - repl.Read(string(contents)) + file := args[1] + + if strings.HasSuffix(file, "nr") || strings.HasSuffix(file, ".sw") { + contents, err := ioutil.ReadFile(file) + if err != nil { + fmt.Printf("\x1b[%dm%s%s\x1b[0m\n", 31, "Error: Nimeshindwa kusoma file: ", args[0]) + os.Exit(0) + } + + repl.Read(string(contents)) + } else { + fmt.Printf("\x1b[%dm%s%s\x1b[0m", 31, file, " sii file sahihi. Tumia file la '.nr' au '.sw'\n") + os.Exit(0) + } } else { - fmt.Println(fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, "Error: Opereshen imeshindikana boss.")) - fmt.Println(fmt.Sprintf("\x1b[%dm%s\x1b[0m", 32, "\nTumia Command: 'nuru' kutumia program AU\nTumia Command: 'nuru' ikifuatwa na program file.\n\n\tMfano:\tnuru fileYangu.nr\n")) + fmt.Printf("\x1b[%dm%s\x1b[0m\n", 31, "Error: Operesheni imeshindikana boss.") + fmt.Printf("\x1b[%dm%s\x1b[0m\n", 32, "\nTumia 'nuru' kuprogram\n\nAU\n\nTumia 'nuru' ikifuatiwa na jina la file.\n\n\tMfano:\tnuru fileYangu.nr") os.Exit(0) } } From ce8fd1883b902413c418f2add0b258e7e74ee5d6 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Fri, 23 Dec 2022 16:10:38 +0300 Subject: [PATCH 28/59] Minor house cleaning --- main.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/main.go b/main.go index f9ba7a9..be2286b 100644 --- a/main.go +++ b/main.go @@ -21,14 +21,6 @@ const ( func main() { - // version := flag.Bool("v", false, "Onyesha version namba ya program") - // msaada := flag.Bool - // flag.Parse() - - // if *version { - // fmt.Printf("\x1b[%dm%s%s\x1b[0m", 32, "Nuru Programming Language || Version: ", VERSION) - // os.Exit(0) - // } args := os.Args coloredLogo := fmt.Sprintf("\x1b[%dm%s\x1b[0m", 34, LOGO) From 905c35177071db16f2fc7a3e06cee76e63115095 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Fri, 23 Dec 2022 16:26:15 +0300 Subject: [PATCH 29/59] Slightly improved help menu --- main.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index be2286b..4a6941a 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,7 @@ const ( func main() { args := os.Args - coloredLogo := fmt.Sprintf("\x1b[%dm%s\x1b[0m", 34, LOGO) + coloredLogo := fmt.Sprintf("\x1b[%dm%s\x1b[0m", 36, LOGO) if len(args) < 2 { @@ -36,10 +36,10 @@ func main() { if len(args) == 2 { switch args[1] { - case "msaada", "--msaada", "help", "--help", "-h": + case "msaada", "-msaada", "--msaada", "help", "-help", "--help", "-h": fmt.Printf("\x1b[%dm%s\x1b[0m\n", 32, "\nTumia 'nuru' kuanza program\n\nAU\n\nTumia 'nuru' ikifuatiwa na jina la file.\n\n\tMfano:\tnuru fileYangu.nr") os.Exit(0) - case "version", "--version", "-v", "v": + case "version", "-version", "--version", "-v", "v": fmt.Println(coloredLogo) os.Exit(0) } From 8503ad2e79f62cf1953cbeddf0204158cdfb5573 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Sat, 24 Dec 2022 23:15:37 +0300 Subject: [PATCH 30/59] Fixed typo when looping over dicts Additional typo fixes --- .gitignore | 2 +- ast/ast_test.go | 4 ++-- evaluator/builtins.go | 21 --------------------- evaluator/evaluator.go | 4 ++-- examples/example.nr | 22 +++++++++++----------- lexer/lexer.go | 4 ++-- lexer/lexer_test.go | 18 +++++++++--------- object/object.go | 4 ++-- parser/parser.go | 2 +- token/token.go | 2 +- 10 files changed, 31 insertions(+), 52 deletions(-) diff --git a/.gitignore b/.gitignore index 55c0ff1..f0c810b 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,4 @@ tests_random parser/tracingZeAnnoyingParser.go testTracing.go Notes.md - +nuru \ No newline at end of file diff --git a/ast/ast_test.go b/ast/ast_test.go index be9d6e3..c5ae5fa 100644 --- a/ast/ast_test.go +++ b/ast/ast_test.go @@ -10,7 +10,7 @@ func TestString(t *testing.T) { program := &Program{ Statements: []Statement{ &LetStatement{ - Token: token.Token{Type: token.LET, Literal: "acha"}, + Token: token.Token{Type: token.LET, Literal: "fanya"}, Name: &Identifier{ Token: token.Token{Type: token.IDENT, Literal: "myVar"}, Value: "myVar", @@ -23,7 +23,7 @@ func TestString(t *testing.T) { }, } - if program.String() != "acha myVar = anotherVar;" { + if program.String() != "fanya myVar = anotherVar;" { t.Errorf("program.String() wrong. got=%q", program.String()) } } diff --git a/evaluator/builtins.go b/evaluator/builtins.go index 67a2a22..6a24052 100644 --- a/evaluator/builtins.go +++ b/evaluator/builtins.go @@ -114,27 +114,6 @@ var builtins = map[string]*object.Builtin{ } return &object.String{Value: string(args[0].Type())} - // switch args[0].(type) { - // case *object.String: - // return &object.String{Value: "NENO (STRING)"} - // case *object.Boolean: - // return &object.String{Value: "BOOLEAN (KWELI AU SIKWELI)"} - // case *object.Builtin: - // return &object.String{Value: "YA_NDANI (Builtin Function)"} - // case *object.Array: - // return &object.String{Value: "ORODHA (Array)"} - // case *object.Function: - // return &object.String{Value: "UNDO (Function)"} - // case *object.Integer: - // return &object.String{Value: "NAMBA (Integer)"} - // case *object.Float: - // return &object.String{Value: "DESIMALI (Float)"} - // case *object.Dict: - // return &object.String{Value: "KAMUSI (Dict)"} - // default: - // return newError("argument to `type` not supported, got=%s", - // args[0].Type()) - // } }, }, } diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 9dec937..4e81541 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -494,7 +494,7 @@ func evalPostfixExpression(env *object.Environment, operator string, node *ast.P v := arg.Value + 1 return env.Set(node.Token.Literal, &object.Float{Value: v}) default: - return newError("%s sio kitambulishi cha namba. Tumia '++' na kitambulishi cha namba au desimali.\nMfano:\tacha i = 2; i++", node.Token.Literal) + return newError("Mstari %d: %s sio kitambulishi cha namba. Tumia '++' na kitambulishi cha namba au desimali.\nMfano:\tfanya i = 2; i++", node.Token.Line, node.Token.Literal) } case "--": @@ -506,7 +506,7 @@ func evalPostfixExpression(env *object.Environment, operator string, node *ast.P v := arg.Value - 1 return env.Set(node.Token.Literal, &object.Float{Value: v}) default: - return newError("%s sio kitambulishi cha namba. Tumia '--' na kitambulishi cha namba au desimali.\nMfano:\tacha i = 2; i++", node.Token.Literal) + return newError("Mstari %d: %s sio kitambulishi cha namba. Tumia '--' na kitambulishi cha namba au desimali.\nMfano:\tfanya i = 2; i++", node.Token.Line, node.Token.Literal) } default: return newError("Haifahamiki: %s", operator) diff --git a/examples/example.nr b/examples/example.nr index f8f2c79..7ebacef 100644 --- a/examples/example.nr +++ b/examples/example.nr @@ -1,8 +1,8 @@ andika("Testing basic types..."); andika(2 + 2); andika(4 * 4); -acha a = 10; -acha b = 20; +fanya a = 10; +fanya b = 20; andika(a + b); @@ -15,23 +15,23 @@ andika("Mambo vipi"); andika("Testing Functions... "); -acha jumlisha = fn(x, y) {x + y}; +fanya jumlisha = unda(x, y) {x + y}; andika(jumlisha(20,30)); andika(jumlisha(100,1000)); -acha zidisha = fn(x, y) {x * y}; +fanya zidisha = unda(x, y) {x * y}; andika(zidisha(100,1000)); andika(zidisha(200, 20)); // lists can hold any value andika("Testing lists..."); -acha list = [1, "a", kweli, sikweli]; +fanya list = [1, "a", kweli, sikweli]; // a few builtins -acha list = sukuma(list, jumlisha(4,5)); +fanya list = sukuma(list, jumlisha(4,5)); andika(list); andika(list[2]); @@ -57,7 +57,7 @@ kama (idadi("Habari") == 6) { // fibonacci example andika("Testing fibonacci..."); -acha fibo = fn(x) { +fanya fibo = unda(x) { kama (x == 0) { rudisha 0; } au kama (x == 1) { @@ -72,8 +72,8 @@ andika(fibo(10)); // testing input andika("Testing input from user..."); -acha salamu = fn() { - acha jina = jaza("Unaitwa nani rafiki? "); +fanya salamu = unda() { + fanya jina = jaza("Unaitwa nani rafiki? "); rudisha "Mambo vipi " + jina; } @@ -87,7 +87,7 @@ Multiline comment andika("Testing dictionaries...") -acha watu = [{"jina": "Mojo", "kabila": "Mnyakusa"}, {"jina": "Avi", "kabila": "Mwarabu wa dubai"}] +fanya watu = [{"jina": "Mojo", "kabila": "Mnyakusa"}, {"jina": "Avi", "kabila": "Mwarabu wa dubai"}] andika(watu, watu[0], watu[0]["jina"], watu[0]["kabila"]) @@ -99,7 +99,7 @@ andika({"a":1} + {"b": 2}) andika("Testing while loop..."); -acha i = 10; +fanya i = 10; wakati (i > 0) { andika(i); diff --git a/lexer/lexer.go b/lexer/lexer.go index 5634ff6..7ce59e2 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -5,10 +5,10 @@ import ( ) type Lexer struct { - input string // string or runes. Runes should be better. + input string position int readPosition int - ch byte // make this a rune too + ch byte line int } diff --git a/lexer/lexer_test.go b/lexer/lexer_test.go index ccf358e..74b3b42 100644 --- a/lexer/lexer_test.go +++ b/lexer/lexer_test.go @@ -9,14 +9,14 @@ import ( func TestNextToken(t *testing.T) { input := ` // Testing kama lex luther iko sawa - acha tano = 5; - acha kumi = 10; + fanya tano = 5; + fanya kumi = 10; - acha jumla = fn(x, y){ + fanya jumla = unda(x, y){ x + y; }; - acha jibu = jumla(tano, kumi); + fanya jibu = jumla(tano, kumi); !-/5; 5 < 10 > 5; @@ -46,20 +46,20 @@ func TestNextToken(t *testing.T) { expectedType token.TokenType expectedLiteral string }{ - {token.LET, "acha"}, + {token.LET, "fanya"}, {token.IDENT, "tano"}, {token.ASSIGN, "="}, {token.INT, "5"}, {token.SEMICOLON, ";"}, - {token.LET, "acha"}, + {token.LET, "fanya"}, {token.IDENT, "kumi"}, {token.ASSIGN, "="}, {token.INT, "10"}, {token.SEMICOLON, ";"}, - {token.LET, "acha"}, + {token.LET, "fanya"}, {token.IDENT, "jumla"}, {token.ASSIGN, "="}, - {token.FUNCTION, "fn"}, + {token.FUNCTION, "unda"}, {token.LPAREN, "("}, {token.IDENT, "x"}, {token.COMMA, ","}, @@ -72,7 +72,7 @@ func TestNextToken(t *testing.T) { {token.SEMICOLON, ";"}, {token.RBRACE, "}"}, {token.SEMICOLON, ";"}, - {token.LET, "acha"}, + {token.LET, "fanya"}, {token.IDENT, "jibu"}, {token.ASSIGN, "="}, {token.IDENT, "jumla"}, diff --git a/object/object.go b/object/object.go index 3090165..a6a2ddf 100644 --- a/object/object.go +++ b/object/object.go @@ -98,7 +98,7 @@ func (f *Function) Inspect() string { params = append(params, p.String()) } - out.WriteString("fn") + out.WriteString("unda") out.WriteString("(") out.WriteString(strings.Join(params, ", ")) out.WriteString(") {\n") @@ -252,7 +252,7 @@ func (d *Dict) Next() (Object, Object) { return nil, nil } -func (d *Dict) Resest() { +func (d *Dict) Reset() { d.offset = 0 } diff --git a/parser/parser.go b/parser/parser.go index 3d418a8..9072e9f 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -198,7 +198,7 @@ func (p *Parser) parseAssignmentExpression(exp ast.Expression) ast.Expression { switch node := exp.(type) { case *ast.Identifier, *ast.IndexExpression: default: - msg := fmt.Sprintf("Tulitegemea kupata kitambulishi au array, badala yake tumepata: %T %#v", node, exp) + msg := fmt.Sprintf("Mstari %d:Tulitegemea kupata kitambulishi au array, badala yake tumepata: %s %s", p.curToken.Line, p.prevToken.Type, node.TokenLiteral()) p.errors = append(p.errors, msg) return nil } diff --git a/token/token.go b/token/token.go index 39b0895..12c3f29 100644 --- a/token/token.go +++ b/token/token.go @@ -56,7 +56,7 @@ const ( // Keywords FUNCTION = "FUNCTION" - LET = "ACHA" + LET = "FANYA" TRUE = "KWELI" FALSE = "SIKWELI" IF = "KAMA" From a21957ea6ebbc8a82e9187d74bd16045c3e79c77 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Sun, 25 Dec 2022 06:03:56 +0300 Subject: [PATCH 31/59] Change switch to badili and added numbers to allowed identifiers --- lexer/lexer.go | 2 +- token/token.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lexer/lexer.go b/lexer/lexer.go index 7ce59e2..5afcecb 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -191,7 +191,7 @@ func newToken(tokenType token.TokenType, line int, ch byte) token.Token { func (l *Lexer) readIdentifier() string { position := l.position - for isLetter(l.ch) { + for isLetter(l.ch) || isDigit(l.ch) { l.readChar() } return l.input[position:l.position] diff --git a/token/token.go b/token/token.go index 12c3f29..bb950f7 100644 --- a/token/token.go +++ b/token/token.go @@ -68,7 +68,7 @@ const ( CONTINUE = "ENDELEA" IN = "KTK" FOR = "KWA" - SWITCH = "ENDAPO" + SWITCH = "BADILI" CASE = "IKIWA" DEFAULT = "KAWAIDA" ) @@ -88,7 +88,7 @@ var keywords = map[string]TokenType{ "tupu": NULL, "ktk": IN, "kwa": FOR, - "endapo": SWITCH, + "badili": SWITCH, "ikiwa": CASE, "kawaida": DEFAULT, } From 2b42d440f48e4062be71230b8f135ee1326ecacb Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Sun, 25 Dec 2022 06:18:19 +0300 Subject: [PATCH 32/59] Updated README --- README.md | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 57b4ddc..84074cc 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,13 @@ instructions for your device below: - Download the binary: ``` -curl -O -L https://github.com/AvicennaJr/Nuru/releases/download/v0.1.5/nuru_linux_amd64_v0.1.5.tar.gz +curl -O -L https://github.com/AvicennaJr/Nuru/releases/download/v0.2.0/nuru_linux_amd64_v0.2.0.tar.gz ``` - Extract the file to make global available: ``` -sudo tar -C /usr/local/bin -xzvf nuru_linux_amd64_v0.1.5.tar.gz +sudo tar -C /usr/local/bin -xzvf nuru_linux_amd64_v0.2.0.tar.gz ``` - Confirm installation with: @@ -40,12 +40,12 @@ nuru -v - Download the binary with this command: ``` -curl -O -L https://github.com/AvicennaJr/Nuru/releases/download/v0.1.5/nuru_android_arm64_v0.1.5.tar.gz +curl -O -L https://github.com/AvicennaJr/Nuru/releases/download/v0.2.0/nuru_android_arm64_v0.2.0.tar.gz ``` - Extract the file: ``` -tar -xzvf nuru_android_arm64_v0.1.5.tar.gz +tar -xzvf nuru_android_arm64_v0.2.0.tar.gz ``` - Add it to path: @@ -65,8 +65,8 @@ nuru -v ``` mkdir C:\bin ``` - - Download the Nuru Program [Here](https://github.com/AvicennaJr/Nuru/releases/download/v0.1.5/nuru_windows_amd64_v0.1.5.exe) - - Rename the downloaded program from `nuru_windows_amd64_v0.1.5.exe` to `nuru.exe` + - Download the Nuru Program [Here](https://github.com/AvicennaJr/Nuru/releases/download/v0.2.0/nuru_windows_amd64_v0.2.0.exe) + - Rename the downloaded program from `nuru_windows_amd64_v0.2.0.exe` to `nuru.exe` - Move the file `nuru.exe` to the folder `C:\bin` - Add the bin folder to Path with this command: @@ -96,6 +96,9 @@ nuru -v ## Syntax +**NOTE** +> There is a more detailed documentation of the language [here](https://github.com/AvicennaJr/NuruDocs). + Nuru, although still in its early stage, intends to be a fully functional programming language, and thus it has been baked with many features. ### Defining A Variable @@ -152,9 +155,11 @@ Type | Syntax | Comments --------- | ----------------------------------------- | ----------------------- BOOL | `kweli sikweli` | kweli == true, sikweli == false INT | `1, 100, 342, -4` | These are signed 64 bit integers -STRING | `"" "mambo" "habari yako"` | They MUST be in DOUBLE QUOTES `"` -ARRAY | `[] [1, 2, 3] [1, "moja", kweli]` | Arrays can hold any types +FLOAT | `2.3, 4.5. 100.8094` | Signed 64 bit floats +STRING | `"" "mambo" "habari yako"` | They can be in double `"` or single `'` quotes +ARRAY | `[] [1, 2, 3] [1, "moja", kweli]` | Arrays can hold any types DICT | `{} {"a": 3, 1: "moja", kweli: 2}` | Keys can be int, string or bool. Values can be anything +NULL | `tupu` | These are nil objects ### Functions @@ -205,7 +210,7 @@ fanya i = 10 wakati (i > 0) { andika(i) - i = i - 1 + i-- } ``` @@ -270,6 +275,15 @@ mtu = mtu + kazi andika(mtu) // output = {"jina": "Avicenna", "kabila": "Mnyakusa", "anapoishi": "Dar Es Salaam", "kazi": "jambazi"} ``` +### For Loops + +These can iterate over strings, arrays and dictionaries: +``` +kwa i ktk "habari" { + andika(i) +} +``` + ### Getting Input From User In Nuru you can get input from users using the `jaza()` keyword as follows: @@ -297,7 +311,7 @@ Kindly Note that everything should be placed in a single line. Here's an example ``` ### Running From File -To run a Nuru script, write the `nuru` command followed by the name of the file with a `.nr` extension: +To run a Nuru script, write the `nuru` command followed by the name of the file with a `.nr` or `.sw` extension: ``` nuru myFile.nr From 29c1eeaba2d7fb9754d577cb7f826dcfbe4e83b2 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Sun, 25 Dec 2022 17:57:44 +0300 Subject: [PATCH 33/59] fixed null pointer error on parser-202 --- parser/parser.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/parser/parser.go b/parser/parser.go index 9072e9f..ba09966 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -198,8 +198,13 @@ func (p *Parser) parseAssignmentExpression(exp ast.Expression) ast.Expression { switch node := exp.(type) { case *ast.Identifier, *ast.IndexExpression: default: - msg := fmt.Sprintf("Mstari %d:Tulitegemea kupata kitambulishi au array, badala yake tumepata: %s %s", p.curToken.Line, p.prevToken.Type, node.TokenLiteral()) - p.errors = append(p.errors, msg) + if node != nil { + msg := fmt.Sprintf("Mstari %d:Tulitegemea kupata kitambulishi au array, badala yake tumepata: %s", p.curToken.Line, node.TokenLiteral()) + p.errors = append(p.errors, msg) + } else { + msg := fmt.Sprintf("Mstari %d: Umekosea mkuu", p.curToken.Line) + p.errors = append(p.errors, msg) + } return nil } From 09559d228b7da88bcd6bae8f7ff2812a96f9f6dd Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Sun, 25 Dec 2022 21:37:43 +0300 Subject: [PATCH 34/59] Restructured and added docs to source code --- src/Makefile | 23 + src/ast/ast.go | 491 +++++++++++++++ src/ast/ast_test.go | 29 + src/evaluator/builtins.go | 119 ++++ src/evaluator/evaluator.go | 943 +++++++++++++++++++++++++++++ src/evaluator/evaluator_test.go | 518 ++++++++++++++++ src/examples/example.nr | 107 ++++ src/examples/sorting_algorithm.nr | 63 ++ src/examples/sudoku_solver.nr | 101 ++++ src/go.mod | 3 + src/lexer/lexer.go | 327 ++++++++++ src/lexer/lexer_test.go | 153 +++++ src/main.go | 67 +++ src/object/environment.go | 31 + src/object/object.go | 277 +++++++++ src/object/object_test.go | 22 + src/parser/parser.go | 810 +++++++++++++++++++++++++ src/parser/parser_test.go | 970 ++++++++++++++++++++++++++++++ src/repl/repl.go | 116 ++++ src/token/token.go | 101 ++++ 20 files changed, 5271 insertions(+) create mode 100644 src/Makefile create mode 100644 src/ast/ast.go create mode 100644 src/ast/ast_test.go create mode 100644 src/evaluator/builtins.go create mode 100644 src/evaluator/evaluator.go create mode 100644 src/evaluator/evaluator_test.go create mode 100644 src/examples/example.nr create mode 100644 src/examples/sorting_algorithm.nr create mode 100644 src/examples/sudoku_solver.nr create mode 100644 src/go.mod create mode 100644 src/lexer/lexer.go create mode 100644 src/lexer/lexer_test.go create mode 100644 src/main.go create mode 100644 src/object/environment.go create mode 100644 src/object/object.go create mode 100644 src/object/object_test.go create mode 100644 src/parser/parser.go create mode 100644 src/parser/parser_test.go create mode 100644 src/repl/repl.go create mode 100644 src/token/token.go diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..08ffece --- /dev/null +++ b/src/Makefile @@ -0,0 +1,23 @@ +VERSION=0.2.0 + +build_linux: + env GOOS=linux GOARCH=amd64 go build -o nuru + tar -zcvf nuru_linux_amd64_v${VERSION}.tar.gz nuru + rm nuru + +build_windows: + env GOOS=windows GOARCH=amd64 go build -o nuru_windows_amd64_v${VERSION}.exe + +build_android: + env GOOS=android GOARCH=arm64 go build -o nuru + tar -zcvf nuru_linux_amd64_v${VERSION}.tar.gz nuru + rm nuru + +test: + go test ./parser/ + go test ./ast/ + go test ./evaluator/ + go test ./object/ + +clean: + go clean \ No newline at end of file diff --git a/src/ast/ast.go b/src/ast/ast.go new file mode 100644 index 0000000..810ccfd --- /dev/null +++ b/src/ast/ast.go @@ -0,0 +1,491 @@ +package ast + +import ( + "bytes" + "strings" + + "github.com/AvicennaJr/Nuru/token" +) + +type Node interface { + TokenLiteral() string + String() string // to help debug the many errors lmao +} + +type Statement interface { + Node + statementNode() +} + +type Expression interface { + Node + expressionNode() +} + +type Program struct { + Statements []Statement +} + +func (p *Program) TokenLiteral() string { + if len(p.Statements) > 0 { + return p.Statements[0].TokenLiteral() + } else { + return "" + } +} + +func (p *Program) String() string { + var out bytes.Buffer + + for _, s := range p.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +type LetStatement struct { + Token token.Token + Name *Identifier + Value Expression +} + +func (ls *LetStatement) statementNode() {} +func (ls *LetStatement) TokenLiteral() string { return ls.Token.Literal } +func (ls *LetStatement) String() string { + var out bytes.Buffer + + out.WriteString(ls.TokenLiteral() + " ") + out.WriteString(ls.Name.String()) + out.WriteString(" = ") + + if ls.Value != nil { + out.WriteString(ls.Value.String()) + } + + out.WriteString(";") + return out.String() +} + +type Identifier struct { + Token token.Token + Value string +} + +func (i *Identifier) expressionNode() {} +func (i *Identifier) TokenLiteral() string { return i.Token.Literal } +func (i *Identifier) String() string { return i.Value } + +type ReturnStatement struct { + Token token.Token + ReturnValue Expression +} + +func (rs *ReturnStatement) statementNode() {} +func (rs *ReturnStatement) TokenLiteral() string { return rs.Token.Literal } +func (rs *ReturnStatement) String() string { + var out bytes.Buffer + + out.WriteString(rs.TokenLiteral() + " ") + + if rs.ReturnValue != nil { + out.WriteString(rs.ReturnValue.String()) + } + out.WriteString(";") + return out.String() +} + +type ExpressionStatement struct { + Token token.Token + Expression Expression +} + +func (es *ExpressionStatement) statementNode() {} +func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal } +func (es *ExpressionStatement) String() string { + if es.Expression != nil { + return es.Expression.String() + } + + return "" +} + +type IntegerLiteral struct { + Token token.Token + Value int64 +} + +func (il *IntegerLiteral) expressionNode() {} +func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal } +func (il *IntegerLiteral) String() string { return il.Token.Literal } + +type PrefixExpression struct { + Token token.Token + Operator string + Right Expression +} + +func (pe *PrefixExpression) expressionNode() {} +func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal } +func (pe *PrefixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(pe.Operator) + out.WriteString(pe.Right.String()) + out.WriteString(")") + + return out.String() +} + +type InfixExpression struct { + Token token.Token + Left Expression + Operator string + Right Expression +} + +func (oe *InfixExpression) expressionNode() {} +func (oe *InfixExpression) TokenLiteral() string { return oe.Token.Literal } +func (oe *InfixExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(oe.Left.String()) + out.WriteString(" " + oe.Operator + " ") + out.WriteString(oe.Right.String()) + out.WriteString(")") + + return out.String() +} + +type Boolean struct { + Token token.Token + Value bool +} + +func (b *Boolean) expressionNode() {} +func (b *Boolean) TokenLiteral() string { return b.Token.Literal } +func (b *Boolean) String() string { return b.Token.Literal } + +type IfExpression struct { + Token token.Token + Condition Expression + Consequence *BlockStatement + Alternative *BlockStatement +} + +func (ie *IfExpression) expressionNode() {} +func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IfExpression) String() string { + var out bytes.Buffer + out.WriteString("if") + out.WriteString(ie.Condition.String()) + out.WriteString(" ") + out.WriteString(ie.Consequence.String()) + + if ie.Alternative != nil { + out.WriteString("else") + out.WriteString(ie.Alternative.String()) + } + + return out.String() +} + +type BlockStatement struct { + Token token.Token + Statements []Statement +} + +func (bs *BlockStatement) statementNode() {} +func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal } +func (bs *BlockStatement) String() string { + var out bytes.Buffer + + for _, s := range bs.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +type FunctionLiteral struct { + Token token.Token + Parameters []*Identifier + Body *BlockStatement +} + +func (fl *FunctionLiteral) expressionNode() {} +func (fl *FunctionLiteral) TokenLiteral() string { return fl.Token.Literal } +func (fl *FunctionLiteral) String() string { + var out bytes.Buffer + + params := []string{} + + for _, p := range fl.Parameters { + params = append(params, p.String()) + } + + out.WriteString(fl.TokenLiteral()) + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") ") + out.WriteString(fl.Body.String()) + + return out.String() +} + +type CallExpression struct { + Token token.Token + Function Expression // can be Identifier or FunctionLiteral + Arguments []Expression +} + +func (ce *CallExpression) expressionNode() {} +func (ce *CallExpression) TokenLiteral() string { return ce.Token.Literal } +func (ce *CallExpression) String() string { + var out bytes.Buffer + + args := []string{} + for _, a := range ce.Arguments { + args = append(args, a.String()) + } + + out.WriteString(ce.Function.String()) + out.WriteString("(") + out.WriteString(strings.Join(args, ", ")) + out.WriteString(")") + + return out.String() +} + +type StringLiteral struct { + Token token.Token + Value string +} + +func (sl *StringLiteral) expressionNode() {} +func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal } +func (sl *StringLiteral) String() string { return sl.Token.Literal } + +type ArrayLiteral struct { + Token token.Token + Elements []Expression +} + +func (al *ArrayLiteral) expressionNode() {} +func (al *ArrayLiteral) TokenLiteral() string { return al.Token.Literal } +func (al *ArrayLiteral) String() string { + var out bytes.Buffer + + elements := []string{} + for _, el := range al.Elements { + elements = append(elements, el.String()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +type IndexExpression struct { + Token token.Token + Left Expression + Index Expression +} + +func (ie *IndexExpression) expressionNode() {} +func (ie *IndexExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IndexExpression) String() string { + var out bytes.Buffer + + out.WriteString("(") + out.WriteString(ie.Left.String()) + out.WriteString("[") + out.WriteString(ie.Index.String()) + out.WriteString("])") + + return out.String() +} + +type DictLiteral struct { + Token token.Token + Pairs map[Expression]Expression +} + +func (dl *DictLiteral) expressionNode() {} +func (dl *DictLiteral) TokenLiteral() string { return dl.Token.Literal } +func (dl *DictLiteral) String() string { + var out bytes.Buffer + pairs := []string{} + for key, value := range dl.Pairs { + pairs = append(pairs, key.String()+":"+value.String()) + } + + out.WriteString("(") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} + +type AssignmentExpression struct { + Token token.Token + Left Expression + Value Expression +} + +func (ae *AssignmentExpression) expressionNode() {} +func (ae *AssignmentExpression) TokenLiteral() string { return ae.Token.Literal } +func (ae *AssignmentExpression) String() string { + var out bytes.Buffer + + out.WriteString(ae.Left.String()) + out.WriteString(ae.TokenLiteral()) + out.WriteString(ae.Value.String()) + + return out.String() +} + +type WhileExpression struct { + Token token.Token + Condition Expression + Consequence *BlockStatement +} + +func (we *WhileExpression) expressionNode() {} +func (we *WhileExpression) TokenLiteral() string { return we.Token.Literal } +func (we *WhileExpression) String() string { + var out bytes.Buffer + + out.WriteString("while") + out.WriteString(we.Condition.String()) + out.WriteString(" ") + out.WriteString(we.Consequence.String()) + + return out.String() +} + +type Null struct { + Token token.Token +} + +func (n *Null) expressionNode() {} +func (n *Null) TokenLiteral() string { return n.Token.Literal } +func (n *Null) String() string { return n.Token.Literal } + +type Break struct { + Statement + Token token.Token // the 'break' token +} + +func (b *Break) expressionNode() {} +func (b *Break) TokenLiteral() string { return b.Token.Literal } +func (b *Break) String() string { return b.Token.Literal } + +type Continue struct { + Statement + Token token.Token // the 'continue' token +} + +func (c *Continue) expressionNode() {} +func (c *Continue) TokenLiteral() string { return c.Token.Literal } +func (c *Continue) String() string { return c.Token.Literal } + +type PostfixExpression struct { + Token token.Token + Operator string +} + +func (pe *PostfixExpression) expressionNode() {} +func (pe *PostfixExpression) TokenLiteral() string { return pe.Token.Literal } +func (pe *PostfixExpression) String() string { + var out bytes.Buffer + out.WriteString("(") + out.WriteString(pe.Token.Literal) + out.WriteString(pe.Operator) + out.WriteString(")") + return out.String() +} + +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 } + +type For struct { + Expression + Token token.Token + Identifier string // "i" + StarterName *Identifier // i = 0 + StarterValue Expression + Closer Expression // i++ + Condition Expression // i < 1 + Block *BlockStatement +} + +type ForIn struct { + Expression + Token token.Token + Key string + Value string + Iterable Expression + Block *BlockStatement +} + +type CaseExpression struct { + Token token.Token + Default bool + Expr []Expression + Block *BlockStatement +} + +func (ce *CaseExpression) expressionNode() {} +func (ce *CaseExpression) TokenLiteral() string { return ce.Token.Literal } +func (ce *CaseExpression) String() string { + var out bytes.Buffer + + if ce.Default { + out.WriteString("default ") + } else { + out.WriteString("case ") + + tmp := []string{} + for _, exp := range ce.Expr { + tmp = append(tmp, exp.String()) + } + out.WriteString(strings.Join(tmp, ",")) + } + out.WriteString(ce.Block.String()) + return out.String() +} + +type SwitchExpression struct { + Token token.Token + Value Expression + Choices []*CaseExpression +} + +func (se *SwitchExpression) expressionNode() {} +func (se *SwitchExpression) TokenLiteral() string { return se.Token.Literal } +func (se *SwitchExpression) String() string { + var out bytes.Buffer + out.WriteString("\nswitch (") + out.WriteString(se.Value.String()) + out.WriteString(")\n{\n") + + for _, tmp := range se.Choices { + if tmp != nil { + out.WriteString(tmp.String()) + } + } + out.WriteString("}\n") + + return out.String() +} diff --git a/src/ast/ast_test.go b/src/ast/ast_test.go new file mode 100644 index 0000000..c5ae5fa --- /dev/null +++ b/src/ast/ast_test.go @@ -0,0 +1,29 @@ +package ast + +import ( + "testing" + + "github.com/AvicennaJr/Nuru/token" +) + +func TestString(t *testing.T) { + program := &Program{ + Statements: []Statement{ + &LetStatement{ + Token: token.Token{Type: token.LET, Literal: "fanya"}, + Name: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "myVar"}, + Value: "myVar", + }, + Value: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "anotherVar"}, + Value: "anotherVar", + }, + }, + }, + } + + if program.String() != "fanya myVar = anotherVar;" { + t.Errorf("program.String() wrong. got=%q", program.String()) + } +} diff --git a/src/evaluator/builtins.go b/src/evaluator/builtins.go new file mode 100644 index 0000000..6a24052 --- /dev/null +++ b/src/evaluator/builtins.go @@ -0,0 +1,119 @@ +package evaluator + +import ( + "bufio" + "fmt" + "io" + "os" + "strings" + + "github.com/AvicennaJr/Nuru/object" +) + +var builtins = map[string]*object.Builtin{ + "idadi": { + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("Hoja hazilingani, tunahitaji=1, tumepewa=%d", len(args)) + } + + switch arg := args[0].(type) { + case *object.Array: + return &object.Integer{Value: int64(len(arg.Elements))} + case *object.String: + return &object.Integer{Value: int64(len(arg.Value))} + default: + return newError("Samahani, hii function haitumiki na %s", args[0].Type()) + } + }, + }, + "yamwisho": { + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("Samahani, tunahitaji Hoja moja tu, wewe umeweka %d", len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("Samahani, hii function haitumiki na %s", args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + if length > 0 { + return arr.Elements[length-1] + } + + return NULL + }, + }, + "sukuma": { + Fn: func(args ...object.Object) object.Object { + if len(args) != 2 { + return newError("Samahani, tunahitaji Hoja 2, wewe umeweka %d", len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("Samahani, hii function haitumiki na %s", args[0].Type()) + } + + arr := args[0].(*object.Array) + length := len(arr.Elements) + + newElements := make([]object.Object, length+1) + copy(newElements, arr.Elements) + newElements[length] = args[1] + + return &object.Array{Elements: newElements} + }, + }, + "jaza": { + Fn: func(args ...object.Object) object.Object { + + if len(args) > 1 { + return newError("Samahani, hii function inapokea hoja 0 au 1, wewe umeweka %d", len(args)) + } + + if len(args) > 0 && args[0].Type() != object.STRING_OBJ { + return newError(fmt.Sprintf(`Tafadhali tumia alama ya nukuu: "%s"`, args[0].Inspect())) + } + if len(args) == 1 { + prompt := args[0].(*object.String).Value + fmt.Fprint(os.Stdout, prompt) + } + + buffer := bufio.NewReader(os.Stdin) + + line, _, err := buffer.ReadLine() + if err != nil && err != io.EOF { + return newError("Nimeshindwa kusoma uliyo yajaza") + } + + return &object.String{Value: string(line)} + }, + }, + "andika": { + Fn: func(args ...object.Object) object.Object { + if len(args) == 0 { + fmt.Println("") + } else { + var arr []string + for _, arg := range args { + if arg == nil { + return newError("Hauwezi kufanya operesheni hii") + } + arr = append(arr, arg.Inspect()) + } + str := strings.Join(arr, " ") + print(str + "\n") + } + return nil + }, + }, + "aina": { + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("Samahani, tunahitaji Hoja 1, wewe umeweka %d", len(args)) + } + + return &object.String{Value: string(args[0].Type())} + }, + }, +} diff --git a/src/evaluator/evaluator.go b/src/evaluator/evaluator.go new file mode 100644 index 0000000..4e81541 --- /dev/null +++ b/src/evaluator/evaluator.go @@ -0,0 +1,943 @@ +package evaluator + +import ( + "fmt" + "math" + "strings" + + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/object" +) + +var ( + NULL = &object.Null{} + TRUE = &object.Boolean{Value: true} + FALSE = &object.Boolean{Value: false} + BREAK = &object.Break{} + CONTINUE = &object.Continue{} +) + +func Eval(node ast.Node, env *object.Environment) object.Object { + switch node := node.(type) { + case *ast.Program: + return evalProgram(node, env) + + case *ast.ExpressionStatement: + return Eval(node.Expression, env) + + 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) + + case *ast.PrefixExpression: + right := Eval(node.Right, env) + if isError(right) { + return right + } + return evalPrefixExpression(node.Operator, right, node.Token.Line) + + case *ast.InfixExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + right := Eval(node.Right, env) + if isError(right) { + return right + } + return evalInfixExpression(node.Operator, left, right, node.Token.Line) + case *ast.PostfixExpression: + return evalPostfixExpression(env, node.Operator, node) + + case *ast.BlockStatement: + return evalBlockStatement(node, env) + + case *ast.IfExpression: + return evalIfExpression(node, env) + + case *ast.ReturnStatement: + val := Eval(node.ReturnValue, env) + if isError(val) { + return val + } + return &object.ReturnValue{Value: val} + + case *ast.LetStatement: + val := Eval(node.Value, env) + if isError(val) { + return val + } + + env.Set(node.Name.Value, val) + + case *ast.Identifier: + return evalIdentifier(node, env) + + case *ast.FunctionLiteral: + params := node.Parameters + body := node.Body + return &object.Function{Parameters: params, Env: env, Body: body} + + case *ast.CallExpression: + function := Eval(node.Function, env) + if isError(function) { + return function + } + args := evalExpressions(node.Arguments, env) + if len(args) == 1 && isError(args[0]) { + return args[0] + } + return applyFunction(function, args, node.Token.Line) + case *ast.StringLiteral: + return &object.String{Value: node.Value} + + case *ast.ArrayLiteral: + elements := evalExpressions(node.Elements, env) + if len(elements) == 1 && isError(elements[0]) { + return elements[0] + } + return &object.Array{Elements: elements} + case *ast.IndexExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + index := Eval(node.Index, env) + if isError(index) { + return index + } + return evalIndexExpression(left, index, node.Token.Line) + case *ast.DictLiteral: + return evalDictLiteral(node, env) + case *ast.WhileExpression: + return evalWhileExpression(node, env) + case *ast.Break: + return evalBreak(node) + case *ast.Continue: + return evalContinue(node) + case *ast.SwitchExpression: + return evalSwitchStatement(node, env) + case *ast.Null: + return NULL + // case *ast.For: + // return evalForExpression(node, env) + case *ast.ForIn: + return evalForInExpression(node, env, node.Token.Line) + case *ast.AssignmentExpression: + left := Eval(node.Left, env) + if isError(left) { + return left + } + + value := Eval(node.Value, env) + if isError(value) { + return value + } + + // This is an easy way to assign operators like +=, -= etc + // I'm surprised it work at the first try lol + // basically separate the += to + and =, take the + only and + // then perform the operation as normal + op := node.Token.Literal + if len(op) >= 2 { + op = op[:len(op)-1] + value = evalInfixExpression(op, left, value, node.Token.Line) + if isError(value) { + return value + } + } + + if ident, ok := node.Left.(*ast.Identifier); ok { + env.Set(ident.Value, value) + } else if ie, ok := node.Left.(*ast.IndexExpression); ok { + obj := Eval(ie.Left, env) + if isError(obj) { + return obj + } + + if array, ok := obj.(*object.Array); ok { + index := Eval(ie.Index, env) + if isError(index) { + return index + } + if idx, ok := index.(*object.Integer); ok { + if int(idx.Value) > len(array.Elements) { + return newError("Index imezidi idadi ya elements") + } + array.Elements[idx.Value] = value + } else { + return newError("Hauwezi kufanya opereshen hii na %#v", index) + } + } else if hash, ok := obj.(*object.Dict); ok { + key := Eval(ie.Index, env) + if isError(key) { + return key + } + if hashKey, ok := key.(object.Hashable); ok { + hashed := hashKey.HashKey() + hash.Pairs[hashed] = object.DictPair{Key: key, Value: value} + } else { + return newError("Hauwezi kufanya opereshen hii na %T", key) + } + } else { + return newError("%T haifanyi operation hii", obj) + } + } else { + return newError("Tumia neno kama variable, sio %T", left) + } + + } + + return nil +} + +func evalProgram(program *ast.Program, env *object.Environment) object.Object { + var result object.Object + + for _, statment := range program.Statements { + result = Eval(statment, env) + + switch result := result.(type) { + case *object.ReturnValue: + return result.Value + case *object.Error: + return result + } + } + + return result +} + +func nativeBoolToBooleanObject(input bool) *object.Boolean { + if input { + return TRUE + } + return FALSE +} + +func evalPrefixExpression(operator string, right object.Object, line int) object.Object { + switch operator { + case "!": + return evalBangOperatorExpression(right) + case "-": + return evalMinusPrefixOperatorExpression(right, line) + default: + return newError("Mstari %d: Operesheni haieleweki: %s%s", line, operator, right.Type()) + } +} + +func evalBangOperatorExpression(right object.Object) object.Object { + switch right { + case TRUE: + return FALSE + case FALSE: + return TRUE + case NULL: + return TRUE + default: + return FALSE + } +} + +func evalMinusPrefixOperatorExpression(right object.Object, line int) object.Object { + switch obj := right.(type) { + + case *object.Integer: + return &object.Integer{Value: -obj.Value} + + case *object.Float: + return &object.Float{Value: -obj.Value} + + default: + return newError("Mstari %d: Operesheni Haielweki: -%s", line, right.Type()) + } +} + +func evalInfixExpression(operator string, left, right object.Object, line int) object.Object { + if left == nil { + return newError("Mstari %d: Umekosea hapa", line) + } + switch { + case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: + return evalStringInfixExpression(operator, left, right, line) + + case operator == "+" && left.Type() == object.DICT_OBJ && right.Type() == object.DICT_OBJ: + leftVal := left.(*object.Dict).Pairs + rightVal := right.(*object.Dict).Pairs + pairs := make(map[object.HashKey]object.DictPair) + for k, v := range leftVal { + pairs[k] = v + } + for k, v := range rightVal { + pairs[k] = v + } + return &object.Dict{Pairs: pairs} + + case operator == "+" && left.Type() == object.ARRAY_OBJ && right.Type() == object.ARRAY_OBJ: + leftVal := left.(*object.Array).Elements + rightVal := right.(*object.Array).Elements + elements := make([]object.Object, len(leftVal)+len(rightVal)) + elements = append(leftVal, rightVal...) + return &object.Array{Elements: elements} + + case operator == "*" && left.Type() == object.ARRAY_OBJ && right.Type() == object.INTEGER_OBJ: + leftVal := left.(*object.Array).Elements + rightVal := int(right.(*object.Integer).Value) + elements := leftVal + for i := rightVal; i > 1; i-- { + elements = append(elements, leftVal...) + } + return &object.Array{Elements: elements} + + case operator == "*" && left.Type() == object.INTEGER_OBJ && right.Type() == object.ARRAY_OBJ: + leftVal := int(left.(*object.Integer).Value) + rightVal := right.(*object.Array).Elements + elements := rightVal + for i := leftVal; i > 1; i-- { + elements = append(elements, rightVal...) + } + return &object.Array{Elements: elements} + + case operator == "*" && left.Type() == object.STRING_OBJ && right.Type() == object.INTEGER_OBJ: + leftVal := left.(*object.String).Value + rightVal := right.(*object.Integer).Value + return &object.String{Value: strings.Repeat(leftVal, int(rightVal))} + + case operator == "*" && left.Type() == object.INTEGER_OBJ && right.Type() == object.STRING_OBJ: + leftVal := left.(*object.Integer).Value + 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.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.FLOAT_OBJ && right.Type() == object.INTEGER_OBJ: + return evalFloatIntegerInfixExpression(operator, left, right, line) + + case operator == "ktk": + return evalInExpression(left, right, line) + + case operator == "==": + return nativeBoolToBooleanObject(left == right) + + case operator == "!=": + return nativeBoolToBooleanObject(left != right) + case left.Type() == object.BOOLEAN_OBJ && right.Type() == object.BOOLEAN_OBJ: + return evalBooleanInfixExpression(operator, left, right, line) + + case left.Type() != right.Type(): + return newError("Mstari %d: Aina Hazilingani: %s %s %s", + line, left.Type(), operator, right.Type()) + + default: + return newError("Mstari %d: Operesheni Haielweki: %s %s %s", + 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.Integer{Value: int64(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 Haielweki: %s %s %s", + line, left.Type(), operator, right.Type()) + } +} + +func evalFloatInfixExpression(operator string, left, right object.Object, line int) object.Object { + leftVal := left.(*object.Float).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 "**": + return &object.Float{Value: math.Pow(float64(leftVal), float64(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("Mstari %d: Operesheni Haielweki: %s %s %s", + line, left.Type(), operator, right.Type()) + } +} + +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 "<": + 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 Haielweki: %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 evalBooleanInfixExpression(operator string, left, right object.Object, line int) object.Object { + leftVal := left.(*object.Boolean).Value + rightVal := right.(*object.Boolean).Value + + switch operator { + case "&&": + return nativeBoolToBooleanObject(leftVal && rightVal) + case "||": + return nativeBoolToBooleanObject(leftVal || rightVal) + default: + return newError("Mstari %d: Operesheni Haielweki: %s %s %s", line, left.Type(), operator, right.Type()) + } +} + +func evalPostfixExpression(env *object.Environment, operator string, node *ast.PostfixExpression) object.Object { + val, ok := env.Get(node.Token.Literal) + if !ok { + return newError("Tumia KITAMBULISHI CHA NAMBA AU DESIMALI, sio %s", node.Token.Type) + } + switch operator { + case "++": + switch arg := val.(type) { + case *object.Integer: + v := arg.Value + 1 + return env.Set(node.Token.Literal, &object.Integer{Value: v}) + case *object.Float: + v := arg.Value + 1 + return env.Set(node.Token.Literal, &object.Float{Value: v}) + default: + return newError("Mstari %d: %s sio kitambulishi cha namba. Tumia '++' na kitambulishi cha namba au desimali.\nMfano:\tfanya i = 2; i++", node.Token.Line, node.Token.Literal) + + } + case "--": + switch arg := val.(type) { + case *object.Integer: + v := arg.Value - 1 + return env.Set(node.Token.Literal, &object.Integer{Value: v}) + case *object.Float: + v := arg.Value - 1 + return env.Set(node.Token.Literal, &object.Float{Value: v}) + default: + return newError("Mstari %d: %s sio kitambulishi cha namba. Tumia '--' na kitambulishi cha namba au desimali.\nMfano:\tfanya i = 2; i++", node.Token.Line, node.Token.Literal) + } + default: + return newError("Haifahamiki: %s", operator) + } +} + +func evalIfExpression(ie *ast.IfExpression, env *object.Environment) object.Object { + condition := Eval(ie.Condition, env) + + if isError(condition) { + return condition + } + + if isTruthy(condition) { + return Eval(ie.Consequence, env) + } else if ie.Alternative != nil { + return Eval(ie.Alternative, env) + } else { + return NULL + } +} + +func isTruthy(obj object.Object) bool { + switch obj { + case NULL: + return false + case TRUE: + return true + case FALSE: + return false + default: + return true + } +} + +func evalBlockStatement(block *ast.BlockStatement, env *object.Environment) object.Object { + var result object.Object + + for _, statment := range block.Statements { + result = Eval(statment, env) + + if result != nil { + rt := result.Type() + if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ || rt == object.CONTINUE_OBJ || rt == object.BREAK_OBJ { + return result + } + } + } + + return result +} + +func newError(format string, a ...interface{}) *object.Error { + format = fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, format) + return &object.Error{Message: fmt.Sprintf(format, a...)} +} + +func isError(obj object.Object) bool { + if obj != nil { + return obj.Type() == object.ERROR_OBJ + } + + return false +} + +func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object { + if val, ok := env.Get(node.Value); ok { + return val + } + if builtin, ok := builtins[node.Value]; ok { + return builtin + } + + return newError("Mstari %d: Neno Halifahamiki: %s", node.Token.Line, node.Value) +} + +func evalExpressions(exps []ast.Expression, env *object.Environment) []object.Object { + var result []object.Object + + for _, e := range exps { + evaluated := Eval(e, env) + if isError(evaluated) { + return []object.Object{evaluated} + } + + result = append(result, evaluated) + } + + return result +} + +func applyFunction(fn object.Object, args []object.Object, line int) object.Object { + switch fn := fn.(type) { + case *object.Function: + extendedEnv := extendedFunctionEnv(fn, args) + evaluated := Eval(fn.Body, extendedEnv) + return unwrapReturnValue(evaluated) + case *object.Builtin: + if result := fn.Fn(args...); result != nil { + return result + } + return NULL + default: + return newError("Mstari %d: Hii sio function: %s", line, fn.Type()) + } + +} + +func extendedFunctionEnv(fn *object.Function, args []object.Object) *object.Environment { + env := object.NewEnclosedEnvironment(fn.Env) + + for paramIdx, param := range fn.Parameters { + if paramIdx < len(args) { + env.Set(param.Value, args[paramIdx]) + } + } + return env +} + +func unwrapReturnValue(obj object.Object) object.Object { + if returnValue, ok := obj.(*object.ReturnValue); ok { + return returnValue.Value + } + + return obj +} + +func evalStringInfixExpression(operator string, left, right object.Object, line int) object.Object { + + leftVal := left.(*object.String).Value + rightVal := right.(*object.String).Value + + switch operator { + case "+": + return &object.String{Value: leftVal + rightVal} + case "==": + return nativeBoolToBooleanObject(leftVal == rightVal) + case "!=": + return nativeBoolToBooleanObject(leftVal != rightVal) + default: + return newError("Mstari %d: Operesheni Haielweki: %s %s %s", line, left.Type(), operator, right.Type()) + } +} + +func evalIndexExpression(left, index object.Object, line int) object.Object { + switch { + case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: + return evalArrayIndexExpression(left, index) + case left.Type() == object.ARRAY_OBJ && index.Type() != object.INTEGER_OBJ: + return newError("Mstari %d: Tafadhali tumia number, sio: %s", line, index.Type()) + case left.Type() == object.DICT_OBJ: + return evalDictIndexExpression(left, index, line) + default: + return newError("Mstari %d: Operesheni hii haiwezekani kwa: %s", line, left.Type()) + } +} + +func evalArrayIndexExpression(array, index object.Object) object.Object { + arrayObject := array.(*object.Array) + idx := index.(*object.Integer).Value + max := int64(len(arrayObject.Elements) - 1) + + if idx < 0 || idx > max { + return NULL + } + + return arrayObject.Elements[idx] +} + +func evalDictLiteral(node *ast.DictLiteral, env *object.Environment) object.Object { + pairs := make(map[object.HashKey]object.DictPair) + + for keyNode, valueNode := range node.Pairs { + key := Eval(keyNode, env) + if isError(key) { + return key + } + + hashKey, ok := key.(object.Hashable) + if !ok { + return newError("Mstari %d: Hashing imeshindikana: %s", node.Token.Line, key.Type()) + } + + value := Eval(valueNode, env) + if isError(value) { + return value + } + + hashed := hashKey.HashKey() + pairs[hashed] = object.DictPair{Key: key, Value: value} + } + + return &object.Dict{Pairs: pairs} +} + +func evalDictIndexExpression(dict, index object.Object, line int) object.Object { + dictObject := dict.(*object.Dict) + + key, ok := index.(object.Hashable) + if !ok { + return newError("Mstari %d: Samahani, %s haitumiki kama key", line, index.Type()) + } + + pair, ok := dictObject.Pairs[key.HashKey()] + if !ok { + return NULL + } + + return pair.Value +} + +func evalWhileExpression(we *ast.WhileExpression, env *object.Environment) object.Object { + condition := Eval(we.Condition, env) + if isError(condition) { + return condition + } + if isTruthy(condition) { + evaluated := Eval(we.Consequence, env) + if isError(evaluated) { + return evaluated + } + if evaluated != nil && evaluated.Type() == object.BREAK_OBJ { + return evaluated + } + evalWhileExpression(we, env) + } + return NULL +} + +func evalBreak(node *ast.Break) object.Object { + return BREAK +} + +func evalContinue(node *ast.Continue) object.Object { + return CONTINUE +} + +func evalInExpression(left, right object.Object, line int) object.Object { + switch right.(type) { + case *object.String: + return evalInStringExpression(left, right) + case *object.Array: + return evalInArrayExpression(left, right) + case *object.Dict: + return evalInDictExpression(left, right, line) + default: + return FALSE + } +} + +func evalInStringExpression(left, right object.Object) object.Object { + if left.Type() != object.STRING_OBJ { + return FALSE + } + leftVal := left.(*object.String) + rightVal := right.(*object.String) + found := strings.Contains(rightVal.Value, leftVal.Value) + return nativeBoolToBooleanObject(found) +} + +func evalInDictExpression(left, right object.Object, line int) object.Object { + leftVal, ok := left.(object.Hashable) + if !ok { + return newError("Huwezi kutumia kama 'key': %s", left.Type()) + } + key := leftVal.HashKey() + rightVal := right.(*object.Dict).Pairs + _, ok = rightVal[key] + return nativeBoolToBooleanObject(ok) +} + +func evalInArrayExpression(left, right object.Object) object.Object { + rightVal := right.(*object.Array) + switch leftVal := left.(type) { + case *object.Null: + for _, v := range rightVal.Elements { + if v.Type() == object.NULL_OBJ { + return TRUE + } + } + case *object.String: + for _, v := range rightVal.Elements { + if v.Type() == object.STRING_OBJ { + elem := v.(*object.String) + if elem.Value == leftVal.Value { + return TRUE + } + } + } + case *object.Integer: + for _, v := range rightVal.Elements { + if v.Type() == object.INTEGER_OBJ { + elem := v.(*object.Integer) + if elem.Value == leftVal.Value { + return TRUE + } + } + } + case *object.Float: + for _, v := range rightVal.Elements { + if v.Type() == object.FLOAT_OBJ { + elem := v.(*object.Float) + if elem.Value == leftVal.Value { + return TRUE + } + } + } + } + return FALSE +} + +// func evalForExpression(fe *ast.For, env *object.Environment) object.Object { +// obj, ok := env.Get(fe.Identifier) +// defer func() { // stay safe and not reassign an existing variable +// if ok { +// env.Set(fe.Identifier, obj) +// } +// }() +// val := Eval(fe.StarterValue, env) +// if isError(val) { +// return val +// } + +// env.Set(fe.StarterName.Value, val) + +// // err := Eval(fe.Starter, env) +// // if isError(err) { +// // return err +// // } +// for { +// evaluated := Eval(fe.Condition, env) +// if isError(evaluated) { +// return evaluated +// } +// if !isTruthy(evaluated) { +// break +// } +// res := Eval(fe.Block, env) +// if isError(res) { +// return res +// } +// if res.Type() == object.BREAK_OBJ { +// break +// } +// if res.Type() == object.CONTINUE_OBJ { +// err := Eval(fe.Closer, env) +// if isError(err) { +// return err +// } +// continue +// } +// if res.Type() == object.RETURN_VALUE_OBJ { +// return res +// } +// err := Eval(fe.Closer, env) +// if isError(err) { +// return err +// } +// } +// return NULL +// } + +func evalForInExpression(fie *ast.ForIn, env *object.Environment, line int) object.Object { + iterable := Eval(fie.Iterable, env) + existingKeyIdentifier, okk := env.Get(fie.Key) // again, stay safe + existingValueIdentifier, okv := env.Get(fie.Value) + defer func() { // restore them later on + if okk { + env.Set(fie.Key, existingKeyIdentifier) + } + if okv { + env.Set(fie.Value, existingValueIdentifier) + } + }() + switch i := iterable.(type) { + case object.Iterable: + defer func() { + i.Reset() + }() + return loopIterable(i.Next, env, fie) + default: + return newError("Mstari %d: Huwezi kufanya operesheni hii na %s", line, i.Type()) + } +} + +func loopIterable(next func() (object.Object, object.Object), env *object.Environment, fi *ast.ForIn) object.Object { + k, v := next() + for k != nil && v != nil { + env.Set(fi.Key, k) + env.Set(fi.Value, v) + res := Eval(fi.Block, env) + if isError(res) { + return res + } + if res != nil { + if res.Type() == object.BREAK_OBJ { + break + } + if res.Type() == object.CONTINUE_OBJ { + k, v = next() + continue + } + if res.Type() == object.RETURN_VALUE_OBJ { + return res + } + } + k, v = next() + } + return NULL +} + +func evalSwitchStatement(se *ast.SwitchExpression, env *object.Environment) object.Object { + obj := Eval(se.Value, env) + for _, opt := range se.Choices { + + if opt.Default { + continue + } + for _, val := range opt.Expr { + out := Eval(val, env) + if obj.Type() == out.Type() && obj.Inspect() == out.Inspect() { + blockOut := evalBlockStatement(opt.Block, env) + return blockOut + } + } + } + for _, opt := range se.Choices { + if opt.Default { + out := evalBlockStatement(opt.Block, env) + return out + } + } + return nil +} diff --git a/src/evaluator/evaluator_test.go b/src/evaluator/evaluator_test.go new file mode 100644 index 0000000..8fe8458 --- /dev/null +++ b/src/evaluator/evaluator_test.go @@ -0,0 +1,518 @@ +package evaluator + +import ( + "fmt" + "testing" + + "github.com/AvicennaJr/Nuru/lexer" + "github.com/AvicennaJr/Nuru/object" + "github.com/AvicennaJr/Nuru/parser" +) + +func TestEvalIntegerExpression(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"5", 5}, + {"10", 10}, + {"-5", -5}, + {"-10", -10}, + {"5 + 5 + 5 + 5 - 10", 10}, + {"2 * 2 * 2 * 2", 16}, + {"2 / 2 + 1", 2}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testIntegerObject(t, evaluated, tt.expected) + } +} + +func TestEvalBooleanExpression(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + {"kweli", true}, + {"sikweli", false}, + {"1 < 2", true}, + {"1 > 2", false}, + {"1 > 1", false}, + {"1 < 1", false}, + {"1 == 1", true}, + {"1 != 1", false}, + {"1 == 2", false}, + {"1 != 2", true}, + {"kweli == kweli", true}, + {"sikweli == sikweli", true}, + {"kweli == sikweli", false}, + {"kweli != sikweli", true}, + {"sikweli != kweli", true}, + {"(1 < 2) == kweli", true}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testBooleanObject(t, evaluated, tt.expected) + } +} + +func TestBangOperator(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + {"!kweli", false}, + {"!sikweli", true}, + {"!5", false}, + {"!!kweli", true}, + {"!!sikweli", false}, + {"!!5", true}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testBooleanObject(t, evaluated, tt.expected) + } +} + +func testEval(input string) object.Object { + l := lexer.New(input) + p := parser.New(l) + program := p.ParseProgram() + env := object.NewEnvironment() + + return Eval(program, env) +} + +func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { + result, ok := obj.(*object.Integer) + + if !ok { + t.Errorf("Object is not Integer, got=%T(%+v)", obj, obj) + return false + } + + if result.Value != expected { + t.Errorf("object has wrong value. got=%d, want=%d", result.Value, expected) + return false + } + + return true +} + +func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool { + result, ok := obj.(*object.Boolean) + if !ok { + t.Errorf("object is not Boolean, got=%T(%+v)", obj, obj) + return false + } + + if result.Value != expected { + t.Errorf("object has wrong value, got=%t, want=%t", result.Value, expected) + return false + } + + return true +} + +func TestIfElseExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {"kama (kweli) {10}", 10}, + {"kama (sikweli) {10}", nil}, + {"kama (1) {10}", 10}, + {"kama (1 < 2) {10}", 10}, + {"kama (1 > 2) {10}", nil}, + {"kama (1 > 2) {10} sivyo {20}", 20}, + {"kama (1 < 2) {10} sivyo {20}", 10}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func testNullObject(t *testing.T, obj object.Object) bool { + if obj != NULL { + t.Errorf("object is not null, got=%T(+%v)", obj, obj) + return false + } + return true +} + +func TestReturnStatements(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"rudisha 10", 10}, + {"rudisha 10; 9;", 10}, + {"rudisha 2 * 5; 9;", 10}, + {"9; rudisha 2 * 5; 9;", 10}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testIntegerObject(t, evaluated, tt.expected) + } +} + +func TestErrorHandling(t *testing.T) { + tests := []struct { + input string + expectedMessage string + }{ + { + "5 + kweli", + "Mstari 0: Aina Hazilingani: NAMBA + BOOLEAN", + }, + { + "5 + kweli; 5;", + "Mstari 0: Aina Hazilingani: NAMBA + BOOLEAN", + }, + { + "-kweli", + "Mstari 0: Operesheni Haielweki: -BOOLEAN", + }, + { + "kweli + sikweli", + "Mstari 0: Operesheni Haielweki: BOOLEAN + BOOLEAN", + }, + { + "5; kweli + sikweli; 5", + "Mstari 0: Operesheni Haielweki: BOOLEAN + BOOLEAN", + }, + { + "kama (10 > 1) { kweli + sikweli;}", + "Mstari 0: Operesheni Haielweki: BOOLEAN + BOOLEAN", + }, + { + ` +kama (10 > 1) { + kama (10 > 1) { + rudisha kweli + kweli; + } + + rudisha 1; +} + `, + "Mstari 3: Operesheni Haielweki: BOOLEAN + BOOLEAN", + }, + { + "bangi", + "Mstari 0: Neno Halifahamiki: bangi", + }, + { + `"Habari" - "Habari"`, + "Mstari 0: Operesheni Haielweki: NENO - NENO", + }, + { + `{"jina": "Avi"}[unda(x) {x}];`, + "Mstari 0: Samahani, UNDO (FUNCTION) haitumiki kama key", + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("no error object return, got=%T(%+v)", evaluated, evaluated) + continue + } + + if errObj.Message != fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, tt.expectedMessage) { + t.Errorf("wrong error message, expected=%q, got=%q", fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, tt.expectedMessage), errObj.Message) + } + } +} + +func TestLetStatement(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"fanya a = 5; a;", 5}, + {"fanya a = 5 * 5; a;", 25}, + {"fanya a = 5; fanya b = a; b;", 5}, + {"fanya a = 5; fanya b = a; fanya c = a + b + 5; c;", 15}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + +func TestFunctionObject(t *testing.T) { + input := "unda(x) { x + 2 ;};" + + evaluated := testEval(input) + unda, ok := evaluated.(*object.Function) + if !ok { + t.Fatalf("object is not a Function, got=%T(%+v)", evaluated, evaluated) + } + + if len(unda.Parameters) != 1 { + t.Fatalf("function haas wrong paramters,Parameters=%+v", unda.Parameters) + } + + if unda.Parameters[0].String() != "x" { + t.Fatalf("parameter is not x, got=%q", unda.Parameters[0]) + } + + expectedBody := "(x + 2)" + + if unda.Body.String() != expectedBody { + t.Fatalf("body is not %q, got=%q", expectedBody, unda.Body.String()) + } +} + +func TestFunctionApplication(t *testing.T) { + tests := []struct { + input string + expected int64 + }{ + {"fanya mfano = unda(x) {x;}; mfano(5);", 5}, + {"fanya mfano = unda(x) {rudisha x;}; mfano(5);", 5}, + {"fanya double = unda(x) { x * 2;}; double(5);", 10}, + {"fanya add = unda(x, y) {x + y;}; add(5,5);", 10}, + {"fanya add = unda(x, y) {x + y;}; add(5 + 5, add(5, 5));", 20}, + {"unda(x) {x;}(5)", 5}, + } + + for _, tt := range tests { + testIntegerObject(t, testEval(tt.input), tt.expected) + } +} + +func TestClosures(t *testing.T) { + input := ` +fanya newAdder = unda(x) { + unda(y) { x + y}; +}; + +fanya addTwo = newAdder(2); +addTwo(2); +` + testIntegerObject(t, testEval(input), 4) +} + +func TestStringLiteral(t *testing.T) { + input := `"Habari yako!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("Object is not string, got=%T(%+v)", evaluated, evaluated) + } + + if str.Value != "Habari yako!" { + t.Errorf("String has wrong value, got=%q", str.Value) + } +} + +func TestStringconcatenation(t *testing.T) { + input := `"Mambo" + " " + "Vipi" + "?"` + + evaluated := testEval(input) + + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not a string, got=%T(%+v)", evaluated, evaluated) + } + + if str.Value != "Mambo Vipi?" { + t.Errorf("String has wrong value, got=%q", str.Value) + } +} + +func TestBuiltinFunctions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {`idadi("")`, 0}, + {`idadi("four")`, 4}, + {`idadi("hello world")`, 11}, + {`idadi(1)`, "Samahani, hii function haitumiki na NAMBA"}, + {`idadi("one", "two")`, "Hoja hazilingani, tunahitaji=1, tumepewa=2"}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + switch expected := tt.expected.(type) { + case int: + testIntegerObject(t, evaluated, int64(expected)) + case string: + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("Object is not Error, got=%T(%+v)", evaluated, evaluated) + continue + } + if errObj.Message != fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, expected) { + t.Errorf("Wrong eror message, expected=%q, got=%q", expected, errObj.Message) + } + } + } +} + +func TestArrayLiterals(t *testing.T) { + input := "[1, 2 * 2, 3 + 3]" + + evaluated := testEval(input) + result, ok := evaluated.(*object.Array) + if !ok { + t.Fatalf("Object is not an Array, got=%T(%+v)", evaluated, evaluated) + } + + if len(result.Elements) != 3 { + t.Fatalf("Array has wrong number of elements, got=%d", len(result.Elements)) + } + + testIntegerObject(t, result.Elements[0], 1) + testIntegerObject(t, result.Elements[1], 4) + testIntegerObject(t, result.Elements[2], 6) +} + +func TestArrayIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + "[1, 2, 3][0]", + 1, + }, + { + "[1, 2, 3][1]", + 2, + }, + { + "[1, 2, 3][2]", + 3, + }, + { + "fanya i = 0; [1][i];", + 1, + }, + { + "fanya myArr = [1, 2, 3]; myArr[2];", + 3, + }, + { + "[1, 2, 3][3]", + nil, + }, + { + "[1, 2, 3][-1]", + nil, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} + +func TestDictLiterals(t *testing.T) { + input := `fanya two = "two"; +{ + "one": 10 - 9, + two: 1 +1, + "thr" + "ee": 6 / 2, + 4: 4, + kweli: 5, + sikweli: 6 +}` + + evaluated := testEval(input) + result, ok := evaluated.(*object.Dict) + if !ok { + t.Fatalf("Eval didn't return a dict, got=%T(%+v)", evaluated, evaluated) + } + + expected := map[object.HashKey]int64{ + (&object.String{Value: "one"}).HashKey(): 1, + (&object.String{Value: "two"}).HashKey(): 2, + (&object.String{Value: "three"}).HashKey(): 3, + (&object.Integer{Value: 4}).HashKey(): 4, + TRUE.HashKey(): 5, + FALSE.HashKey(): 6, + } + + if len(result.Pairs) != len(expected) { + t.Fatalf("Dict has wrong number of pairs, got=%d", len(result.Pairs)) + } + + for expectedKey, expectedValue := range expected { + pair, ok := result.Pairs[expectedKey] + if !ok { + t.Errorf("No pair for give key") + } + + testIntegerObject(t, pair.Value, expectedValue) + } +} + +func TestDictIndexExpression(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + `{"foo": 5}["foo"]`, + 5, + }, + { + `{"foo": 5}["bar"]`, + nil, + }, + { + `fanya key = "foo"; {"foo": 5}[key]`, + 5, + }, + { + `{}["foo"]`, + nil, + }, + { + `{5: 5}[5]`, + 5, + }, + { + `{kweli: 5}[kweli]`, + 5, + }, + { + `{sikweli: 5}[sikweli]`, + 5, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if ok { + testIntegerObject(t, evaluated, int64(integer)) + } else { + testNullObject(t, evaluated) + } + } +} diff --git a/src/examples/example.nr b/src/examples/example.nr new file mode 100644 index 0000000..7ebacef --- /dev/null +++ b/src/examples/example.nr @@ -0,0 +1,107 @@ +andika("Testing basic types..."); +andika(2 + 2); +andika(4 * 4); +fanya a = 10; +fanya b = 20; + +andika(a + b); + +andika(kweli == sikweli); +andika("Testing empty lines...") +andika(); +andika(); +andika(); +andika("Mambo vipi"); + +andika("Testing Functions... "); + +fanya jumlisha = unda(x, y) {x + y}; + +andika(jumlisha(20,30)); +andika(jumlisha(100,1000)); + +fanya zidisha = unda(x, y) {x * y}; + +andika(zidisha(100,1000)); +andika(zidisha(200, 20)); + +// lists can hold any value +andika("Testing lists..."); +fanya list = [1, "a", kweli, sikweli]; + +// a few builtins + +fanya list = sukuma(list, jumlisha(4,5)); + +andika(list); +andika(list[2]); +andika(list[-100]); // prints null +andika(idadi(list)); +andika(yamwisho(list)); +andika([1,2,3] + [4,5,6]); +list[0] = 1000 +andika(list[0]) + +// if statements +andika("Testing if statements..."); +kama (1 > 2) { + andika("Moja ni zaidi ya mbili"); +} sivyo { + andika("Moja si zaidi ya mbili"); +} + +kama (idadi("Habari") == 6) { + andika("Habari yako ndugu"); +} + +// fibonacci example +andika("Testing fibonacci..."); + +fanya fibo = unda(x) { + kama (x == 0) { + rudisha 0; + } au kama (x == 1) { + rudisha 1; + } sivyo { + rudisha fibo(x - 1) + fibo(x - 2); + } +} + + +andika(fibo(10)); + +// testing input +andika("Testing input from user..."); +fanya salamu = unda() { + fanya jina = jaza("Unaitwa nani rafiki? "); + rudisha "Mambo vipi " + jina; + } + +andika(salamu()); + +/* +Multiline comment +*/ + +// test dictionaries + +andika("Testing dictionaries...") + +fanya watu = [{"jina": "Mojo", "kabila": "Mnyakusa"}, {"jina": "Avi", "kabila": "Mwarabu wa dubai"}] + +andika(watu, watu[0], watu[0]["jina"], watu[0]["kabila"]) + +watu[0]["jina"] = "MJ"; +andika(watu[0]["jina"]); + +andika({"a":1} + {"b": 2}) +// testing while loop + +andika("Testing while loop..."); + +fanya i = 10; + +wakati (i > 0) { + andika(i); + i = i - 1; +} diff --git a/src/examples/sorting_algorithm.nr b/src/examples/sorting_algorithm.nr new file mode 100644 index 0000000..10d6131 --- /dev/null +++ b/src/examples/sorting_algorithm.nr @@ -0,0 +1,63 @@ +/* +############ Sorting Algorithm ############## + + By @VictorKariuki + + https://github.com/VictorKariuki + +############################################# +*/ + +fanya slice = unda(arr,start, end) { + fanya result = [] + wakati (start < end) { + result = result + [arr[start]] + start = start + 1 + } + rudisha result +} + +fanya merge = unda(left, right) { + fanya result = [] + fanya lLen = idadi(left) + fanya rLen = idadi(right) + fanya l = 0 + fanya r = 0 + wakati (l < lLen && r < rLen) { + kama (left[l] < right[r]) { + result = result + [left[l]] + l = l + 1 + } sivyo { + result = result + [right[r]] + r = r + 1 + } + } + andika(result) +} + + +fanya mergeSort = unda(arr){ + fanya len = idadi(arr) + andika("arr is ", arr," of length ", len) + kama (len < 2) { + rudisha arr + } + andika("len is greater than or == to 2", len > 1) + + fanya mid = (len / 2) + andika("arr has a mid point of ", mid) + + fanya left = slice(arr, 0, mid) + fanya right = slice(arr, mid, len) + andika("left slice is ", left) + andika("right slice is ", right) + fanya sortedLeft = mergeSort(left) + fanya sortedRight = mergeSort(right) + andika("sortedLeft is ", sortedLeft) + andika("sortedRight is ", sortedRight) + rudisha merge(sortedLeft, sortedRight) +} + +fanya arr = [6, 5, 3, 1, 8, 7, 2, 4] +fanya sortedArray = mergeSort(arr) +andika(sortedArray) \ No newline at end of file diff --git a/src/examples/sudoku_solver.nr b/src/examples/sudoku_solver.nr new file mode 100644 index 0000000..de5d3a6 --- /dev/null +++ b/src/examples/sudoku_solver.nr @@ -0,0 +1,101 @@ +/*########### Backtracking Algorithm ############## + + By @VictorKariuki + + https://github.com/VictorKariuki + +NURU program to solve Sudoku using Backtracking Algorithm + +The sudoku puzzle is represented as a 2D array. The empty +cells are represented by 0. The algorithm works by trying +out all possible numbers for an empty cell. If the number +is valid, it is placed in the cell. If the number is invalid, +the algorithm backtracks to the previous cell and tries +another number. The algorithm terminates when all cells +are filled. The algorithm is implemented in the solveSudoku +function. The isValid function checks kama a number is +valid in a given cell. The printSudoku function prints +the sudoku puzzle. The solveSudoku function solves the +sudoku puzzle. The main function initializes the sudoku +puzzle and calls the solveSudoku function. + +#################################################*/ + + +fanya printing = unda(sudoku) { + fanya row = 0 + wakati (row < 9){ + andika(sudoku[row]) + row++ + } +} + +fanya sudoku = [[3, 0, 6, 5, 0, 8, 4, 0, 0],[5, 2, 0, 0, 0, 0, 0, 0, 0],[0, 8, 7, 0, 0, 0, 0, 3, 1],[0, 0, 3, 0, 1, 0, 0, 8, 0],[9, 0, 0, 8, 6, 3, 0, 0, 5],[0, 5, 0, 0, 9, 0, 6, 0, 0],[1, 3, 0, 0, 0, 0, 2, 5, 0],[0, 0, 0, 0, 0, 0, 0, 7, 4],[0, 0, 5, 2, 0, 6, 3, 0, 0]] + + + +fanya isSafe = unda(grid, row, col, num) { + kwa x ktk [0,1,2,3,4,5,6,7,8] { + kama (grid[row][x] == num) { + rudisha sikweli + } + } + + kwa x ktk [0,1,2,3,4,5,6,7,8] { + kama (grid[x][col] == num) { + rudisha sikweli + } + } + + fanya startRow = row - row % 3 + fanya startCol = col - col % 3 + + kwa i ktk [0, 1, 2] { + kwa j ktk [0, 1, 2] { + kama (grid[i + startRow][j + startCol] == num) { + rudisha sikweli + } + } + } + + rudisha kweli +} + +fanya solveSudoku = unda(grid, row, col) { + kama (row == 8 && col == 9) { + rudisha kweli + } + + kama (col == 9) { + row += 1 + col = 0 + } + + kama (grid[row][col] > 0) { + rudisha solveSudoku(grid, row, col + 1) + } + + kwa num ktk [1,2,3,4,5,6,7,8,9] { + kama (isSafe(grid, row, col, num)) { + grid[row][col] = num + kama (solveSudoku(grid, row, col + 1)) { + rudisha kweli + } + } + + grid[row][col] = 0 + } + + rudisha sikweli +} +andika() +andika("----- PUZZLE TO SOLVE -----") +printing(sudoku) +kama (solveSudoku(sudoku, 0, 0)){ + andika() + andika("--------- SOLUTION --------") + printing(sudoku) + andika() +} sivyo { + andika("imeshindikana") +} \ No newline at end of file diff --git a/src/go.mod b/src/go.mod new file mode 100644 index 0000000..352462e --- /dev/null +++ b/src/go.mod @@ -0,0 +1,3 @@ +module github.com/AvicennaJr/Nuru + +go 1.18 diff --git a/src/lexer/lexer.go b/src/lexer/lexer.go new file mode 100644 index 0000000..5afcecb --- /dev/null +++ b/src/lexer/lexer.go @@ -0,0 +1,327 @@ +package lexer + +import ( + "github.com/AvicennaJr/Nuru/token" +) + +type Lexer struct { + input string + position int + readPosition int + ch byte + line int +} + +func New(input string) *Lexer { + l := &Lexer{input: input} + l.readChar() + return l +} + +func (l *Lexer) readChar() { + if l.readPosition >= len(l.input) { + l.ch = 0 + } else { + l.ch = l.input[l.readPosition] + } + + l.position = l.readPosition + l.readPosition += 1 +} + +func (l *Lexer) NextToken() token.Token { + var tok token.Token + l.skipWhitespace() + if l.ch == '/' && l.peekChar() == '/' { + l.skipSingleLineComment() + return l.NextToken() + } + if l.ch == '/' && l.peekChar() == '*' { + l.skipMultiLineComment() + return l.NextToken() + } + + switch l.ch { + case '=': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.EQ, Literal: string(ch) + string(l.ch), Line: l.line} + } else { + tok = newToken(token.ASSIGN, l.line, l.ch) + } + case ';': + tok = newToken(token.SEMICOLON, l.line, l.ch) + case '(': + tok = newToken(token.LPAREN, l.line, l.ch) + case ')': + tok = newToken(token.RPAREN, l.line, l.ch) + case '{': + tok = newToken(token.LBRACE, l.line, l.ch) + case '}': + tok = newToken(token.RBRACE, l.line, l.ch) + case ',': + tok = newToken(token.COMMA, l.line, l.ch) + case '+': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.PLUS_ASSIGN, Line: l.line, Literal: string(ch) + string(l.ch)} + } else if l.peekChar() == '+' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.PLUS_PLUS, Literal: string(ch) + string(l.ch), Line: l.line} + } else { + tok = newToken(token.PLUS, l.line, l.ch) + } + case '-': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.MINUS_ASSIGN, Line: l.line, Literal: string(ch) + string(l.ch)} + } else if l.peekChar() == '-' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.MINUS_MINUS, Literal: string(ch) + string(l.ch), Line: l.line} + } else { + tok = newToken(token.MINUS, l.line, l.ch) + } + case '!': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.NOT_EQ, Literal: string(ch) + string(l.ch), Line: l.line} + } else { + tok = newToken(token.BANG, l.line, l.ch) + } + case '/': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.SLASH_ASSIGN, Line: l.line, Literal: string(ch) + string(l.ch)} + } else { + tok = newToken(token.SLASH, l.line, l.ch) + } + case '*': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.ASTERISK_ASSIGN, Line: l.line, Literal: string(ch) + string(l.ch)} + } else if l.peekChar() == '*' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.POW, Literal: string(ch) + string(l.ch), Line: l.line} + } else { + tok = newToken(token.ASTERISK, l.line, l.ch) + } + case '<': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.LTE, Literal: string(ch) + string(l.ch), Line: l.line} + } else { + tok = newToken(token.LT, l.line, l.ch) + } + case '>': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.GTE, Literal: string(ch) + string(l.ch), Line: l.line} + } else { + tok = newToken(token.GT, l.line, l.ch) + } + case '"': + tok.Type = token.STRING + tok.Literal = l.readString() + tok.Line = l.line + case '\'': + tok = token.Token{Type: token.STRING, Literal: l.readSingleQuoteString(), Line: l.line} + case '[': + tok = newToken(token.LBRACKET, l.line, l.ch) + case ']': + tok = newToken(token.RBRACKET, l.line, l.ch) + case ':': + tok = newToken(token.COLON, l.line, l.ch) + case '&': + if l.peekChar() == '&' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.AND, Literal: string(ch) + string(l.ch), Line: l.line} + } + case '|': + if l.peekChar() == '|' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.OR, Literal: string(ch) + string(l.ch), Line: l.line} + } + case '%': + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.MODULUS_ASSIGN, Line: l.line, Literal: string(ch) + string(l.ch)} + } else { + tok = newToken(token.MODULUS, l.line, l.ch) + } + case 0: + tok.Literal = "" + tok.Type = token.EOF + tok.Line = l.line + default: + if isLetter(l.ch) { + tok.Literal = l.readIdentifier() + tok.Type = token.LookupIdent(tok.Literal) + tok.Line = l.line + return tok + } else if isDigit(l.ch) { + tok = l.readDecimal() + return tok + } else { + tok = newToken(token.ILLEGAL, l.line, l.ch) + } + } + + l.readChar() + return tok +} + +func newToken(tokenType token.TokenType, line int, ch byte) token.Token { + return token.Token{Type: tokenType, Literal: string(ch), Line: line} +} + +func (l *Lexer) readIdentifier() string { + position := l.position + + for isLetter(l.ch) || isDigit(l.ch) { + l.readChar() + } + return l.input[position:l.position] +} + +func isLetter(ch byte) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' +} + +func (l *Lexer) skipWhitespace() { + for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' { + if l.ch == '\n' { + l.line++ + } + l.readChar() + } +} + +func isDigit(ch byte) bool { + return '0' <= ch && ch <= '9' +} + +func (l *Lexer) readNumber() string { + position := l.position + for isDigit(l.ch) { + l.readChar() + } + return l.input[position:l.position] +} + +func (l *Lexer) readDecimal() token.Token { + integer := l.readNumber() + if l.ch == '.' && isDigit(l.peekChar()) { + l.readChar() + fraction := l.readNumber() + return token.Token{Type: token.FLOAT, Literal: integer + "." + fraction, Line: l.line} + } + return token.Token{Type: token.INT, Literal: integer, Line: l.line} +} + +func (l *Lexer) peekChar() byte { + if l.readPosition >= len(l.input) { + return 0 + } else { + return l.input[l.readPosition] + } +} + +func (l *Lexer) skipSingleLineComment() { + for l.ch != '\n' && l.ch != 0 { + l.readChar() + } + l.skipWhitespace() +} + +func (l *Lexer) skipMultiLineComment() { + endFound := false + + for !endFound { + if l.ch == 0 { + endFound = true + } + + if l.ch == '*' && l.peekChar() == '/' { + endFound = true + l.readChar() + } + + l.readChar() + l.skipWhitespace() + } + +} + +func (l *Lexer) readString() string { + var str string + for { + l.readChar() + if l.ch == '"' || l.ch == 0 { + break + } else if l.ch == '\\' { + switch l.peekChar() { + case 'n': + l.readChar() + l.ch = '\n' + case 'r': + l.readChar() + l.ch = '\r' + case 't': + l.readChar() + l.ch = '\t' + case '"': + l.readChar() + l.ch = '"' + case '\\': + l.readChar() + l.ch = '\\' + } + } + str += string(l.ch) + } + return str +} + +func (l *Lexer) readSingleQuoteString() string { + var str string + for { + l.readChar() + if l.ch == '\'' || l.ch == 0 { + break + } else if l.ch == '\\' { + switch l.peekChar() { + case 'n': + l.readChar() + l.ch = '\n' + case 'r': + l.readChar() + l.ch = '\r' + case 't': + l.readChar() + l.ch = '\t' + case '"': + l.readChar() + l.ch = '"' + case '\\': + l.readChar() + l.ch = '\\' + } + } + str += string(l.ch) + } + return str +} diff --git a/src/lexer/lexer_test.go b/src/lexer/lexer_test.go new file mode 100644 index 0000000..74b3b42 --- /dev/null +++ b/src/lexer/lexer_test.go @@ -0,0 +1,153 @@ +package lexer + +import ( + "testing" + + "github.com/AvicennaJr/Nuru/token" +) + +func TestNextToken(t *testing.T) { + input := ` + // Testing kama lex luther iko sawa + fanya tano = 5; + fanya kumi = 10; + + fanya jumla = unda(x, y){ + x + y; + }; + + fanya jibu = jumla(tano, kumi); + + !-/5; + 5 < 10 > 5; + + kama (5 < 10) { + rudisha kweli; + } sivyo { + rudisha sikweli; + } + + 10 == 10; + 10 != 9; // Hii ni comment + // Comment nyingine + + /* + multiline comment + */ + + /* multiline comment number twooooooooooo */ + 5 + "bangi" + "ba ngi" + [1, 2]; + {"mambo": "vipi"}` + + tests := []struct { + expectedType token.TokenType + expectedLiteral string + }{ + {token.LET, "fanya"}, + {token.IDENT, "tano"}, + {token.ASSIGN, "="}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.LET, "fanya"}, + {token.IDENT, "kumi"}, + {token.ASSIGN, "="}, + {token.INT, "10"}, + {token.SEMICOLON, ";"}, + {token.LET, "fanya"}, + {token.IDENT, "jumla"}, + {token.ASSIGN, "="}, + {token.FUNCTION, "unda"}, + {token.LPAREN, "("}, + {token.IDENT, "x"}, + {token.COMMA, ","}, + {token.IDENT, "y"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.IDENT, "x"}, + {token.PLUS, "+"}, + {token.IDENT, "y"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.SEMICOLON, ";"}, + {token.LET, "fanya"}, + {token.IDENT, "jibu"}, + {token.ASSIGN, "="}, + {token.IDENT, "jumla"}, + {token.LPAREN, "("}, + {token.IDENT, "tano"}, + {token.COMMA, ","}, + {token.IDENT, "kumi"}, + {token.RPAREN, ")"}, + {token.SEMICOLON, ";"}, + {token.BANG, "!"}, + {token.MINUS, "-"}, + {token.SLASH, "/"}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.INT, "5"}, + {token.LT, "<"}, + {token.INT, "10"}, + {token.GT, ">"}, + {token.INT, "5"}, + {token.SEMICOLON, ";"}, + {token.IF, "kama"}, + {token.LPAREN, "("}, + {token.INT, "5"}, + {token.LT, "<"}, + {token.INT, "10"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.RETURN, "rudisha"}, + {token.TRUE, "kweli"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.ELSE, "sivyo"}, + {token.LBRACE, "{"}, + {token.RETURN, "rudisha"}, + {token.FALSE, "sikweli"}, + {token.SEMICOLON, ";"}, + {token.RBRACE, "}"}, + {token.INT, "10"}, + {token.EQ, "=="}, + {token.INT, "10"}, + {token.SEMICOLON, ";"}, + {token.INT, "10"}, + {token.NOT_EQ, "!="}, + {token.INT, "9"}, + {token.SEMICOLON, ";"}, + {token.INT, "5"}, + {token.STRING, "bangi"}, + {token.STRING, "ba ngi"}, + {token.LBRACKET, "["}, + {token.INT, "1"}, + {token.COMMA, ","}, + {token.INT, "2"}, + {token.RBRACKET, "]"}, + {token.SEMICOLON, ";"}, + {token.LBRACE, "{"}, + {token.STRING, "mambo"}, + {token.COLON, ":"}, + {token.STRING, "vipi"}, + {token.RBRACE, "}"}, + {token.EOF, ""}, + } + + l := New(input) + + for i, tt := range tests { + tok := l.NextToken() + + if tok.Type != tt.expectedType { + t.Fatalf("tests[%d] - tokentype wrong. expected=%q, got=%q", + i, tt.expectedType, tok.Type) + } + + if tok.Literal != tt.expectedLiteral { + t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q", + i, tt.expectedLiteral, tok.Literal) + } + } +} diff --git a/src/main.go b/src/main.go new file mode 100644 index 0000000..4a6941a --- /dev/null +++ b/src/main.go @@ -0,0 +1,67 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + "strings" + + "github.com/AvicennaJr/Nuru/repl" +) + +const ( + LOGO = ` + +█░░ █░█ █▀▀ █░█ ▄▀█   █▄█ ▄▀█   █▄░█ █░█ █▀█ █░█ +█▄▄ █▄█ █▄█ █▀█ █▀█   ░█░ █▀█   █░▀█ █▄█ █▀▄ █▄█ + + | Authored by Avicenna | v0.2.0 | +` +) + +func main() { + + args := os.Args + coloredLogo := fmt.Sprintf("\x1b[%dm%s\x1b[0m", 36, LOGO) + + if len(args) < 2 { + + fmt.Println(coloredLogo) + fmt.Println("𝑯𝒂𝒃𝒂𝒓𝒊, 𝒌𝒂𝒓𝒊𝒃𝒖 𝒖𝒕𝒖𝒎𝒊𝒆 𝒍𝒖𝒈𝒉𝒂 𝒚𝒂 𝑵𝒖𝒓𝒖 ✨") + fmt.Println("\nTumia exit() au toka() kuondoka") + + repl.Start(os.Stdin, os.Stdout) + } + + if len(args) == 2 { + + switch args[1] { + case "msaada", "-msaada", "--msaada", "help", "-help", "--help", "-h": + fmt.Printf("\x1b[%dm%s\x1b[0m\n", 32, "\nTumia 'nuru' kuanza program\n\nAU\n\nTumia 'nuru' ikifuatiwa na jina la file.\n\n\tMfano:\tnuru fileYangu.nr") + os.Exit(0) + case "version", "-version", "--version", "-v", "v": + fmt.Println(coloredLogo) + os.Exit(0) + } + + file := args[1] + + if strings.HasSuffix(file, "nr") || strings.HasSuffix(file, ".sw") { + contents, err := ioutil.ReadFile(file) + if err != nil { + fmt.Printf("\x1b[%dm%s%s\x1b[0m\n", 31, "Error: Nimeshindwa kusoma file: ", args[0]) + os.Exit(0) + } + + repl.Read(string(contents)) + } else { + fmt.Printf("\x1b[%dm%s%s\x1b[0m", 31, file, " sii file sahihi. Tumia file la '.nr' au '.sw'\n") + os.Exit(0) + } + + } else { + fmt.Printf("\x1b[%dm%s\x1b[0m\n", 31, "Error: Operesheni imeshindikana boss.") + fmt.Printf("\x1b[%dm%s\x1b[0m\n", 32, "\nTumia 'nuru' kuprogram\n\nAU\n\nTumia 'nuru' ikifuatiwa na jina la file.\n\n\tMfano:\tnuru fileYangu.nr") + os.Exit(0) + } +} diff --git a/src/object/environment.go b/src/object/environment.go new file mode 100644 index 0000000..6f876e8 --- /dev/null +++ b/src/object/environment.go @@ -0,0 +1,31 @@ +package object + +func NewEnclosedEnvironment(outer *Environment) *Environment { + env := NewEnvironment() + env.outer = outer + return env +} + +func NewEnvironment() *Environment { + s := make(map[string]Object) + return &Environment{store: s, outer: nil} +} + +type Environment struct { + store map[string]Object + outer *Environment +} + +func (e *Environment) Get(name string) (Object, bool) { + obj, ok := e.store[name] + + if !ok && e.outer != nil { + obj, ok = e.outer.Get(name) + } + return obj, ok +} + +func (e *Environment) Set(name string, val Object) Object { + e.store[name] = val + return val +} diff --git a/src/object/object.go b/src/object/object.go new file mode 100644 index 0000000..a6a2ddf --- /dev/null +++ b/src/object/object.go @@ -0,0 +1,277 @@ +package object + +import ( + "bytes" + "fmt" + "hash/fnv" + "sort" + "strconv" + "strings" + + "github.com/AvicennaJr/Nuru/ast" +) + +type ObjectType string + +const ( + INTEGER_OBJ = "NAMBA" + FLOAT_OBJ = "DESIMALI" + BOOLEAN_OBJ = "BOOLEAN" + NULL_OBJ = "TUPU" + RETURN_VALUE_OBJ = "RUDISHA" + ERROR_OBJ = "KOSA" + FUNCTION_OBJ = "UNDO (FUNCTION)" + STRING_OBJ = "NENO" + BUILTIN_OBJ = "YA_NDANI" + ARRAY_OBJ = "ORODHA" + DICT_OBJ = "KAMUSI" + CONTINUE_OBJ = "ENDELEA" + BREAK_OBJ = "VUNJA" +) + +type Object interface { + Type() ObjectType + Inspect() string +} + +type Integer struct { + Value int64 +} + +func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) } +func (i *Integer) Type() ObjectType { return INTEGER_OBJ } + +type Float struct { + Value float64 +} + +func (f *Float) Inspect() string { return strconv.FormatFloat(f.Value, 'f', -1, 64) } +func (f *Float) Type() ObjectType { return FLOAT_OBJ } + +type Boolean struct { + Value bool +} + +func (b *Boolean) Inspect() string { + if b.Value { + return "kweli" + } else { + return "sikweli" + } +} +func (b *Boolean) Type() ObjectType { return BOOLEAN_OBJ } + +type Null struct{} + +func (n *Null) Inspect() string { return "null" } +func (n *Null) Type() ObjectType { return NULL_OBJ } + +type ReturnValue struct { + Value Object +} + +func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() } +func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ } + +type Error struct { + Message string +} + +func (e *Error) Inspect() string { + msg := fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, "ERROR: ") + return msg + e.Message +} +func (e *Error) Type() ObjectType { return ERROR_OBJ } + +type Function struct { + Parameters []*ast.Identifier + Body *ast.BlockStatement + Env *Environment +} + +func (f *Function) Type() ObjectType { return FUNCTION_OBJ } +func (f *Function) Inspect() string { + var out bytes.Buffer + + params := []string{} + for _, p := range f.Parameters { + params = append(params, p.String()) + } + + out.WriteString("unda") + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") {\n") + out.WriteString(f.Body.String()) + out.WriteString("\n}") + + return out.String() +} + +type String struct { + Value string + offset int +} + +func (s *String) Inspect() string { return s.Value } +func (s *String) Type() ObjectType { return STRING_OBJ } +func (s *String) Next() (Object, Object) { + offset := s.offset + if len(s.Value) > offset { + s.offset = offset + 1 + return &Integer{Value: int64(offset)}, &String{Value: string(s.Value[offset])} + } + return nil, nil +} +func (s *String) Reset() { + s.offset = 0 +} + +type BuiltinFunction func(args ...Object) Object + +type Builtin struct { + Fn BuiltinFunction +} + +func (b *Builtin) Inspect() string { return "builtin function" } +func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ } + +type Array struct { + Elements []Object + offset int +} + +func (ao *Array) Type() ObjectType { return ARRAY_OBJ } +func (ao *Array) Inspect() string { + var out bytes.Buffer + + elements := []string{} + for _, e := range ao.Elements { + elements = append(elements, e.Inspect()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +func (ao *Array) Next() (Object, Object) { + idx := ao.offset + if len(ao.Elements) > idx { + ao.offset = idx + 1 + return &Integer{Value: int64(idx)}, ao.Elements[idx] + } + return nil, nil +} + +func (ao *Array) Reset() { + ao.offset = 0 +} + +type HashKey struct { + Type ObjectType + Value uint64 +} + +func (b *Boolean) HashKey() HashKey { + var value uint64 + + if b.Value { + value = 1 + } else { + value = 0 + } + + return HashKey{Type: b.Type(), Value: value} +} + +func (i *Integer) HashKey() HashKey { + return HashKey{Type: i.Type(), Value: uint64(i.Value)} +} + +func (f *Float) HashKey() HashKey { + h := fnv.New64a() + h.Write([]byte(f.Inspect())) + return HashKey{Type: f.Type(), Value: h.Sum64()} +} + +func (s *String) HashKey() HashKey { + h := fnv.New64a() + h.Write([]byte(s.Value)) + + return HashKey{Type: s.Type(), Value: h.Sum64()} +} + +type DictPair struct { + Key Object + Value Object +} + +type Dict struct { + Pairs map[HashKey]DictPair + offset int +} + +func (d *Dict) Type() ObjectType { return DICT_OBJ } +func (d *Dict) Inspect() string { + var out bytes.Buffer + + pairs := []string{} + + for _, pair := range d.Pairs { + pairs = append(pairs, fmt.Sprintf("%s: %s", pair.Key.Inspect(), pair.Value.Inspect())) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} + +func (d *Dict) Next() (Object, Object) { + idx := 0 + dict := make(map[string]DictPair) + var keys []string + for _, v := range d.Pairs { + dict[v.Key.Inspect()] = v + keys = append(keys, v.Key.Inspect()) + } + + sort.Strings(keys) + + for _, k := range keys { + if d.offset == idx { + d.offset += 1 + return dict[k].Key, dict[k].Value + } + idx += 1 + } + return nil, nil +} + +func (d *Dict) Reset() { + d.offset = 0 +} + +type Hashable interface { + HashKey() HashKey +} + +type Continue struct{} + +func (c *Continue) Type() ObjectType { return CONTINUE_OBJ } +func (c *Continue) Inspect() string { return "continue" } + +type Break struct{} + +func (b *Break) Type() ObjectType { return BREAK_OBJ } +func (b *Break) Inspect() string { return "break" } + +// Iterable interface for dicts, strings and arrays +type Iterable interface { + Next() (Object, Object) + Reset() +} diff --git a/src/object/object_test.go b/src/object/object_test.go new file mode 100644 index 0000000..8f4d2a5 --- /dev/null +++ b/src/object/object_test.go @@ -0,0 +1,22 @@ +package object + +import "testing" + +func TestStringHashKey(t *testing.T) { + hello1 := &String{Value: "Hello World"} + hello2 := &String{Value: "Hello World"} + diff1 := &String{Value: "My name is Avi"} + diff2 := &String{Value: "My name is Avi"} + + if hello1.HashKey() != hello2.HashKey() { + t.Errorf("string with the same content have different dict keys") + } + + if diff1.HashKey() != diff2.HashKey() { + t.Errorf("String with the same content have different dict keys") + } + + if hello1.HashKey() == diff1.HashKey() { + t.Errorf("Strings with different content have the same dict keys") + } +} diff --git a/src/parser/parser.go b/src/parser/parser.go new file mode 100644 index 0000000..ba09966 --- /dev/null +++ b/src/parser/parser.go @@ -0,0 +1,810 @@ +package parser + +import ( + "fmt" + "strconv" + + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/lexer" + "github.com/AvicennaJr/Nuru/token" +) + +const ( + // Think of BODMAS + _ int = iota + LOWEST + COND // OR or AND + ASSIGN // = + EQUALS // == + LESSGREATER // > OR < + SUM // + + PRODUCT // * + POWER // ** we got the power XD + MODULUS // % + PREFIX // -X OR !X + CALL // myFunction(X) + INDEX // Arrays +) + +var precedences = map[token.TokenType]int{ + token.AND: COND, + token.OR: COND, + token.IN: COND, + token.ASSIGN: ASSIGN, + token.EQ: EQUALS, + token.NOT_EQ: EQUALS, + token.LT: LESSGREATER, + token.LTE: LESSGREATER, + token.GT: LESSGREATER, + token.GTE: LESSGREATER, + token.PLUS: SUM, + token.PLUS_ASSIGN: SUM, + token.MINUS: SUM, + token.MINUS_ASSIGN: SUM, + token.SLASH: PRODUCT, + token.SLASH_ASSIGN: PRODUCT, + token.ASTERISK: PRODUCT, + token.ASTERISK_ASSIGN: PRODUCT, + token.POW: POWER, + token.MODULUS: MODULUS, + token.MODULUS_ASSIGN: MODULUS, + // token.BANG: PREFIX, + token.LPAREN: CALL, + token.LBRACKET: INDEX, // Highest priority +} + +type ( + prefixParseFn func() ast.Expression + infixParseFn func(ast.Expression) ast.Expression + postfixParseFn func() ast.Expression +) + +type Parser struct { + l *lexer.Lexer + + curToken token.Token + peekToken token.Token + prevToken token.Token + + errors []string + + prefixParseFns map[token.TokenType]prefixParseFn + infixParseFns map[token.TokenType]infixParseFn + postfixParseFns map[token.TokenType]postfixParseFn +} + +func New(l *lexer.Lexer) *Parser { + p := &Parser{l: l, errors: []string{}} + + // Gotta set these niggas + p.nextToken() + p.nextToken() + + p.prefixParseFns = make(map[token.TokenType]prefixParseFn) + p.registerPrefix(token.STRING, p.parseStringLiteral) + 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.MINUS, p.parsePrefixExpression) + p.registerPrefix(token.TRUE, p.parseBoolean) + p.registerPrefix(token.FALSE, p.parseBoolean) + p.registerPrefix(token.LPAREN, p.parseGroupedExpression) + p.registerPrefix(token.IF, p.parseIfExpression) + p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral) + p.registerPrefix(token.LBRACKET, p.parseArrayLiteral) + p.registerPrefix(token.LBRACE, p.parseDictLiteral) + p.registerPrefix(token.WHILE, p.parseWhileExpression) + p.registerPrefix(token.NULL, p.parseNull) + p.registerPrefix(token.FOR, p.parseForExpression) + p.registerPrefix(token.SWITCH, p.parseSwitchStatement) + + p.infixParseFns = make(map[token.TokenType]infixParseFn) + p.registerInfix(token.AND, p.parseInfixExpression) + p.registerInfix(token.OR, p.parseInfixExpression) + p.registerInfix(token.PLUS, p.parseInfixExpression) + p.registerInfix(token.PLUS_ASSIGN, p.parseAssignmentExpression) + p.registerInfix(token.MINUS, p.parseInfixExpression) + p.registerInfix(token.MINUS_ASSIGN, p.parseAssignmentExpression) + p.registerInfix(token.SLASH, p.parseInfixExpression) + p.registerInfix(token.SLASH_ASSIGN, p.parseAssignmentExpression) + p.registerInfix(token.ASTERISK, p.parseInfixExpression) + p.registerInfix(token.ASTERISK_ASSIGN, p.parseAssignmentExpression) + p.registerInfix(token.POW, p.parseInfixExpression) + p.registerInfix(token.MODULUS, p.parseInfixExpression) + p.registerInfix(token.MODULUS_ASSIGN, p.parseAssignmentExpression) + p.registerInfix(token.EQ, p.parseInfixExpression) + p.registerInfix(token.NOT_EQ, p.parseInfixExpression) + p.registerInfix(token.LT, p.parseInfixExpression) + p.registerInfix(token.LTE, p.parseInfixExpression) + p.registerInfix(token.GT, p.parseInfixExpression) + p.registerInfix(token.GTE, p.parseInfixExpression) + p.registerInfix(token.LPAREN, p.parseCallExpression) + p.registerInfix(token.LBRACKET, p.parseIndexExpression) + p.registerInfix(token.ASSIGN, p.parseAssignmentExpression) + p.registerInfix(token.IN, p.parseInfixExpression) + + p.postfixParseFns = make(map[token.TokenType]postfixParseFn) + p.registerPostfix(token.PLUS_PLUS, p.parsePostfixExpression) + p.registerPostfix(token.MINUS_MINUS, p.parsePostfixExpression) + + return p +} + +func (p *Parser) nextToken() { + p.prevToken = p.curToken + p.curToken = p.peekToken + p.peekToken = p.l.NextToken() +} + +func (p *Parser) ParseProgram() *ast.Program { + program := &ast.Program{} + program.Statements = []ast.Statement{} + + for !p.curTokenIs(token.EOF) { + stmt := p.parseStatement() + program.Statements = append(program.Statements, stmt) + + p.nextToken() + } + return program +} + +func (p *Parser) parseStatement() ast.Statement { + // Remember to add switch statements to the language + switch p.curToken.Type { + case token.LET: + return p.parseLetStatment() + case token.RETURN: + return p.parseReturnStatement() + case token.BREAK: + return p.parseBreak() + case token.CONTINUE: + return p.parseContinue() + default: + return p.parseExpressionStatement() + } +} + +func (p *Parser) parseLetStatment() *ast.LetStatement { + stmt := &ast.LetStatement{Token: p.curToken} + + if !p.expectPeek(token.IDENT) { + return nil + } + + stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + + if !p.expectPeek(token.ASSIGN) { + return nil + } + + p.nextToken() + + stmt.Value = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseNull() ast.Expression { + return &ast.Null{Token: p.curToken} +} + +func (p *Parser) parseAssignmentExpression(exp ast.Expression) ast.Expression { + switch node := exp.(type) { + case *ast.Identifier, *ast.IndexExpression: + default: + if node != nil { + msg := fmt.Sprintf("Mstari %d:Tulitegemea kupata kitambulishi au array, badala yake tumepata: %s", p.curToken.Line, node.TokenLiteral()) + p.errors = append(p.errors, msg) + } else { + msg := fmt.Sprintf("Mstari %d: Umekosea mkuu", p.curToken.Line) + p.errors = append(p.errors, msg) + } + return nil + } + + ae := &ast.AssignmentExpression{Token: p.curToken, Left: exp} + + p.nextToken() + + ae.Value = p.parseExpression(LOWEST) + + return ae +} + +func (p *Parser) curTokenIs(t token.TokenType) bool { + return p.curToken.Type == t +} + +func (p *Parser) peekTokenIs(t token.TokenType) bool { + return p.peekToken.Type == t +} + +func (p *Parser) expectPeek(t token.TokenType) bool { + if p.peekTokenIs(t) { + p.nextToken() + return true + } else { + p.peekError(t) + return false + } +} + +func (p *Parser) Errors() []string { + return p.errors +} + +func (p *Parser) peekError(t token.TokenType) { + msg := fmt.Sprintf("Mstari %d: Tulitegemea kupata %s, badala yake tumepata %s", p.curToken.Line, t, p.peekToken.Type) + p.errors = append(p.errors, msg) +} + +func (p *Parser) parseReturnStatement() *ast.ReturnStatement { + stmt := &ast.ReturnStatement{Token: p.curToken} + p.nextToken() + + stmt.ReturnValue = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) { + p.prefixParseFns[tokenType] = fn +} + +func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) { + p.infixParseFns[tokenType] = fn +} + +func (p *Parser) registerPostfix(tokenType token.TokenType, fn postfixParseFn) { + p.postfixParseFns[tokenType] = fn +} + +func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { + stmt := &ast.ExpressionStatement{Token: p.curToken} + + stmt.Expression = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) noPrefixParseFnError(t token.TokenType) { + msg := fmt.Sprintf("Mstari %d: Tumeshindwa kuparse %s", p.curToken.Line, t) + p.errors = append(p.errors, msg) +} + +func (p *Parser) noInfixParseFnError(t token.TokenType) { + msg := fmt.Sprintf("Mstari %d: Tumeshindwa kuparse %s", p.curToken.Line, t) + p.errors = append(p.errors, msg) +} + +func (p *Parser) parseExpression(precedence int) ast.Expression { + postfix := p.postfixParseFns[p.curToken.Type] + if postfix != nil { + return (postfix()) + } + prefix := p.prefixParseFns[p.curToken.Type] + if prefix == nil { + p.noPrefixParseFnError(p.curToken.Type) + return nil + } + leftExp := prefix() + + for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() { + infix := p.infixParseFns[p.peekToken.Type] + if infix == nil { + p.noInfixParseFnError(p.peekToken.Type) + return nil + } + + p.nextToken() + leftExp = infix(leftExp) + } + return leftExp + +} + +func (p *Parser) parseIdentifier() ast.Expression { + return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parseIntegerLiteral() ast.Expression { + lit := &ast.IntegerLiteral{Token: p.curToken} + + value, err := strconv.ParseInt(p.curToken.Literal, 0, 64) + if err != nil { + msg := fmt.Sprintf("Mstari %d: Hatuwezi kuparse %q kama namba", p.curToken.Line, p.curToken.Literal) + p.errors = append(p.errors, msg) + return nil + } + lit.Value = value + + return lit +} + +func (p *Parser) parseFloatLiteral() ast.Expression { + fl := &ast.FloatLiteral{Token: p.curToken} + value, err := strconv.ParseFloat(p.curToken.Literal, 64) + if err != nil { + msg := fmt.Sprintf("Mstari %d: Hatuwezi kuparse %q kama desimali", p.curToken.Line, p.curToken.Literal) + p.errors = append(p.errors, msg) + return nil + } + fl.Value = value + return fl +} + +func (p *Parser) parsePrefixExpression() ast.Expression { + expression := &ast.PrefixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + } + + p.nextToken() + + expression.Right = p.parseExpression(PREFIX) + + return expression +} + +func (p *Parser) peekPrecedence() int { + if p, ok := precedences[p.peekToken.Type]; ok { + return p + } + return LOWEST +} + +func (p *Parser) curPrecedence() int { + if p, ok := precedences[p.curToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression { + expression := &ast.InfixExpression{ + Token: p.curToken, + Operator: p.curToken.Literal, + Left: left, + } + + precedence := p.curPrecedence() + p.nextToken() + expression.Right = p.parseExpression(precedence) + return expression +} + +func (p *Parser) parseBoolean() ast.Expression { + return &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)} +} + +func (p *Parser) parseGroupedExpression() ast.Expression { + p.nextToken() + + exp := p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return exp +} + +func (p *Parser) parsePostfixExpression() ast.Expression { + expression := &ast.PostfixExpression{ + Token: p.prevToken, + Operator: p.curToken.Literal, + } + return expression +} + +func (p *Parser) parseIfExpression() ast.Expression { + expression := &ast.IfExpression{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + p.nextToken() + expression.Condition = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Consequence = p.parseBlockStatement() + + if p.peekTokenIs(token.ELSE) { + p.nextToken() + if p.peekTokenIs(token.IF) { + p.nextToken() + expression.Alternative = &ast.BlockStatement{ + Statements: []ast.Statement{ + &ast.ExpressionStatement{ + Expression: p.parseIfExpression(), + }, + }, + } + return expression + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Alternative = p.parseBlockStatement() + } + + return expression +} + +func (p *Parser) parseBlockStatement() *ast.BlockStatement { + block := &ast.BlockStatement{Token: p.curToken} + block.Statements = []ast.Statement{} + + p.nextToken() + + for !p.curTokenIs(token.RBRACE) { + if p.curTokenIs(token.EOF) { + msg := fmt.Sprintf("Mstari %d: Hukufunga Mabano '}'", p.curToken.Line) + p.errors = append(p.errors, msg) + return nil + } + stmt := p.parseStatement() + block.Statements = append(block.Statements, stmt) + p.nextToken() + } + + return block +} + +func (p *Parser) parseFunctionLiteral() ast.Expression { + lit := &ast.FunctionLiteral{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + lit.Parameters = p.parseFunctionParameters() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + lit.Body = p.parseBlockStatement() + + return lit +} + +func (p *Parser) parseFunctionParameters() []*ast.Identifier { + identifiers := []*ast.Identifier{} + + if p.peekTokenIs(token.RPAREN) { + p.nextToken() + return identifiers + } + + p.nextToken() + + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + } + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return identifiers +} + +func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression { + exp := &ast.CallExpression{Token: p.curToken, Function: function} + exp.Arguments = p.parseExpressionList(token.RPAREN) + return exp +} + +func (p *Parser) parseStringLiteral() ast.Expression { + return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal} +} + +func (p *Parser) parseArrayLiteral() ast.Expression { + array := &ast.ArrayLiteral{Token: p.curToken} + + array.Elements = p.parseExpressionList(token.RBRACKET) + + return array +} + +func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression { + list := []ast.Expression{} + + if p.peekTokenIs(end) { + p.nextToken() + return list + } + + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + } + + if !p.expectPeek(end) { + return nil + } + return list +} + +func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression { + exp := &ast.IndexExpression{Token: p.curToken, Left: left} + + p.nextToken() + exp.Index = p.parseExpression(LOWEST) + if !p.expectPeek(token.RBRACKET) { + return nil + } + + return exp +} + +func (p *Parser) parseDictLiteral() ast.Expression { + dict := &ast.DictLiteral{Token: p.curToken} + dict.Pairs = make(map[ast.Expression]ast.Expression) + + for !p.peekTokenIs(token.RBRACE) { + p.nextToken() + key := p.parseExpression(LOWEST) + + if !p.expectPeek(token.COLON) { + return nil + } + + p.nextToken() + value := p.parseExpression(LOWEST) + + dict.Pairs[key] = value + + if !p.peekTokenIs(token.RBRACE) && !p.expectPeek(token.COMMA) { + return nil + } + } + + if !p.expectPeek(token.RBRACE) { + return nil + } + + return dict +} + +func (p *Parser) parseWhileExpression() ast.Expression { + expression := &ast.WhileExpression{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + p.nextToken() + expression.Condition = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Consequence = p.parseBlockStatement() + + return expression +} + +func (p *Parser) parseBreak() *ast.Break { + stmt := &ast.Break{Token: p.curToken} + for p.curTokenIs(token.SEMICOLON) { + p.nextToken() + } + return stmt +} + +func (p *Parser) parseContinue() *ast.Continue { + stmt := &ast.Continue{Token: p.curToken} + for p.curTokenIs(token.SEMICOLON) { + p.nextToken() + } + return stmt +} + +func (p *Parser) parseForExpression() ast.Expression { + expression := &ast.For{Token: p.curToken} + p.nextToken() + if !p.curTokenIs(token.IDENT) { + return nil + } + if !p.peekTokenIs(token.ASSIGN) { + return p.parseForInExpression(expression) + } + + // In future will allow: kwa i = 0; i<10; i++ {andika(i)} + // expression.Identifier = p.curToken.Literal + // expression.StarterName = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + // if expression.StarterName == nil { + // return nil + // } + // if !p.expectPeek(token.ASSIGN) { + // return nil + // } + + // p.nextToken() + + // expression.StarterValue = p.parseExpression(LOWEST) + // // expression.Starter = p.parseExpression(LOWEST) + // if expression.StarterValue == nil { + // return nil + // } + // p.nextToken() + // for p.curTokenIs(token.SEMICOLON) { + // p.nextToken() + // } + // expression.Condition = p.parseExpression(LOWEST) + // if expression.Condition == nil { + // return nil + // } + // p.nextToken() + // for p.curTokenIs(token.SEMICOLON) { + // p.nextToken() + // } + // expression.Closer = p.parseExpression(LOWEST) + // if expression.Closer == nil { + // return nil + // } + // p.nextToken() + // for p.curTokenIs(token.SEMICOLON) { + // p.nextToken() + // } + // if !p.curTokenIs(token.LBRACE) { + // return nil + // } + // expression.Block = p.parseBlockStatement() + // return expression + return nil +} + +func (p *Parser) parseForInExpression(initialExpression *ast.For) ast.Expression { + expression := &ast.ForIn{Token: initialExpression.Token} + if !p.curTokenIs(token.IDENT) { + return nil + } + val := p.curToken.Literal + var key string + p.nextToken() + if p.curTokenIs(token.COMMA) { + p.nextToken() + if !p.curTokenIs(token.IDENT) { + return nil + } + key = val + val = p.curToken.Literal + p.nextToken() + } + expression.Key = key + expression.Value = val + if !p.curTokenIs(token.IN) { + return nil + } + p.nextToken() + expression.Iterable = p.parseExpression(LOWEST) + if !p.expectPeek(token.LBRACE) { + return nil + } + expression.Block = p.parseBlockStatement() + return expression +} + +func (p *Parser) parseSwitchStatement() ast.Expression { + expression := &ast.SwitchExpression{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + p.nextToken() + expression.Value = p.parseExpression(LOWEST) + + if expression.Value == nil { + return nil + } + + if !p.expectPeek(token.RPAREN) { + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + p.nextToken() + + for !p.curTokenIs(token.RBRACE) { + + if p.curTokenIs(token.EOF) { + msg := fmt.Sprintf("Mstari %d: Haukufunga ENDAPO (SWITCH)", p.curToken.Line) + p.errors = append(p.errors, msg) + return nil + } + tmp := &ast.CaseExpression{Token: p.curToken} + + if p.curTokenIs(token.DEFAULT) { + + tmp.Default = true + + } else if p.curTokenIs(token.CASE) { + + p.nextToken() + + if p.curTokenIs(token.DEFAULT) { + tmp.Default = true + } else { + tmp.Expr = append(tmp.Expr, p.parseExpression(LOWEST)) + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + tmp.Expr = append(tmp.Expr, p.parseExpression(LOWEST)) + } + } + } else { + msg := fmt.Sprintf("Mstari %d: Tulitegemea Kauli IKIWA (CASE) au KAWAIDA (DEFAULT) lakini tumepewa: %s", p.curToken.Line, p.curToken.Type) + p.errors = append(p.errors, msg) + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + tmp.Block = p.parseBlockStatement() + p.nextToken() + expression.Choices = append(expression.Choices, tmp) + } + + count := 0 + for _, c := range expression.Choices { + if c.Default { + count++ + } + } + if count > 1 { + msg := fmt.Sprintf("Kauli ENDAPO (SWITCH) hua na kauli 'KAWAIDA' (DEFAULT) moja tu! Wewe umeweka %d", count) + p.errors = append(p.errors, msg) + return nil + + } + return expression + +} diff --git a/src/parser/parser_test.go b/src/parser/parser_test.go new file mode 100644 index 0000000..e205a4d --- /dev/null +++ b/src/parser/parser_test.go @@ -0,0 +1,970 @@ +package parser + +import ( + "fmt" + "testing" + + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/lexer" +) + +func TestLetStatements(t *testing.T) { + tests := []struct { + input string + expectedIdentifier string + expectedValue interface{} + }{ + {"fanya x = 5;", "x", 5}, + {"fanya y = x;", "y", "x"}, + {"fanya bangi = y;", "bangi", "y"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + stmt := program.Statements[0] + if !testLetStatement(t, stmt, tt.expectedIdentifier) { + return + } + + val := stmt.(*ast.LetStatement).Value + if !testLiteralExpression(t, val, tt.expectedValue) { + return + } + } +} + +func testLetStatement(t *testing.T, s ast.Statement, name string) bool { + if s.TokenLiteral() != "fanya" { + t.Errorf("s.TokenLiteral not 'fanya', got = %q", s.TokenLiteral()) + return false + } + + letStmt, ok := s.(*ast.LetStatement) + if !ok { + t.Errorf("s not *ast.LetStatement, got = %T", s) + return false + } + + if letStmt.Name.Value != name { + t.Errorf("letStmt.Name.Value not '%s', got='%s'", name, letStmt.Name.Value) + return false + } + + if letStmt.Name.TokenLiteral() != name { + t.Errorf("s.Name not %s, got=%s", name, letStmt.Name) + return false + } + return true +} + +func checkParserErrors(t *testing.T, p *Parser) { + errors := p.Errors() + + if len(errors) == 0 { + return + } + + t.Errorf("Parser has %d errors", len(errors)) + + for _, msg := range errors { + t.Errorf("Parser error: %q", msg) + } + t.FailNow() +} + +func TestReturnStatements(t *testing.T) { + tests := []struct { + input string + expectedValue interface{} + }{ + {"rudisha 5;", 5}, + {"rudisha kweli;", true}, + {"rudisha bangi;", "bangi"}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statements. got=%d", + len(program.Statements)) + } + + stmt := program.Statements[0] + returnStmt, ok := stmt.(*ast.ReturnStatement) + if !ok { + t.Fatalf("stmt not *ast.returnStatement. got=%T", stmt) + } + if returnStmt.TokenLiteral() != "rudisha" { + t.Fatalf("returnStmt.TokenLiteral not 'rudisha', got %q", + returnStmt.TokenLiteral()) + } + if testLiteralExpression(t, returnStmt.ReturnValue, tt.expectedValue) { + return + } + } +} + +func TestIdentifierExpression(t *testing.T) { + input := "foobar;" + + l := lexer.New(input) + p := New(l) + + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not an ast.ExpressionStatement, got=%T", program.Statements[0]) + } + + ident, ok := stmt.Expression.(*ast.Identifier) + if !ok { + t.Fatalf("exp not *ast.Identifier, got=%T", stmt.Expression) + } + + if ident.Value != "foobar" { + t.Errorf("ident.Value not %s, got=%s", "foobar", ident.Value) + } + + if ident.TokenLiteral() != "foobar" { + t.Errorf("ident.TokenLiteral not %s, got=%s", "foobar", ident.TokenLiteral()) + } +} + +func TestIntergerLiteral(t *testing.T) { + input := "5;" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements, got=%d", len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.statements[0] is not ast.ExpressionStatement, got=%T", program.Statements[0]) + } + + literal, ok := stmt.Expression.(*ast.IntegerLiteral) + if !ok { + t.Fatalf("exp not *ast.IntegerLiteral, got=%T", stmt.Expression) + } + + if literal.Value != 5 { + t.Errorf("literal.Value not %d, got=%d", 5, literal.Value) + } + + if literal.TokenLiteral() != "5" { + t.Errorf("literal.TokenLiteral not %s, got=%s", "5", literal.TokenLiteral()) + } +} + +func TestParsingPrefixExpressions(t *testing.T) { + prefixTests := []struct { + input string + operator string + value interface{} + }{ + {"!5;", "!", 5}, + {"-15;", "-", 15}, + {"!kweli", "!", true}, + {"!sikweli", "!", false}, + } + + for _, tt := range prefixTests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("Program statements does not contain %d statements, got=%d\n", 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement, got=%T", program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.PrefixExpression) + if !ok { + t.Fatalf("stmt is not ast.PrefixExpression, got=%T", stmt.Expression) + } + if exp.Operator != tt.operator { + t.Fatalf("exp.Operator is not %s, got=%s", tt.operator, exp.Operator) + } + + if !testLiteralExpression(t, exp.Right, tt.value) { + return + } + + } +} + +func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool { + integ, ok := il.(*ast.IntegerLiteral) + if !ok { + t.Errorf("il not *ast.IntegerLiteral, got=%T", il) + return false + } + + if integ.Value != value { + t.Errorf("il not %d, got=%d", value, integ.Value) + return false + } + + if integ.TokenLiteral() != fmt.Sprintf("%d", value) { + t.Errorf("integ.TokenLiteral not %d, got=%s", value, integ.TokenLiteral()) + return false + } + + return true +} + +func TestParsingInfixExpressions(t *testing.T) { + infixTests := []struct { + input string + leftValue interface{} + operator string + rightValue interface{} + }{ + {"5 + 5;", 5, "+", 5}, + {"5 - 5;", 5, "-", 5}, + {"5 * 5;", 5, "*", 5}, + {"5 / 5;", 5, "/", 5}, + {"5 > 5;", 5, ">", 5}, + {"5 < 5;", 5, "<", 5}, + {"5 == 5;", 5, "==", 5}, + {"5 != 5;", 5, "!=", 5}, + {"kweli == kweli", true, "==", true}, + {"kweli != sikweli", true, "!=", false}, + {"sikweli == sikweli", false, "==", false}, + } + + for _, tt := range infixTests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("Program.statements does not contain %d statements, got =%d", 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.statements[0] is not ast.ExpressionStatement, got=%T", program.Statements[0]) + } + + if !testInfixExpression(t, stmt.Expression, tt.leftValue, tt.operator, tt.rightValue) { + return + } + } +} + +func TestOperatorPrecedenceParsing(t *testing.T) { + tests := []struct { + input string + expected string + }{ + { + "-a * b", + "((-a) * b)", + }, + { + "!-a", + "(!(-a))", + }, + { + "a + b + c", + "((a + b) + c)", + }, + { + "a + b - c", + "((a + b) - c)", + }, + { + "a * b * c", + "((a * b) * c)", + }, + { + "a*b /c", + "((a * b) / c)", + }, + { + "a + b / c", + "(a + (b / c))", + }, + { + "a + b * c + d / e - f", + "(((a + (b * c)) + (d / e)) - f)", + }, + { + "3 + 4; -5 * 5", + "(3 + 4)((-5) * 5)", + }, + { + "5 > 4 == 3 < 4", + "((5 > 4) == (3 < 4))", + }, + { + "5 < 4 != 3 > 4", + "((5 < 4) != (3 > 4))", + }, + { + "3 + 4 * 5 == 3 * 1 + 4 * 5", + "((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))", + }, + { + "kweli", + "kweli", + }, + { + "sikweli", + "sikweli", + }, + { + "3 > 5 == sikweli", + "((3 > 5) == sikweli)", + }, + { + "3 < 5 == kweli", + "((3 < 5) == kweli)", + }, + { + "1 + (2 + 3) + 4", + "((1 + (2 + 3)) + 4)", + }, + { + "(5 + 5) * 2", + "((5 + 5) * 2)", + }, + { + "2 / (5 + 5)", + "(2 / (5 + 5))", + }, + { + "-(5 + 5)", + "(-(5 + 5))", + }, + { + "!(kweli == kweli)", + "(!(kweli == kweli))", + }, + { + "a + add(b * c) + d", + "((a + add((b * c))) + d)", + }, + { + "add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", + "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))", + }, + { + "add(a + b + c * d / f + g)", + "add((((a + b) + ((c * d) / f)) + g))", + }, + { + "a * [1, 2, 3, 4][b * c] * d", + "((a * ([1, 2, 3, 4][(b * c)])) * d)", + }, + { + "add(a *b[2], b[1], 2 * [1, 2][1])", + "add((a * (b[2])), (b[1]), (2 * ([1, 2][1])))", + }, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + actual := program.String() + + if actual != tt.expected { + t.Errorf("expected=%q, got=%q", tt.expected, actual) + } + } +} + +func testIdentifier(t *testing.T, exp ast.Expression, value string) bool { + ident, ok := exp.(*ast.Identifier) + if !ok { + t.Errorf("exp not *ast.Identifier, got=%T", exp) + return false + } + + if ident.Value != value { + t.Errorf("ident.Value not %s, got=%s", value, ident.Value) + return false + } + + if ident.TokenLiteral() != value { + t.Errorf("ident.TokenLiteral is not %s, got=%s", value, ident.TokenLiteral()) + return false + } + + return true +} + +func testLiteralExpression( + t *testing.T, + exp ast.Expression, + expected interface{}, +) bool { + switch v := expected.(type) { + case int: + return testIntegerLiteral(t, exp, int64(v)) + case int64: + return testIntegerLiteral(t, exp, v) + case string: + return testIdentifier(t, exp, v) + case bool: + return testBooleanLiteral(t, exp, v) + } + + t.Errorf("type of exp not handled, got=%T", exp) + return false +} + +func testBooleanLiteral(t *testing.T, exp ast.Expression, value bool) bool { + bo, ok := exp.(*ast.Boolean) + if !ok { + t.Errorf("exp not *ast.Boolean, got=%T", exp) + return false + } + + if bo.Value != value { + t.Errorf("bo.Value not %t,got=%t", value, bo.Value) + return false + } + + return true +} + +func testInfixExpression( + t *testing.T, + exp ast.Expression, + left interface{}, + operator string, + right interface{}, +) bool { + opExp, ok := exp.(*ast.InfixExpression) + if !ok { + t.Errorf("exp is not ast.OperatorExpression, got=%T(%s)", exp, exp) + return false + } + + if !testLiteralExpression(t, opExp.Left, left) { + return false + } + + if opExp.Operator != operator { + t.Errorf("exp.Operator is not %s, got=%q", operator, opExp.Operator) + return false + } + + if !testLiteralExpression(t, opExp.Right, right) { + return false + } + + return true +} + +func TestBooleanExpression(t *testing.T) { + tests := []struct { + input string + expectedBoolean bool + }{ + {"kweli;", true}, + {"sikweli;", false}, + } + + for _, tt := range tests { + + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not an ast.ExpressionStatement, got=%T", program.Statements[0]) + } + + boolean, ok := stmt.Expression.(*ast.Boolean) + if !ok { + t.Fatalf("exp not *ast.Boolean, got=%T", stmt.Expression) + } + + if boolean.Value != tt.expectedBoolean { + t.Errorf("boolean.Value not %t, got=%t", tt.expectedBoolean, boolean.Value) + } + + } +} + +func TestIfExpression(t *testing.T) { + input := `kama (x < y) { x }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Body does not contain %d statements, got=%d", 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not an ast.ExpressionStatement, got=%T", program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression, got=%T", stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("Consequences is not 1 statement, got=%d\n", len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.Expression, got=%T", exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if exp.Alternative != nil { + t.Errorf("exp.Alternative.Statement was not nil, got=%+v", exp.Alternative) + } +} + +func TestIfElseExpression(t *testing.T) { + input := `kama (x < y) { x } sivyo { y }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Body does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.IfExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", "<", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("consequence is not 1 statements. got=%d\n", + len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Consequence.Statements[0]) + } + + if !testIdentifier(t, consequence.Expression, "x") { + return + } + + if len(exp.Alternative.Statements) != 1 { + t.Errorf("exp.Alternative.Statements does not contain 1 statements. got=%d\n", + len(exp.Alternative.Statements)) + } + + alternative, ok := exp.Alternative.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", + exp.Alternative.Statements[0]) + } + + if !testIdentifier(t, alternative.Expression, "y") { + return + } +} + +func TestFunctionLiteralParsing(t *testing.T) { + input := `unda(x, y) {x + y}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Body does not contain %d statements, got=%d\n", 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement, got=%T", program.Statements[0]) + } + + function, ok := stmt.Expression.(*ast.FunctionLiteral) + if !ok { + t.Fatalf("stmt.Expression is not ast.FunctionLiteral, got=%T", stmt.Expression) + } + + if len(function.Parameters) != 2 { + t.Fatalf("function literal parameters wrong, want 2, got=%d\n", len(function.Parameters)) + } + + testLiteralExpression(t, function.Parameters[0], "x") + testLiteralExpression(t, function.Parameters[1], "y") + + if len(function.Body.Statements) != 1 { + t.Fatalf("function.Body.Statements has not 1 statement, got=%d\n", len(function.Body.Statements)) + } + + bodyStmt, ok := function.Body.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("function body stmt is not ast.ExpressionStatement, got=%T", function.Body.Statements[0]) + } + + testInfixExpression(t, bodyStmt.Expression, "x", "+", "y") +} + +func TestFunctionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedParams []string + }{ + {input: "unda() {};", expectedParams: []string{}}, + {input: "unda(x) {};", expectedParams: []string{"x"}}, + {input: "unda(x, y, z) {};", expectedParams: []string{"x", "y", "z"}}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + function := stmt.Expression.(*ast.FunctionLiteral) + + if len(function.Parameters) != len(tt.expectedParams) { + t.Errorf("length parameters wrong,want %d, got=%d\n", len(tt.expectedParams), len(function.Parameters)) + } + + for i, ident := range tt.expectedParams { + testLiteralExpression(t, function.Parameters[i], ident) + } + } +} + +func TestCallExpressionParsing(t *testing.T) { + input := "jumlisha(1, 2 * 3, 4 + 5);" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not have 1 statements, got=%d\n", len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("stmt is not ast.ExpressionStatement, got=%T", program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.CallExpression, got=%T", stmt.Expression) + } + + if !testIdentifier(t, exp.Function, "jumlisha") { + return + } + + if len(exp.Arguments) != 3 { + t.Fatalf("wrong length of arguments, got=%d", len(exp.Arguments)) + } + + testLiteralExpression(t, exp.Arguments[0], 1) + testInfixExpression(t, exp.Arguments[1], 2, "*", 3) + testInfixExpression(t, exp.Arguments[2], 4, "+", 5) + +} + +func TestStringLiteralExpression(t *testing.T) { + input := `"habari yako"` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + literal, ok := stmt.Expression.(*ast.StringLiteral) + if !ok { + t.Fatalf("exp not *ast.StringLiteral, got=%T", stmt.Expression) + } + + if literal.Value != "habari yako" { + t.Errorf("literal.Value not %q, got=%q", "habari yako", literal.Value) + } +} + +func TestParsingArrayLiterals(t *testing.T) { + input := "[1,2*2,3+3]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + array, ok := stmt.Expression.(*ast.ArrayLiteral) + if !ok { + t.Fatalf("Expression not ast.ArrayLiteral, got=%T", len(array.Elements)) + } + + testIntegerLiteral(t, array.Elements[0], 1) + testInfixExpression(t, array.Elements[1], 2, "*", 2) + testInfixExpression(t, array.Elements[2], 3, "+", 3) +} + +func TestParsingIndexExpressions(t *testing.T) { + input := "myArray[1+1]" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + indexExp, ok := stmt.Expression.(*ast.IndexExpression) + if !ok { + t.Fatalf("Expression not *ast.IndexExpression, got=%T", stmt.Expression) + } + + if !testIdentifier(t, indexExp.Left, "myArray") { + return + } + + if !testInfixExpression(t, indexExp.Index, 1, "+", 1) { + return + } +} + +func TestParsingDictLiteralsStringKeys(t *testing.T) { + input := `{"one": 1, "two": 2, "three": 3}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + dict, ok := stmt.Expression.(*ast.DictLiteral) + if !ok { + t.Fatalf("Expression is not a Dict, got=%T", stmt.Expression) + } + + if len(dict.Pairs) != 3 { + t.Errorf("dict.Pairs wrong, got=%d", len(dict.Pairs)) + } + + expected := map[string]int64{ + "one": 1, + "two": 2, + "three": 3, + } + + for key, value := range dict.Pairs { + literal, ok := key.(*ast.StringLiteral) + if !ok { + t.Errorf("Key is not a string, got=%T", key) + } + + expectedValue := expected[literal.String()] + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingDictLiteralsIntegerKeys(t *testing.T) { + input := `{1: 1, 2: 2, 3: 3}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + dict, ok := stmt.Expression.(*ast.DictLiteral) + if !ok { + t.Fatalf("Expression is not a Dict, got=%T", stmt.Expression) + } + + if len(dict.Pairs) != 3 { + t.Errorf("dict.Pairs wrong, got=%d", len(dict.Pairs)) + } + + expected := map[int64]int64{ + 1: 1, + 2: 2, + 3: 3, + } + + for key, value := range dict.Pairs { + literal, ok := key.(*ast.IntegerLiteral) + if !ok { + t.Errorf("Key is not a string, got=%T", key) + } + + expectedValue := expected[literal.Value] + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingDictLiteralsBoolKeys(t *testing.T) { + input := `{kweli: 1, sikweli: 2}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + dict, ok := stmt.Expression.(*ast.DictLiteral) + if !ok { + t.Fatalf("Expression is not a Dict, got=%T", stmt.Expression) + } + + if len(dict.Pairs) != 2 { + t.Errorf("dict.Pairs wrong, got=%d", len(dict.Pairs)) + } + + expected := map[bool]int64{ + true: 1, + false: 2, + } + + for key, value := range dict.Pairs { + literal, ok := key.(*ast.Boolean) + if !ok { + t.Errorf("Key is not a string, got=%T", key) + } + + expectedValue := expected[literal.Value] + testIntegerLiteral(t, value, expectedValue) + } +} + +func TestParsingDictLiteralWithExpressions(t *testing.T) { + input := `{"one": 0+1, "two": 100-98, "three": 15/5}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + dict, ok := stmt.Expression.(*ast.DictLiteral) + if !ok { + t.Fatalf("Expression is not a dict, got=%T", stmt.Expression) + } + + if len(dict.Pairs) != 3 { + t.Errorf("Dict has wrong length, got=%d", len(dict.Pairs)) + } + + tests := map[string]func(ast.Expression){ + "one": func(e ast.Expression) { + testInfixExpression(t, e, 0, "+", 1) + }, + "two": func(e ast.Expression) { + testInfixExpression(t, e, 100, "-", 98) + }, + "three": func(e ast.Expression) { + testInfixExpression(t, e, 15, "/", 5) + }, + } + + for key, value := range dict.Pairs { + literal, ok := key.(*ast.StringLiteral) + if !ok { + t.Errorf("key is not a string, got=%T", key) + continue + } + + testFunc, ok := tests[literal.String()] + if !ok { + t.Errorf("No test function for key %q found", literal.String()) + continue + } + + testFunc(value) + } +} + +func TestParsingEmptyDict(t *testing.T) { + input := "{}" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + dict, ok := stmt.Expression.(*ast.DictLiteral) + if !ok { + t.Fatalf("Expression not a dict, got=%T", stmt.Expression) + } + + if len(dict.Pairs) != 0 { + t.Errorf("Dict pairs has wrong length, got=%d", len(dict.Pairs)) + } +} diff --git a/src/repl/repl.go b/src/repl/repl.go new file mode 100644 index 0000000..9700152 --- /dev/null +++ b/src/repl/repl.go @@ -0,0 +1,116 @@ +package repl + +import ( + "bufio" + "fmt" + "io" + "os" + "strings" + + "github.com/AvicennaJr/Nuru/evaluator" + "github.com/AvicennaJr/Nuru/lexer" + "github.com/AvicennaJr/Nuru/object" + "github.com/AvicennaJr/Nuru/parser" +) + +const PROMPT = ">>> " +const ERROR_FACE = ` + ███████████████████████████ + ███████▀▀▀░░░░░░░▀▀▀███████ + ████▀░░░░░░░░░░░░░░░░░▀████ + ███│░░░░░░░░░░░░░░░░░░░│███ + ██▌│░░░░░░░░░░░░░░░░░░░│▐██ + ██░└┐░░░░░░░░░░░░░░░░░┌┘░██ + ██░░└┐░░░░░░░░░░░░░░░┌┘░░██ + ██░░┌┘▄▄▄▄▄░░░░░▄▄▄▄▄└┐░░██ + ██▌░│██████▌░░░▐██████│░▐██ + ███░│▐███▀▀░░▄░░▀▀███▌│░███ + ██▀─┘░░░░░░░▐█▌░░░░░░░└─▀██ + ██▄░░░▄▄▄▓░░▀█▀░░▓▄▄▄░░░▄██ + ████▄─┘██▌░░░░░░░▐██└─▄████ + █████░░▐█─┬┬┬┬┬┬┬─█▌░░█████ + ████▌░░░▀┬┼┼┼┼┼┼┼┬▀░░░▐████ + █████▄░░░└┴┴┴┴┴┴┴┘░░░▄█████ + ███████▄░░░░░░░░░░░▄███████ + ██████████▄▄▄▄▄▄▄██████████ + ███████████████████████████ + + █▄▀ █░█ █▄░█ ▄▀█   █▀ █░█ █ █▀▄ ▄▀█ + █░█ █▄█ █░▀█ █▀█   ▄█ █▀█ █ █▄▀ █▀█ + +` + +func Read(contents string) { + env := object.NewEnvironment() + + l := lexer.New(contents) + p := parser.New(l) + + program := p.ParseProgram() + + if len(p.Errors()) != 0 { + fmt.Println(colorfy(ERROR_FACE, 31)) + fmt.Println("Kuna Errors Zifuatazo:") + + for _, msg := range p.Errors() { + fmt.Println("\t" + colorfy(msg, 31)) + } + + } + evaluated := evaluator.Eval(program, env) + if evaluated != nil { + if evaluated.Type() != object.NULL_OBJ { + fmt.Println(colorfy(evaluated.Inspect(), 32)) + } + } + +} + +func Start(in io.Reader, out io.Writer) { + + scanner := bufio.NewScanner(in) + env := object.NewEnvironment() + + for { + fmt.Print(PROMPT) + scanned := scanner.Scan() + if !scanned { + return + } + + line := scanner.Text() + if strings.TrimSpace(line) == "exit()" || strings.TrimSpace(line) == "toka()" { + fmt.Println("✨🅺🅰🆁🅸🅱🆄 🆃🅴🅽🅰✨") + os.Exit(0) + } + l := lexer.New(line) + p := parser.New(l) + + program := p.ParseProgram() + + if len(p.Errors()) != 0 { + printParseErrors(out, p.Errors()) + continue + } + evaluated := evaluator.Eval(program, env) + if evaluated != nil { + if evaluated.Type() != object.NULL_OBJ { + io.WriteString(out, colorfy(evaluated.Inspect(), 32)) + io.WriteString(out, "\n") + } + } + } +} + +func printParseErrors(out io.Writer, errors []string) { + //io.WriteString(out, colorfy(ERROR_FACE, 31)) + io.WriteString(out, "Kuna Errors Zifuatazo:\n") + + for _, msg := range errors { + io.WriteString(out, "\t"+colorfy(msg, 31)+"\n") + } +} + +func colorfy(str string, colorCode int) string { + return fmt.Sprintf("\x1b[%dm%s\x1b[0m", colorCode, str) +} diff --git a/src/token/token.go b/src/token/token.go new file mode 100644 index 0000000..bb950f7 --- /dev/null +++ b/src/token/token.go @@ -0,0 +1,101 @@ +package token + +type TokenType string + +type Token struct { + Type TokenType + Literal string + Line int +} + +const ( + ILLEGAL = "HARAMU" + EOF = "MWISHO" + + // Identifiers + literals + IDENT = "KITAMBULISHI" + INT = "NAMBA" + STRING = "NENO" + FLOAT = "DESIMALI" + + // Operators + ASSIGN = "=" + PLUS = "+" + MINUS = "-" + BANG = "!" + ASTERISK = "*" + POW = "**" + SLASH = "/" + MODULUS = "%" + LT = "<" + LTE = "<=" + GT = ">" + GTE = ">=" + EQ = "==" + NOT_EQ = "!=" + AND = "&&" + OR = "||" + PLUS_ASSIGN = "+=" + PLUS_PLUS = "++" + MINUS_ASSIGN = "-=" + MINUS_MINUS = "--" + ASTERISK_ASSIGN = "*=" + SLASH_ASSIGN = "/=" + MODULUS_ASSIGN = "%=" + + //Delimiters + COMMA = "," + SEMICOLON = ";" + LPAREN = "(" + RPAREN = ")" + LBRACE = "{" + RBRACE = "}" + LBRACKET = "[" + RBRACKET = "]" + COLON = ":" + + // Keywords + FUNCTION = "FUNCTION" + LET = "FANYA" + TRUE = "KWELI" + FALSE = "SIKWELI" + IF = "KAMA" + ELSE = "SIVYO" + RETURN = "RUDISHA" + WHILE = "WAKATI" + NULL = "TUPU" + BREAK = "VUNJA" + CONTINUE = "ENDELEA" + IN = "KTK" + FOR = "KWA" + SWITCH = "BADILI" + CASE = "IKIWA" + DEFAULT = "KAWAIDA" +) + +var keywords = map[string]TokenType{ + "unda": FUNCTION, + "fanya": LET, + "kweli": TRUE, + "sikweli": FALSE, + "kama": IF, + "au": ELSE, + "sivyo": ELSE, + "wakati": WHILE, + "rudisha": RETURN, + "vunja": BREAK, + "endelea": CONTINUE, + "tupu": NULL, + "ktk": IN, + "kwa": FOR, + "badili": SWITCH, + "ikiwa": CASE, + "kawaida": DEFAULT, +} + +func LookupIdent(ident string) TokenType { + if tok, ok := keywords[ident]; ok { + return tok + } + return IDENT +} From fbb69c309f4fcb5239502d7410aaedb8422a1780 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Sun, 25 Dec 2022 21:38:12 +0300 Subject: [PATCH 35/59] Restructured and added docs to source code --- .gitignore | 12 +- README.md | 4 +- ast/ast.go | 491 ----------------- ast/ast_test.go | 29 - docs/README.md | 78 +++ docs/arrays.md | 121 +++++ docs/bool.md | 32 ++ docs/builtins.md | 61 +++ docs/comments.md | 14 + docs/dictionaries.md | 96 ++++ docs/for.md | 110 ++++ docs/function.md | 55 ++ docs/identifiers.md | 15 + docs/ifStatements.md | 27 + docs/keywords.md | 57 ++ docs/null.md | 17 + docs/numbers.md | 74 +++ docs/operators.md | 73 +++ docs/strings.md | 104 ++++ docs/switch.md | 52 ++ docs/while.md | 65 +++ evaluator/builtins.go | 119 ----- evaluator/evaluator.go | 943 --------------------------------- evaluator/evaluator_test.go | 518 ------------------ examples/example.nr | 107 ---- examples/sorting_algorithm.nr | 63 --- examples/sudoku_solver.nr | 101 ---- go.mod | 3 - lexer/lexer.go | 327 ------------ lexer/lexer_test.go | 153 ------ main.go | 67 --- object/environment.go | 31 -- object/object.go | 277 ---------- object/object_test.go | 22 - parser/parser.go | 810 ---------------------------- parser/parser_test.go | 970 ---------------------------------- repl/repl.go | 116 ---- token/token.go | 101 ---- 38 files changed, 1059 insertions(+), 5256 deletions(-) delete mode 100644 ast/ast.go delete mode 100644 ast/ast_test.go create mode 100644 docs/README.md create mode 100644 docs/arrays.md create mode 100644 docs/bool.md create mode 100644 docs/builtins.md create mode 100644 docs/comments.md create mode 100644 docs/dictionaries.md create mode 100644 docs/for.md create mode 100644 docs/function.md create mode 100644 docs/identifiers.md create mode 100644 docs/ifStatements.md create mode 100644 docs/keywords.md create mode 100644 docs/null.md create mode 100644 docs/numbers.md create mode 100644 docs/operators.md create mode 100644 docs/strings.md create mode 100644 docs/switch.md create mode 100644 docs/while.md delete mode 100644 evaluator/builtins.go delete mode 100644 evaluator/evaluator.go delete mode 100644 evaluator/evaluator_test.go delete mode 100644 examples/example.nr delete mode 100644 examples/sorting_algorithm.nr delete mode 100644 examples/sudoku_solver.nr delete mode 100644 go.mod delete mode 100644 lexer/lexer.go delete mode 100644 lexer/lexer_test.go delete mode 100644 main.go delete mode 100644 object/environment.go delete mode 100644 object/object.go delete mode 100644 object/object_test.go delete mode 100644 parser/parser.go delete mode 100644 parser/parser_test.go delete mode 100644 repl/repl.go delete mode 100644 token/token.go diff --git a/.gitignore b/.gitignore index f0c810b..66b461b 100644 --- a/.gitignore +++ b/.gitignore @@ -46,9 +46,9 @@ _testmain.go #Personal -testbinaries -tests_random -parser/tracingZeAnnoyingParser.go -testTracing.go -Notes.md -nuru \ No newline at end of file +src/testbinaries +src/tests_random +src/parser/tracingZeAnnoyingParser.go +src/testTracing.go +src/nuru +Notes.md \ No newline at end of file diff --git a/README.md b/README.md index 84074cc..46f321e 100644 --- a/README.md +++ b/README.md @@ -94,10 +94,10 @@ go build -o nuru main.go nuru -v ``` -## Syntax +## Syntax At A Glance **NOTE** -> There is a more detailed documentation of the language [here](https://github.com/AvicennaJr/NuruDocs). +> There is a more detailed documentation of the language [here](./docs/README.md). Nuru, although still in its early stage, intends to be a fully functional programming language, and thus it has been baked with many features. diff --git a/ast/ast.go b/ast/ast.go deleted file mode 100644 index 810ccfd..0000000 --- a/ast/ast.go +++ /dev/null @@ -1,491 +0,0 @@ -package ast - -import ( - "bytes" - "strings" - - "github.com/AvicennaJr/Nuru/token" -) - -type Node interface { - TokenLiteral() string - String() string // to help debug the many errors lmao -} - -type Statement interface { - Node - statementNode() -} - -type Expression interface { - Node - expressionNode() -} - -type Program struct { - Statements []Statement -} - -func (p *Program) TokenLiteral() string { - if len(p.Statements) > 0 { - return p.Statements[0].TokenLiteral() - } else { - return "" - } -} - -func (p *Program) String() string { - var out bytes.Buffer - - for _, s := range p.Statements { - out.WriteString(s.String()) - } - - return out.String() -} - -type LetStatement struct { - Token token.Token - Name *Identifier - Value Expression -} - -func (ls *LetStatement) statementNode() {} -func (ls *LetStatement) TokenLiteral() string { return ls.Token.Literal } -func (ls *LetStatement) String() string { - var out bytes.Buffer - - out.WriteString(ls.TokenLiteral() + " ") - out.WriteString(ls.Name.String()) - out.WriteString(" = ") - - if ls.Value != nil { - out.WriteString(ls.Value.String()) - } - - out.WriteString(";") - return out.String() -} - -type Identifier struct { - Token token.Token - Value string -} - -func (i *Identifier) expressionNode() {} -func (i *Identifier) TokenLiteral() string { return i.Token.Literal } -func (i *Identifier) String() string { return i.Value } - -type ReturnStatement struct { - Token token.Token - ReturnValue Expression -} - -func (rs *ReturnStatement) statementNode() {} -func (rs *ReturnStatement) TokenLiteral() string { return rs.Token.Literal } -func (rs *ReturnStatement) String() string { - var out bytes.Buffer - - out.WriteString(rs.TokenLiteral() + " ") - - if rs.ReturnValue != nil { - out.WriteString(rs.ReturnValue.String()) - } - out.WriteString(";") - return out.String() -} - -type ExpressionStatement struct { - Token token.Token - Expression Expression -} - -func (es *ExpressionStatement) statementNode() {} -func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal } -func (es *ExpressionStatement) String() string { - if es.Expression != nil { - return es.Expression.String() - } - - return "" -} - -type IntegerLiteral struct { - Token token.Token - Value int64 -} - -func (il *IntegerLiteral) expressionNode() {} -func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal } -func (il *IntegerLiteral) String() string { return il.Token.Literal } - -type PrefixExpression struct { - Token token.Token - Operator string - Right Expression -} - -func (pe *PrefixExpression) expressionNode() {} -func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal } -func (pe *PrefixExpression) String() string { - var out bytes.Buffer - - out.WriteString("(") - out.WriteString(pe.Operator) - out.WriteString(pe.Right.String()) - out.WriteString(")") - - return out.String() -} - -type InfixExpression struct { - Token token.Token - Left Expression - Operator string - Right Expression -} - -func (oe *InfixExpression) expressionNode() {} -func (oe *InfixExpression) TokenLiteral() string { return oe.Token.Literal } -func (oe *InfixExpression) String() string { - var out bytes.Buffer - - out.WriteString("(") - out.WriteString(oe.Left.String()) - out.WriteString(" " + oe.Operator + " ") - out.WriteString(oe.Right.String()) - out.WriteString(")") - - return out.String() -} - -type Boolean struct { - Token token.Token - Value bool -} - -func (b *Boolean) expressionNode() {} -func (b *Boolean) TokenLiteral() string { return b.Token.Literal } -func (b *Boolean) String() string { return b.Token.Literal } - -type IfExpression struct { - Token token.Token - Condition Expression - Consequence *BlockStatement - Alternative *BlockStatement -} - -func (ie *IfExpression) expressionNode() {} -func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal } -func (ie *IfExpression) String() string { - var out bytes.Buffer - out.WriteString("if") - out.WriteString(ie.Condition.String()) - out.WriteString(" ") - out.WriteString(ie.Consequence.String()) - - if ie.Alternative != nil { - out.WriteString("else") - out.WriteString(ie.Alternative.String()) - } - - return out.String() -} - -type BlockStatement struct { - Token token.Token - Statements []Statement -} - -func (bs *BlockStatement) statementNode() {} -func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal } -func (bs *BlockStatement) String() string { - var out bytes.Buffer - - for _, s := range bs.Statements { - out.WriteString(s.String()) - } - - return out.String() -} - -type FunctionLiteral struct { - Token token.Token - Parameters []*Identifier - Body *BlockStatement -} - -func (fl *FunctionLiteral) expressionNode() {} -func (fl *FunctionLiteral) TokenLiteral() string { return fl.Token.Literal } -func (fl *FunctionLiteral) String() string { - var out bytes.Buffer - - params := []string{} - - for _, p := range fl.Parameters { - params = append(params, p.String()) - } - - out.WriteString(fl.TokenLiteral()) - out.WriteString("(") - out.WriteString(strings.Join(params, ", ")) - out.WriteString(") ") - out.WriteString(fl.Body.String()) - - return out.String() -} - -type CallExpression struct { - Token token.Token - Function Expression // can be Identifier or FunctionLiteral - Arguments []Expression -} - -func (ce *CallExpression) expressionNode() {} -func (ce *CallExpression) TokenLiteral() string { return ce.Token.Literal } -func (ce *CallExpression) String() string { - var out bytes.Buffer - - args := []string{} - for _, a := range ce.Arguments { - args = append(args, a.String()) - } - - out.WriteString(ce.Function.String()) - out.WriteString("(") - out.WriteString(strings.Join(args, ", ")) - out.WriteString(")") - - return out.String() -} - -type StringLiteral struct { - Token token.Token - Value string -} - -func (sl *StringLiteral) expressionNode() {} -func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal } -func (sl *StringLiteral) String() string { return sl.Token.Literal } - -type ArrayLiteral struct { - Token token.Token - Elements []Expression -} - -func (al *ArrayLiteral) expressionNode() {} -func (al *ArrayLiteral) TokenLiteral() string { return al.Token.Literal } -func (al *ArrayLiteral) String() string { - var out bytes.Buffer - - elements := []string{} - for _, el := range al.Elements { - elements = append(elements, el.String()) - } - - out.WriteString("[") - out.WriteString(strings.Join(elements, ", ")) - out.WriteString("]") - - return out.String() -} - -type IndexExpression struct { - Token token.Token - Left Expression - Index Expression -} - -func (ie *IndexExpression) expressionNode() {} -func (ie *IndexExpression) TokenLiteral() string { return ie.Token.Literal } -func (ie *IndexExpression) String() string { - var out bytes.Buffer - - out.WriteString("(") - out.WriteString(ie.Left.String()) - out.WriteString("[") - out.WriteString(ie.Index.String()) - out.WriteString("])") - - return out.String() -} - -type DictLiteral struct { - Token token.Token - Pairs map[Expression]Expression -} - -func (dl *DictLiteral) expressionNode() {} -func (dl *DictLiteral) TokenLiteral() string { return dl.Token.Literal } -func (dl *DictLiteral) String() string { - var out bytes.Buffer - pairs := []string{} - for key, value := range dl.Pairs { - pairs = append(pairs, key.String()+":"+value.String()) - } - - out.WriteString("(") - out.WriteString(strings.Join(pairs, ", ")) - out.WriteString("}") - - return out.String() -} - -type AssignmentExpression struct { - Token token.Token - Left Expression - Value Expression -} - -func (ae *AssignmentExpression) expressionNode() {} -func (ae *AssignmentExpression) TokenLiteral() string { return ae.Token.Literal } -func (ae *AssignmentExpression) String() string { - var out bytes.Buffer - - out.WriteString(ae.Left.String()) - out.WriteString(ae.TokenLiteral()) - out.WriteString(ae.Value.String()) - - return out.String() -} - -type WhileExpression struct { - Token token.Token - Condition Expression - Consequence *BlockStatement -} - -func (we *WhileExpression) expressionNode() {} -func (we *WhileExpression) TokenLiteral() string { return we.Token.Literal } -func (we *WhileExpression) String() string { - var out bytes.Buffer - - out.WriteString("while") - out.WriteString(we.Condition.String()) - out.WriteString(" ") - out.WriteString(we.Consequence.String()) - - return out.String() -} - -type Null struct { - Token token.Token -} - -func (n *Null) expressionNode() {} -func (n *Null) TokenLiteral() string { return n.Token.Literal } -func (n *Null) String() string { return n.Token.Literal } - -type Break struct { - Statement - Token token.Token // the 'break' token -} - -func (b *Break) expressionNode() {} -func (b *Break) TokenLiteral() string { return b.Token.Literal } -func (b *Break) String() string { return b.Token.Literal } - -type Continue struct { - Statement - Token token.Token // the 'continue' token -} - -func (c *Continue) expressionNode() {} -func (c *Continue) TokenLiteral() string { return c.Token.Literal } -func (c *Continue) String() string { return c.Token.Literal } - -type PostfixExpression struct { - Token token.Token - Operator string -} - -func (pe *PostfixExpression) expressionNode() {} -func (pe *PostfixExpression) TokenLiteral() string { return pe.Token.Literal } -func (pe *PostfixExpression) String() string { - var out bytes.Buffer - out.WriteString("(") - out.WriteString(pe.Token.Literal) - out.WriteString(pe.Operator) - out.WriteString(")") - return out.String() -} - -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 } - -type For struct { - Expression - Token token.Token - Identifier string // "i" - StarterName *Identifier // i = 0 - StarterValue Expression - Closer Expression // i++ - Condition Expression // i < 1 - Block *BlockStatement -} - -type ForIn struct { - Expression - Token token.Token - Key string - Value string - Iterable Expression - Block *BlockStatement -} - -type CaseExpression struct { - Token token.Token - Default bool - Expr []Expression - Block *BlockStatement -} - -func (ce *CaseExpression) expressionNode() {} -func (ce *CaseExpression) TokenLiteral() string { return ce.Token.Literal } -func (ce *CaseExpression) String() string { - var out bytes.Buffer - - if ce.Default { - out.WriteString("default ") - } else { - out.WriteString("case ") - - tmp := []string{} - for _, exp := range ce.Expr { - tmp = append(tmp, exp.String()) - } - out.WriteString(strings.Join(tmp, ",")) - } - out.WriteString(ce.Block.String()) - return out.String() -} - -type SwitchExpression struct { - Token token.Token - Value Expression - Choices []*CaseExpression -} - -func (se *SwitchExpression) expressionNode() {} -func (se *SwitchExpression) TokenLiteral() string { return se.Token.Literal } -func (se *SwitchExpression) String() string { - var out bytes.Buffer - out.WriteString("\nswitch (") - out.WriteString(se.Value.String()) - out.WriteString(")\n{\n") - - for _, tmp := range se.Choices { - if tmp != nil { - out.WriteString(tmp.String()) - } - } - out.WriteString("}\n") - - return out.String() -} diff --git a/ast/ast_test.go b/ast/ast_test.go deleted file mode 100644 index c5ae5fa..0000000 --- a/ast/ast_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package ast - -import ( - "testing" - - "github.com/AvicennaJr/Nuru/token" -) - -func TestString(t *testing.T) { - program := &Program{ - Statements: []Statement{ - &LetStatement{ - Token: token.Token{Type: token.LET, Literal: "fanya"}, - Name: &Identifier{ - Token: token.Token{Type: token.IDENT, Literal: "myVar"}, - Value: "myVar", - }, - Value: &Identifier{ - Token: token.Token{Type: token.IDENT, Literal: "anotherVar"}, - Value: "anotherVar", - }, - }, - }, - } - - if program.String() != "fanya myVar = anotherVar;" { - t.Errorf("program.String() wrong. got=%q", program.String()) - } -} diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..8f5a3a3 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,78 @@ +# NURU PROGRAMMING LANGUAGE DOCUMENTATION + +This documentation is intended for people with some experience in programming. It describes the syntax, types and how to perform various operations using the language. + +## Table Of Contents + +- [Comments](./comments.md) +- [Numbers](./numbers.md) + * [Precedence](./numbers.md#precedence) + * [Unary Increments](./numbers.md#unary-increments) + * [Shorthand Assignments](./numbers.md#shorthand-assignment) + * [Negative Numbers](./numbers.md#negative-numbers) +- [Strings](./strings.md) + * [Definition](./strings.md#definition) + * [Concatenation](./strings.md#concatenation) + * [Looping over a String](./strings.md#looping-over-a-string) + * [Comparing Strings](./strings.md#comparing-strings) + * [Length of a String](./strings.md#length-of-a-string) +- [Arrays](./arrays.md) + * [Definition](./arrays.md#definition) + * [Accessing Elements](./arrays.md#accessing-elements) + * [Reassigning Elements](./arrays.md#reassigning-elements) + * [Looping over an Array](./arrays.md#looping-over-an-array) + * [Check if an Element Exists](./arrays.md#check-if-an-element-exists) + * [Concatenating Arrays](./arrays.md#concatenating-arrays) + * [Length of an Array](./arrays.md#length-of-an-array) + * [Adding Elements to an Array](./arrays.md#adding-elements-to-an-array) + * [Getting the last item in an Array](./arrays.md#getting-the-last-element-in-an-array) +- [Dictionaries](./dictionaries.md) + * [Definition](./dictionaries.md#definition) + * [Accessing Elements](./dictionaries.md#accessing-elements) + * [Updating Elements](./dictionaries.md#updating-elements) + * [Adding New Elements](./dictionaries.md#adding-new-elements) + * [Concatenating Dictionaries](./dictionaries.md#concatenating-dictionaries) + * [Checking if a Key Exists](./dictionaries.md#checking-if-key-exists-in-a-dictionary) + * [Looping Over a Dictionary](./dictionaries.md#looping-over-a-dictionary) +- [Booleans](./bool.md) + * [Example 1](./bool.md#example-1) + * [Example 2](./bool.md#example-2) +- [Identifiers](./identifiers.md) + * [Example 1](./identifiers.md#example-1) +- [For Loops](./for.md) + * [Definition](./for.md#definition) + * [Key-Value Pairs](./for.md#key-value-pairs) + * [Break and Continue](./for.md#break-vunja-and-continue-endelea) +- [While Loops](./while.md) + * [Definition](./while.md#definition) + * [Break and Continue](./while.md#break-vunja-and-continue-endelea) +- [If/Else](./ifStatements.md) + * [Definition](./ifStatements.md#definition) + * [Else Block](./ifStatements.md#else-block) +- [Switch Statements](./switch.md) + * [Definition](./switch.md#definition) + * [Multiple Values in Case](./switch.md#multiple-values-in-a-case) + * [Default Keyword](./switch.md#default-kawaida) +- [Functions](./function.md) + * [Definition](./function.md#definition) + * [Parameters](./function.md#parameters) + * [Return](./function.md#return-rudisha) + * [Recursion](./function.md#recursion) +- [Builtins](./builtins.md) + * [andika()](./builtins.md#andika) + * [jaza()](./builtins.md#jaza) + * [aina()](./builtins.md#aina) + * [idadi()](./builtins.md#idadi) + * [sukuma()](./builtins.md#sukuma) + * [yamwisho()](./builtins.md#yamwisho) +- [Null](./null.md) +- [Operators](./operators.md) + * [Assignment](./operators.md#assignment) + * [Arithmetic](./operators.md#arithmetic-operators) + * [Comparison](./operators.md#comparison-operators) + * [Member](./operators.md#member-operator) + * [Logic](./operators.md#logic-operators) + * [Precedence](./operators.md#precedence-of-operators) +- [Keywords](./keywords.md) + * [Reserved](./keywords.md#reserved) + * [Builtins](./keywords.md#builtins) \ No newline at end of file diff --git a/docs/arrays.md b/docs/arrays.md new file mode 100644 index 0000000..f36ee0d --- /dev/null +++ b/docs/arrays.md @@ -0,0 +1,121 @@ +## ARRAYS (ORODHA) + +### Definition + +Arrays are enclosed in square brackets `[]` and they can hold any type, even function definitions: +```go +fanya arr = [1, "mambo", kweli, unda(x, y){rudisha x + y}, 2 * 3 + 20] + +andika(arr) + +/* +[1, mambo, kweli, unda(x, y) {rudisha (x + y);}, 26] +*/ +``` + +### Accessing Elements + +You can access individual elements through indexes starting from zero: +```go +fanya herufi = ["a", "b", "c"] + +andika(herufi[0]) // a +``` + +### Reassigning Elements + +You can also reassign values in elements: +```go +fanya herufi = ["a", "b", "c"] + +herufi[1] = "z" + +andika(herufi) // ["a", "z", "c"] +``` + +### Looping over an Array + +- You can also iterate through an array: +```go +fanya herufi = ["a", "b", "c"] + +kwa i ktk herufi { + andika(i) +} +/* a + b + c */ +``` + +- And for a key, value pair: +```go +kwa i, v ktk herufi { + andika(i, "=>", v) +} + +/* 0 => a + 1 => b + 2 => c */ +``` + +### Check if an Element exists + +You can also check if elements exist in an array: +```go +andika("d" ktk herufi) // sikweli +andika("a" ktk herufi) // kweli +``` + +### Concatenating Arrays + +- You can also add two arrays as follows: +``` +fanya h1 = ["a", "b", "c"] +fanya h2 = [1, 2, 3] +fanya h3 = h1 + h2 + +andika(h3) // ["a", "b", "c", 1, 2, 3] + +h2 += h3 + +andika(h2) // [1, 2, 3, "a", "b", "c", 1, 2, 3] +``` + +- You can also multiply an array as follows: +``` +fanya a = [1, 2, 3] + +andika(a * 2) // [1, 2, 3, 1, 2, 3] +``` + +### Length of an Array + +You can get the length of an array with `idadi`: +``` +fanya a = ["a", "b", "c"] + +andika(idadi(a)) // 3 +``` + +### Adding Elements to an Array + +You can add new elements to an array with `sukuma`: +```go +fanya a = [1, 2, 3] + +// you must reassign for the new value to be saved +a = sukuma(a, "mambo") + +andika(a) // [1, 2, 3, "mambo"] +``` + +### Getting the Last Element in an Array + +You can get the last element of an array with `yamwisho`: +``` +fanya a = [1, 2, 3] + +andika(yamwisho(a)) // 3 +``` +**Please Note** +> A lot more array methods will be added in the future \ No newline at end of file diff --git a/docs/bool.md b/docs/bool.md new file mode 100644 index 0000000..4c0aa09 --- /dev/null +++ b/docs/bool.md @@ -0,0 +1,32 @@ +## BOOLEANS + +Boolean objects are `truthy`, meaning any value is true. A value is false only when its null (ie. `tupu`) or false (ie `sikweli`): +### Example 1 +``` +fanya x = 0 + +kama (x) { + andika("I am true") +} sivyo { + andika("I am not true") +} + +// it will print "I am true" +``` + +### Example 2 +``` +kama (tupu) { +andika("I am true") +} sivyo { + andika("I am not true") +} + +// will print "I am not true" +``` + +Expressions can also be evaluated to true or false: +``` +andika(1 > 2) // sikweli + +andika(1 + 3 < 10) // kweli \ No newline at end of file diff --git a/docs/builtins.md b/docs/builtins.md new file mode 100644 index 0000000..c27af01 --- /dev/null +++ b/docs/builtins.md @@ -0,0 +1,61 @@ +## BUILTINS + +Nuru has a few builtin functions and more will be added in the future + +### andika() + +This function will print out whatever is placed inside the parenthesis `()`. It can take zero or multiple number of arguments. Arguments will be printed out with a space in between them: +``` +andika(1,2,3) // 1 2 3 +``` +`andika()` also supports some basic formatting such as: +- `/n` for a new line +- `/t` for a tab space +- `\\` for a backslash + +### jaza() + +This is a function to get input from a user. It can have zero or one argument. The only acceptable argument is a string: +``` +fanya salamu = unda(){ + fanya jina = jaza("Unaitwa nani? ") + andika("mambo vipi", jina) +} + +salamu() +``` + +### aina() + +`Aina()` is a function to help identify the type of an object. It only accepts one argument: +``` +aina(2) // NAMBA +``` + +### idadi() + +`idadi` is a function to know a length of an object. It accepts only one argument which can be a `string`, `list` or `dictionary`: +``` +idadi("mambo") // 5 +``` + +### sukuma() + +`sukuma()` is a function that adds a new element to an array. The function accepts two arguments, the first must be a list and the second is the element to be added/appended: +``` +fanya majina = ["juma", "asha"] + +majina = sukuma(majina, "mojo") +``` +**Notice that the list is reassigned for the change to take effect** + +### yamwisho() + +This is a function to get the last element in an array. It only accepts one argument which must be an array: +``` +fanya namba = [1,2,3,4,5] + +yamwisho(namba) // 5 +``` + +**MORE BUILTIN FUNCTIONS WILL BE ADDED WITH TIME** \ No newline at end of file diff --git a/docs/comments.md b/docs/comments.md new file mode 100644 index 0000000..b10f988 --- /dev/null +++ b/docs/comments.md @@ -0,0 +1,14 @@ +## COMMENTS + +- You can write single line comments with `//`: +``` +// This line will be ignored +``` +- Multiline comments start with `/*` and end with `*/`: +``` +/* +These lines +Will +be +ignored +*/ \ No newline at end of file diff --git a/docs/dictionaries.md b/docs/dictionaries.md new file mode 100644 index 0000000..5e195f4 --- /dev/null +++ b/docs/dictionaries.md @@ -0,0 +1,96 @@ +## DICTIONARIES (KAMUSI) + +### DEFINITION + +Dictionaries are enclosed by curly braces `{}` and have keys and values. You can define a dictionary as follows: +``` +fanya k = {"jina": "juma"} +``` +- The `keys` can be `string, int, float` and `boolean` +- The `values` can be of any type; `string, int, float, boolean, null` and even a `function`: +``` +fanya k = { + "jina": "juma", + "umri": 2, + kweli : "true", + "mi ni function": unda(x){andika("habari", x)} + "sina value": tupu +} + +andika(k["sina value"]) // tupu +``` + +### Accessing Elements + +You can access individual elements as follows: +``` +andika(k[kweli]) // true + +andika(k["mi ni function"]("juma")) // habari juma +``` + +### Updating Elements +You can update the value of an element as follows: +``` +k['umri'] = 50 + +andika(k['umri']) // 50 +``` + +### Adding New Elements +If a key-value pair doesn't exist, you can add one as follows: +``` +k["I am new"] = "new element" + +andika(k["I am new"]) // new element +``` + +### Concatenating Dictionaries + +You can add two dictionaries as follows: +``` +fanya a = {"a": "andazi"} +fanya b = {"b": "bunduki"} +fanya c = a + b + +andika(c) // {"a": "andazi", "b": "bunduki"} +``` + +### Checking If Key Exists In A Dictionary + +Use the `ktk` keyword to check if a key exists: +``` +"umri" ktk k // kweli +"ubini" ktk k // sikweli +``` + +### Looping Over A Dictionary + +- You can loop over a dictionary as follows: + +```go +fanya k = {"a": "afya", "b": "buibui", "c": "chapa"} +kwa i, v ktk k { + andika(i, "=>", v) +} +/* a => afya + b => buibui + c => chapa */ +``` + +- You can also loop over just values as follows: + +``` +kwa v ktk k { + andika(v) +} + +/* +afya +buibui +chapa +*/ +``` + +**Please Note** +> A lot more dict methods will be added in the future \ No newline at end of file diff --git a/docs/for.md b/docs/for.md new file mode 100644 index 0000000..cd56a6c --- /dev/null +++ b/docs/for.md @@ -0,0 +1,110 @@ +## FOR (KWA) + +### Definition + +For is used to iterate over an iterable object. An iterable object is a `string`, `array` or `dictionaries`. You use the `kwa` keyword followed by an identifier such as `k` or `v` followed by an iterable. The iterable block must be enclosed in a bracket `{}`. Here's an example: +``` +fanya jina = "lugano" + +kwa i ktk jina { + andika(i) +} + +/* +l +u +g +a +n +o +*/ +``` + +### Key Value Pairs + +Nuru allows you to get both the value or the key/value pair of an iterable. To get only the value, use one temporary identifier as such: +``` +fanya kamusi = {"a": "andaa", "b": "baba"} + +kwa v ktk kamusi { + andika(v) +} + +/* +andaa +baba +*/ +``` +To get both the key and the value, use two temporary identifiers: +``` +kwa k, v ktk kamusi { + andika(k + " ni + " v) +} + +/* +a ni andaa +b ni baba +*/ +``` +- Note that key-value pair iteration also works for `strings` and `lists`: +``` +kwa i, v ktk "mojo" { + andika(i, "->", v) +} +/* +0 -> m +1 -> o +2 -> j +3 -> o +*/ +fanya majina = ["juma", "asha", "haruna"] + +kwa i, v ktk majina { + andika(i, "-", v) +} + +/* +0 - juma +1 - asha +2 - haruna +*/ +``` + +### Break (Vunja) and Continue (Endelea) + +- A loop can be terminated using the `vunja` keyword: +``` +kwa i, v ktk "mojo" { + kama (i == 2) { + andika("nimevunja") + vunja + } + andika(v) +} +/* +m +o +nimevunja +*/ +``` + +- A specific iteration can be skipped using the `endelea` keyword: +``` +kwa i, v ktk "mojo" { + kama (i == 2) { + andika("nimeruka") + endelea + } + andika(v) +} + +/* +m +o +nimeruka +o +*/ +``` + +**CAUTION** +> In nested loops, the `vunja` and `endelea` keyword MIGHT misbehave \ No newline at end of file diff --git a/docs/function.md b/docs/function.md new file mode 100644 index 0000000..0a22134 --- /dev/null +++ b/docs/function.md @@ -0,0 +1,55 @@ +## FUNCTIONS (UNDA) + +### Definition + +A function block starts with the `unda` keyword, parameters are surrounded by `()` and the body by `{}`. They must also be assigned to a variable as follows: +``` +fanya jum = unda(x, y) { + rudisha x + y +} + +jum(2, 3) // 5 +``` + +### Parameters + +Functions can have zero or any number of arguments. Arguments can be of any type, even other functions: +``` +fanya salamu = unda() { + andika("Habari yako") +} + +salamu() + +salamu = unda(jina) { + andika("Habari yako", jina) +} + +salamu(asha) // Habari yako asha +``` + +### Return (rudisha) + +You can return items with the `rudisha` keyword. The `rudisha` keyword will terminate the block and return the value: +``` +fanya mfano = unda(x) { + rudisha "nimerudi" + andika(x) +} + +mfano(x) // nimerudi +``` + +### Recursion + +Nuru also supports recursion. Here's an example: +``` +fanya fib = unda(n) { + kama (n < 3) { + rudisha 1 + } sivyo { + rudisha fib(n-1) + fib(n-2) + } +} + +andika(fib(10)) // 55 \ No newline at end of file diff --git a/docs/identifiers.md b/docs/identifiers.md new file mode 100644 index 0000000..58cf756 --- /dev/null +++ b/docs/identifiers.md @@ -0,0 +1,15 @@ +## IDENTIFIERS + +Identifiers can contain letters, numbers and underscores. However, identifiers cannot start with a number. + +### Example 1 + +``` +fanya b2020 = 2020 + +andika(b2020) + +fanya c2p = "C to P" + +andika(c2p) // "C to P" +``` \ No newline at end of file diff --git a/docs/ifStatements.md b/docs/ifStatements.md new file mode 100644 index 0000000..3d7f12b --- /dev/null +++ b/docs/ifStatements.md @@ -0,0 +1,27 @@ +## IF/ELSE (KAMA/SIVYO) + +### Definition + +You initiliaze an if block with `kama`, the condition must be inside a paranthesis `()` and the consequence inside a `{}`: +``` +kama (2>1) { + andika(kweli) // kweli +} +``` + +### Else Block + +- For multiple conditions, you can use `kama` , `au kama` and `sivyo`: +``` +fanya a = 10 + +kama (a > 100) { + andika("a imezidi 100") +} au kama (a < 10) { + andika("a ndogo kuliko 10") +} sivyo { + andika("Thamani ya a ni", a) +} + +// it will print 'Thamani ya a ni 10' +``` \ No newline at end of file diff --git a/docs/keywords.md b/docs/keywords.md new file mode 100644 index 0000000..97e60e7 --- /dev/null +++ b/docs/keywords.md @@ -0,0 +1,57 @@ +## KEYWORDS + +### Reserved + +The keywords used in Nuru are below. Note that these words cannot be used as identifiers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
kwelisikweliundafanya
kamaausivyowakati
rudishavunjaendeleatupu
ktkkwabadiliikiwa
kawaida
+ +### BuiltIns + +The following are some of the in-built functions in Nuru. They are reserved and thus cannot be used as identifiers: + + + + + + + + + + + + + +
andikaainajaza
idadisukumayamwisho
\ No newline at end of file diff --git a/docs/null.md b/docs/null.md new file mode 100644 index 0000000..c21e74a --- /dev/null +++ b/docs/null.md @@ -0,0 +1,17 @@ +## NULL (TUPU) + +- A null data type is a data type with no value. It is defined as: +``` +fanya a = `tupu` +``` + +- Obviously a null data type will evaluate to false: +``` +kama (a) { + andika("niko tupu") +} sivyo { + andika("nimevaa nguo") +} + +// will print 'nimevaa nguo' +``` \ No newline at end of file diff --git a/docs/numbers.md b/docs/numbers.md new file mode 100644 index 0000000..a857de6 --- /dev/null +++ b/docs/numbers.md @@ -0,0 +1,74 @@ +## INTEGERS (NAMBA) AND FLOATS (DESIMALI) + +### PRECEDENCE + +Integers and floats work the way you'd expect them to. They precedence in mathematical operations follow the BODMAS rule: + +```go +2 + 3 * 5 // 17 + +fanya a = 2.5 +fanya b = 3/5 + +a + b // 2.8 +``` + +### UNARY INCREMENTS + +You can perform unary increments (++ and --) on both floats and integers. These will add or subtract 1 from the current value. Note that the float or int have to be assigned to a variable for this operation to work. Here's an example: + +```go +fanya i = 2.4 + +i++ // 3.4 +``` + +### SHORTHAND ASSIGNMENT + +You can also perform shorthand assignments with `+=`, `-=`, `/=`, `*=` and `%=` as follows: + +```go +fanya i = 2 + +i *= 3 // 6 +i /= 2 // 3 +i += 100 // 103 +i -= 10 // 93 +i %= 90 // 3 +``` + +### NEGATIVE NUMBERS + +Negative numbers also behave as expected: + +```go +fanya i = -10 + +wakati (i < 0) { + andika(i) + i++ +} + +/* +-10 +-9 +-8 +-7 +-6 +-5 +-4 +-3 +-2 +-1 +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +*/ +``` diff --git a/docs/operators.md b/docs/operators.md new file mode 100644 index 0000000..142b20e --- /dev/null +++ b/docs/operators.md @@ -0,0 +1,73 @@ +## OPERATORS + +### ASSIGNMENT + +Assuming `i` and `v` are predefined variables, Nuru supports the following assignment operators: + +- `i = v`: which is the regular assign operator +- `i += v`: which is the equivalent of `i = i + v` +- `i -= v`: which is the equivalent of `i = i - v` +- `i *= v`: which is the equivalent of `i = i * v` +- `i /= v`: which is the equivalent of `i = i / v` +- `i += v`: which is the equivalent of `i = i + v` + +For `strings`, `arrays` and `dictionaries`, the `+=` sign operator is permissible. Example: +``` +list1 += list2 // this is equivalent to list1 = list1 + list2 +``` + +### ARITHMETIC OPERATORS + +The following arithmetic operators are supported: + +- `+`: Additon +- `-`: Subtraction +- `*`: Multiplication +- `/`: Division +- `%`: Modulo (ie the remainder of a division) +- `**`: Exponential power (eg: `2**3 = 8`) + +### COMPARISON OPERATORS + +The following comparison operators are supported: + +- `==`: Equal to +- `!=`: Not equal to +- `>`: Greater than +- `>=`: Greater than or equal to +- `<`: Less than +- `<=`: Less than or equal to + +### MEMBER OPERATOR + +The member operator in Nuru is `ktk`. It will check if an object exists in another object: +```go +fanya majina = ['juma', 'asha', 'haruna'] + +"haruna" ktk majina // kweli +"halima" ktk majina // sikweli +``` + +### LOGIC OPERATORS + +The following logic operators are supported: + +- `&&`: Logical `AND`. It will evaluate to true if both are true, otherwise it will evaluate to false. +- `||`: Logical `OR`. It will evaluate to false if both are false, otherwise it will evaluate to true. +- `!`: Logical `NOT`. It will evaluate to the opposite of a given expression. + +### PRECEDENCE OF OPERATORS + +The following is the precedence of operators, starting from the HIGHEST PRIORITY to LOWEST. + +- `()` : Items in paranthesis have the highest priority +- `!`: Negation +- `%`: Modulo +- `**`: Exponential power +- `/, *`: Division and Multiplication +- `+, +=, -, -=`: Addition and Subtraction +- `>, >=, <, <=`: Comparison operators +- `==, !=`: Equal or Not Equal to +- `=`: Assignment Operator +- `ktk`: Member Operator +- `&&, ||`: Logical AND and OR \ No newline at end of file diff --git a/docs/strings.md b/docs/strings.md new file mode 100644 index 0000000..ea1dbec --- /dev/null +++ b/docs/strings.md @@ -0,0 +1,104 @@ +## STRINGS (NENO) + +### Definition + +Strings can be enclosed in either a single quote `''` or double quotes `""`: + +``` +andika("mambo") // mambo + +fanya a = 'niaje' + +andika("mambo", a) // mambo niaje +``` + +### Concatenation + +- Strings can also be concatenated as follows: + +``` +fanya a = "habari" + " " + "yako" + +andika(a) // habari yako + +fanya b = "habari" + +b += " yako" + +// habari yako +``` + +- You can also multiply a string `n` number of times: + +``` +andika("mambo " * 4) + +// mambo mambo mambo mambo + +fanya a = "habari" + +a *= 4 + +// habarihabarihabarihabari +``` + +### Looping over a String + +- You can loop through a string as follows + +``` +fanya jina = "avicenna" + +kwa i ktk jina {andika(i)} + +/* + a + v + i + c + e + n + n + a +*/ +``` + +- And for key, value pairs: +```go +kwa i, v ktk jina { + andika(i, "=>", v) +} +/* +0 => a +1 => v +2 => i +3 => c +4 => e +5 => n +6 => n +7 => a +*/ +``` + +### Comparing Strings + +- You can also check if two strings are the same: +``` +fanya a = "nuru" + +andika(a == "nuru") // kweli + +andika(a == "mambo") // sikweli +``` + +### Length of a String + +You can also check the length of a string with the `idadi` function +``` +fanya a = "mambo" + +idadi(a) // 5 +``` + +**Please Note** +> A lot more string methods will be added in the future \ No newline at end of file diff --git a/docs/switch.md b/docs/switch.md new file mode 100644 index 0000000..5f5b034 --- /dev/null +++ b/docs/switch.md @@ -0,0 +1,52 @@ +## SWITCH (BADILI) + +### Definition + +You initialize a switch statement with `badili`, the expression inside parenthesis `()` and all cases will be enclosed inside a bracket `{}`. + +A case statement has the keyword `ikiwa` followed by a value to check. Multiple values can be in a single case separated by commas `,`. The consequence to execute if a condition is fulfiled must be inside a bracket `{}`. Here's an example: +``` +fanya a = 2 + +badili (a){ + ikiwa 3 { + andika("a ni tatu") + } + ikiwa 2 { + andika ("a ni mbili") + } +} +``` + +### Multiple Values in a Case + +Multiple possibilites can be assigned to a single case (`ikiwa`) statement: +``` +badili (a) { + ikiwa 1,2,3 { + andika("a ni kati ya 1, 2 au 3") + } + ikiwa 4 { + andika("a ni 4") + } +} +``` + +### Default (kawaida) + +The default statement will be executed when no condition is satisfied. The default statement is represented by `kawaida`: +``` +fanya z = 20 + +badili(z) { + ikiwa 10 { + andika("kumi") + } + ikiwa 30 { + andika("thelathini") + } + kawaida { + andika("ishirini") + } +} +``` \ No newline at end of file diff --git a/docs/while.md b/docs/while.md new file mode 100644 index 0000000..791e619 --- /dev/null +++ b/docs/while.md @@ -0,0 +1,65 @@ +## WHILE (WAKATI) + +### Definition + +A while loop is executed when a specified condition is true. You initiliaze a while loop with the `wakati` keyword followed by the condition in paranthesis `()`. The consequence of the loop should be enclosed in brackets `{}`: +``` +fanya i = 1 + +wakati (i <= 5) { + andika(i) + i++ +} +/* +1 +2 +3 +4 +5 +*/ +``` + +### Break (vunja) and Continue (endelea) + +- A loop can be terminated using the `vunja` keyword: +``` +fanya i = 1 + +wakati (i < 5) { + kama (i == 3) { + andika("nimevunja") + vunja + } + andika(i) + i++ +} +/* +1 +2 +nimevunja +*/ +``` + +- A specific iteration can be skipped using the `endelea` keyword: +``` +fanya i = 0 + +wakati (i < 5) { + i++ + kama (i == 3) { + andika("nimeruka") + endelea + } + andika(i) +} +/* +1 +2 +nimeruka +4 +5 +*/ +``` + +**CAUTION** +> In nested loops, the `vunja` and `endelea` keyword MIGHT misbehave diff --git a/evaluator/builtins.go b/evaluator/builtins.go deleted file mode 100644 index 6a24052..0000000 --- a/evaluator/builtins.go +++ /dev/null @@ -1,119 +0,0 @@ -package evaluator - -import ( - "bufio" - "fmt" - "io" - "os" - "strings" - - "github.com/AvicennaJr/Nuru/object" -) - -var builtins = map[string]*object.Builtin{ - "idadi": { - Fn: func(args ...object.Object) object.Object { - if len(args) != 1 { - return newError("Hoja hazilingani, tunahitaji=1, tumepewa=%d", len(args)) - } - - switch arg := args[0].(type) { - case *object.Array: - return &object.Integer{Value: int64(len(arg.Elements))} - case *object.String: - return &object.Integer{Value: int64(len(arg.Value))} - default: - return newError("Samahani, hii function haitumiki na %s", args[0].Type()) - } - }, - }, - "yamwisho": { - Fn: func(args ...object.Object) object.Object { - if len(args) != 1 { - return newError("Samahani, tunahitaji Hoja moja tu, wewe umeweka %d", len(args)) - } - if args[0].Type() != object.ARRAY_OBJ { - return newError("Samahani, hii function haitumiki na %s", args[0].Type()) - } - - arr := args[0].(*object.Array) - length := len(arr.Elements) - if length > 0 { - return arr.Elements[length-1] - } - - return NULL - }, - }, - "sukuma": { - Fn: func(args ...object.Object) object.Object { - if len(args) != 2 { - return newError("Samahani, tunahitaji Hoja 2, wewe umeweka %d", len(args)) - } - if args[0].Type() != object.ARRAY_OBJ { - return newError("Samahani, hii function haitumiki na %s", args[0].Type()) - } - - arr := args[0].(*object.Array) - length := len(arr.Elements) - - newElements := make([]object.Object, length+1) - copy(newElements, arr.Elements) - newElements[length] = args[1] - - return &object.Array{Elements: newElements} - }, - }, - "jaza": { - Fn: func(args ...object.Object) object.Object { - - if len(args) > 1 { - return newError("Samahani, hii function inapokea hoja 0 au 1, wewe umeweka %d", len(args)) - } - - if len(args) > 0 && args[0].Type() != object.STRING_OBJ { - return newError(fmt.Sprintf(`Tafadhali tumia alama ya nukuu: "%s"`, args[0].Inspect())) - } - if len(args) == 1 { - prompt := args[0].(*object.String).Value - fmt.Fprint(os.Stdout, prompt) - } - - buffer := bufio.NewReader(os.Stdin) - - line, _, err := buffer.ReadLine() - if err != nil && err != io.EOF { - return newError("Nimeshindwa kusoma uliyo yajaza") - } - - return &object.String{Value: string(line)} - }, - }, - "andika": { - Fn: func(args ...object.Object) object.Object { - if len(args) == 0 { - fmt.Println("") - } else { - var arr []string - for _, arg := range args { - if arg == nil { - return newError("Hauwezi kufanya operesheni hii") - } - arr = append(arr, arg.Inspect()) - } - str := strings.Join(arr, " ") - print(str + "\n") - } - return nil - }, - }, - "aina": { - Fn: func(args ...object.Object) object.Object { - if len(args) != 1 { - return newError("Samahani, tunahitaji Hoja 1, wewe umeweka %d", len(args)) - } - - return &object.String{Value: string(args[0].Type())} - }, - }, -} diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go deleted file mode 100644 index 4e81541..0000000 --- a/evaluator/evaluator.go +++ /dev/null @@ -1,943 +0,0 @@ -package evaluator - -import ( - "fmt" - "math" - "strings" - - "github.com/AvicennaJr/Nuru/ast" - "github.com/AvicennaJr/Nuru/object" -) - -var ( - NULL = &object.Null{} - TRUE = &object.Boolean{Value: true} - FALSE = &object.Boolean{Value: false} - BREAK = &object.Break{} - CONTINUE = &object.Continue{} -) - -func Eval(node ast.Node, env *object.Environment) object.Object { - switch node := node.(type) { - case *ast.Program: - return evalProgram(node, env) - - case *ast.ExpressionStatement: - return Eval(node.Expression, env) - - 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) - - case *ast.PrefixExpression: - right := Eval(node.Right, env) - if isError(right) { - return right - } - return evalPrefixExpression(node.Operator, right, node.Token.Line) - - case *ast.InfixExpression: - left := Eval(node.Left, env) - if isError(left) { - return left - } - right := Eval(node.Right, env) - if isError(right) { - return right - } - return evalInfixExpression(node.Operator, left, right, node.Token.Line) - case *ast.PostfixExpression: - return evalPostfixExpression(env, node.Operator, node) - - case *ast.BlockStatement: - return evalBlockStatement(node, env) - - case *ast.IfExpression: - return evalIfExpression(node, env) - - case *ast.ReturnStatement: - val := Eval(node.ReturnValue, env) - if isError(val) { - return val - } - return &object.ReturnValue{Value: val} - - case *ast.LetStatement: - val := Eval(node.Value, env) - if isError(val) { - return val - } - - env.Set(node.Name.Value, val) - - case *ast.Identifier: - return evalIdentifier(node, env) - - case *ast.FunctionLiteral: - params := node.Parameters - body := node.Body - return &object.Function{Parameters: params, Env: env, Body: body} - - case *ast.CallExpression: - function := Eval(node.Function, env) - if isError(function) { - return function - } - args := evalExpressions(node.Arguments, env) - if len(args) == 1 && isError(args[0]) { - return args[0] - } - return applyFunction(function, args, node.Token.Line) - case *ast.StringLiteral: - return &object.String{Value: node.Value} - - case *ast.ArrayLiteral: - elements := evalExpressions(node.Elements, env) - if len(elements) == 1 && isError(elements[0]) { - return elements[0] - } - return &object.Array{Elements: elements} - case *ast.IndexExpression: - left := Eval(node.Left, env) - if isError(left) { - return left - } - index := Eval(node.Index, env) - if isError(index) { - return index - } - return evalIndexExpression(left, index, node.Token.Line) - case *ast.DictLiteral: - return evalDictLiteral(node, env) - case *ast.WhileExpression: - return evalWhileExpression(node, env) - case *ast.Break: - return evalBreak(node) - case *ast.Continue: - return evalContinue(node) - case *ast.SwitchExpression: - return evalSwitchStatement(node, env) - case *ast.Null: - return NULL - // case *ast.For: - // return evalForExpression(node, env) - case *ast.ForIn: - return evalForInExpression(node, env, node.Token.Line) - case *ast.AssignmentExpression: - left := Eval(node.Left, env) - if isError(left) { - return left - } - - value := Eval(node.Value, env) - if isError(value) { - return value - } - - // This is an easy way to assign operators like +=, -= etc - // I'm surprised it work at the first try lol - // basically separate the += to + and =, take the + only and - // then perform the operation as normal - op := node.Token.Literal - if len(op) >= 2 { - op = op[:len(op)-1] - value = evalInfixExpression(op, left, value, node.Token.Line) - if isError(value) { - return value - } - } - - if ident, ok := node.Left.(*ast.Identifier); ok { - env.Set(ident.Value, value) - } else if ie, ok := node.Left.(*ast.IndexExpression); ok { - obj := Eval(ie.Left, env) - if isError(obj) { - return obj - } - - if array, ok := obj.(*object.Array); ok { - index := Eval(ie.Index, env) - if isError(index) { - return index - } - if idx, ok := index.(*object.Integer); ok { - if int(idx.Value) > len(array.Elements) { - return newError("Index imezidi idadi ya elements") - } - array.Elements[idx.Value] = value - } else { - return newError("Hauwezi kufanya opereshen hii na %#v", index) - } - } else if hash, ok := obj.(*object.Dict); ok { - key := Eval(ie.Index, env) - if isError(key) { - return key - } - if hashKey, ok := key.(object.Hashable); ok { - hashed := hashKey.HashKey() - hash.Pairs[hashed] = object.DictPair{Key: key, Value: value} - } else { - return newError("Hauwezi kufanya opereshen hii na %T", key) - } - } else { - return newError("%T haifanyi operation hii", obj) - } - } else { - return newError("Tumia neno kama variable, sio %T", left) - } - - } - - return nil -} - -func evalProgram(program *ast.Program, env *object.Environment) object.Object { - var result object.Object - - for _, statment := range program.Statements { - result = Eval(statment, env) - - switch result := result.(type) { - case *object.ReturnValue: - return result.Value - case *object.Error: - return result - } - } - - return result -} - -func nativeBoolToBooleanObject(input bool) *object.Boolean { - if input { - return TRUE - } - return FALSE -} - -func evalPrefixExpression(operator string, right object.Object, line int) object.Object { - switch operator { - case "!": - return evalBangOperatorExpression(right) - case "-": - return evalMinusPrefixOperatorExpression(right, line) - default: - return newError("Mstari %d: Operesheni haieleweki: %s%s", line, operator, right.Type()) - } -} - -func evalBangOperatorExpression(right object.Object) object.Object { - switch right { - case TRUE: - return FALSE - case FALSE: - return TRUE - case NULL: - return TRUE - default: - return FALSE - } -} - -func evalMinusPrefixOperatorExpression(right object.Object, line int) object.Object { - switch obj := right.(type) { - - case *object.Integer: - return &object.Integer{Value: -obj.Value} - - case *object.Float: - return &object.Float{Value: -obj.Value} - - default: - return newError("Mstari %d: Operesheni Haielweki: -%s", line, right.Type()) - } -} - -func evalInfixExpression(operator string, left, right object.Object, line int) object.Object { - if left == nil { - return newError("Mstari %d: Umekosea hapa", line) - } - switch { - case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: - return evalStringInfixExpression(operator, left, right, line) - - case operator == "+" && left.Type() == object.DICT_OBJ && right.Type() == object.DICT_OBJ: - leftVal := left.(*object.Dict).Pairs - rightVal := right.(*object.Dict).Pairs - pairs := make(map[object.HashKey]object.DictPair) - for k, v := range leftVal { - pairs[k] = v - } - for k, v := range rightVal { - pairs[k] = v - } - return &object.Dict{Pairs: pairs} - - case operator == "+" && left.Type() == object.ARRAY_OBJ && right.Type() == object.ARRAY_OBJ: - leftVal := left.(*object.Array).Elements - rightVal := right.(*object.Array).Elements - elements := make([]object.Object, len(leftVal)+len(rightVal)) - elements = append(leftVal, rightVal...) - return &object.Array{Elements: elements} - - case operator == "*" && left.Type() == object.ARRAY_OBJ && right.Type() == object.INTEGER_OBJ: - leftVal := left.(*object.Array).Elements - rightVal := int(right.(*object.Integer).Value) - elements := leftVal - for i := rightVal; i > 1; i-- { - elements = append(elements, leftVal...) - } - return &object.Array{Elements: elements} - - case operator == "*" && left.Type() == object.INTEGER_OBJ && right.Type() == object.ARRAY_OBJ: - leftVal := int(left.(*object.Integer).Value) - rightVal := right.(*object.Array).Elements - elements := rightVal - for i := leftVal; i > 1; i-- { - elements = append(elements, rightVal...) - } - return &object.Array{Elements: elements} - - case operator == "*" && left.Type() == object.STRING_OBJ && right.Type() == object.INTEGER_OBJ: - leftVal := left.(*object.String).Value - rightVal := right.(*object.Integer).Value - return &object.String{Value: strings.Repeat(leftVal, int(rightVal))} - - case operator == "*" && left.Type() == object.INTEGER_OBJ && right.Type() == object.STRING_OBJ: - leftVal := left.(*object.Integer).Value - 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.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.FLOAT_OBJ && right.Type() == object.INTEGER_OBJ: - return evalFloatIntegerInfixExpression(operator, left, right, line) - - case operator == "ktk": - return evalInExpression(left, right, line) - - case operator == "==": - return nativeBoolToBooleanObject(left == right) - - case operator == "!=": - return nativeBoolToBooleanObject(left != right) - case left.Type() == object.BOOLEAN_OBJ && right.Type() == object.BOOLEAN_OBJ: - return evalBooleanInfixExpression(operator, left, right, line) - - case left.Type() != right.Type(): - return newError("Mstari %d: Aina Hazilingani: %s %s %s", - line, left.Type(), operator, right.Type()) - - default: - return newError("Mstari %d: Operesheni Haielweki: %s %s %s", - 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.Integer{Value: int64(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 Haielweki: %s %s %s", - line, left.Type(), operator, right.Type()) - } -} - -func evalFloatInfixExpression(operator string, left, right object.Object, line int) object.Object { - leftVal := left.(*object.Float).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 "**": - return &object.Float{Value: math.Pow(float64(leftVal), float64(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("Mstari %d: Operesheni Haielweki: %s %s %s", - line, left.Type(), operator, right.Type()) - } -} - -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 "<": - 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 Haielweki: %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 evalBooleanInfixExpression(operator string, left, right object.Object, line int) object.Object { - leftVal := left.(*object.Boolean).Value - rightVal := right.(*object.Boolean).Value - - switch operator { - case "&&": - return nativeBoolToBooleanObject(leftVal && rightVal) - case "||": - return nativeBoolToBooleanObject(leftVal || rightVal) - default: - return newError("Mstari %d: Operesheni Haielweki: %s %s %s", line, left.Type(), operator, right.Type()) - } -} - -func evalPostfixExpression(env *object.Environment, operator string, node *ast.PostfixExpression) object.Object { - val, ok := env.Get(node.Token.Literal) - if !ok { - return newError("Tumia KITAMBULISHI CHA NAMBA AU DESIMALI, sio %s", node.Token.Type) - } - switch operator { - case "++": - switch arg := val.(type) { - case *object.Integer: - v := arg.Value + 1 - return env.Set(node.Token.Literal, &object.Integer{Value: v}) - case *object.Float: - v := arg.Value + 1 - return env.Set(node.Token.Literal, &object.Float{Value: v}) - default: - return newError("Mstari %d: %s sio kitambulishi cha namba. Tumia '++' na kitambulishi cha namba au desimali.\nMfano:\tfanya i = 2; i++", node.Token.Line, node.Token.Literal) - - } - case "--": - switch arg := val.(type) { - case *object.Integer: - v := arg.Value - 1 - return env.Set(node.Token.Literal, &object.Integer{Value: v}) - case *object.Float: - v := arg.Value - 1 - return env.Set(node.Token.Literal, &object.Float{Value: v}) - default: - return newError("Mstari %d: %s sio kitambulishi cha namba. Tumia '--' na kitambulishi cha namba au desimali.\nMfano:\tfanya i = 2; i++", node.Token.Line, node.Token.Literal) - } - default: - return newError("Haifahamiki: %s", operator) - } -} - -func evalIfExpression(ie *ast.IfExpression, env *object.Environment) object.Object { - condition := Eval(ie.Condition, env) - - if isError(condition) { - return condition - } - - if isTruthy(condition) { - return Eval(ie.Consequence, env) - } else if ie.Alternative != nil { - return Eval(ie.Alternative, env) - } else { - return NULL - } -} - -func isTruthy(obj object.Object) bool { - switch obj { - case NULL: - return false - case TRUE: - return true - case FALSE: - return false - default: - return true - } -} - -func evalBlockStatement(block *ast.BlockStatement, env *object.Environment) object.Object { - var result object.Object - - for _, statment := range block.Statements { - result = Eval(statment, env) - - if result != nil { - rt := result.Type() - if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ || rt == object.CONTINUE_OBJ || rt == object.BREAK_OBJ { - return result - } - } - } - - return result -} - -func newError(format string, a ...interface{}) *object.Error { - format = fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, format) - return &object.Error{Message: fmt.Sprintf(format, a...)} -} - -func isError(obj object.Object) bool { - if obj != nil { - return obj.Type() == object.ERROR_OBJ - } - - return false -} - -func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object { - if val, ok := env.Get(node.Value); ok { - return val - } - if builtin, ok := builtins[node.Value]; ok { - return builtin - } - - return newError("Mstari %d: Neno Halifahamiki: %s", node.Token.Line, node.Value) -} - -func evalExpressions(exps []ast.Expression, env *object.Environment) []object.Object { - var result []object.Object - - for _, e := range exps { - evaluated := Eval(e, env) - if isError(evaluated) { - return []object.Object{evaluated} - } - - result = append(result, evaluated) - } - - return result -} - -func applyFunction(fn object.Object, args []object.Object, line int) object.Object { - switch fn := fn.(type) { - case *object.Function: - extendedEnv := extendedFunctionEnv(fn, args) - evaluated := Eval(fn.Body, extendedEnv) - return unwrapReturnValue(evaluated) - case *object.Builtin: - if result := fn.Fn(args...); result != nil { - return result - } - return NULL - default: - return newError("Mstari %d: Hii sio function: %s", line, fn.Type()) - } - -} - -func extendedFunctionEnv(fn *object.Function, args []object.Object) *object.Environment { - env := object.NewEnclosedEnvironment(fn.Env) - - for paramIdx, param := range fn.Parameters { - if paramIdx < len(args) { - env.Set(param.Value, args[paramIdx]) - } - } - return env -} - -func unwrapReturnValue(obj object.Object) object.Object { - if returnValue, ok := obj.(*object.ReturnValue); ok { - return returnValue.Value - } - - return obj -} - -func evalStringInfixExpression(operator string, left, right object.Object, line int) object.Object { - - leftVal := left.(*object.String).Value - rightVal := right.(*object.String).Value - - switch operator { - case "+": - return &object.String{Value: leftVal + rightVal} - case "==": - return nativeBoolToBooleanObject(leftVal == rightVal) - case "!=": - return nativeBoolToBooleanObject(leftVal != rightVal) - default: - return newError("Mstari %d: Operesheni Haielweki: %s %s %s", line, left.Type(), operator, right.Type()) - } -} - -func evalIndexExpression(left, index object.Object, line int) object.Object { - switch { - case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: - return evalArrayIndexExpression(left, index) - case left.Type() == object.ARRAY_OBJ && index.Type() != object.INTEGER_OBJ: - return newError("Mstari %d: Tafadhali tumia number, sio: %s", line, index.Type()) - case left.Type() == object.DICT_OBJ: - return evalDictIndexExpression(left, index, line) - default: - return newError("Mstari %d: Operesheni hii haiwezekani kwa: %s", line, left.Type()) - } -} - -func evalArrayIndexExpression(array, index object.Object) object.Object { - arrayObject := array.(*object.Array) - idx := index.(*object.Integer).Value - max := int64(len(arrayObject.Elements) - 1) - - if idx < 0 || idx > max { - return NULL - } - - return arrayObject.Elements[idx] -} - -func evalDictLiteral(node *ast.DictLiteral, env *object.Environment) object.Object { - pairs := make(map[object.HashKey]object.DictPair) - - for keyNode, valueNode := range node.Pairs { - key := Eval(keyNode, env) - if isError(key) { - return key - } - - hashKey, ok := key.(object.Hashable) - if !ok { - return newError("Mstari %d: Hashing imeshindikana: %s", node.Token.Line, key.Type()) - } - - value := Eval(valueNode, env) - if isError(value) { - return value - } - - hashed := hashKey.HashKey() - pairs[hashed] = object.DictPair{Key: key, Value: value} - } - - return &object.Dict{Pairs: pairs} -} - -func evalDictIndexExpression(dict, index object.Object, line int) object.Object { - dictObject := dict.(*object.Dict) - - key, ok := index.(object.Hashable) - if !ok { - return newError("Mstari %d: Samahani, %s haitumiki kama key", line, index.Type()) - } - - pair, ok := dictObject.Pairs[key.HashKey()] - if !ok { - return NULL - } - - return pair.Value -} - -func evalWhileExpression(we *ast.WhileExpression, env *object.Environment) object.Object { - condition := Eval(we.Condition, env) - if isError(condition) { - return condition - } - if isTruthy(condition) { - evaluated := Eval(we.Consequence, env) - if isError(evaluated) { - return evaluated - } - if evaluated != nil && evaluated.Type() == object.BREAK_OBJ { - return evaluated - } - evalWhileExpression(we, env) - } - return NULL -} - -func evalBreak(node *ast.Break) object.Object { - return BREAK -} - -func evalContinue(node *ast.Continue) object.Object { - return CONTINUE -} - -func evalInExpression(left, right object.Object, line int) object.Object { - switch right.(type) { - case *object.String: - return evalInStringExpression(left, right) - case *object.Array: - return evalInArrayExpression(left, right) - case *object.Dict: - return evalInDictExpression(left, right, line) - default: - return FALSE - } -} - -func evalInStringExpression(left, right object.Object) object.Object { - if left.Type() != object.STRING_OBJ { - return FALSE - } - leftVal := left.(*object.String) - rightVal := right.(*object.String) - found := strings.Contains(rightVal.Value, leftVal.Value) - return nativeBoolToBooleanObject(found) -} - -func evalInDictExpression(left, right object.Object, line int) object.Object { - leftVal, ok := left.(object.Hashable) - if !ok { - return newError("Huwezi kutumia kama 'key': %s", left.Type()) - } - key := leftVal.HashKey() - rightVal := right.(*object.Dict).Pairs - _, ok = rightVal[key] - return nativeBoolToBooleanObject(ok) -} - -func evalInArrayExpression(left, right object.Object) object.Object { - rightVal := right.(*object.Array) - switch leftVal := left.(type) { - case *object.Null: - for _, v := range rightVal.Elements { - if v.Type() == object.NULL_OBJ { - return TRUE - } - } - case *object.String: - for _, v := range rightVal.Elements { - if v.Type() == object.STRING_OBJ { - elem := v.(*object.String) - if elem.Value == leftVal.Value { - return TRUE - } - } - } - case *object.Integer: - for _, v := range rightVal.Elements { - if v.Type() == object.INTEGER_OBJ { - elem := v.(*object.Integer) - if elem.Value == leftVal.Value { - return TRUE - } - } - } - case *object.Float: - for _, v := range rightVal.Elements { - if v.Type() == object.FLOAT_OBJ { - elem := v.(*object.Float) - if elem.Value == leftVal.Value { - return TRUE - } - } - } - } - return FALSE -} - -// func evalForExpression(fe *ast.For, env *object.Environment) object.Object { -// obj, ok := env.Get(fe.Identifier) -// defer func() { // stay safe and not reassign an existing variable -// if ok { -// env.Set(fe.Identifier, obj) -// } -// }() -// val := Eval(fe.StarterValue, env) -// if isError(val) { -// return val -// } - -// env.Set(fe.StarterName.Value, val) - -// // err := Eval(fe.Starter, env) -// // if isError(err) { -// // return err -// // } -// for { -// evaluated := Eval(fe.Condition, env) -// if isError(evaluated) { -// return evaluated -// } -// if !isTruthy(evaluated) { -// break -// } -// res := Eval(fe.Block, env) -// if isError(res) { -// return res -// } -// if res.Type() == object.BREAK_OBJ { -// break -// } -// if res.Type() == object.CONTINUE_OBJ { -// err := Eval(fe.Closer, env) -// if isError(err) { -// return err -// } -// continue -// } -// if res.Type() == object.RETURN_VALUE_OBJ { -// return res -// } -// err := Eval(fe.Closer, env) -// if isError(err) { -// return err -// } -// } -// return NULL -// } - -func evalForInExpression(fie *ast.ForIn, env *object.Environment, line int) object.Object { - iterable := Eval(fie.Iterable, env) - existingKeyIdentifier, okk := env.Get(fie.Key) // again, stay safe - existingValueIdentifier, okv := env.Get(fie.Value) - defer func() { // restore them later on - if okk { - env.Set(fie.Key, existingKeyIdentifier) - } - if okv { - env.Set(fie.Value, existingValueIdentifier) - } - }() - switch i := iterable.(type) { - case object.Iterable: - defer func() { - i.Reset() - }() - return loopIterable(i.Next, env, fie) - default: - return newError("Mstari %d: Huwezi kufanya operesheni hii na %s", line, i.Type()) - } -} - -func loopIterable(next func() (object.Object, object.Object), env *object.Environment, fi *ast.ForIn) object.Object { - k, v := next() - for k != nil && v != nil { - env.Set(fi.Key, k) - env.Set(fi.Value, v) - res := Eval(fi.Block, env) - if isError(res) { - return res - } - if res != nil { - if res.Type() == object.BREAK_OBJ { - break - } - if res.Type() == object.CONTINUE_OBJ { - k, v = next() - continue - } - if res.Type() == object.RETURN_VALUE_OBJ { - return res - } - } - k, v = next() - } - return NULL -} - -func evalSwitchStatement(se *ast.SwitchExpression, env *object.Environment) object.Object { - obj := Eval(se.Value, env) - for _, opt := range se.Choices { - - if opt.Default { - continue - } - for _, val := range opt.Expr { - out := Eval(val, env) - if obj.Type() == out.Type() && obj.Inspect() == out.Inspect() { - blockOut := evalBlockStatement(opt.Block, env) - return blockOut - } - } - } - for _, opt := range se.Choices { - if opt.Default { - out := evalBlockStatement(opt.Block, env) - return out - } - } - return nil -} diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go deleted file mode 100644 index 8fe8458..0000000 --- a/evaluator/evaluator_test.go +++ /dev/null @@ -1,518 +0,0 @@ -package evaluator - -import ( - "fmt" - "testing" - - "github.com/AvicennaJr/Nuru/lexer" - "github.com/AvicennaJr/Nuru/object" - "github.com/AvicennaJr/Nuru/parser" -) - -func TestEvalIntegerExpression(t *testing.T) { - tests := []struct { - input string - expected int64 - }{ - {"5", 5}, - {"10", 10}, - {"-5", -5}, - {"-10", -10}, - {"5 + 5 + 5 + 5 - 10", 10}, - {"2 * 2 * 2 * 2", 16}, - {"2 / 2 + 1", 2}, - } - - for _, tt := range tests { - evaluated := testEval(tt.input) - testIntegerObject(t, evaluated, tt.expected) - } -} - -func TestEvalBooleanExpression(t *testing.T) { - tests := []struct { - input string - expected bool - }{ - {"kweli", true}, - {"sikweli", false}, - {"1 < 2", true}, - {"1 > 2", false}, - {"1 > 1", false}, - {"1 < 1", false}, - {"1 == 1", true}, - {"1 != 1", false}, - {"1 == 2", false}, - {"1 != 2", true}, - {"kweli == kweli", true}, - {"sikweli == sikweli", true}, - {"kweli == sikweli", false}, - {"kweli != sikweli", true}, - {"sikweli != kweli", true}, - {"(1 < 2) == kweli", true}, - } - - for _, tt := range tests { - evaluated := testEval(tt.input) - testBooleanObject(t, evaluated, tt.expected) - } -} - -func TestBangOperator(t *testing.T) { - tests := []struct { - input string - expected bool - }{ - {"!kweli", false}, - {"!sikweli", true}, - {"!5", false}, - {"!!kweli", true}, - {"!!sikweli", false}, - {"!!5", true}, - } - - for _, tt := range tests { - evaluated := testEval(tt.input) - testBooleanObject(t, evaluated, tt.expected) - } -} - -func testEval(input string) object.Object { - l := lexer.New(input) - p := parser.New(l) - program := p.ParseProgram() - env := object.NewEnvironment() - - return Eval(program, env) -} - -func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { - result, ok := obj.(*object.Integer) - - if !ok { - t.Errorf("Object is not Integer, got=%T(%+v)", obj, obj) - return false - } - - if result.Value != expected { - t.Errorf("object has wrong value. got=%d, want=%d", result.Value, expected) - return false - } - - return true -} - -func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool { - result, ok := obj.(*object.Boolean) - if !ok { - t.Errorf("object is not Boolean, got=%T(%+v)", obj, obj) - return false - } - - if result.Value != expected { - t.Errorf("object has wrong value, got=%t, want=%t", result.Value, expected) - return false - } - - return true -} - -func TestIfElseExpressions(t *testing.T) { - tests := []struct { - input string - expected interface{} - }{ - {"kama (kweli) {10}", 10}, - {"kama (sikweli) {10}", nil}, - {"kama (1) {10}", 10}, - {"kama (1 < 2) {10}", 10}, - {"kama (1 > 2) {10}", nil}, - {"kama (1 > 2) {10} sivyo {20}", 20}, - {"kama (1 < 2) {10} sivyo {20}", 10}, - } - - for _, tt := range tests { - evaluated := testEval(tt.input) - integer, ok := tt.expected.(int) - if ok { - testIntegerObject(t, evaluated, int64(integer)) - } else { - testNullObject(t, evaluated) - } - } -} - -func testNullObject(t *testing.T, obj object.Object) bool { - if obj != NULL { - t.Errorf("object is not null, got=%T(+%v)", obj, obj) - return false - } - return true -} - -func TestReturnStatements(t *testing.T) { - tests := []struct { - input string - expected int64 - }{ - {"rudisha 10", 10}, - {"rudisha 10; 9;", 10}, - {"rudisha 2 * 5; 9;", 10}, - {"9; rudisha 2 * 5; 9;", 10}, - } - - for _, tt := range tests { - evaluated := testEval(tt.input) - testIntegerObject(t, evaluated, tt.expected) - } -} - -func TestErrorHandling(t *testing.T) { - tests := []struct { - input string - expectedMessage string - }{ - { - "5 + kweli", - "Mstari 0: Aina Hazilingani: NAMBA + BOOLEAN", - }, - { - "5 + kweli; 5;", - "Mstari 0: Aina Hazilingani: NAMBA + BOOLEAN", - }, - { - "-kweli", - "Mstari 0: Operesheni Haielweki: -BOOLEAN", - }, - { - "kweli + sikweli", - "Mstari 0: Operesheni Haielweki: BOOLEAN + BOOLEAN", - }, - { - "5; kweli + sikweli; 5", - "Mstari 0: Operesheni Haielweki: BOOLEAN + BOOLEAN", - }, - { - "kama (10 > 1) { kweli + sikweli;}", - "Mstari 0: Operesheni Haielweki: BOOLEAN + BOOLEAN", - }, - { - ` -kama (10 > 1) { - kama (10 > 1) { - rudisha kweli + kweli; - } - - rudisha 1; -} - `, - "Mstari 3: Operesheni Haielweki: BOOLEAN + BOOLEAN", - }, - { - "bangi", - "Mstari 0: Neno Halifahamiki: bangi", - }, - { - `"Habari" - "Habari"`, - "Mstari 0: Operesheni Haielweki: NENO - NENO", - }, - { - `{"jina": "Avi"}[unda(x) {x}];`, - "Mstari 0: Samahani, UNDO (FUNCTION) haitumiki kama key", - }, - } - - for _, tt := range tests { - evaluated := testEval(tt.input) - - errObj, ok := evaluated.(*object.Error) - if !ok { - t.Errorf("no error object return, got=%T(%+v)", evaluated, evaluated) - continue - } - - if errObj.Message != fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, tt.expectedMessage) { - t.Errorf("wrong error message, expected=%q, got=%q", fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, tt.expectedMessage), errObj.Message) - } - } -} - -func TestLetStatement(t *testing.T) { - tests := []struct { - input string - expected int64 - }{ - {"fanya a = 5; a;", 5}, - {"fanya a = 5 * 5; a;", 25}, - {"fanya a = 5; fanya b = a; b;", 5}, - {"fanya a = 5; fanya b = a; fanya c = a + b + 5; c;", 15}, - } - - for _, tt := range tests { - testIntegerObject(t, testEval(tt.input), tt.expected) - } -} - -func TestFunctionObject(t *testing.T) { - input := "unda(x) { x + 2 ;};" - - evaluated := testEval(input) - unda, ok := evaluated.(*object.Function) - if !ok { - t.Fatalf("object is not a Function, got=%T(%+v)", evaluated, evaluated) - } - - if len(unda.Parameters) != 1 { - t.Fatalf("function haas wrong paramters,Parameters=%+v", unda.Parameters) - } - - if unda.Parameters[0].String() != "x" { - t.Fatalf("parameter is not x, got=%q", unda.Parameters[0]) - } - - expectedBody := "(x + 2)" - - if unda.Body.String() != expectedBody { - t.Fatalf("body is not %q, got=%q", expectedBody, unda.Body.String()) - } -} - -func TestFunctionApplication(t *testing.T) { - tests := []struct { - input string - expected int64 - }{ - {"fanya mfano = unda(x) {x;}; mfano(5);", 5}, - {"fanya mfano = unda(x) {rudisha x;}; mfano(5);", 5}, - {"fanya double = unda(x) { x * 2;}; double(5);", 10}, - {"fanya add = unda(x, y) {x + y;}; add(5,5);", 10}, - {"fanya add = unda(x, y) {x + y;}; add(5 + 5, add(5, 5));", 20}, - {"unda(x) {x;}(5)", 5}, - } - - for _, tt := range tests { - testIntegerObject(t, testEval(tt.input), tt.expected) - } -} - -func TestClosures(t *testing.T) { - input := ` -fanya newAdder = unda(x) { - unda(y) { x + y}; -}; - -fanya addTwo = newAdder(2); -addTwo(2); -` - testIntegerObject(t, testEval(input), 4) -} - -func TestStringLiteral(t *testing.T) { - input := `"Habari yako!"` - - evaluated := testEval(input) - str, ok := evaluated.(*object.String) - if !ok { - t.Fatalf("Object is not string, got=%T(%+v)", evaluated, evaluated) - } - - if str.Value != "Habari yako!" { - t.Errorf("String has wrong value, got=%q", str.Value) - } -} - -func TestStringconcatenation(t *testing.T) { - input := `"Mambo" + " " + "Vipi" + "?"` - - evaluated := testEval(input) - - str, ok := evaluated.(*object.String) - if !ok { - t.Fatalf("object is not a string, got=%T(%+v)", evaluated, evaluated) - } - - if str.Value != "Mambo Vipi?" { - t.Errorf("String has wrong value, got=%q", str.Value) - } -} - -func TestBuiltinFunctions(t *testing.T) { - tests := []struct { - input string - expected interface{} - }{ - {`idadi("")`, 0}, - {`idadi("four")`, 4}, - {`idadi("hello world")`, 11}, - {`idadi(1)`, "Samahani, hii function haitumiki na NAMBA"}, - {`idadi("one", "two")`, "Hoja hazilingani, tunahitaji=1, tumepewa=2"}, - } - - for _, tt := range tests { - evaluated := testEval(tt.input) - - switch expected := tt.expected.(type) { - case int: - testIntegerObject(t, evaluated, int64(expected)) - case string: - errObj, ok := evaluated.(*object.Error) - if !ok { - t.Errorf("Object is not Error, got=%T(%+v)", evaluated, evaluated) - continue - } - if errObj.Message != fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, expected) { - t.Errorf("Wrong eror message, expected=%q, got=%q", expected, errObj.Message) - } - } - } -} - -func TestArrayLiterals(t *testing.T) { - input := "[1, 2 * 2, 3 + 3]" - - evaluated := testEval(input) - result, ok := evaluated.(*object.Array) - if !ok { - t.Fatalf("Object is not an Array, got=%T(%+v)", evaluated, evaluated) - } - - if len(result.Elements) != 3 { - t.Fatalf("Array has wrong number of elements, got=%d", len(result.Elements)) - } - - testIntegerObject(t, result.Elements[0], 1) - testIntegerObject(t, result.Elements[1], 4) - testIntegerObject(t, result.Elements[2], 6) -} - -func TestArrayIndexExpressions(t *testing.T) { - tests := []struct { - input string - expected interface{} - }{ - { - "[1, 2, 3][0]", - 1, - }, - { - "[1, 2, 3][1]", - 2, - }, - { - "[1, 2, 3][2]", - 3, - }, - { - "fanya i = 0; [1][i];", - 1, - }, - { - "fanya myArr = [1, 2, 3]; myArr[2];", - 3, - }, - { - "[1, 2, 3][3]", - nil, - }, - { - "[1, 2, 3][-1]", - nil, - }, - } - - for _, tt := range tests { - evaluated := testEval(tt.input) - integer, ok := tt.expected.(int) - if ok { - testIntegerObject(t, evaluated, int64(integer)) - } else { - testNullObject(t, evaluated) - } - } -} - -func TestDictLiterals(t *testing.T) { - input := `fanya two = "two"; -{ - "one": 10 - 9, - two: 1 +1, - "thr" + "ee": 6 / 2, - 4: 4, - kweli: 5, - sikweli: 6 -}` - - evaluated := testEval(input) - result, ok := evaluated.(*object.Dict) - if !ok { - t.Fatalf("Eval didn't return a dict, got=%T(%+v)", evaluated, evaluated) - } - - expected := map[object.HashKey]int64{ - (&object.String{Value: "one"}).HashKey(): 1, - (&object.String{Value: "two"}).HashKey(): 2, - (&object.String{Value: "three"}).HashKey(): 3, - (&object.Integer{Value: 4}).HashKey(): 4, - TRUE.HashKey(): 5, - FALSE.HashKey(): 6, - } - - if len(result.Pairs) != len(expected) { - t.Fatalf("Dict has wrong number of pairs, got=%d", len(result.Pairs)) - } - - for expectedKey, expectedValue := range expected { - pair, ok := result.Pairs[expectedKey] - if !ok { - t.Errorf("No pair for give key") - } - - testIntegerObject(t, pair.Value, expectedValue) - } -} - -func TestDictIndexExpression(t *testing.T) { - tests := []struct { - input string - expected interface{} - }{ - { - `{"foo": 5}["foo"]`, - 5, - }, - { - `{"foo": 5}["bar"]`, - nil, - }, - { - `fanya key = "foo"; {"foo": 5}[key]`, - 5, - }, - { - `{}["foo"]`, - nil, - }, - { - `{5: 5}[5]`, - 5, - }, - { - `{kweli: 5}[kweli]`, - 5, - }, - { - `{sikweli: 5}[sikweli]`, - 5, - }, - } - - for _, tt := range tests { - evaluated := testEval(tt.input) - integer, ok := tt.expected.(int) - if ok { - testIntegerObject(t, evaluated, int64(integer)) - } else { - testNullObject(t, evaluated) - } - } -} diff --git a/examples/example.nr b/examples/example.nr deleted file mode 100644 index 7ebacef..0000000 --- a/examples/example.nr +++ /dev/null @@ -1,107 +0,0 @@ -andika("Testing basic types..."); -andika(2 + 2); -andika(4 * 4); -fanya a = 10; -fanya b = 20; - -andika(a + b); - -andika(kweli == sikweli); -andika("Testing empty lines...") -andika(); -andika(); -andika(); -andika("Mambo vipi"); - -andika("Testing Functions... "); - -fanya jumlisha = unda(x, y) {x + y}; - -andika(jumlisha(20,30)); -andika(jumlisha(100,1000)); - -fanya zidisha = unda(x, y) {x * y}; - -andika(zidisha(100,1000)); -andika(zidisha(200, 20)); - -// lists can hold any value -andika("Testing lists..."); -fanya list = [1, "a", kweli, sikweli]; - -// a few builtins - -fanya list = sukuma(list, jumlisha(4,5)); - -andika(list); -andika(list[2]); -andika(list[-100]); // prints null -andika(idadi(list)); -andika(yamwisho(list)); -andika([1,2,3] + [4,5,6]); -list[0] = 1000 -andika(list[0]) - -// if statements -andika("Testing if statements..."); -kama (1 > 2) { - andika("Moja ni zaidi ya mbili"); -} sivyo { - andika("Moja si zaidi ya mbili"); -} - -kama (idadi("Habari") == 6) { - andika("Habari yako ndugu"); -} - -// fibonacci example -andika("Testing fibonacci..."); - -fanya fibo = unda(x) { - kama (x == 0) { - rudisha 0; - } au kama (x == 1) { - rudisha 1; - } sivyo { - rudisha fibo(x - 1) + fibo(x - 2); - } -} - - -andika(fibo(10)); - -// testing input -andika("Testing input from user..."); -fanya salamu = unda() { - fanya jina = jaza("Unaitwa nani rafiki? "); - rudisha "Mambo vipi " + jina; - } - -andika(salamu()); - -/* -Multiline comment -*/ - -// test dictionaries - -andika("Testing dictionaries...") - -fanya watu = [{"jina": "Mojo", "kabila": "Mnyakusa"}, {"jina": "Avi", "kabila": "Mwarabu wa dubai"}] - -andika(watu, watu[0], watu[0]["jina"], watu[0]["kabila"]) - -watu[0]["jina"] = "MJ"; -andika(watu[0]["jina"]); - -andika({"a":1} + {"b": 2}) -// testing while loop - -andika("Testing while loop..."); - -fanya i = 10; - -wakati (i > 0) { - andika(i); - i = i - 1; -} diff --git a/examples/sorting_algorithm.nr b/examples/sorting_algorithm.nr deleted file mode 100644 index 10d6131..0000000 --- a/examples/sorting_algorithm.nr +++ /dev/null @@ -1,63 +0,0 @@ -/* -############ Sorting Algorithm ############## - - By @VictorKariuki - - https://github.com/VictorKariuki - -############################################# -*/ - -fanya slice = unda(arr,start, end) { - fanya result = [] - wakati (start < end) { - result = result + [arr[start]] - start = start + 1 - } - rudisha result -} - -fanya merge = unda(left, right) { - fanya result = [] - fanya lLen = idadi(left) - fanya rLen = idadi(right) - fanya l = 0 - fanya r = 0 - wakati (l < lLen && r < rLen) { - kama (left[l] < right[r]) { - result = result + [left[l]] - l = l + 1 - } sivyo { - result = result + [right[r]] - r = r + 1 - } - } - andika(result) -} - - -fanya mergeSort = unda(arr){ - fanya len = idadi(arr) - andika("arr is ", arr," of length ", len) - kama (len < 2) { - rudisha arr - } - andika("len is greater than or == to 2", len > 1) - - fanya mid = (len / 2) - andika("arr has a mid point of ", mid) - - fanya left = slice(arr, 0, mid) - fanya right = slice(arr, mid, len) - andika("left slice is ", left) - andika("right slice is ", right) - fanya sortedLeft = mergeSort(left) - fanya sortedRight = mergeSort(right) - andika("sortedLeft is ", sortedLeft) - andika("sortedRight is ", sortedRight) - rudisha merge(sortedLeft, sortedRight) -} - -fanya arr = [6, 5, 3, 1, 8, 7, 2, 4] -fanya sortedArray = mergeSort(arr) -andika(sortedArray) \ No newline at end of file diff --git a/examples/sudoku_solver.nr b/examples/sudoku_solver.nr deleted file mode 100644 index de5d3a6..0000000 --- a/examples/sudoku_solver.nr +++ /dev/null @@ -1,101 +0,0 @@ -/*########### Backtracking Algorithm ############## - - By @VictorKariuki - - https://github.com/VictorKariuki - -NURU program to solve Sudoku using Backtracking Algorithm - -The sudoku puzzle is represented as a 2D array. The empty -cells are represented by 0. The algorithm works by trying -out all possible numbers for an empty cell. If the number -is valid, it is placed in the cell. If the number is invalid, -the algorithm backtracks to the previous cell and tries -another number. The algorithm terminates when all cells -are filled. The algorithm is implemented in the solveSudoku -function. The isValid function checks kama a number is -valid in a given cell. The printSudoku function prints -the sudoku puzzle. The solveSudoku function solves the -sudoku puzzle. The main function initializes the sudoku -puzzle and calls the solveSudoku function. - -#################################################*/ - - -fanya printing = unda(sudoku) { - fanya row = 0 - wakati (row < 9){ - andika(sudoku[row]) - row++ - } -} - -fanya sudoku = [[3, 0, 6, 5, 0, 8, 4, 0, 0],[5, 2, 0, 0, 0, 0, 0, 0, 0],[0, 8, 7, 0, 0, 0, 0, 3, 1],[0, 0, 3, 0, 1, 0, 0, 8, 0],[9, 0, 0, 8, 6, 3, 0, 0, 5],[0, 5, 0, 0, 9, 0, 6, 0, 0],[1, 3, 0, 0, 0, 0, 2, 5, 0],[0, 0, 0, 0, 0, 0, 0, 7, 4],[0, 0, 5, 2, 0, 6, 3, 0, 0]] - - - -fanya isSafe = unda(grid, row, col, num) { - kwa x ktk [0,1,2,3,4,5,6,7,8] { - kama (grid[row][x] == num) { - rudisha sikweli - } - } - - kwa x ktk [0,1,2,3,4,5,6,7,8] { - kama (grid[x][col] == num) { - rudisha sikweli - } - } - - fanya startRow = row - row % 3 - fanya startCol = col - col % 3 - - kwa i ktk [0, 1, 2] { - kwa j ktk [0, 1, 2] { - kama (grid[i + startRow][j + startCol] == num) { - rudisha sikweli - } - } - } - - rudisha kweli -} - -fanya solveSudoku = unda(grid, row, col) { - kama (row == 8 && col == 9) { - rudisha kweli - } - - kama (col == 9) { - row += 1 - col = 0 - } - - kama (grid[row][col] > 0) { - rudisha solveSudoku(grid, row, col + 1) - } - - kwa num ktk [1,2,3,4,5,6,7,8,9] { - kama (isSafe(grid, row, col, num)) { - grid[row][col] = num - kama (solveSudoku(grid, row, col + 1)) { - rudisha kweli - } - } - - grid[row][col] = 0 - } - - rudisha sikweli -} -andika() -andika("----- PUZZLE TO SOLVE -----") -printing(sudoku) -kama (solveSudoku(sudoku, 0, 0)){ - andika() - andika("--------- SOLUTION --------") - printing(sudoku) - andika() -} sivyo { - andika("imeshindikana") -} \ No newline at end of file diff --git a/go.mod b/go.mod deleted file mode 100644 index 352462e..0000000 --- a/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/AvicennaJr/Nuru - -go 1.18 diff --git a/lexer/lexer.go b/lexer/lexer.go deleted file mode 100644 index 5afcecb..0000000 --- a/lexer/lexer.go +++ /dev/null @@ -1,327 +0,0 @@ -package lexer - -import ( - "github.com/AvicennaJr/Nuru/token" -) - -type Lexer struct { - input string - position int - readPosition int - ch byte - line int -} - -func New(input string) *Lexer { - l := &Lexer{input: input} - l.readChar() - return l -} - -func (l *Lexer) readChar() { - if l.readPosition >= len(l.input) { - l.ch = 0 - } else { - l.ch = l.input[l.readPosition] - } - - l.position = l.readPosition - l.readPosition += 1 -} - -func (l *Lexer) NextToken() token.Token { - var tok token.Token - l.skipWhitespace() - if l.ch == '/' && l.peekChar() == '/' { - l.skipSingleLineComment() - return l.NextToken() - } - if l.ch == '/' && l.peekChar() == '*' { - l.skipMultiLineComment() - return l.NextToken() - } - - switch l.ch { - case '=': - if l.peekChar() == '=' { - ch := l.ch - l.readChar() - tok = token.Token{Type: token.EQ, Literal: string(ch) + string(l.ch), Line: l.line} - } else { - tok = newToken(token.ASSIGN, l.line, l.ch) - } - case ';': - tok = newToken(token.SEMICOLON, l.line, l.ch) - case '(': - tok = newToken(token.LPAREN, l.line, l.ch) - case ')': - tok = newToken(token.RPAREN, l.line, l.ch) - case '{': - tok = newToken(token.LBRACE, l.line, l.ch) - case '}': - tok = newToken(token.RBRACE, l.line, l.ch) - case ',': - tok = newToken(token.COMMA, l.line, l.ch) - case '+': - if l.peekChar() == '=' { - ch := l.ch - l.readChar() - tok = token.Token{Type: token.PLUS_ASSIGN, Line: l.line, Literal: string(ch) + string(l.ch)} - } else if l.peekChar() == '+' { - ch := l.ch - l.readChar() - tok = token.Token{Type: token.PLUS_PLUS, Literal: string(ch) + string(l.ch), Line: l.line} - } else { - tok = newToken(token.PLUS, l.line, l.ch) - } - case '-': - if l.peekChar() == '=' { - ch := l.ch - l.readChar() - tok = token.Token{Type: token.MINUS_ASSIGN, Line: l.line, Literal: string(ch) + string(l.ch)} - } else if l.peekChar() == '-' { - ch := l.ch - l.readChar() - tok = token.Token{Type: token.MINUS_MINUS, Literal: string(ch) + string(l.ch), Line: l.line} - } else { - tok = newToken(token.MINUS, l.line, l.ch) - } - case '!': - if l.peekChar() == '=' { - ch := l.ch - l.readChar() - tok = token.Token{Type: token.NOT_EQ, Literal: string(ch) + string(l.ch), Line: l.line} - } else { - tok = newToken(token.BANG, l.line, l.ch) - } - case '/': - if l.peekChar() == '=' { - ch := l.ch - l.readChar() - tok = token.Token{Type: token.SLASH_ASSIGN, Line: l.line, Literal: string(ch) + string(l.ch)} - } else { - tok = newToken(token.SLASH, l.line, l.ch) - } - case '*': - if l.peekChar() == '=' { - ch := l.ch - l.readChar() - tok = token.Token{Type: token.ASTERISK_ASSIGN, Line: l.line, Literal: string(ch) + string(l.ch)} - } else if l.peekChar() == '*' { - ch := l.ch - l.readChar() - tok = token.Token{Type: token.POW, Literal: string(ch) + string(l.ch), Line: l.line} - } else { - tok = newToken(token.ASTERISK, l.line, l.ch) - } - case '<': - if l.peekChar() == '=' { - ch := l.ch - l.readChar() - tok = token.Token{Type: token.LTE, Literal: string(ch) + string(l.ch), Line: l.line} - } else { - tok = newToken(token.LT, l.line, l.ch) - } - case '>': - if l.peekChar() == '=' { - ch := l.ch - l.readChar() - tok = token.Token{Type: token.GTE, Literal: string(ch) + string(l.ch), Line: l.line} - } else { - tok = newToken(token.GT, l.line, l.ch) - } - case '"': - tok.Type = token.STRING - tok.Literal = l.readString() - tok.Line = l.line - case '\'': - tok = token.Token{Type: token.STRING, Literal: l.readSingleQuoteString(), Line: l.line} - case '[': - tok = newToken(token.LBRACKET, l.line, l.ch) - case ']': - tok = newToken(token.RBRACKET, l.line, l.ch) - case ':': - tok = newToken(token.COLON, l.line, l.ch) - case '&': - if l.peekChar() == '&' { - ch := l.ch - l.readChar() - tok = token.Token{Type: token.AND, Literal: string(ch) + string(l.ch), Line: l.line} - } - case '|': - if l.peekChar() == '|' { - ch := l.ch - l.readChar() - tok = token.Token{Type: token.OR, Literal: string(ch) + string(l.ch), Line: l.line} - } - case '%': - if l.peekChar() == '=' { - ch := l.ch - l.readChar() - tok = token.Token{Type: token.MODULUS_ASSIGN, Line: l.line, Literal: string(ch) + string(l.ch)} - } else { - tok = newToken(token.MODULUS, l.line, l.ch) - } - case 0: - tok.Literal = "" - tok.Type = token.EOF - tok.Line = l.line - default: - if isLetter(l.ch) { - tok.Literal = l.readIdentifier() - tok.Type = token.LookupIdent(tok.Literal) - tok.Line = l.line - return tok - } else if isDigit(l.ch) { - tok = l.readDecimal() - return tok - } else { - tok = newToken(token.ILLEGAL, l.line, l.ch) - } - } - - l.readChar() - return tok -} - -func newToken(tokenType token.TokenType, line int, ch byte) token.Token { - return token.Token{Type: tokenType, Literal: string(ch), Line: line} -} - -func (l *Lexer) readIdentifier() string { - position := l.position - - for isLetter(l.ch) || isDigit(l.ch) { - l.readChar() - } - return l.input[position:l.position] -} - -func isLetter(ch byte) bool { - return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' -} - -func (l *Lexer) skipWhitespace() { - for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' { - if l.ch == '\n' { - l.line++ - } - l.readChar() - } -} - -func isDigit(ch byte) bool { - return '0' <= ch && ch <= '9' -} - -func (l *Lexer) readNumber() string { - position := l.position - for isDigit(l.ch) { - l.readChar() - } - return l.input[position:l.position] -} - -func (l *Lexer) readDecimal() token.Token { - integer := l.readNumber() - if l.ch == '.' && isDigit(l.peekChar()) { - l.readChar() - fraction := l.readNumber() - return token.Token{Type: token.FLOAT, Literal: integer + "." + fraction, Line: l.line} - } - return token.Token{Type: token.INT, Literal: integer, Line: l.line} -} - -func (l *Lexer) peekChar() byte { - if l.readPosition >= len(l.input) { - return 0 - } else { - return l.input[l.readPosition] - } -} - -func (l *Lexer) skipSingleLineComment() { - for l.ch != '\n' && l.ch != 0 { - l.readChar() - } - l.skipWhitespace() -} - -func (l *Lexer) skipMultiLineComment() { - endFound := false - - for !endFound { - if l.ch == 0 { - endFound = true - } - - if l.ch == '*' && l.peekChar() == '/' { - endFound = true - l.readChar() - } - - l.readChar() - l.skipWhitespace() - } - -} - -func (l *Lexer) readString() string { - var str string - for { - l.readChar() - if l.ch == '"' || l.ch == 0 { - break - } else if l.ch == '\\' { - switch l.peekChar() { - case 'n': - l.readChar() - l.ch = '\n' - case 'r': - l.readChar() - l.ch = '\r' - case 't': - l.readChar() - l.ch = '\t' - case '"': - l.readChar() - l.ch = '"' - case '\\': - l.readChar() - l.ch = '\\' - } - } - str += string(l.ch) - } - return str -} - -func (l *Lexer) readSingleQuoteString() string { - var str string - for { - l.readChar() - if l.ch == '\'' || l.ch == 0 { - break - } else if l.ch == '\\' { - switch l.peekChar() { - case 'n': - l.readChar() - l.ch = '\n' - case 'r': - l.readChar() - l.ch = '\r' - case 't': - l.readChar() - l.ch = '\t' - case '"': - l.readChar() - l.ch = '"' - case '\\': - l.readChar() - l.ch = '\\' - } - } - str += string(l.ch) - } - return str -} diff --git a/lexer/lexer_test.go b/lexer/lexer_test.go deleted file mode 100644 index 74b3b42..0000000 --- a/lexer/lexer_test.go +++ /dev/null @@ -1,153 +0,0 @@ -package lexer - -import ( - "testing" - - "github.com/AvicennaJr/Nuru/token" -) - -func TestNextToken(t *testing.T) { - input := ` - // Testing kama lex luther iko sawa - fanya tano = 5; - fanya kumi = 10; - - fanya jumla = unda(x, y){ - x + y; - }; - - fanya jibu = jumla(tano, kumi); - - !-/5; - 5 < 10 > 5; - - kama (5 < 10) { - rudisha kweli; - } sivyo { - rudisha sikweli; - } - - 10 == 10; - 10 != 9; // Hii ni comment - // Comment nyingine - - /* - multiline comment - */ - - /* multiline comment number twooooooooooo */ - 5 - "bangi" - "ba ngi" - [1, 2]; - {"mambo": "vipi"}` - - tests := []struct { - expectedType token.TokenType - expectedLiteral string - }{ - {token.LET, "fanya"}, - {token.IDENT, "tano"}, - {token.ASSIGN, "="}, - {token.INT, "5"}, - {token.SEMICOLON, ";"}, - {token.LET, "fanya"}, - {token.IDENT, "kumi"}, - {token.ASSIGN, "="}, - {token.INT, "10"}, - {token.SEMICOLON, ";"}, - {token.LET, "fanya"}, - {token.IDENT, "jumla"}, - {token.ASSIGN, "="}, - {token.FUNCTION, "unda"}, - {token.LPAREN, "("}, - {token.IDENT, "x"}, - {token.COMMA, ","}, - {token.IDENT, "y"}, - {token.RPAREN, ")"}, - {token.LBRACE, "{"}, - {token.IDENT, "x"}, - {token.PLUS, "+"}, - {token.IDENT, "y"}, - {token.SEMICOLON, ";"}, - {token.RBRACE, "}"}, - {token.SEMICOLON, ";"}, - {token.LET, "fanya"}, - {token.IDENT, "jibu"}, - {token.ASSIGN, "="}, - {token.IDENT, "jumla"}, - {token.LPAREN, "("}, - {token.IDENT, "tano"}, - {token.COMMA, ","}, - {token.IDENT, "kumi"}, - {token.RPAREN, ")"}, - {token.SEMICOLON, ";"}, - {token.BANG, "!"}, - {token.MINUS, "-"}, - {token.SLASH, "/"}, - {token.INT, "5"}, - {token.SEMICOLON, ";"}, - {token.INT, "5"}, - {token.LT, "<"}, - {token.INT, "10"}, - {token.GT, ">"}, - {token.INT, "5"}, - {token.SEMICOLON, ";"}, - {token.IF, "kama"}, - {token.LPAREN, "("}, - {token.INT, "5"}, - {token.LT, "<"}, - {token.INT, "10"}, - {token.RPAREN, ")"}, - {token.LBRACE, "{"}, - {token.RETURN, "rudisha"}, - {token.TRUE, "kweli"}, - {token.SEMICOLON, ";"}, - {token.RBRACE, "}"}, - {token.ELSE, "sivyo"}, - {token.LBRACE, "{"}, - {token.RETURN, "rudisha"}, - {token.FALSE, "sikweli"}, - {token.SEMICOLON, ";"}, - {token.RBRACE, "}"}, - {token.INT, "10"}, - {token.EQ, "=="}, - {token.INT, "10"}, - {token.SEMICOLON, ";"}, - {token.INT, "10"}, - {token.NOT_EQ, "!="}, - {token.INT, "9"}, - {token.SEMICOLON, ";"}, - {token.INT, "5"}, - {token.STRING, "bangi"}, - {token.STRING, "ba ngi"}, - {token.LBRACKET, "["}, - {token.INT, "1"}, - {token.COMMA, ","}, - {token.INT, "2"}, - {token.RBRACKET, "]"}, - {token.SEMICOLON, ";"}, - {token.LBRACE, "{"}, - {token.STRING, "mambo"}, - {token.COLON, ":"}, - {token.STRING, "vipi"}, - {token.RBRACE, "}"}, - {token.EOF, ""}, - } - - l := New(input) - - for i, tt := range tests { - tok := l.NextToken() - - if tok.Type != tt.expectedType { - t.Fatalf("tests[%d] - tokentype wrong. expected=%q, got=%q", - i, tt.expectedType, tok.Type) - } - - if tok.Literal != tt.expectedLiteral { - t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q", - i, tt.expectedLiteral, tok.Literal) - } - } -} diff --git a/main.go b/main.go deleted file mode 100644 index 4a6941a..0000000 --- a/main.go +++ /dev/null @@ -1,67 +0,0 @@ -package main - -import ( - "fmt" - "io/ioutil" - "os" - "strings" - - "github.com/AvicennaJr/Nuru/repl" -) - -const ( - LOGO = ` - -█░░ █░█ █▀▀ █░█ ▄▀█   █▄█ ▄▀█   █▄░█ █░█ █▀█ █░█ -█▄▄ █▄█ █▄█ █▀█ █▀█   ░█░ █▀█   █░▀█ █▄█ █▀▄ █▄█ - - | Authored by Avicenna | v0.2.0 | -` -) - -func main() { - - args := os.Args - coloredLogo := fmt.Sprintf("\x1b[%dm%s\x1b[0m", 36, LOGO) - - if len(args) < 2 { - - fmt.Println(coloredLogo) - fmt.Println("𝑯𝒂𝒃𝒂𝒓𝒊, 𝒌𝒂𝒓𝒊𝒃𝒖 𝒖𝒕𝒖𝒎𝒊𝒆 𝒍𝒖𝒈𝒉𝒂 𝒚𝒂 𝑵𝒖𝒓𝒖 ✨") - fmt.Println("\nTumia exit() au toka() kuondoka") - - repl.Start(os.Stdin, os.Stdout) - } - - if len(args) == 2 { - - switch args[1] { - case "msaada", "-msaada", "--msaada", "help", "-help", "--help", "-h": - fmt.Printf("\x1b[%dm%s\x1b[0m\n", 32, "\nTumia 'nuru' kuanza program\n\nAU\n\nTumia 'nuru' ikifuatiwa na jina la file.\n\n\tMfano:\tnuru fileYangu.nr") - os.Exit(0) - case "version", "-version", "--version", "-v", "v": - fmt.Println(coloredLogo) - os.Exit(0) - } - - file := args[1] - - if strings.HasSuffix(file, "nr") || strings.HasSuffix(file, ".sw") { - contents, err := ioutil.ReadFile(file) - if err != nil { - fmt.Printf("\x1b[%dm%s%s\x1b[0m\n", 31, "Error: Nimeshindwa kusoma file: ", args[0]) - os.Exit(0) - } - - repl.Read(string(contents)) - } else { - fmt.Printf("\x1b[%dm%s%s\x1b[0m", 31, file, " sii file sahihi. Tumia file la '.nr' au '.sw'\n") - os.Exit(0) - } - - } else { - fmt.Printf("\x1b[%dm%s\x1b[0m\n", 31, "Error: Operesheni imeshindikana boss.") - fmt.Printf("\x1b[%dm%s\x1b[0m\n", 32, "\nTumia 'nuru' kuprogram\n\nAU\n\nTumia 'nuru' ikifuatiwa na jina la file.\n\n\tMfano:\tnuru fileYangu.nr") - os.Exit(0) - } -} diff --git a/object/environment.go b/object/environment.go deleted file mode 100644 index 6f876e8..0000000 --- a/object/environment.go +++ /dev/null @@ -1,31 +0,0 @@ -package object - -func NewEnclosedEnvironment(outer *Environment) *Environment { - env := NewEnvironment() - env.outer = outer - return env -} - -func NewEnvironment() *Environment { - s := make(map[string]Object) - return &Environment{store: s, outer: nil} -} - -type Environment struct { - store map[string]Object - outer *Environment -} - -func (e *Environment) Get(name string) (Object, bool) { - obj, ok := e.store[name] - - if !ok && e.outer != nil { - obj, ok = e.outer.Get(name) - } - return obj, ok -} - -func (e *Environment) Set(name string, val Object) Object { - e.store[name] = val - return val -} diff --git a/object/object.go b/object/object.go deleted file mode 100644 index a6a2ddf..0000000 --- a/object/object.go +++ /dev/null @@ -1,277 +0,0 @@ -package object - -import ( - "bytes" - "fmt" - "hash/fnv" - "sort" - "strconv" - "strings" - - "github.com/AvicennaJr/Nuru/ast" -) - -type ObjectType string - -const ( - INTEGER_OBJ = "NAMBA" - FLOAT_OBJ = "DESIMALI" - BOOLEAN_OBJ = "BOOLEAN" - NULL_OBJ = "TUPU" - RETURN_VALUE_OBJ = "RUDISHA" - ERROR_OBJ = "KOSA" - FUNCTION_OBJ = "UNDO (FUNCTION)" - STRING_OBJ = "NENO" - BUILTIN_OBJ = "YA_NDANI" - ARRAY_OBJ = "ORODHA" - DICT_OBJ = "KAMUSI" - CONTINUE_OBJ = "ENDELEA" - BREAK_OBJ = "VUNJA" -) - -type Object interface { - Type() ObjectType - Inspect() string -} - -type Integer struct { - Value int64 -} - -func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) } -func (i *Integer) Type() ObjectType { return INTEGER_OBJ } - -type Float struct { - Value float64 -} - -func (f *Float) Inspect() string { return strconv.FormatFloat(f.Value, 'f', -1, 64) } -func (f *Float) Type() ObjectType { return FLOAT_OBJ } - -type Boolean struct { - Value bool -} - -func (b *Boolean) Inspect() string { - if b.Value { - return "kweli" - } else { - return "sikweli" - } -} -func (b *Boolean) Type() ObjectType { return BOOLEAN_OBJ } - -type Null struct{} - -func (n *Null) Inspect() string { return "null" } -func (n *Null) Type() ObjectType { return NULL_OBJ } - -type ReturnValue struct { - Value Object -} - -func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() } -func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ } - -type Error struct { - Message string -} - -func (e *Error) Inspect() string { - msg := fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, "ERROR: ") - return msg + e.Message -} -func (e *Error) Type() ObjectType { return ERROR_OBJ } - -type Function struct { - Parameters []*ast.Identifier - Body *ast.BlockStatement - Env *Environment -} - -func (f *Function) Type() ObjectType { return FUNCTION_OBJ } -func (f *Function) Inspect() string { - var out bytes.Buffer - - params := []string{} - for _, p := range f.Parameters { - params = append(params, p.String()) - } - - out.WriteString("unda") - out.WriteString("(") - out.WriteString(strings.Join(params, ", ")) - out.WriteString(") {\n") - out.WriteString(f.Body.String()) - out.WriteString("\n}") - - return out.String() -} - -type String struct { - Value string - offset int -} - -func (s *String) Inspect() string { return s.Value } -func (s *String) Type() ObjectType { return STRING_OBJ } -func (s *String) Next() (Object, Object) { - offset := s.offset - if len(s.Value) > offset { - s.offset = offset + 1 - return &Integer{Value: int64(offset)}, &String{Value: string(s.Value[offset])} - } - return nil, nil -} -func (s *String) Reset() { - s.offset = 0 -} - -type BuiltinFunction func(args ...Object) Object - -type Builtin struct { - Fn BuiltinFunction -} - -func (b *Builtin) Inspect() string { return "builtin function" } -func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ } - -type Array struct { - Elements []Object - offset int -} - -func (ao *Array) Type() ObjectType { return ARRAY_OBJ } -func (ao *Array) Inspect() string { - var out bytes.Buffer - - elements := []string{} - for _, e := range ao.Elements { - elements = append(elements, e.Inspect()) - } - - out.WriteString("[") - out.WriteString(strings.Join(elements, ", ")) - out.WriteString("]") - - return out.String() -} - -func (ao *Array) Next() (Object, Object) { - idx := ao.offset - if len(ao.Elements) > idx { - ao.offset = idx + 1 - return &Integer{Value: int64(idx)}, ao.Elements[idx] - } - return nil, nil -} - -func (ao *Array) Reset() { - ao.offset = 0 -} - -type HashKey struct { - Type ObjectType - Value uint64 -} - -func (b *Boolean) HashKey() HashKey { - var value uint64 - - if b.Value { - value = 1 - } else { - value = 0 - } - - return HashKey{Type: b.Type(), Value: value} -} - -func (i *Integer) HashKey() HashKey { - return HashKey{Type: i.Type(), Value: uint64(i.Value)} -} - -func (f *Float) HashKey() HashKey { - h := fnv.New64a() - h.Write([]byte(f.Inspect())) - return HashKey{Type: f.Type(), Value: h.Sum64()} -} - -func (s *String) HashKey() HashKey { - h := fnv.New64a() - h.Write([]byte(s.Value)) - - return HashKey{Type: s.Type(), Value: h.Sum64()} -} - -type DictPair struct { - Key Object - Value Object -} - -type Dict struct { - Pairs map[HashKey]DictPair - offset int -} - -func (d *Dict) Type() ObjectType { return DICT_OBJ } -func (d *Dict) Inspect() string { - var out bytes.Buffer - - pairs := []string{} - - for _, pair := range d.Pairs { - pairs = append(pairs, fmt.Sprintf("%s: %s", pair.Key.Inspect(), pair.Value.Inspect())) - } - - out.WriteString("{") - out.WriteString(strings.Join(pairs, ", ")) - out.WriteString("}") - - return out.String() -} - -func (d *Dict) Next() (Object, Object) { - idx := 0 - dict := make(map[string]DictPair) - var keys []string - for _, v := range d.Pairs { - dict[v.Key.Inspect()] = v - keys = append(keys, v.Key.Inspect()) - } - - sort.Strings(keys) - - for _, k := range keys { - if d.offset == idx { - d.offset += 1 - return dict[k].Key, dict[k].Value - } - idx += 1 - } - return nil, nil -} - -func (d *Dict) Reset() { - d.offset = 0 -} - -type Hashable interface { - HashKey() HashKey -} - -type Continue struct{} - -func (c *Continue) Type() ObjectType { return CONTINUE_OBJ } -func (c *Continue) Inspect() string { return "continue" } - -type Break struct{} - -func (b *Break) Type() ObjectType { return BREAK_OBJ } -func (b *Break) Inspect() string { return "break" } - -// Iterable interface for dicts, strings and arrays -type Iterable interface { - Next() (Object, Object) - Reset() -} diff --git a/object/object_test.go b/object/object_test.go deleted file mode 100644 index 8f4d2a5..0000000 --- a/object/object_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package object - -import "testing" - -func TestStringHashKey(t *testing.T) { - hello1 := &String{Value: "Hello World"} - hello2 := &String{Value: "Hello World"} - diff1 := &String{Value: "My name is Avi"} - diff2 := &String{Value: "My name is Avi"} - - if hello1.HashKey() != hello2.HashKey() { - t.Errorf("string with the same content have different dict keys") - } - - if diff1.HashKey() != diff2.HashKey() { - t.Errorf("String with the same content have different dict keys") - } - - if hello1.HashKey() == diff1.HashKey() { - t.Errorf("Strings with different content have the same dict keys") - } -} diff --git a/parser/parser.go b/parser/parser.go deleted file mode 100644 index ba09966..0000000 --- a/parser/parser.go +++ /dev/null @@ -1,810 +0,0 @@ -package parser - -import ( - "fmt" - "strconv" - - "github.com/AvicennaJr/Nuru/ast" - "github.com/AvicennaJr/Nuru/lexer" - "github.com/AvicennaJr/Nuru/token" -) - -const ( - // Think of BODMAS - _ int = iota - LOWEST - COND // OR or AND - ASSIGN // = - EQUALS // == - LESSGREATER // > OR < - SUM // + - PRODUCT // * - POWER // ** we got the power XD - MODULUS // % - PREFIX // -X OR !X - CALL // myFunction(X) - INDEX // Arrays -) - -var precedences = map[token.TokenType]int{ - token.AND: COND, - token.OR: COND, - token.IN: COND, - token.ASSIGN: ASSIGN, - token.EQ: EQUALS, - token.NOT_EQ: EQUALS, - token.LT: LESSGREATER, - token.LTE: LESSGREATER, - token.GT: LESSGREATER, - token.GTE: LESSGREATER, - token.PLUS: SUM, - token.PLUS_ASSIGN: SUM, - token.MINUS: SUM, - token.MINUS_ASSIGN: SUM, - token.SLASH: PRODUCT, - token.SLASH_ASSIGN: PRODUCT, - token.ASTERISK: PRODUCT, - token.ASTERISK_ASSIGN: PRODUCT, - token.POW: POWER, - token.MODULUS: MODULUS, - token.MODULUS_ASSIGN: MODULUS, - // token.BANG: PREFIX, - token.LPAREN: CALL, - token.LBRACKET: INDEX, // Highest priority -} - -type ( - prefixParseFn func() ast.Expression - infixParseFn func(ast.Expression) ast.Expression - postfixParseFn func() ast.Expression -) - -type Parser struct { - l *lexer.Lexer - - curToken token.Token - peekToken token.Token - prevToken token.Token - - errors []string - - prefixParseFns map[token.TokenType]prefixParseFn - infixParseFns map[token.TokenType]infixParseFn - postfixParseFns map[token.TokenType]postfixParseFn -} - -func New(l *lexer.Lexer) *Parser { - p := &Parser{l: l, errors: []string{}} - - // Gotta set these niggas - p.nextToken() - p.nextToken() - - p.prefixParseFns = make(map[token.TokenType]prefixParseFn) - p.registerPrefix(token.STRING, p.parseStringLiteral) - 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.MINUS, p.parsePrefixExpression) - p.registerPrefix(token.TRUE, p.parseBoolean) - p.registerPrefix(token.FALSE, p.parseBoolean) - p.registerPrefix(token.LPAREN, p.parseGroupedExpression) - p.registerPrefix(token.IF, p.parseIfExpression) - p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral) - p.registerPrefix(token.LBRACKET, p.parseArrayLiteral) - p.registerPrefix(token.LBRACE, p.parseDictLiteral) - p.registerPrefix(token.WHILE, p.parseWhileExpression) - p.registerPrefix(token.NULL, p.parseNull) - p.registerPrefix(token.FOR, p.parseForExpression) - p.registerPrefix(token.SWITCH, p.parseSwitchStatement) - - p.infixParseFns = make(map[token.TokenType]infixParseFn) - p.registerInfix(token.AND, p.parseInfixExpression) - p.registerInfix(token.OR, p.parseInfixExpression) - p.registerInfix(token.PLUS, p.parseInfixExpression) - p.registerInfix(token.PLUS_ASSIGN, p.parseAssignmentExpression) - p.registerInfix(token.MINUS, p.parseInfixExpression) - p.registerInfix(token.MINUS_ASSIGN, p.parseAssignmentExpression) - p.registerInfix(token.SLASH, p.parseInfixExpression) - p.registerInfix(token.SLASH_ASSIGN, p.parseAssignmentExpression) - p.registerInfix(token.ASTERISK, p.parseInfixExpression) - p.registerInfix(token.ASTERISK_ASSIGN, p.parseAssignmentExpression) - p.registerInfix(token.POW, p.parseInfixExpression) - p.registerInfix(token.MODULUS, p.parseInfixExpression) - p.registerInfix(token.MODULUS_ASSIGN, p.parseAssignmentExpression) - p.registerInfix(token.EQ, p.parseInfixExpression) - p.registerInfix(token.NOT_EQ, p.parseInfixExpression) - p.registerInfix(token.LT, p.parseInfixExpression) - p.registerInfix(token.LTE, p.parseInfixExpression) - p.registerInfix(token.GT, p.parseInfixExpression) - p.registerInfix(token.GTE, p.parseInfixExpression) - p.registerInfix(token.LPAREN, p.parseCallExpression) - p.registerInfix(token.LBRACKET, p.parseIndexExpression) - p.registerInfix(token.ASSIGN, p.parseAssignmentExpression) - p.registerInfix(token.IN, p.parseInfixExpression) - - p.postfixParseFns = make(map[token.TokenType]postfixParseFn) - p.registerPostfix(token.PLUS_PLUS, p.parsePostfixExpression) - p.registerPostfix(token.MINUS_MINUS, p.parsePostfixExpression) - - return p -} - -func (p *Parser) nextToken() { - p.prevToken = p.curToken - p.curToken = p.peekToken - p.peekToken = p.l.NextToken() -} - -func (p *Parser) ParseProgram() *ast.Program { - program := &ast.Program{} - program.Statements = []ast.Statement{} - - for !p.curTokenIs(token.EOF) { - stmt := p.parseStatement() - program.Statements = append(program.Statements, stmt) - - p.nextToken() - } - return program -} - -func (p *Parser) parseStatement() ast.Statement { - // Remember to add switch statements to the language - switch p.curToken.Type { - case token.LET: - return p.parseLetStatment() - case token.RETURN: - return p.parseReturnStatement() - case token.BREAK: - return p.parseBreak() - case token.CONTINUE: - return p.parseContinue() - default: - return p.parseExpressionStatement() - } -} - -func (p *Parser) parseLetStatment() *ast.LetStatement { - stmt := &ast.LetStatement{Token: p.curToken} - - if !p.expectPeek(token.IDENT) { - return nil - } - - stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} - - if !p.expectPeek(token.ASSIGN) { - return nil - } - - p.nextToken() - - stmt.Value = p.parseExpression(LOWEST) - - if p.peekTokenIs(token.SEMICOLON) { - p.nextToken() - } - - return stmt -} - -func (p *Parser) parseNull() ast.Expression { - return &ast.Null{Token: p.curToken} -} - -func (p *Parser) parseAssignmentExpression(exp ast.Expression) ast.Expression { - switch node := exp.(type) { - case *ast.Identifier, *ast.IndexExpression: - default: - if node != nil { - msg := fmt.Sprintf("Mstari %d:Tulitegemea kupata kitambulishi au array, badala yake tumepata: %s", p.curToken.Line, node.TokenLiteral()) - p.errors = append(p.errors, msg) - } else { - msg := fmt.Sprintf("Mstari %d: Umekosea mkuu", p.curToken.Line) - p.errors = append(p.errors, msg) - } - return nil - } - - ae := &ast.AssignmentExpression{Token: p.curToken, Left: exp} - - p.nextToken() - - ae.Value = p.parseExpression(LOWEST) - - return ae -} - -func (p *Parser) curTokenIs(t token.TokenType) bool { - return p.curToken.Type == t -} - -func (p *Parser) peekTokenIs(t token.TokenType) bool { - return p.peekToken.Type == t -} - -func (p *Parser) expectPeek(t token.TokenType) bool { - if p.peekTokenIs(t) { - p.nextToken() - return true - } else { - p.peekError(t) - return false - } -} - -func (p *Parser) Errors() []string { - return p.errors -} - -func (p *Parser) peekError(t token.TokenType) { - msg := fmt.Sprintf("Mstari %d: Tulitegemea kupata %s, badala yake tumepata %s", p.curToken.Line, t, p.peekToken.Type) - p.errors = append(p.errors, msg) -} - -func (p *Parser) parseReturnStatement() *ast.ReturnStatement { - stmt := &ast.ReturnStatement{Token: p.curToken} - p.nextToken() - - stmt.ReturnValue = p.parseExpression(LOWEST) - - if p.peekTokenIs(token.SEMICOLON) { - p.nextToken() - } - - return stmt -} - -func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) { - p.prefixParseFns[tokenType] = fn -} - -func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) { - p.infixParseFns[tokenType] = fn -} - -func (p *Parser) registerPostfix(tokenType token.TokenType, fn postfixParseFn) { - p.postfixParseFns[tokenType] = fn -} - -func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { - stmt := &ast.ExpressionStatement{Token: p.curToken} - - stmt.Expression = p.parseExpression(LOWEST) - - if p.peekTokenIs(token.SEMICOLON) { - p.nextToken() - } - - return stmt -} - -func (p *Parser) noPrefixParseFnError(t token.TokenType) { - msg := fmt.Sprintf("Mstari %d: Tumeshindwa kuparse %s", p.curToken.Line, t) - p.errors = append(p.errors, msg) -} - -func (p *Parser) noInfixParseFnError(t token.TokenType) { - msg := fmt.Sprintf("Mstari %d: Tumeshindwa kuparse %s", p.curToken.Line, t) - p.errors = append(p.errors, msg) -} - -func (p *Parser) parseExpression(precedence int) ast.Expression { - postfix := p.postfixParseFns[p.curToken.Type] - if postfix != nil { - return (postfix()) - } - prefix := p.prefixParseFns[p.curToken.Type] - if prefix == nil { - p.noPrefixParseFnError(p.curToken.Type) - return nil - } - leftExp := prefix() - - for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() { - infix := p.infixParseFns[p.peekToken.Type] - if infix == nil { - p.noInfixParseFnError(p.peekToken.Type) - return nil - } - - p.nextToken() - leftExp = infix(leftExp) - } - return leftExp - -} - -func (p *Parser) parseIdentifier() ast.Expression { - return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} -} - -func (p *Parser) parseIntegerLiteral() ast.Expression { - lit := &ast.IntegerLiteral{Token: p.curToken} - - value, err := strconv.ParseInt(p.curToken.Literal, 0, 64) - if err != nil { - msg := fmt.Sprintf("Mstari %d: Hatuwezi kuparse %q kama namba", p.curToken.Line, p.curToken.Literal) - p.errors = append(p.errors, msg) - return nil - } - lit.Value = value - - return lit -} - -func (p *Parser) parseFloatLiteral() ast.Expression { - fl := &ast.FloatLiteral{Token: p.curToken} - value, err := strconv.ParseFloat(p.curToken.Literal, 64) - if err != nil { - msg := fmt.Sprintf("Mstari %d: Hatuwezi kuparse %q kama desimali", p.curToken.Line, p.curToken.Literal) - p.errors = append(p.errors, msg) - return nil - } - fl.Value = value - return fl -} - -func (p *Parser) parsePrefixExpression() ast.Expression { - expression := &ast.PrefixExpression{ - Token: p.curToken, - Operator: p.curToken.Literal, - } - - p.nextToken() - - expression.Right = p.parseExpression(PREFIX) - - return expression -} - -func (p *Parser) peekPrecedence() int { - if p, ok := precedences[p.peekToken.Type]; ok { - return p - } - return LOWEST -} - -func (p *Parser) curPrecedence() int { - if p, ok := precedences[p.curToken.Type]; ok { - return p - } - - return LOWEST -} - -func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression { - expression := &ast.InfixExpression{ - Token: p.curToken, - Operator: p.curToken.Literal, - Left: left, - } - - precedence := p.curPrecedence() - p.nextToken() - expression.Right = p.parseExpression(precedence) - return expression -} - -func (p *Parser) parseBoolean() ast.Expression { - return &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)} -} - -func (p *Parser) parseGroupedExpression() ast.Expression { - p.nextToken() - - exp := p.parseExpression(LOWEST) - - if !p.expectPeek(token.RPAREN) { - return nil - } - - return exp -} - -func (p *Parser) parsePostfixExpression() ast.Expression { - expression := &ast.PostfixExpression{ - Token: p.prevToken, - Operator: p.curToken.Literal, - } - return expression -} - -func (p *Parser) parseIfExpression() ast.Expression { - expression := &ast.IfExpression{Token: p.curToken} - - if !p.expectPeek(token.LPAREN) { - return nil - } - - p.nextToken() - expression.Condition = p.parseExpression(LOWEST) - - if !p.expectPeek(token.RPAREN) { - return nil - } - - if !p.expectPeek(token.LBRACE) { - return nil - } - - expression.Consequence = p.parseBlockStatement() - - if p.peekTokenIs(token.ELSE) { - p.nextToken() - if p.peekTokenIs(token.IF) { - p.nextToken() - expression.Alternative = &ast.BlockStatement{ - Statements: []ast.Statement{ - &ast.ExpressionStatement{ - Expression: p.parseIfExpression(), - }, - }, - } - return expression - } - - if !p.expectPeek(token.LBRACE) { - return nil - } - - expression.Alternative = p.parseBlockStatement() - } - - return expression -} - -func (p *Parser) parseBlockStatement() *ast.BlockStatement { - block := &ast.BlockStatement{Token: p.curToken} - block.Statements = []ast.Statement{} - - p.nextToken() - - for !p.curTokenIs(token.RBRACE) { - if p.curTokenIs(token.EOF) { - msg := fmt.Sprintf("Mstari %d: Hukufunga Mabano '}'", p.curToken.Line) - p.errors = append(p.errors, msg) - return nil - } - stmt := p.parseStatement() - block.Statements = append(block.Statements, stmt) - p.nextToken() - } - - return block -} - -func (p *Parser) parseFunctionLiteral() ast.Expression { - lit := &ast.FunctionLiteral{Token: p.curToken} - - if !p.expectPeek(token.LPAREN) { - return nil - } - - lit.Parameters = p.parseFunctionParameters() - - if !p.expectPeek(token.LBRACE) { - return nil - } - - lit.Body = p.parseBlockStatement() - - return lit -} - -func (p *Parser) parseFunctionParameters() []*ast.Identifier { - identifiers := []*ast.Identifier{} - - if p.peekTokenIs(token.RPAREN) { - p.nextToken() - return identifiers - } - - p.nextToken() - - ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} - identifiers = append(identifiers, ident) - - for p.peekTokenIs(token.COMMA) { - p.nextToken() - p.nextToken() - ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} - identifiers = append(identifiers, ident) - } - - if !p.expectPeek(token.RPAREN) { - return nil - } - - return identifiers -} - -func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression { - exp := &ast.CallExpression{Token: p.curToken, Function: function} - exp.Arguments = p.parseExpressionList(token.RPAREN) - return exp -} - -func (p *Parser) parseStringLiteral() ast.Expression { - return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal} -} - -func (p *Parser) parseArrayLiteral() ast.Expression { - array := &ast.ArrayLiteral{Token: p.curToken} - - array.Elements = p.parseExpressionList(token.RBRACKET) - - return array -} - -func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression { - list := []ast.Expression{} - - if p.peekTokenIs(end) { - p.nextToken() - return list - } - - p.nextToken() - list = append(list, p.parseExpression(LOWEST)) - - for p.peekTokenIs(token.COMMA) { - p.nextToken() - p.nextToken() - list = append(list, p.parseExpression(LOWEST)) - } - - if !p.expectPeek(end) { - return nil - } - return list -} - -func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression { - exp := &ast.IndexExpression{Token: p.curToken, Left: left} - - p.nextToken() - exp.Index = p.parseExpression(LOWEST) - if !p.expectPeek(token.RBRACKET) { - return nil - } - - return exp -} - -func (p *Parser) parseDictLiteral() ast.Expression { - dict := &ast.DictLiteral{Token: p.curToken} - dict.Pairs = make(map[ast.Expression]ast.Expression) - - for !p.peekTokenIs(token.RBRACE) { - p.nextToken() - key := p.parseExpression(LOWEST) - - if !p.expectPeek(token.COLON) { - return nil - } - - p.nextToken() - value := p.parseExpression(LOWEST) - - dict.Pairs[key] = value - - if !p.peekTokenIs(token.RBRACE) && !p.expectPeek(token.COMMA) { - return nil - } - } - - if !p.expectPeek(token.RBRACE) { - return nil - } - - return dict -} - -func (p *Parser) parseWhileExpression() ast.Expression { - expression := &ast.WhileExpression{Token: p.curToken} - - if !p.expectPeek(token.LPAREN) { - return nil - } - - p.nextToken() - expression.Condition = p.parseExpression(LOWEST) - - if !p.expectPeek(token.RPAREN) { - return nil - } - - if !p.expectPeek(token.LBRACE) { - return nil - } - - expression.Consequence = p.parseBlockStatement() - - return expression -} - -func (p *Parser) parseBreak() *ast.Break { - stmt := &ast.Break{Token: p.curToken} - for p.curTokenIs(token.SEMICOLON) { - p.nextToken() - } - return stmt -} - -func (p *Parser) parseContinue() *ast.Continue { - stmt := &ast.Continue{Token: p.curToken} - for p.curTokenIs(token.SEMICOLON) { - p.nextToken() - } - return stmt -} - -func (p *Parser) parseForExpression() ast.Expression { - expression := &ast.For{Token: p.curToken} - p.nextToken() - if !p.curTokenIs(token.IDENT) { - return nil - } - if !p.peekTokenIs(token.ASSIGN) { - return p.parseForInExpression(expression) - } - - // In future will allow: kwa i = 0; i<10; i++ {andika(i)} - // expression.Identifier = p.curToken.Literal - // expression.StarterName = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} - // if expression.StarterName == nil { - // return nil - // } - // if !p.expectPeek(token.ASSIGN) { - // return nil - // } - - // p.nextToken() - - // expression.StarterValue = p.parseExpression(LOWEST) - // // expression.Starter = p.parseExpression(LOWEST) - // if expression.StarterValue == nil { - // return nil - // } - // p.nextToken() - // for p.curTokenIs(token.SEMICOLON) { - // p.nextToken() - // } - // expression.Condition = p.parseExpression(LOWEST) - // if expression.Condition == nil { - // return nil - // } - // p.nextToken() - // for p.curTokenIs(token.SEMICOLON) { - // p.nextToken() - // } - // expression.Closer = p.parseExpression(LOWEST) - // if expression.Closer == nil { - // return nil - // } - // p.nextToken() - // for p.curTokenIs(token.SEMICOLON) { - // p.nextToken() - // } - // if !p.curTokenIs(token.LBRACE) { - // return nil - // } - // expression.Block = p.parseBlockStatement() - // return expression - return nil -} - -func (p *Parser) parseForInExpression(initialExpression *ast.For) ast.Expression { - expression := &ast.ForIn{Token: initialExpression.Token} - if !p.curTokenIs(token.IDENT) { - return nil - } - val := p.curToken.Literal - var key string - p.nextToken() - if p.curTokenIs(token.COMMA) { - p.nextToken() - if !p.curTokenIs(token.IDENT) { - return nil - } - key = val - val = p.curToken.Literal - p.nextToken() - } - expression.Key = key - expression.Value = val - if !p.curTokenIs(token.IN) { - return nil - } - p.nextToken() - expression.Iterable = p.parseExpression(LOWEST) - if !p.expectPeek(token.LBRACE) { - return nil - } - expression.Block = p.parseBlockStatement() - return expression -} - -func (p *Parser) parseSwitchStatement() ast.Expression { - expression := &ast.SwitchExpression{Token: p.curToken} - - if !p.expectPeek(token.LPAREN) { - return nil - } - - p.nextToken() - expression.Value = p.parseExpression(LOWEST) - - if expression.Value == nil { - return nil - } - - if !p.expectPeek(token.RPAREN) { - return nil - } - - if !p.expectPeek(token.LBRACE) { - return nil - } - p.nextToken() - - for !p.curTokenIs(token.RBRACE) { - - if p.curTokenIs(token.EOF) { - msg := fmt.Sprintf("Mstari %d: Haukufunga ENDAPO (SWITCH)", p.curToken.Line) - p.errors = append(p.errors, msg) - return nil - } - tmp := &ast.CaseExpression{Token: p.curToken} - - if p.curTokenIs(token.DEFAULT) { - - tmp.Default = true - - } else if p.curTokenIs(token.CASE) { - - p.nextToken() - - if p.curTokenIs(token.DEFAULT) { - tmp.Default = true - } else { - tmp.Expr = append(tmp.Expr, p.parseExpression(LOWEST)) - for p.peekTokenIs(token.COMMA) { - p.nextToken() - p.nextToken() - tmp.Expr = append(tmp.Expr, p.parseExpression(LOWEST)) - } - } - } else { - msg := fmt.Sprintf("Mstari %d: Tulitegemea Kauli IKIWA (CASE) au KAWAIDA (DEFAULT) lakini tumepewa: %s", p.curToken.Line, p.curToken.Type) - p.errors = append(p.errors, msg) - return nil - } - - if !p.expectPeek(token.LBRACE) { - return nil - } - - tmp.Block = p.parseBlockStatement() - p.nextToken() - expression.Choices = append(expression.Choices, tmp) - } - - count := 0 - for _, c := range expression.Choices { - if c.Default { - count++ - } - } - if count > 1 { - msg := fmt.Sprintf("Kauli ENDAPO (SWITCH) hua na kauli 'KAWAIDA' (DEFAULT) moja tu! Wewe umeweka %d", count) - p.errors = append(p.errors, msg) - return nil - - } - return expression - -} diff --git a/parser/parser_test.go b/parser/parser_test.go deleted file mode 100644 index e205a4d..0000000 --- a/parser/parser_test.go +++ /dev/null @@ -1,970 +0,0 @@ -package parser - -import ( - "fmt" - "testing" - - "github.com/AvicennaJr/Nuru/ast" - "github.com/AvicennaJr/Nuru/lexer" -) - -func TestLetStatements(t *testing.T) { - tests := []struct { - input string - expectedIdentifier string - expectedValue interface{} - }{ - {"fanya x = 5;", "x", 5}, - {"fanya y = x;", "y", "x"}, - {"fanya bangi = y;", "bangi", "y"}, - } - - for _, tt := range tests { - l := lexer.New(tt.input) - p := New(l) - program := p.ParseProgram() - checkParserErrors(t, p) - - if len(program.Statements) != 1 { - t.Fatalf("program.Statements does not contain 1 statements. got=%d", - len(program.Statements)) - } - - stmt := program.Statements[0] - if !testLetStatement(t, stmt, tt.expectedIdentifier) { - return - } - - val := stmt.(*ast.LetStatement).Value - if !testLiteralExpression(t, val, tt.expectedValue) { - return - } - } -} - -func testLetStatement(t *testing.T, s ast.Statement, name string) bool { - if s.TokenLiteral() != "fanya" { - t.Errorf("s.TokenLiteral not 'fanya', got = %q", s.TokenLiteral()) - return false - } - - letStmt, ok := s.(*ast.LetStatement) - if !ok { - t.Errorf("s not *ast.LetStatement, got = %T", s) - return false - } - - if letStmt.Name.Value != name { - t.Errorf("letStmt.Name.Value not '%s', got='%s'", name, letStmt.Name.Value) - return false - } - - if letStmt.Name.TokenLiteral() != name { - t.Errorf("s.Name not %s, got=%s", name, letStmt.Name) - return false - } - return true -} - -func checkParserErrors(t *testing.T, p *Parser) { - errors := p.Errors() - - if len(errors) == 0 { - return - } - - t.Errorf("Parser has %d errors", len(errors)) - - for _, msg := range errors { - t.Errorf("Parser error: %q", msg) - } - t.FailNow() -} - -func TestReturnStatements(t *testing.T) { - tests := []struct { - input string - expectedValue interface{} - }{ - {"rudisha 5;", 5}, - {"rudisha kweli;", true}, - {"rudisha bangi;", "bangi"}, - } - - for _, tt := range tests { - l := lexer.New(tt.input) - p := New(l) - program := p.ParseProgram() - checkParserErrors(t, p) - - if len(program.Statements) != 1 { - t.Fatalf("program.Statements does not contain 1 statements. got=%d", - len(program.Statements)) - } - - stmt := program.Statements[0] - returnStmt, ok := stmt.(*ast.ReturnStatement) - if !ok { - t.Fatalf("stmt not *ast.returnStatement. got=%T", stmt) - } - if returnStmt.TokenLiteral() != "rudisha" { - t.Fatalf("returnStmt.TokenLiteral not 'rudisha', got %q", - returnStmt.TokenLiteral()) - } - if testLiteralExpression(t, returnStmt.ReturnValue, tt.expectedValue) { - return - } - } -} - -func TestIdentifierExpression(t *testing.T) { - input := "foobar;" - - l := lexer.New(input) - p := New(l) - - program := p.ParseProgram() - checkParserErrors(t, p) - - if len(program.Statements) != 1 { - t.Fatalf("program has not enough statements. got=%d", len(program.Statements)) - } - - stmt, ok := program.Statements[0].(*ast.ExpressionStatement) - if !ok { - t.Fatalf("program.Statements[0] is not an ast.ExpressionStatement, got=%T", program.Statements[0]) - } - - ident, ok := stmt.Expression.(*ast.Identifier) - if !ok { - t.Fatalf("exp not *ast.Identifier, got=%T", stmt.Expression) - } - - if ident.Value != "foobar" { - t.Errorf("ident.Value not %s, got=%s", "foobar", ident.Value) - } - - if ident.TokenLiteral() != "foobar" { - t.Errorf("ident.TokenLiteral not %s, got=%s", "foobar", ident.TokenLiteral()) - } -} - -func TestIntergerLiteral(t *testing.T) { - input := "5;" - - l := lexer.New(input) - p := New(l) - program := p.ParseProgram() - checkParserErrors(t, p) - - if len(program.Statements) != 1 { - t.Fatalf("program has not enough statements, got=%d", len(program.Statements)) - } - - stmt, ok := program.Statements[0].(*ast.ExpressionStatement) - if !ok { - t.Fatalf("program.statements[0] is not ast.ExpressionStatement, got=%T", program.Statements[0]) - } - - literal, ok := stmt.Expression.(*ast.IntegerLiteral) - if !ok { - t.Fatalf("exp not *ast.IntegerLiteral, got=%T", stmt.Expression) - } - - if literal.Value != 5 { - t.Errorf("literal.Value not %d, got=%d", 5, literal.Value) - } - - if literal.TokenLiteral() != "5" { - t.Errorf("literal.TokenLiteral not %s, got=%s", "5", literal.TokenLiteral()) - } -} - -func TestParsingPrefixExpressions(t *testing.T) { - prefixTests := []struct { - input string - operator string - value interface{} - }{ - {"!5;", "!", 5}, - {"-15;", "-", 15}, - {"!kweli", "!", true}, - {"!sikweli", "!", false}, - } - - for _, tt := range prefixTests { - l := lexer.New(tt.input) - p := New(l) - program := p.ParseProgram() - checkParserErrors(t, p) - - if len(program.Statements) != 1 { - t.Fatalf("Program statements does not contain %d statements, got=%d\n", 1, len(program.Statements)) - } - - stmt, ok := program.Statements[0].(*ast.ExpressionStatement) - if !ok { - t.Fatalf("program.Statements[0] is not ast.ExpressionStatement, got=%T", program.Statements[0]) - } - - exp, ok := stmt.Expression.(*ast.PrefixExpression) - if !ok { - t.Fatalf("stmt is not ast.PrefixExpression, got=%T", stmt.Expression) - } - if exp.Operator != tt.operator { - t.Fatalf("exp.Operator is not %s, got=%s", tt.operator, exp.Operator) - } - - if !testLiteralExpression(t, exp.Right, tt.value) { - return - } - - } -} - -func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool { - integ, ok := il.(*ast.IntegerLiteral) - if !ok { - t.Errorf("il not *ast.IntegerLiteral, got=%T", il) - return false - } - - if integ.Value != value { - t.Errorf("il not %d, got=%d", value, integ.Value) - return false - } - - if integ.TokenLiteral() != fmt.Sprintf("%d", value) { - t.Errorf("integ.TokenLiteral not %d, got=%s", value, integ.TokenLiteral()) - return false - } - - return true -} - -func TestParsingInfixExpressions(t *testing.T) { - infixTests := []struct { - input string - leftValue interface{} - operator string - rightValue interface{} - }{ - {"5 + 5;", 5, "+", 5}, - {"5 - 5;", 5, "-", 5}, - {"5 * 5;", 5, "*", 5}, - {"5 / 5;", 5, "/", 5}, - {"5 > 5;", 5, ">", 5}, - {"5 < 5;", 5, "<", 5}, - {"5 == 5;", 5, "==", 5}, - {"5 != 5;", 5, "!=", 5}, - {"kweli == kweli", true, "==", true}, - {"kweli != sikweli", true, "!=", false}, - {"sikweli == sikweli", false, "==", false}, - } - - for _, tt := range infixTests { - l := lexer.New(tt.input) - p := New(l) - program := p.ParseProgram() - checkParserErrors(t, p) - - if len(program.Statements) != 1 { - t.Fatalf("Program.statements does not contain %d statements, got =%d", 1, len(program.Statements)) - } - - stmt, ok := program.Statements[0].(*ast.ExpressionStatement) - if !ok { - t.Fatalf("program.statements[0] is not ast.ExpressionStatement, got=%T", program.Statements[0]) - } - - if !testInfixExpression(t, stmt.Expression, tt.leftValue, tt.operator, tt.rightValue) { - return - } - } -} - -func TestOperatorPrecedenceParsing(t *testing.T) { - tests := []struct { - input string - expected string - }{ - { - "-a * b", - "((-a) * b)", - }, - { - "!-a", - "(!(-a))", - }, - { - "a + b + c", - "((a + b) + c)", - }, - { - "a + b - c", - "((a + b) - c)", - }, - { - "a * b * c", - "((a * b) * c)", - }, - { - "a*b /c", - "((a * b) / c)", - }, - { - "a + b / c", - "(a + (b / c))", - }, - { - "a + b * c + d / e - f", - "(((a + (b * c)) + (d / e)) - f)", - }, - { - "3 + 4; -5 * 5", - "(3 + 4)((-5) * 5)", - }, - { - "5 > 4 == 3 < 4", - "((5 > 4) == (3 < 4))", - }, - { - "5 < 4 != 3 > 4", - "((5 < 4) != (3 > 4))", - }, - { - "3 + 4 * 5 == 3 * 1 + 4 * 5", - "((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))", - }, - { - "kweli", - "kweli", - }, - { - "sikweli", - "sikweli", - }, - { - "3 > 5 == sikweli", - "((3 > 5) == sikweli)", - }, - { - "3 < 5 == kweli", - "((3 < 5) == kweli)", - }, - { - "1 + (2 + 3) + 4", - "((1 + (2 + 3)) + 4)", - }, - { - "(5 + 5) * 2", - "((5 + 5) * 2)", - }, - { - "2 / (5 + 5)", - "(2 / (5 + 5))", - }, - { - "-(5 + 5)", - "(-(5 + 5))", - }, - { - "!(kweli == kweli)", - "(!(kweli == kweli))", - }, - { - "a + add(b * c) + d", - "((a + add((b * c))) + d)", - }, - { - "add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", - "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))", - }, - { - "add(a + b + c * d / f + g)", - "add((((a + b) + ((c * d) / f)) + g))", - }, - { - "a * [1, 2, 3, 4][b * c] * d", - "((a * ([1, 2, 3, 4][(b * c)])) * d)", - }, - { - "add(a *b[2], b[1], 2 * [1, 2][1])", - "add((a * (b[2])), (b[1]), (2 * ([1, 2][1])))", - }, - } - - for _, tt := range tests { - l := lexer.New(tt.input) - p := New(l) - program := p.ParseProgram() - checkParserErrors(t, p) - - actual := program.String() - - if actual != tt.expected { - t.Errorf("expected=%q, got=%q", tt.expected, actual) - } - } -} - -func testIdentifier(t *testing.T, exp ast.Expression, value string) bool { - ident, ok := exp.(*ast.Identifier) - if !ok { - t.Errorf("exp not *ast.Identifier, got=%T", exp) - return false - } - - if ident.Value != value { - t.Errorf("ident.Value not %s, got=%s", value, ident.Value) - return false - } - - if ident.TokenLiteral() != value { - t.Errorf("ident.TokenLiteral is not %s, got=%s", value, ident.TokenLiteral()) - return false - } - - return true -} - -func testLiteralExpression( - t *testing.T, - exp ast.Expression, - expected interface{}, -) bool { - switch v := expected.(type) { - case int: - return testIntegerLiteral(t, exp, int64(v)) - case int64: - return testIntegerLiteral(t, exp, v) - case string: - return testIdentifier(t, exp, v) - case bool: - return testBooleanLiteral(t, exp, v) - } - - t.Errorf("type of exp not handled, got=%T", exp) - return false -} - -func testBooleanLiteral(t *testing.T, exp ast.Expression, value bool) bool { - bo, ok := exp.(*ast.Boolean) - if !ok { - t.Errorf("exp not *ast.Boolean, got=%T", exp) - return false - } - - if bo.Value != value { - t.Errorf("bo.Value not %t,got=%t", value, bo.Value) - return false - } - - return true -} - -func testInfixExpression( - t *testing.T, - exp ast.Expression, - left interface{}, - operator string, - right interface{}, -) bool { - opExp, ok := exp.(*ast.InfixExpression) - if !ok { - t.Errorf("exp is not ast.OperatorExpression, got=%T(%s)", exp, exp) - return false - } - - if !testLiteralExpression(t, opExp.Left, left) { - return false - } - - if opExp.Operator != operator { - t.Errorf("exp.Operator is not %s, got=%q", operator, opExp.Operator) - return false - } - - if !testLiteralExpression(t, opExp.Right, right) { - return false - } - - return true -} - -func TestBooleanExpression(t *testing.T) { - tests := []struct { - input string - expectedBoolean bool - }{ - {"kweli;", true}, - {"sikweli;", false}, - } - - for _, tt := range tests { - - l := lexer.New(tt.input) - p := New(l) - program := p.ParseProgram() - checkParserErrors(t, p) - - if len(program.Statements) != 1 { - t.Fatalf("program has not enough statements. got=%d", len(program.Statements)) - } - - stmt, ok := program.Statements[0].(*ast.ExpressionStatement) - if !ok { - t.Fatalf("program.Statements[0] is not an ast.ExpressionStatement, got=%T", program.Statements[0]) - } - - boolean, ok := stmt.Expression.(*ast.Boolean) - if !ok { - t.Fatalf("exp not *ast.Boolean, got=%T", stmt.Expression) - } - - if boolean.Value != tt.expectedBoolean { - t.Errorf("boolean.Value not %t, got=%t", tt.expectedBoolean, boolean.Value) - } - - } -} - -func TestIfExpression(t *testing.T) { - input := `kama (x < y) { x }` - - l := lexer.New(input) - p := New(l) - program := p.ParseProgram() - checkParserErrors(t, p) - - if len(program.Statements) != 1 { - t.Fatalf("program.Body does not contain %d statements, got=%d", 1, len(program.Statements)) - } - - stmt, ok := program.Statements[0].(*ast.ExpressionStatement) - if !ok { - t.Fatalf("program.Statements[0] is not an ast.ExpressionStatement, got=%T", program.Statements[0]) - } - - exp, ok := stmt.Expression.(*ast.IfExpression) - if !ok { - t.Fatalf("stmt.Expression is not ast.IfExpression, got=%T", stmt.Expression) - } - - if !testInfixExpression(t, exp.Condition, "x", "<", "y") { - return - } - - if len(exp.Consequence.Statements) != 1 { - t.Errorf("Consequences is not 1 statement, got=%d\n", len(exp.Consequence.Statements)) - } - - consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) - if !ok { - t.Fatalf("Statements[0] is not ast.Expression, got=%T", exp.Consequence.Statements[0]) - } - - if !testIdentifier(t, consequence.Expression, "x") { - return - } - - if exp.Alternative != nil { - t.Errorf("exp.Alternative.Statement was not nil, got=%+v", exp.Alternative) - } -} - -func TestIfElseExpression(t *testing.T) { - input := `kama (x < y) { x } sivyo { y }` - - l := lexer.New(input) - p := New(l) - program := p.ParseProgram() - checkParserErrors(t, p) - - if len(program.Statements) != 1 { - t.Fatalf("program.Body does not contain %d statements. got=%d\n", - 1, len(program.Statements)) - } - - stmt, ok := program.Statements[0].(*ast.ExpressionStatement) - if !ok { - t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", - program.Statements[0]) - } - - exp, ok := stmt.Expression.(*ast.IfExpression) - if !ok { - t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", stmt.Expression) - } - - if !testInfixExpression(t, exp.Condition, "x", "<", "y") { - return - } - - if len(exp.Consequence.Statements) != 1 { - t.Errorf("consequence is not 1 statements. got=%d\n", - len(exp.Consequence.Statements)) - } - - consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) - if !ok { - t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", - exp.Consequence.Statements[0]) - } - - if !testIdentifier(t, consequence.Expression, "x") { - return - } - - if len(exp.Alternative.Statements) != 1 { - t.Errorf("exp.Alternative.Statements does not contain 1 statements. got=%d\n", - len(exp.Alternative.Statements)) - } - - alternative, ok := exp.Alternative.Statements[0].(*ast.ExpressionStatement) - if !ok { - t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", - exp.Alternative.Statements[0]) - } - - if !testIdentifier(t, alternative.Expression, "y") { - return - } -} - -func TestFunctionLiteralParsing(t *testing.T) { - input := `unda(x, y) {x + y}` - - l := lexer.New(input) - p := New(l) - program := p.ParseProgram() - checkParserErrors(t, p) - - if len(program.Statements) != 1 { - t.Fatalf("program.Body does not contain %d statements, got=%d\n", 1, len(program.Statements)) - } - - stmt, ok := program.Statements[0].(*ast.ExpressionStatement) - if !ok { - t.Fatalf("program.Statements[0] is not ast.ExpressionStatement, got=%T", program.Statements[0]) - } - - function, ok := stmt.Expression.(*ast.FunctionLiteral) - if !ok { - t.Fatalf("stmt.Expression is not ast.FunctionLiteral, got=%T", stmt.Expression) - } - - if len(function.Parameters) != 2 { - t.Fatalf("function literal parameters wrong, want 2, got=%d\n", len(function.Parameters)) - } - - testLiteralExpression(t, function.Parameters[0], "x") - testLiteralExpression(t, function.Parameters[1], "y") - - if len(function.Body.Statements) != 1 { - t.Fatalf("function.Body.Statements has not 1 statement, got=%d\n", len(function.Body.Statements)) - } - - bodyStmt, ok := function.Body.Statements[0].(*ast.ExpressionStatement) - if !ok { - t.Fatalf("function body stmt is not ast.ExpressionStatement, got=%T", function.Body.Statements[0]) - } - - testInfixExpression(t, bodyStmt.Expression, "x", "+", "y") -} - -func TestFunctionParameterParsing(t *testing.T) { - tests := []struct { - input string - expectedParams []string - }{ - {input: "unda() {};", expectedParams: []string{}}, - {input: "unda(x) {};", expectedParams: []string{"x"}}, - {input: "unda(x, y, z) {};", expectedParams: []string{"x", "y", "z"}}, - } - - for _, tt := range tests { - l := lexer.New(tt.input) - p := New(l) - program := p.ParseProgram() - checkParserErrors(t, p) - - stmt := program.Statements[0].(*ast.ExpressionStatement) - function := stmt.Expression.(*ast.FunctionLiteral) - - if len(function.Parameters) != len(tt.expectedParams) { - t.Errorf("length parameters wrong,want %d, got=%d\n", len(tt.expectedParams), len(function.Parameters)) - } - - for i, ident := range tt.expectedParams { - testLiteralExpression(t, function.Parameters[i], ident) - } - } -} - -func TestCallExpressionParsing(t *testing.T) { - input := "jumlisha(1, 2 * 3, 4 + 5);" - - l := lexer.New(input) - p := New(l) - program := p.ParseProgram() - checkParserErrors(t, p) - - if len(program.Statements) != 1 { - t.Fatalf("program.Statements does not have 1 statements, got=%d\n", len(program.Statements)) - } - - stmt, ok := program.Statements[0].(*ast.ExpressionStatement) - if !ok { - t.Fatalf("stmt is not ast.ExpressionStatement, got=%T", program.Statements[0]) - } - - exp, ok := stmt.Expression.(*ast.CallExpression) - if !ok { - t.Fatalf("stmt.Expression is not ast.CallExpression, got=%T", stmt.Expression) - } - - if !testIdentifier(t, exp.Function, "jumlisha") { - return - } - - if len(exp.Arguments) != 3 { - t.Fatalf("wrong length of arguments, got=%d", len(exp.Arguments)) - } - - testLiteralExpression(t, exp.Arguments[0], 1) - testInfixExpression(t, exp.Arguments[1], 2, "*", 3) - testInfixExpression(t, exp.Arguments[2], 4, "+", 5) - -} - -func TestStringLiteralExpression(t *testing.T) { - input := `"habari yako"` - - l := lexer.New(input) - p := New(l) - program := p.ParseProgram() - checkParserErrors(t, p) - - stmt := program.Statements[0].(*ast.ExpressionStatement) - literal, ok := stmt.Expression.(*ast.StringLiteral) - if !ok { - t.Fatalf("exp not *ast.StringLiteral, got=%T", stmt.Expression) - } - - if literal.Value != "habari yako" { - t.Errorf("literal.Value not %q, got=%q", "habari yako", literal.Value) - } -} - -func TestParsingArrayLiterals(t *testing.T) { - input := "[1,2*2,3+3]" - - l := lexer.New(input) - p := New(l) - program := p.ParseProgram() - checkParserErrors(t, p) - - stmt := program.Statements[0].(*ast.ExpressionStatement) - array, ok := stmt.Expression.(*ast.ArrayLiteral) - if !ok { - t.Fatalf("Expression not ast.ArrayLiteral, got=%T", len(array.Elements)) - } - - testIntegerLiteral(t, array.Elements[0], 1) - testInfixExpression(t, array.Elements[1], 2, "*", 2) - testInfixExpression(t, array.Elements[2], 3, "+", 3) -} - -func TestParsingIndexExpressions(t *testing.T) { - input := "myArray[1+1]" - - l := lexer.New(input) - p := New(l) - program := p.ParseProgram() - checkParserErrors(t, p) - - stmt := program.Statements[0].(*ast.ExpressionStatement) - indexExp, ok := stmt.Expression.(*ast.IndexExpression) - if !ok { - t.Fatalf("Expression not *ast.IndexExpression, got=%T", stmt.Expression) - } - - if !testIdentifier(t, indexExp.Left, "myArray") { - return - } - - if !testInfixExpression(t, indexExp.Index, 1, "+", 1) { - return - } -} - -func TestParsingDictLiteralsStringKeys(t *testing.T) { - input := `{"one": 1, "two": 2, "three": 3}` - - l := lexer.New(input) - p := New(l) - program := p.ParseProgram() - checkParserErrors(t, p) - - stmt := program.Statements[0].(*ast.ExpressionStatement) - dict, ok := stmt.Expression.(*ast.DictLiteral) - if !ok { - t.Fatalf("Expression is not a Dict, got=%T", stmt.Expression) - } - - if len(dict.Pairs) != 3 { - t.Errorf("dict.Pairs wrong, got=%d", len(dict.Pairs)) - } - - expected := map[string]int64{ - "one": 1, - "two": 2, - "three": 3, - } - - for key, value := range dict.Pairs { - literal, ok := key.(*ast.StringLiteral) - if !ok { - t.Errorf("Key is not a string, got=%T", key) - } - - expectedValue := expected[literal.String()] - testIntegerLiteral(t, value, expectedValue) - } -} - -func TestParsingDictLiteralsIntegerKeys(t *testing.T) { - input := `{1: 1, 2: 2, 3: 3}` - - l := lexer.New(input) - p := New(l) - program := p.ParseProgram() - checkParserErrors(t, p) - - stmt := program.Statements[0].(*ast.ExpressionStatement) - dict, ok := stmt.Expression.(*ast.DictLiteral) - if !ok { - t.Fatalf("Expression is not a Dict, got=%T", stmt.Expression) - } - - if len(dict.Pairs) != 3 { - t.Errorf("dict.Pairs wrong, got=%d", len(dict.Pairs)) - } - - expected := map[int64]int64{ - 1: 1, - 2: 2, - 3: 3, - } - - for key, value := range dict.Pairs { - literal, ok := key.(*ast.IntegerLiteral) - if !ok { - t.Errorf("Key is not a string, got=%T", key) - } - - expectedValue := expected[literal.Value] - testIntegerLiteral(t, value, expectedValue) - } -} - -func TestParsingDictLiteralsBoolKeys(t *testing.T) { - input := `{kweli: 1, sikweli: 2}` - - l := lexer.New(input) - p := New(l) - program := p.ParseProgram() - checkParserErrors(t, p) - - stmt := program.Statements[0].(*ast.ExpressionStatement) - dict, ok := stmt.Expression.(*ast.DictLiteral) - if !ok { - t.Fatalf("Expression is not a Dict, got=%T", stmt.Expression) - } - - if len(dict.Pairs) != 2 { - t.Errorf("dict.Pairs wrong, got=%d", len(dict.Pairs)) - } - - expected := map[bool]int64{ - true: 1, - false: 2, - } - - for key, value := range dict.Pairs { - literal, ok := key.(*ast.Boolean) - if !ok { - t.Errorf("Key is not a string, got=%T", key) - } - - expectedValue := expected[literal.Value] - testIntegerLiteral(t, value, expectedValue) - } -} - -func TestParsingDictLiteralWithExpressions(t *testing.T) { - input := `{"one": 0+1, "two": 100-98, "three": 15/5}` - - l := lexer.New(input) - p := New(l) - program := p.ParseProgram() - checkParserErrors(t, p) - - stmt := program.Statements[0].(*ast.ExpressionStatement) - dict, ok := stmt.Expression.(*ast.DictLiteral) - if !ok { - t.Fatalf("Expression is not a dict, got=%T", stmt.Expression) - } - - if len(dict.Pairs) != 3 { - t.Errorf("Dict has wrong length, got=%d", len(dict.Pairs)) - } - - tests := map[string]func(ast.Expression){ - "one": func(e ast.Expression) { - testInfixExpression(t, e, 0, "+", 1) - }, - "two": func(e ast.Expression) { - testInfixExpression(t, e, 100, "-", 98) - }, - "three": func(e ast.Expression) { - testInfixExpression(t, e, 15, "/", 5) - }, - } - - for key, value := range dict.Pairs { - literal, ok := key.(*ast.StringLiteral) - if !ok { - t.Errorf("key is not a string, got=%T", key) - continue - } - - testFunc, ok := tests[literal.String()] - if !ok { - t.Errorf("No test function for key %q found", literal.String()) - continue - } - - testFunc(value) - } -} - -func TestParsingEmptyDict(t *testing.T) { - input := "{}" - - l := lexer.New(input) - p := New(l) - program := p.ParseProgram() - checkParserErrors(t, p) - - stmt := program.Statements[0].(*ast.ExpressionStatement) - dict, ok := stmt.Expression.(*ast.DictLiteral) - if !ok { - t.Fatalf("Expression not a dict, got=%T", stmt.Expression) - } - - if len(dict.Pairs) != 0 { - t.Errorf("Dict pairs has wrong length, got=%d", len(dict.Pairs)) - } -} diff --git a/repl/repl.go b/repl/repl.go deleted file mode 100644 index 9700152..0000000 --- a/repl/repl.go +++ /dev/null @@ -1,116 +0,0 @@ -package repl - -import ( - "bufio" - "fmt" - "io" - "os" - "strings" - - "github.com/AvicennaJr/Nuru/evaluator" - "github.com/AvicennaJr/Nuru/lexer" - "github.com/AvicennaJr/Nuru/object" - "github.com/AvicennaJr/Nuru/parser" -) - -const PROMPT = ">>> " -const ERROR_FACE = ` - ███████████████████████████ - ███████▀▀▀░░░░░░░▀▀▀███████ - ████▀░░░░░░░░░░░░░░░░░▀████ - ███│░░░░░░░░░░░░░░░░░░░│███ - ██▌│░░░░░░░░░░░░░░░░░░░│▐██ - ██░└┐░░░░░░░░░░░░░░░░░┌┘░██ - ██░░└┐░░░░░░░░░░░░░░░┌┘░░██ - ██░░┌┘▄▄▄▄▄░░░░░▄▄▄▄▄└┐░░██ - ██▌░│██████▌░░░▐██████│░▐██ - ███░│▐███▀▀░░▄░░▀▀███▌│░███ - ██▀─┘░░░░░░░▐█▌░░░░░░░└─▀██ - ██▄░░░▄▄▄▓░░▀█▀░░▓▄▄▄░░░▄██ - ████▄─┘██▌░░░░░░░▐██└─▄████ - █████░░▐█─┬┬┬┬┬┬┬─█▌░░█████ - ████▌░░░▀┬┼┼┼┼┼┼┼┬▀░░░▐████ - █████▄░░░└┴┴┴┴┴┴┴┘░░░▄█████ - ███████▄░░░░░░░░░░░▄███████ - ██████████▄▄▄▄▄▄▄██████████ - ███████████████████████████ - - █▄▀ █░█ █▄░█ ▄▀█   █▀ █░█ █ █▀▄ ▄▀█ - █░█ █▄█ █░▀█ █▀█   ▄█ █▀█ █ █▄▀ █▀█ - -` - -func Read(contents string) { - env := object.NewEnvironment() - - l := lexer.New(contents) - p := parser.New(l) - - program := p.ParseProgram() - - if len(p.Errors()) != 0 { - fmt.Println(colorfy(ERROR_FACE, 31)) - fmt.Println("Kuna Errors Zifuatazo:") - - for _, msg := range p.Errors() { - fmt.Println("\t" + colorfy(msg, 31)) - } - - } - evaluated := evaluator.Eval(program, env) - if evaluated != nil { - if evaluated.Type() != object.NULL_OBJ { - fmt.Println(colorfy(evaluated.Inspect(), 32)) - } - } - -} - -func Start(in io.Reader, out io.Writer) { - - scanner := bufio.NewScanner(in) - env := object.NewEnvironment() - - for { - fmt.Print(PROMPT) - scanned := scanner.Scan() - if !scanned { - return - } - - line := scanner.Text() - if strings.TrimSpace(line) == "exit()" || strings.TrimSpace(line) == "toka()" { - fmt.Println("✨🅺🅰🆁🅸🅱🆄 🆃🅴🅽🅰✨") - os.Exit(0) - } - l := lexer.New(line) - p := parser.New(l) - - program := p.ParseProgram() - - if len(p.Errors()) != 0 { - printParseErrors(out, p.Errors()) - continue - } - evaluated := evaluator.Eval(program, env) - if evaluated != nil { - if evaluated.Type() != object.NULL_OBJ { - io.WriteString(out, colorfy(evaluated.Inspect(), 32)) - io.WriteString(out, "\n") - } - } - } -} - -func printParseErrors(out io.Writer, errors []string) { - //io.WriteString(out, colorfy(ERROR_FACE, 31)) - io.WriteString(out, "Kuna Errors Zifuatazo:\n") - - for _, msg := range errors { - io.WriteString(out, "\t"+colorfy(msg, 31)+"\n") - } -} - -func colorfy(str string, colorCode int) string { - return fmt.Sprintf("\x1b[%dm%s\x1b[0m", colorCode, str) -} diff --git a/token/token.go b/token/token.go deleted file mode 100644 index bb950f7..0000000 --- a/token/token.go +++ /dev/null @@ -1,101 +0,0 @@ -package token - -type TokenType string - -type Token struct { - Type TokenType - Literal string - Line int -} - -const ( - ILLEGAL = "HARAMU" - EOF = "MWISHO" - - // Identifiers + literals - IDENT = "KITAMBULISHI" - INT = "NAMBA" - STRING = "NENO" - FLOAT = "DESIMALI" - - // Operators - ASSIGN = "=" - PLUS = "+" - MINUS = "-" - BANG = "!" - ASTERISK = "*" - POW = "**" - SLASH = "/" - MODULUS = "%" - LT = "<" - LTE = "<=" - GT = ">" - GTE = ">=" - EQ = "==" - NOT_EQ = "!=" - AND = "&&" - OR = "||" - PLUS_ASSIGN = "+=" - PLUS_PLUS = "++" - MINUS_ASSIGN = "-=" - MINUS_MINUS = "--" - ASTERISK_ASSIGN = "*=" - SLASH_ASSIGN = "/=" - MODULUS_ASSIGN = "%=" - - //Delimiters - COMMA = "," - SEMICOLON = ";" - LPAREN = "(" - RPAREN = ")" - LBRACE = "{" - RBRACE = "}" - LBRACKET = "[" - RBRACKET = "]" - COLON = ":" - - // Keywords - FUNCTION = "FUNCTION" - LET = "FANYA" - TRUE = "KWELI" - FALSE = "SIKWELI" - IF = "KAMA" - ELSE = "SIVYO" - RETURN = "RUDISHA" - WHILE = "WAKATI" - NULL = "TUPU" - BREAK = "VUNJA" - CONTINUE = "ENDELEA" - IN = "KTK" - FOR = "KWA" - SWITCH = "BADILI" - CASE = "IKIWA" - DEFAULT = "KAWAIDA" -) - -var keywords = map[string]TokenType{ - "unda": FUNCTION, - "fanya": LET, - "kweli": TRUE, - "sikweli": FALSE, - "kama": IF, - "au": ELSE, - "sivyo": ELSE, - "wakati": WHILE, - "rudisha": RETURN, - "vunja": BREAK, - "endelea": CONTINUE, - "tupu": NULL, - "ktk": IN, - "kwa": FOR, - "badili": SWITCH, - "ikiwa": CASE, - "kawaida": DEFAULT, -} - -func LookupIdent(ident string) TokenType { - if tok, ok := keywords[ident]; ok { - return tok - } - return IDENT -} From ab07556f608d677aef1311c18647be42019efd36 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Sun, 25 Dec 2022 21:58:46 +0300 Subject: [PATCH 36/59] Add download count --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 46f321e..373949a 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ Nuru Programming Language Nuru Programming Language Nuru Programming Language +
+ Nuru Programming Language Nuru Programming Language
Nuru Programming Language From e530be3b6e847923adbe7b0d38d1ec1da055559e Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Sun, 25 Dec 2022 22:36:23 +0300 Subject: [PATCH 37/59] Update readme --- README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 373949a..206bbff 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,6 @@ Nuru supports both single line and multiple line comments as shown below: // Single line comment /* - Multiple Line Comment @@ -139,9 +138,7 @@ Comment ### Arithmetic Operations -For now Nuru supports `+`, `-`, `/` and `*`. More will be added. The `/` operation will truncate (round to a whole number) as Floating points are not supported yet. - -Nuru also provides precedence of operations using the BODMAS rule: +For now Nuru supports `+`, `-`, `/`, `*` and `%`. More will be added. Nuru also provides precedence of operations using the BODMAS rule: ``` 2 + 2 * 3 // output = 8 @@ -284,6 +281,14 @@ These can iterate over strings, arrays and dictionaries: kwa i ktk "habari" { andika(i) } +/* //output +h +a +b +a +r +i +*/ ``` ### Getting Input From User From a85c8e4d350db1c95b643dc7420775b750ffd786 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Sun, 25 Dec 2022 22:37:39 +0300 Subject: [PATCH 38/59] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 206bbff..d64dcce 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ Comment ### Arithmetic Operations -For now Nuru supports `+`, `-`, `/`, `*` and `%`. More will be added. Nuru also provides precedence of operations using the BODMAS rule: +For now Nuru supports `+`, `-`, `/`, `*` and `%`. Nuru also provides precedence of operations using the BODMAS rule: ``` 2 + 2 * 3 // output = 8 From 4b501caeffda08f2a47f6e35880d6e6fe9283378 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Mon, 26 Dec 2022 15:26:29 +0300 Subject: [PATCH 39/59] Add more tests -> Add ability to parse positive integers eg: +5 -> Add tests for while and for loops -> Fixed typos on ast --- src/ast/ast.go | 30 +++++++++--- src/evaluator/evaluator.go | 14 ++++++ src/parser/parser.go | 1 + src/parser/parser_test.go | 93 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+), 6 deletions(-) diff --git a/src/ast/ast.go b/src/ast/ast.go index 810ccfd..9505542 100644 --- a/src/ast/ast.go +++ b/src/ast/ast.go @@ -179,13 +179,13 @@ func (ie *IfExpression) expressionNode() {} func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal } func (ie *IfExpression) String() string { var out bytes.Buffer - out.WriteString("if") + out.WriteString("kama") out.WriteString(ie.Condition.String()) out.WriteString(" ") out.WriteString(ie.Consequence.String()) if ie.Alternative != nil { - out.WriteString("else") + out.WriteString("sivyo") out.WriteString(ie.Alternative.String()) } @@ -360,7 +360,7 @@ func (we *WhileExpression) TokenLiteral() string { return we.Token.Literal } func (we *WhileExpression) String() string { var out bytes.Buffer - out.WriteString("while") + out.WriteString("wakati") out.WriteString(we.Condition.String()) out.WriteString(" ") out.WriteString(we.Consequence.String()) @@ -439,6 +439,24 @@ type ForIn struct { Block *BlockStatement } +func (fi *ForIn) expressionNode() {} +func (fi *ForIn) TokenLiteral() string { return fi.Token.Literal } +func (fi *ForIn) String() string { + var out bytes.Buffer + + out.WriteString("kwa ") + if fi.Key != "" { + out.WriteString(fi.Key + ", ") + } + out.WriteString(fi.Value + " ") + out.WriteString("ktk ") + out.WriteString(fi.Iterable.String() + " {\n") + out.WriteString("\t" + fi.Block.String()) + out.WriteString("\n}") + + return out.String() +} + type CaseExpression struct { Token token.Token Default bool @@ -452,9 +470,9 @@ func (ce *CaseExpression) String() string { var out bytes.Buffer if ce.Default { - out.WriteString("default ") + out.WriteString("kawaida ") } else { - out.WriteString("case ") + out.WriteString("ikiwa ") tmp := []string{} for _, exp := range ce.Expr { @@ -476,7 +494,7 @@ func (se *SwitchExpression) expressionNode() {} func (se *SwitchExpression) TokenLiteral() string { return se.Token.Literal } func (se *SwitchExpression) String() string { var out bytes.Buffer - out.WriteString("\nswitch (") + out.WriteString("\nbadili (") out.WriteString(se.Value.String()) out.WriteString(")\n{\n") diff --git a/src/evaluator/evaluator.go b/src/evaluator/evaluator.go index 4e81541..6aedefb 100644 --- a/src/evaluator/evaluator.go +++ b/src/evaluator/evaluator.go @@ -226,6 +226,8 @@ func evalPrefixExpression(operator string, right object.Object, line int) object return evalBangOperatorExpression(right) case "-": return evalMinusPrefixOperatorExpression(right, line) + case "+": + return evalPlusPrefixOperatorExpression(right, line) default: return newError("Mstari %d: Operesheni haieleweki: %s%s", line, operator, right.Type()) } @@ -257,7 +259,19 @@ func evalMinusPrefixOperatorExpression(right object.Object, line int) object.Obj return newError("Mstari %d: Operesheni Haielweki: -%s", line, right.Type()) } } +func evalPlusPrefixOperatorExpression(right object.Object, line int) object.Object { + switch obj := right.(type) { + + case *object.Integer: + return &object.Integer{Value: obj.Value} + + case *object.Float: + return &object.Float{Value: obj.Value} + default: + return newError("Mstari %d: Operesheni Haielweki: -%s", line, right.Type()) + } +} func evalInfixExpression(operator string, left, right object.Object, line int) object.Object { if left == nil { return newError("Mstari %d: Umekosea hapa", line) diff --git a/src/parser/parser.go b/src/parser/parser.go index ba09966..a258279 100644 --- a/src/parser/parser.go +++ b/src/parser/parser.go @@ -87,6 +87,7 @@ func New(l *lexer.Lexer) *Parser { p.registerPrefix(token.FLOAT, p.parseFloatLiteral) p.registerPrefix(token.BANG, p.parsePrefixExpression) p.registerPrefix(token.MINUS, p.parsePrefixExpression) + p.registerPrefix(token.PLUS, p.parsePrefixExpression) p.registerPrefix(token.TRUE, p.parseBoolean) p.registerPrefix(token.FALSE, p.parseBoolean) p.registerPrefix(token.LPAREN, p.parseGroupedExpression) diff --git a/src/parser/parser_test.go b/src/parser/parser_test.go index e205a4d..631f765 100644 --- a/src/parser/parser_test.go +++ b/src/parser/parser_test.go @@ -968,3 +968,96 @@ func TestParsingEmptyDict(t *testing.T) { t.Errorf("Dict pairs has wrong length, got=%d", len(dict.Pairs)) } } + +func TestWhileLoop(t *testing.T) { + input := `wakati ( x > y ) { fanya x = 2 }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Body does not contain %d statements. got=%d", 1, len(program.Statements)) + } + + fmt.Println(program.Statements) + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.Expression, got=%T", program.Statements[0]) + } + exp, ok := stmt.Expression.(*ast.WhileExpression) + + if !ok { + t.Fatalf("stmt.Expression is not ast.WhileExpression. got=%T", stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", ">", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("Consequence is not 1 statements. got=%d\n", len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.LetStatement) + + if !ok { + t.Fatalf("exp.Consequence.Statements[0] is not ast.ExpressionStatement. got=%T", exp.Consequence.Statements[0]) + } + + if !testLetStatement(t, consequence, "x") { + t.Fatalf("exp.Consequence is not LetStatement") + } +} + +func TestShorthandAssignment(t *testing.T) { + input := []string{ + "fanya x = 10; x *= 20;", + "fanya x = 5; x += 4;", + "fanya x = 7; x /= 2;", + "fanya x = 8; x -= 1;", + "fanya x = 5; x++;", + "fanya x = 3; x--;", + "fanya x = 40; fanya y = 13; x += y;"} + + for _, txt := range input { + l := lexer.New(txt) + p := New(l) + _ = p.ParseProgram() + checkParserErrors(t, p) + } +} + +func TestForExpression(t *testing.T) { + input := `kwa i, v ktk j {andika(i)}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Body does not contain %d statements. got=%d", 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.Expression, got=%T", program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.ForIn) + + if !ok { + t.Fatalf("stmt.Expression is not ast.ForIn. got=%T", stmt.Expression) + } + + if exp.Key != "i" { + t.Fatalf("Wrong Key Index, expected 'i' got %s", exp.Key) + } + + if exp.Value != "v" { + t.Fatalf("Wrong Value Index, expected 'v' got %s", exp.Value) + } +} From 85f57cdb911e0d59ecd9a1e70dbe32a8f4062419 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Thu, 29 Dec 2022 21:12:47 +0300 Subject: [PATCH 40/59] Updated windows installation guide --- .gitignore | 2 -- README.md | 21 +++------------------ 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index 66b461b..37bb5b4 100644 --- a/.gitignore +++ b/.gitignore @@ -48,7 +48,5 @@ _testmain.go src/testbinaries src/tests_random -src/parser/tracingZeAnnoyingParser.go -src/testTracing.go src/nuru Notes.md \ No newline at end of file diff --git a/README.md b/README.md index d64dcce..cd0f42c 100644 --- a/README.md +++ b/README.md @@ -62,24 +62,9 @@ nuru -v ### Windows - - Make a bin directory if it doesn't exist: - -``` -mkdir C:\bin -``` - - Download the Nuru Program [Here](https://github.com/AvicennaJr/Nuru/releases/download/v0.2.0/nuru_windows_amd64_v0.2.0.exe) - - Rename the downloaded program from `nuru_windows_amd64_v0.2.0.exe` to `nuru.exe` - - Move the file `nuru.exe` to the folder `C:\bin` - - Add the bin folder to Path with this command: - -``` -setx PATH "C:\bin;%PATH%" -``` - - Confirm installation with: - -``` -nuru -v -``` + - Download the Nuru Installer [Here](https://github.com/AvicennaJr/Nuru/releases/download/v0.2.0/Nuru_Windows_Installer_v0.2.0.exe) + - Install the downloaded installer + - You can watch a full video guide [Here](https://youtu.be/T-lfaoqIFD4) ### Building From Source From e00c40da63dbbffe313fd7bb8e393bc2ad71582e Mon Sep 17 00:00:00 2001 From: Hopertz Date: Sun, 1 Jan 2023 19:49:17 +0300 Subject: [PATCH 41/59] Update error message --- src/object/object.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/object/object.go b/src/object/object.go index a6a2ddf..113341d 100644 --- a/src/object/object.go +++ b/src/object/object.go @@ -78,7 +78,7 @@ type Error struct { } func (e *Error) Inspect() string { - msg := fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, "ERROR: ") + msg := fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, "Kosa: ") return msg + e.Message } func (e *Error) Type() ObjectType { return ERROR_OBJ } From 22a584d9dbfeafcff5da305f25980ee24d9b9971 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Sun, 1 Jan 2023 22:59:51 +0300 Subject: [PATCH 42/59] Refactoring Array All parser functions should be separated following this convention --- src/parser/parseArray.go | 14 ++++++++++++++ src/parser/parser.go | 8 -------- 2 files changed, 14 insertions(+), 8 deletions(-) create mode 100644 src/parser/parseArray.go diff --git a/src/parser/parseArray.go b/src/parser/parseArray.go new file mode 100644 index 0000000..a58f713 --- /dev/null +++ b/src/parser/parseArray.go @@ -0,0 +1,14 @@ +package parser + +import ( + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/token" +) + +func (p *Parser) parseArrayLiteral() ast.Expression { + array := &ast.ArrayLiteral{Token: p.curToken} + + array.Elements = p.parseExpressionList(token.RBRACKET) + + return array +} diff --git a/src/parser/parser.go b/src/parser/parser.go index a258279..055ad0a 100644 --- a/src/parser/parser.go +++ b/src/parser/parser.go @@ -532,14 +532,6 @@ func (p *Parser) parseStringLiteral() ast.Expression { return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal} } -func (p *Parser) parseArrayLiteral() ast.Expression { - array := &ast.ArrayLiteral{Token: p.curToken} - - array.Elements = p.parseExpressionList(token.RBRACKET) - - return array -} - func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression { list := []ast.Expression{} From 30ae4f71d4bcaf76e72561c5271561cc13afaa85 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Sun, 1 Jan 2023 23:23:06 +0300 Subject: [PATCH 43/59] Add swahili documentation --- docs/{ => en}/README.md | 0 docs/{ => en}/arrays.md | 0 docs/{ => en}/bool.md | 0 docs/{ => en}/builtins.md | 0 docs/{ => en}/comments.md | 0 docs/{ => en}/dictionaries.md | 0 docs/{ => en}/for.md | 0 docs/{ => en}/function.md | 0 docs/{ => en}/identifiers.md | 0 docs/{ => en}/ifStatements.md | 0 docs/{ => en}/keywords.md | 0 docs/{ => en}/null.md | 0 docs/{ => en}/numbers.md | 0 docs/{ => en}/operators.md | 0 docs/{ => en}/strings.md | 0 docs/{ => en}/switch.md | 0 docs/{ => en}/while.md | 0 docs/sw/README.md | 1 + docs/sw/arrays.md | 1 + docs/sw/bools.md | 1 + docs/sw/builtins.md | 1 + docs/sw/comments.md | 1 + docs/sw/dictionaries.md | 1 + docs/sw/for.md | 1 + docs/sw/functions.md | 1 + docs/sw/identifiers.md | 1 + docs/sw/if.md | 1 + docs/sw/keywords.md | 1 + docs/sw/null.md | 1 + docs/sw/numbers.md | 1 + docs/sw/operators.md | 1 + docs/sw/strings.md | 1 + docs/sw/switch.md | 1 + docs/sw/while.md | 1 + 34 files changed, 17 insertions(+) rename docs/{ => en}/README.md (100%) rename docs/{ => en}/arrays.md (100%) rename docs/{ => en}/bool.md (100%) rename docs/{ => en}/builtins.md (100%) rename docs/{ => en}/comments.md (100%) rename docs/{ => en}/dictionaries.md (100%) rename docs/{ => en}/for.md (100%) rename docs/{ => en}/function.md (100%) rename docs/{ => en}/identifiers.md (100%) rename docs/{ => en}/ifStatements.md (100%) rename docs/{ => en}/keywords.md (100%) rename docs/{ => en}/null.md (100%) rename docs/{ => en}/numbers.md (100%) rename docs/{ => en}/operators.md (100%) rename docs/{ => en}/strings.md (100%) rename docs/{ => en}/switch.md (100%) rename docs/{ => en}/while.md (100%) create mode 100644 docs/sw/README.md create mode 100644 docs/sw/arrays.md create mode 100644 docs/sw/bools.md create mode 100644 docs/sw/builtins.md create mode 100644 docs/sw/comments.md create mode 100644 docs/sw/dictionaries.md create mode 100644 docs/sw/for.md create mode 100644 docs/sw/functions.md create mode 100644 docs/sw/identifiers.md create mode 100644 docs/sw/if.md create mode 100644 docs/sw/keywords.md create mode 100644 docs/sw/null.md create mode 100644 docs/sw/numbers.md create mode 100644 docs/sw/operators.md create mode 100644 docs/sw/strings.md create mode 100644 docs/sw/switch.md create mode 100644 docs/sw/while.md diff --git a/docs/README.md b/docs/en/README.md similarity index 100% rename from docs/README.md rename to docs/en/README.md diff --git a/docs/arrays.md b/docs/en/arrays.md similarity index 100% rename from docs/arrays.md rename to docs/en/arrays.md diff --git a/docs/bool.md b/docs/en/bool.md similarity index 100% rename from docs/bool.md rename to docs/en/bool.md diff --git a/docs/builtins.md b/docs/en/builtins.md similarity index 100% rename from docs/builtins.md rename to docs/en/builtins.md diff --git a/docs/comments.md b/docs/en/comments.md similarity index 100% rename from docs/comments.md rename to docs/en/comments.md diff --git a/docs/dictionaries.md b/docs/en/dictionaries.md similarity index 100% rename from docs/dictionaries.md rename to docs/en/dictionaries.md diff --git a/docs/for.md b/docs/en/for.md similarity index 100% rename from docs/for.md rename to docs/en/for.md diff --git a/docs/function.md b/docs/en/function.md similarity index 100% rename from docs/function.md rename to docs/en/function.md diff --git a/docs/identifiers.md b/docs/en/identifiers.md similarity index 100% rename from docs/identifiers.md rename to docs/en/identifiers.md diff --git a/docs/ifStatements.md b/docs/en/ifStatements.md similarity index 100% rename from docs/ifStatements.md rename to docs/en/ifStatements.md diff --git a/docs/keywords.md b/docs/en/keywords.md similarity index 100% rename from docs/keywords.md rename to docs/en/keywords.md diff --git a/docs/null.md b/docs/en/null.md similarity index 100% rename from docs/null.md rename to docs/en/null.md diff --git a/docs/numbers.md b/docs/en/numbers.md similarity index 100% rename from docs/numbers.md rename to docs/en/numbers.md diff --git a/docs/operators.md b/docs/en/operators.md similarity index 100% rename from docs/operators.md rename to docs/en/operators.md diff --git a/docs/strings.md b/docs/en/strings.md similarity index 100% rename from docs/strings.md rename to docs/en/strings.md diff --git a/docs/switch.md b/docs/en/switch.md similarity index 100% rename from docs/switch.md rename to docs/en/switch.md diff --git a/docs/while.md b/docs/en/while.md similarity index 100% rename from docs/while.md rename to docs/en/while.md diff --git a/docs/sw/README.md b/docs/sw/README.md new file mode 100644 index 0000000..698fdad --- /dev/null +++ b/docs/sw/README.md @@ -0,0 +1 @@ +# NURU PROGRAMMING LANGUAGE DOCUMENTATION \ No newline at end of file diff --git a/docs/sw/arrays.md b/docs/sw/arrays.md new file mode 100644 index 0000000..bbc6434 --- /dev/null +++ b/docs/sw/arrays.md @@ -0,0 +1 @@ +# Orodha (Arrays) \ No newline at end of file diff --git a/docs/sw/bools.md b/docs/sw/bools.md new file mode 100644 index 0000000..16f7a44 --- /dev/null +++ b/docs/sw/bools.md @@ -0,0 +1 @@ +# Kweli/Sikweli (Bools) diff --git a/docs/sw/builtins.md b/docs/sw/builtins.md new file mode 100644 index 0000000..f807886 --- /dev/null +++ b/docs/sw/builtins.md @@ -0,0 +1 @@ +# Builtins \ No newline at end of file diff --git a/docs/sw/comments.md b/docs/sw/comments.md new file mode 100644 index 0000000..2a30807 --- /dev/null +++ b/docs/sw/comments.md @@ -0,0 +1 @@ +# Maelezo (Comments) \ No newline at end of file diff --git a/docs/sw/dictionaries.md b/docs/sw/dictionaries.md new file mode 100644 index 0000000..469ad6d --- /dev/null +++ b/docs/sw/dictionaries.md @@ -0,0 +1 @@ +# Kamusi (Dictionaries) \ No newline at end of file diff --git a/docs/sw/for.md b/docs/sw/for.md new file mode 100644 index 0000000..52b9109 --- /dev/null +++ b/docs/sw/for.md @@ -0,0 +1 @@ +# Kwa (For) \ No newline at end of file diff --git a/docs/sw/functions.md b/docs/sw/functions.md new file mode 100644 index 0000000..ed2c3d8 --- /dev/null +++ b/docs/sw/functions.md @@ -0,0 +1 @@ +# Undo (Functions) \ No newline at end of file diff --git a/docs/sw/identifiers.md b/docs/sw/identifiers.md new file mode 100644 index 0000000..d89ef81 --- /dev/null +++ b/docs/sw/identifiers.md @@ -0,0 +1 @@ +# Tambulishi (Identifiers) \ No newline at end of file diff --git a/docs/sw/if.md b/docs/sw/if.md new file mode 100644 index 0000000..a364c61 --- /dev/null +++ b/docs/sw/if.md @@ -0,0 +1 @@ +# Kama/Sivyo (If/Else) \ No newline at end of file diff --git a/docs/sw/keywords.md b/docs/sw/keywords.md new file mode 100644 index 0000000..5160a95 --- /dev/null +++ b/docs/sw/keywords.md @@ -0,0 +1 @@ +# Maneno Muhimu (Keywords) \ No newline at end of file diff --git a/docs/sw/null.md b/docs/sw/null.md new file mode 100644 index 0000000..628c65e --- /dev/null +++ b/docs/sw/null.md @@ -0,0 +1 @@ +# Tupu (Null) \ No newline at end of file diff --git a/docs/sw/numbers.md b/docs/sw/numbers.md new file mode 100644 index 0000000..dd4ec8b --- /dev/null +++ b/docs/sw/numbers.md @@ -0,0 +1 @@ +# Namba na Desimali (Ints/Floats) \ No newline at end of file diff --git a/docs/sw/operators.md b/docs/sw/operators.md new file mode 100644 index 0000000..cf38456 --- /dev/null +++ b/docs/sw/operators.md @@ -0,0 +1 @@ +# Matendaji (Operators) \ No newline at end of file diff --git a/docs/sw/strings.md b/docs/sw/strings.md new file mode 100644 index 0000000..0185efb --- /dev/null +++ b/docs/sw/strings.md @@ -0,0 +1 @@ +# Neno (Strings) \ No newline at end of file diff --git a/docs/sw/switch.md b/docs/sw/switch.md new file mode 100644 index 0000000..ec1fc19 --- /dev/null +++ b/docs/sw/switch.md @@ -0,0 +1 @@ +# Badili (Switch) \ No newline at end of file diff --git a/docs/sw/while.md b/docs/sw/while.md new file mode 100644 index 0000000..e022883 --- /dev/null +++ b/docs/sw/while.md @@ -0,0 +1 @@ +# Wakati (While) \ No newline at end of file From 05450cadc64b6de16a35e7798a026e7995cd15e1 Mon Sep 17 00:00:00 2001 From: Hopertz Date: Mon, 2 Jan 2023 12:33:39 +0300 Subject: [PATCH 44/59] Add jumla --- docs/en/builtins.md | 8 +++++++ src/evaluator/builtins.go | 41 +++++++++++++++++++++++++++++++++ src/evaluator/evaluator_test.go | 28 +++++++++++++++++++++- 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/docs/en/builtins.md b/docs/en/builtins.md index c27af01..43925d9 100644 --- a/docs/en/builtins.md +++ b/docs/en/builtins.md @@ -39,6 +39,14 @@ aina(2) // NAMBA idadi("mambo") // 5 ``` +### jumla() + +`jumla` is a function that gives the sum of numbers (integers/floats) in a list. It accepts only one argument which is a `list` of numbers : +``` +jumla([1,2,3,4]) // 10 +``` + + ### sukuma() `sukuma()` is a function that adds a new element to an array. The function accepts two arguments, the first must be a list and the second is the element to be added/appended: diff --git a/src/evaluator/builtins.go b/src/evaluator/builtins.go index 6a24052..a658efe 100644 --- a/src/evaluator/builtins.go +++ b/src/evaluator/builtins.go @@ -4,7 +4,9 @@ import ( "bufio" "fmt" "io" + "math" "os" + "strconv" "strings" "github.com/AvicennaJr/Nuru/object" @@ -27,6 +29,45 @@ var builtins = map[string]*object.Builtin{ } }, }, + "jumla": { + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("Hoja hazilingani, tunahitaji=1, tumepewa=%d", len(args)) + } + + switch arg := args[0].(type) { + case *object.Array: + + var sums float64 + for _,num := range arg.Elements { + + if num.Type() != object.INTEGER_OBJ && num.Type() != object.FLOAT_OBJ{ + return newError("Samahani namba tu zinahitajika") + }else{ + if num.Type() == object.INTEGER_OBJ{ + no , _ := strconv.Atoi(num.Inspect()) + floatnum := float64(no) + sums += floatnum + }else if num.Type() == object.FLOAT_OBJ { + no , _ := strconv.ParseFloat(num.Inspect(), 64) + sums += no + } + + + } + } + + if math.Mod(sums,1) == 0 { + return &object.Integer{Value : int64(sums)} + } + + return &object.Float {Value: float64(sums)} + + default: + return newError("Samahani, hii function haitumiki na %s", args[0].Type()) + } + }, + }, "yamwisho": { Fn: func(args ...object.Object) object.Object { if len(args) != 1 { diff --git a/src/evaluator/evaluator_test.go b/src/evaluator/evaluator_test.go index 8fe8458..4bd2201 100644 --- a/src/evaluator/evaluator_test.go +++ b/src/evaluator/evaluator_test.go @@ -102,6 +102,22 @@ func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { return true } +func testFloatObject(t *testing.T, obj object.Object, expected float64) bool { + result, ok := obj.(*object.Float) + + if !ok { + t.Errorf("Object is not Float, got=%T(%+v)", obj, obj) + return false + } + + if result.Value != expected { + t.Errorf("object has wrong value. got=%f, want=%f", result.Value, expected) + return false + } + + return true +} + func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool { result, ok := obj.(*object.Boolean) if !ok { @@ -346,14 +362,24 @@ func TestBuiltinFunctions(t *testing.T) { {`idadi("hello world")`, 11}, {`idadi(1)`, "Samahani, hii function haitumiki na NAMBA"}, {`idadi("one", "two")`, "Hoja hazilingani, tunahitaji=1, tumepewa=2"}, + {`jumla()`, "Hoja hazilingani, tunahitaji=1, tumepewa=0"}, + {`jumla("")`, "Samahani, hii function haitumiki na NENO"}, + {`jumla(1)`, "Samahani, hii function haitumiki na NAMBA"}, + {`jumla([1,2,3])`, 6}, + {`jumla([1,2,3.4])`, 6.4}, + {`jumla([1.1,2.5,3.4])`, 7}, + {`jumla([1.1,2.5,"q"])`, "Samahani namba tu zinahitajika"}, } for _, tt := range tests { evaluated := testEval(tt.input) - + switch expected := tt.expected.(type) { case int: testIntegerObject(t, evaluated, int64(expected)) + case float64: + testFloatObject(t, evaluated, float64(expected)) + case string: errObj, ok := evaluated.(*object.Error) if !ok { From d233056c00711dd16cbd7ce2cb8312c88e9df980 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Mon, 2 Jan 2023 14:27:56 +0300 Subject: [PATCH 45/59] Add detailed contribution --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cd0f42c..5fae61a 100644 --- a/README.md +++ b/README.md @@ -315,7 +315,13 @@ Kindly open an [Issue](https://github.com/AvicennaJr/Nuru/issues) to make sugges ## Contributions -All contributions are welcomed. Clone the repo, hack it, make sure all tests are passing then submit a pull request. +### Documentation + +There are documentations for two languages, English and Kiswahili, which are both under the `docs` folder. All files are written in markdown. Feel free to contribute by making a pull request. + +### Code + +Clone the repo, hack it, make sure all tests are passing then submit a pull request. ## License From e4dbd6f4654181f9e304f23f644b00b984dbc15f Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Tue, 3 Jan 2023 16:41:38 +0300 Subject: [PATCH 46/59] Refactored parsers --- src/parser/arrays.go | 37 +++ src/parser/assignment.go | 30 ++ src/parser/boolean.go | 10 + src/parser/break.go | 14 + src/parser/continue.go | 14 + src/parser/dict.go | 35 +++ src/parser/float.go | 20 ++ src/parser/for.go | 92 +++++++ src/parser/function.go | 57 ++++ src/parser/identifier.go | 9 + src/parser/if.go | 50 ++++ src/parser/index.go | 18 ++ src/parser/integer.go | 22 ++ src/parser/null.go | 9 + src/parser/parseArray.go | 14 - src/parser/parser.go | 580 +++------------------------------------ src/parser/statements.go | 81 ++++++ src/parser/string.go | 9 + src/parser/switch.go | 89 ++++++ src/parser/while.go | 29 ++ 20 files changed, 669 insertions(+), 550 deletions(-) create mode 100644 src/parser/arrays.go create mode 100644 src/parser/assignment.go create mode 100644 src/parser/boolean.go create mode 100644 src/parser/break.go create mode 100644 src/parser/continue.go create mode 100644 src/parser/dict.go create mode 100644 src/parser/float.go create mode 100644 src/parser/for.go create mode 100644 src/parser/function.go create mode 100644 src/parser/identifier.go create mode 100644 src/parser/if.go create mode 100644 src/parser/index.go create mode 100644 src/parser/integer.go create mode 100644 src/parser/null.go delete mode 100644 src/parser/parseArray.go create mode 100644 src/parser/statements.go create mode 100644 src/parser/string.go create mode 100644 src/parser/switch.go create mode 100644 src/parser/while.go diff --git a/src/parser/arrays.go b/src/parser/arrays.go new file mode 100644 index 0000000..119a903 --- /dev/null +++ b/src/parser/arrays.go @@ -0,0 +1,37 @@ +package parser + +import ( + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/token" +) + +func (p *Parser) parseArrayLiteral() ast.Expression { + array := &ast.ArrayLiteral{Token: p.curToken} + + array.Elements = p.parseExpressionList(token.RBRACKET) + + return array +} + +func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression { + list := []ast.Expression{} + + if p.peekTokenIs(end) { + p.nextToken() + return list + } + + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + } + + if !p.expectPeek(end) { + return nil + } + return list +} diff --git a/src/parser/assignment.go b/src/parser/assignment.go new file mode 100644 index 0000000..72a8a20 --- /dev/null +++ b/src/parser/assignment.go @@ -0,0 +1,30 @@ +package parser + +import ( + "fmt" + + "github.com/AvicennaJr/Nuru/ast" +) + +func (p *Parser) parseAssignmentExpression(exp ast.Expression) ast.Expression { + switch node := exp.(type) { + case *ast.Identifier, *ast.IndexExpression: + default: + if node != nil { + msg := fmt.Sprintf("Mstari %d:Tulitegemea kupata kitambulishi au array, badala yake tumepata: %s", p.curToken.Line, node.TokenLiteral()) + p.errors = append(p.errors, msg) + } else { + msg := fmt.Sprintf("Mstari %d: Umekosea mkuu", p.curToken.Line) + p.errors = append(p.errors, msg) + } + return nil + } + + ae := &ast.AssignmentExpression{Token: p.curToken, Left: exp} + + p.nextToken() + + ae.Value = p.parseExpression(LOWEST) + + return ae +} diff --git a/src/parser/boolean.go b/src/parser/boolean.go new file mode 100644 index 0000000..9ce95e6 --- /dev/null +++ b/src/parser/boolean.go @@ -0,0 +1,10 @@ +package parser + +import ( + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/token" +) + +func (p *Parser) parseBoolean() ast.Expression { + return &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)} +} diff --git a/src/parser/break.go b/src/parser/break.go new file mode 100644 index 0000000..6c4c8ef --- /dev/null +++ b/src/parser/break.go @@ -0,0 +1,14 @@ +package parser + +import ( + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/token" +) + +func (p *Parser) parseBreak() *ast.Break { + stmt := &ast.Break{Token: p.curToken} + for p.curTokenIs(token.SEMICOLON) { + p.nextToken() + } + return stmt +} diff --git a/src/parser/continue.go b/src/parser/continue.go new file mode 100644 index 0000000..a66b85f --- /dev/null +++ b/src/parser/continue.go @@ -0,0 +1,14 @@ +package parser + +import ( + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/token" +) + +func (p *Parser) parseContinue() *ast.Continue { + stmt := &ast.Continue{Token: p.curToken} + for p.curTokenIs(token.SEMICOLON) { + p.nextToken() + } + return stmt +} diff --git a/src/parser/dict.go b/src/parser/dict.go new file mode 100644 index 0000000..4b3b205 --- /dev/null +++ b/src/parser/dict.go @@ -0,0 +1,35 @@ +package parser + +import ( + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/token" +) + +func (p *Parser) parseDictLiteral() ast.Expression { + dict := &ast.DictLiteral{Token: p.curToken} + dict.Pairs = make(map[ast.Expression]ast.Expression) + + for !p.peekTokenIs(token.RBRACE) { + p.nextToken() + key := p.parseExpression(LOWEST) + + if !p.expectPeek(token.COLON) { + return nil + } + + p.nextToken() + value := p.parseExpression(LOWEST) + + dict.Pairs[key] = value + + if !p.peekTokenIs(token.RBRACE) && !p.expectPeek(token.COMMA) { + return nil + } + } + + if !p.expectPeek(token.RBRACE) { + return nil + } + + return dict +} diff --git a/src/parser/float.go b/src/parser/float.go new file mode 100644 index 0000000..77f6075 --- /dev/null +++ b/src/parser/float.go @@ -0,0 +1,20 @@ +package parser + +import ( + "fmt" + "strconv" + + "github.com/AvicennaJr/Nuru/ast" +) + +func (p *Parser) parseFloatLiteral() ast.Expression { + fl := &ast.FloatLiteral{Token: p.curToken} + value, err := strconv.ParseFloat(p.curToken.Literal, 64) + if err != nil { + msg := fmt.Sprintf("Mstari %d: Hatuwezi kuparse %q kama desimali", p.curToken.Line, p.curToken.Literal) + p.errors = append(p.errors, msg) + return nil + } + fl.Value = value + return fl +} diff --git a/src/parser/for.go b/src/parser/for.go new file mode 100644 index 0000000..7e36038 --- /dev/null +++ b/src/parser/for.go @@ -0,0 +1,92 @@ +package parser + +import ( + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/token" +) + +func (p *Parser) parseForExpression() ast.Expression { + expression := &ast.For{Token: p.curToken} + p.nextToken() + if !p.curTokenIs(token.IDENT) { + return nil + } + if !p.peekTokenIs(token.ASSIGN) { + return p.parseForInExpression(expression) + } + + // In future will allow: kwa i = 0; i<10; i++ {andika(i)} + // expression.Identifier = p.curToken.Literal + // expression.StarterName = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + // if expression.StarterName == nil { + // return nil + // } + // if !p.expectPeek(token.ASSIGN) { + // return nil + // } + + // p.nextToken() + + // expression.StarterValue = p.parseExpression(LOWEST) + // // expression.Starter = p.parseExpression(LOWEST) + // if expression.StarterValue == nil { + // return nil + // } + // p.nextToken() + // for p.curTokenIs(token.SEMICOLON) { + // p.nextToken() + // } + // expression.Condition = p.parseExpression(LOWEST) + // if expression.Condition == nil { + // return nil + // } + // p.nextToken() + // for p.curTokenIs(token.SEMICOLON) { + // p.nextToken() + // } + // expression.Closer = p.parseExpression(LOWEST) + // if expression.Closer == nil { + // return nil + // } + // p.nextToken() + // for p.curTokenIs(token.SEMICOLON) { + // p.nextToken() + // } + // if !p.curTokenIs(token.LBRACE) { + // return nil + // } + // expression.Block = p.parseBlockStatement() + // return expression + return nil +} + +func (p *Parser) parseForInExpression(initialExpression *ast.For) ast.Expression { + expression := &ast.ForIn{Token: initialExpression.Token} + if !p.curTokenIs(token.IDENT) { + return nil + } + val := p.curToken.Literal + var key string + p.nextToken() + if p.curTokenIs(token.COMMA) { + p.nextToken() + if !p.curTokenIs(token.IDENT) { + return nil + } + key = val + val = p.curToken.Literal + p.nextToken() + } + expression.Key = key + expression.Value = val + if !p.curTokenIs(token.IN) { + return nil + } + p.nextToken() + expression.Iterable = p.parseExpression(LOWEST) + if !p.expectPeek(token.LBRACE) { + return nil + } + expression.Block = p.parseBlockStatement() + return expression +} diff --git a/src/parser/function.go b/src/parser/function.go new file mode 100644 index 0000000..a1d7dd7 --- /dev/null +++ b/src/parser/function.go @@ -0,0 +1,57 @@ +package parser + +import ( + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/token" +) + +func (p *Parser) parseFunctionLiteral() ast.Expression { + lit := &ast.FunctionLiteral{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + lit.Parameters = p.parseFunctionParameters() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + lit.Body = p.parseBlockStatement() + + return lit +} + +func (p *Parser) parseFunctionParameters() []*ast.Identifier { + identifiers := []*ast.Identifier{} + + if p.peekTokenIs(token.RPAREN) { + p.nextToken() + return identifiers + } + + p.nextToken() + + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + } + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return identifiers +} + +func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression { + exp := &ast.CallExpression{Token: p.curToken, Function: function} + exp.Arguments = p.parseExpressionList(token.RPAREN) + return exp +} diff --git a/src/parser/identifier.go b/src/parser/identifier.go new file mode 100644 index 0000000..d9d5b7e --- /dev/null +++ b/src/parser/identifier.go @@ -0,0 +1,9 @@ +package parser + +import ( + "github.com/AvicennaJr/Nuru/ast" +) + +func (p *Parser) parseIdentifier() ast.Expression { + return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} +} diff --git a/src/parser/if.go b/src/parser/if.go new file mode 100644 index 0000000..b3eccc6 --- /dev/null +++ b/src/parser/if.go @@ -0,0 +1,50 @@ +package parser + +import ( + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/token" +) + +func (p *Parser) parseIfExpression() ast.Expression { + expression := &ast.IfExpression{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + p.nextToken() + expression.Condition = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Consequence = p.parseBlockStatement() + + if p.peekTokenIs(token.ELSE) { + p.nextToken() + if p.peekTokenIs(token.IF) { + p.nextToken() + expression.Alternative = &ast.BlockStatement{ + Statements: []ast.Statement{ + &ast.ExpressionStatement{ + Expression: p.parseIfExpression(), + }, + }, + } + return expression + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Alternative = p.parseBlockStatement() + } + + return expression +} diff --git a/src/parser/index.go b/src/parser/index.go new file mode 100644 index 0000000..9104bc6 --- /dev/null +++ b/src/parser/index.go @@ -0,0 +1,18 @@ +package parser + +import ( + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/token" +) + +func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression { + exp := &ast.IndexExpression{Token: p.curToken, Left: left} + + p.nextToken() + exp.Index = p.parseExpression(LOWEST) + if !p.expectPeek(token.RBRACKET) { + return nil + } + + return exp +} diff --git a/src/parser/integer.go b/src/parser/integer.go new file mode 100644 index 0000000..c35bcd2 --- /dev/null +++ b/src/parser/integer.go @@ -0,0 +1,22 @@ +package parser + +import ( + "fmt" + "strconv" + + "github.com/AvicennaJr/Nuru/ast" +) + +func (p *Parser) parseIntegerLiteral() ast.Expression { + lit := &ast.IntegerLiteral{Token: p.curToken} + + value, err := strconv.ParseInt(p.curToken.Literal, 0, 64) + if err != nil { + msg := fmt.Sprintf("Mstari %d: Hatuwezi kuparse %q kama namba", p.curToken.Line, p.curToken.Literal) + p.errors = append(p.errors, msg) + return nil + } + lit.Value = value + + return lit +} diff --git a/src/parser/null.go b/src/parser/null.go new file mode 100644 index 0000000..2f4c869 --- /dev/null +++ b/src/parser/null.go @@ -0,0 +1,9 @@ +package parser + +import ( + "github.com/AvicennaJr/Nuru/ast" +) + +func (p *Parser) parseNull() ast.Expression { + return &ast.Null{Token: p.curToken} +} diff --git a/src/parser/parseArray.go b/src/parser/parseArray.go deleted file mode 100644 index a58f713..0000000 --- a/src/parser/parseArray.go +++ /dev/null @@ -1,14 +0,0 @@ -package parser - -import ( - "github.com/AvicennaJr/Nuru/ast" - "github.com/AvicennaJr/Nuru/token" -) - -func (p *Parser) parseArrayLiteral() ast.Expression { - array := &ast.ArrayLiteral{Token: p.curToken} - - array.Elements = p.parseExpressionList(token.RBRACKET) - - return array -} diff --git a/src/parser/parser.go b/src/parser/parser.go index 055ad0a..ae42caf 100644 --- a/src/parser/parser.go +++ b/src/parser/parser.go @@ -2,7 +2,6 @@ package parser import ( "fmt" - "strconv" "github.com/AvicennaJr/Nuru/ast" "github.com/AvicennaJr/Nuru/lexer" @@ -73,6 +72,18 @@ type Parser struct { postfixParseFns map[token.TokenType]postfixParseFn } +func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) { + p.prefixParseFns[tokenType] = fn +} + +func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) { + p.infixParseFns[tokenType] = fn +} + +func (p *Parser) registerPostfix(tokenType token.TokenType, fn postfixParseFn) { + p.postfixParseFns[tokenType] = fn +} + func New(l *lexer.Lexer) *Parser { p := &Parser{l: l, errors: []string{}} @@ -132,12 +143,6 @@ func New(l *lexer.Lexer) *Parser { return p } -func (p *Parser) nextToken() { - p.prevToken = p.curToken - p.curToken = p.peekToken - p.peekToken = p.l.NextToken() -} - func (p *Parser) ParseProgram() *ast.Program { program := &ast.Program{} program.Statements = []ast.Statement{} @@ -151,71 +156,12 @@ func (p *Parser) ParseProgram() *ast.Program { return program } -func (p *Parser) parseStatement() ast.Statement { - // Remember to add switch statements to the language - switch p.curToken.Type { - case token.LET: - return p.parseLetStatment() - case token.RETURN: - return p.parseReturnStatement() - case token.BREAK: - return p.parseBreak() - case token.CONTINUE: - return p.parseContinue() - default: - return p.parseExpressionStatement() - } -} - -func (p *Parser) parseLetStatment() *ast.LetStatement { - stmt := &ast.LetStatement{Token: p.curToken} +// manage token literals: - if !p.expectPeek(token.IDENT) { - return nil - } - - stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} - - if !p.expectPeek(token.ASSIGN) { - return nil - } - - p.nextToken() - - stmt.Value = p.parseExpression(LOWEST) - - if p.peekTokenIs(token.SEMICOLON) { - p.nextToken() - } - - return stmt -} - -func (p *Parser) parseNull() ast.Expression { - return &ast.Null{Token: p.curToken} -} - -func (p *Parser) parseAssignmentExpression(exp ast.Expression) ast.Expression { - switch node := exp.(type) { - case *ast.Identifier, *ast.IndexExpression: - default: - if node != nil { - msg := fmt.Sprintf("Mstari %d:Tulitegemea kupata kitambulishi au array, badala yake tumepata: %s", p.curToken.Line, node.TokenLiteral()) - p.errors = append(p.errors, msg) - } else { - msg := fmt.Sprintf("Mstari %d: Umekosea mkuu", p.curToken.Line) - p.errors = append(p.errors, msg) - } - return nil - } - - ae := &ast.AssignmentExpression{Token: p.curToken, Left: exp} - - p.nextToken() - - ae.Value = p.parseExpression(LOWEST) - - return ae +func (p *Parser) nextToken() { + p.prevToken = p.curToken + p.curToken = p.peekToken + p.peekToken = p.l.NextToken() } func (p *Parser) curTokenIs(t token.TokenType) bool { @@ -236,40 +182,34 @@ func (p *Parser) expectPeek(t token.TokenType) bool { } } -func (p *Parser) Errors() []string { - return p.errors -} - -func (p *Parser) peekError(t token.TokenType) { - msg := fmt.Sprintf("Mstari %d: Tulitegemea kupata %s, badala yake tumepata %s", p.curToken.Line, t, p.peekToken.Type) - p.errors = append(p.errors, msg) +func (p *Parser) peekPrecedence() int { + if p, ok := precedences[p.peekToken.Type]; ok { + return p + } + return LOWEST } -func (p *Parser) parseReturnStatement() *ast.ReturnStatement { - stmt := &ast.ReturnStatement{Token: p.curToken} - p.nextToken() - - stmt.ReturnValue = p.parseExpression(LOWEST) - - if p.peekTokenIs(token.SEMICOLON) { - p.nextToken() +func (p *Parser) curPrecedence() int { + if p, ok := precedences[p.curToken.Type]; ok { + return p } - return stmt + return LOWEST } -func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) { - p.prefixParseFns[tokenType] = fn -} +// error messages -func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) { - p.infixParseFns[tokenType] = fn +func (p *Parser) Errors() []string { + return p.errors } -func (p *Parser) registerPostfix(tokenType token.TokenType, fn postfixParseFn) { - p.postfixParseFns[tokenType] = fn +func (p *Parser) peekError(t token.TokenType) { + msg := fmt.Sprintf("Mstari %d: Tulitegemea kupata %s, badala yake tumepata %s", p.curToken.Line, t, p.peekToken.Type) + p.errors = append(p.errors, msg) } +// parse expressions + func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { stmt := &ast.ExpressionStatement{Token: p.curToken} @@ -282,16 +222,6 @@ func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { return stmt } -func (p *Parser) noPrefixParseFnError(t token.TokenType) { - msg := fmt.Sprintf("Mstari %d: Tumeshindwa kuparse %s", p.curToken.Line, t) - p.errors = append(p.errors, msg) -} - -func (p *Parser) noInfixParseFnError(t token.TokenType) { - msg := fmt.Sprintf("Mstari %d: Tumeshindwa kuparse %s", p.curToken.Line, t) - p.errors = append(p.errors, msg) -} - func (p *Parser) parseExpression(precedence int) ast.Expression { postfix := p.postfixParseFns[p.curToken.Type] if postfix != nil { @@ -318,35 +248,7 @@ func (p *Parser) parseExpression(precedence int) ast.Expression { } -func (p *Parser) parseIdentifier() ast.Expression { - return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} -} - -func (p *Parser) parseIntegerLiteral() ast.Expression { - lit := &ast.IntegerLiteral{Token: p.curToken} - - value, err := strconv.ParseInt(p.curToken.Literal, 0, 64) - if err != nil { - msg := fmt.Sprintf("Mstari %d: Hatuwezi kuparse %q kama namba", p.curToken.Line, p.curToken.Literal) - p.errors = append(p.errors, msg) - return nil - } - lit.Value = value - - return lit -} - -func (p *Parser) parseFloatLiteral() ast.Expression { - fl := &ast.FloatLiteral{Token: p.curToken} - value, err := strconv.ParseFloat(p.curToken.Literal, 64) - if err != nil { - msg := fmt.Sprintf("Mstari %d: Hatuwezi kuparse %q kama desimali", p.curToken.Line, p.curToken.Literal) - p.errors = append(p.errors, msg) - return nil - } - fl.Value = value - return fl -} +// prefix expressions func (p *Parser) parsePrefixExpression() ast.Expression { expression := &ast.PrefixExpression{ @@ -361,20 +263,12 @@ func (p *Parser) parsePrefixExpression() ast.Expression { return expression } -func (p *Parser) peekPrecedence() int { - if p, ok := precedences[p.peekToken.Type]; ok { - return p - } - return LOWEST +func (p *Parser) noPrefixParseFnError(t token.TokenType) { + msg := fmt.Sprintf("Mstari %d: Tumeshindwa kuparse %s", p.curToken.Line, t) + p.errors = append(p.errors, msg) } -func (p *Parser) curPrecedence() int { - if p, ok := precedences[p.curToken.Type]; ok { - return p - } - - return LOWEST -} +// infix expressions func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression { expression := &ast.InfixExpression{ @@ -389,8 +283,9 @@ func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression { return expression } -func (p *Parser) parseBoolean() ast.Expression { - return &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)} +func (p *Parser) noInfixParseFnError(t token.TokenType) { + msg := fmt.Sprintf("Mstari %d: Tumeshindwa kuparse %s", p.curToken.Line, t) + p.errors = append(p.errors, msg) } func (p *Parser) parseGroupedExpression() ast.Expression { @@ -405,6 +300,8 @@ func (p *Parser) parseGroupedExpression() ast.Expression { return exp } +// postfix expressions + func (p *Parser) parsePostfixExpression() ast.Expression { expression := &ast.PostfixExpression{ Token: p.prevToken, @@ -412,392 +309,3 @@ func (p *Parser) parsePostfixExpression() ast.Expression { } return expression } - -func (p *Parser) parseIfExpression() ast.Expression { - expression := &ast.IfExpression{Token: p.curToken} - - if !p.expectPeek(token.LPAREN) { - return nil - } - - p.nextToken() - expression.Condition = p.parseExpression(LOWEST) - - if !p.expectPeek(token.RPAREN) { - return nil - } - - if !p.expectPeek(token.LBRACE) { - return nil - } - - expression.Consequence = p.parseBlockStatement() - - if p.peekTokenIs(token.ELSE) { - p.nextToken() - if p.peekTokenIs(token.IF) { - p.nextToken() - expression.Alternative = &ast.BlockStatement{ - Statements: []ast.Statement{ - &ast.ExpressionStatement{ - Expression: p.parseIfExpression(), - }, - }, - } - return expression - } - - if !p.expectPeek(token.LBRACE) { - return nil - } - - expression.Alternative = p.parseBlockStatement() - } - - return expression -} - -func (p *Parser) parseBlockStatement() *ast.BlockStatement { - block := &ast.BlockStatement{Token: p.curToken} - block.Statements = []ast.Statement{} - - p.nextToken() - - for !p.curTokenIs(token.RBRACE) { - if p.curTokenIs(token.EOF) { - msg := fmt.Sprintf("Mstari %d: Hukufunga Mabano '}'", p.curToken.Line) - p.errors = append(p.errors, msg) - return nil - } - stmt := p.parseStatement() - block.Statements = append(block.Statements, stmt) - p.nextToken() - } - - return block -} - -func (p *Parser) parseFunctionLiteral() ast.Expression { - lit := &ast.FunctionLiteral{Token: p.curToken} - - if !p.expectPeek(token.LPAREN) { - return nil - } - - lit.Parameters = p.parseFunctionParameters() - - if !p.expectPeek(token.LBRACE) { - return nil - } - - lit.Body = p.parseBlockStatement() - - return lit -} - -func (p *Parser) parseFunctionParameters() []*ast.Identifier { - identifiers := []*ast.Identifier{} - - if p.peekTokenIs(token.RPAREN) { - p.nextToken() - return identifiers - } - - p.nextToken() - - ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} - identifiers = append(identifiers, ident) - - for p.peekTokenIs(token.COMMA) { - p.nextToken() - p.nextToken() - ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} - identifiers = append(identifiers, ident) - } - - if !p.expectPeek(token.RPAREN) { - return nil - } - - return identifiers -} - -func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression { - exp := &ast.CallExpression{Token: p.curToken, Function: function} - exp.Arguments = p.parseExpressionList(token.RPAREN) - return exp -} - -func (p *Parser) parseStringLiteral() ast.Expression { - return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal} -} - -func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression { - list := []ast.Expression{} - - if p.peekTokenIs(end) { - p.nextToken() - return list - } - - p.nextToken() - list = append(list, p.parseExpression(LOWEST)) - - for p.peekTokenIs(token.COMMA) { - p.nextToken() - p.nextToken() - list = append(list, p.parseExpression(LOWEST)) - } - - if !p.expectPeek(end) { - return nil - } - return list -} - -func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression { - exp := &ast.IndexExpression{Token: p.curToken, Left: left} - - p.nextToken() - exp.Index = p.parseExpression(LOWEST) - if !p.expectPeek(token.RBRACKET) { - return nil - } - - return exp -} - -func (p *Parser) parseDictLiteral() ast.Expression { - dict := &ast.DictLiteral{Token: p.curToken} - dict.Pairs = make(map[ast.Expression]ast.Expression) - - for !p.peekTokenIs(token.RBRACE) { - p.nextToken() - key := p.parseExpression(LOWEST) - - if !p.expectPeek(token.COLON) { - return nil - } - - p.nextToken() - value := p.parseExpression(LOWEST) - - dict.Pairs[key] = value - - if !p.peekTokenIs(token.RBRACE) && !p.expectPeek(token.COMMA) { - return nil - } - } - - if !p.expectPeek(token.RBRACE) { - return nil - } - - return dict -} - -func (p *Parser) parseWhileExpression() ast.Expression { - expression := &ast.WhileExpression{Token: p.curToken} - - if !p.expectPeek(token.LPAREN) { - return nil - } - - p.nextToken() - expression.Condition = p.parseExpression(LOWEST) - - if !p.expectPeek(token.RPAREN) { - return nil - } - - if !p.expectPeek(token.LBRACE) { - return nil - } - - expression.Consequence = p.parseBlockStatement() - - return expression -} - -func (p *Parser) parseBreak() *ast.Break { - stmt := &ast.Break{Token: p.curToken} - for p.curTokenIs(token.SEMICOLON) { - p.nextToken() - } - return stmt -} - -func (p *Parser) parseContinue() *ast.Continue { - stmt := &ast.Continue{Token: p.curToken} - for p.curTokenIs(token.SEMICOLON) { - p.nextToken() - } - return stmt -} - -func (p *Parser) parseForExpression() ast.Expression { - expression := &ast.For{Token: p.curToken} - p.nextToken() - if !p.curTokenIs(token.IDENT) { - return nil - } - if !p.peekTokenIs(token.ASSIGN) { - return p.parseForInExpression(expression) - } - - // In future will allow: kwa i = 0; i<10; i++ {andika(i)} - // expression.Identifier = p.curToken.Literal - // expression.StarterName = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} - // if expression.StarterName == nil { - // return nil - // } - // if !p.expectPeek(token.ASSIGN) { - // return nil - // } - - // p.nextToken() - - // expression.StarterValue = p.parseExpression(LOWEST) - // // expression.Starter = p.parseExpression(LOWEST) - // if expression.StarterValue == nil { - // return nil - // } - // p.nextToken() - // for p.curTokenIs(token.SEMICOLON) { - // p.nextToken() - // } - // expression.Condition = p.parseExpression(LOWEST) - // if expression.Condition == nil { - // return nil - // } - // p.nextToken() - // for p.curTokenIs(token.SEMICOLON) { - // p.nextToken() - // } - // expression.Closer = p.parseExpression(LOWEST) - // if expression.Closer == nil { - // return nil - // } - // p.nextToken() - // for p.curTokenIs(token.SEMICOLON) { - // p.nextToken() - // } - // if !p.curTokenIs(token.LBRACE) { - // return nil - // } - // expression.Block = p.parseBlockStatement() - // return expression - return nil -} - -func (p *Parser) parseForInExpression(initialExpression *ast.For) ast.Expression { - expression := &ast.ForIn{Token: initialExpression.Token} - if !p.curTokenIs(token.IDENT) { - return nil - } - val := p.curToken.Literal - var key string - p.nextToken() - if p.curTokenIs(token.COMMA) { - p.nextToken() - if !p.curTokenIs(token.IDENT) { - return nil - } - key = val - val = p.curToken.Literal - p.nextToken() - } - expression.Key = key - expression.Value = val - if !p.curTokenIs(token.IN) { - return nil - } - p.nextToken() - expression.Iterable = p.parseExpression(LOWEST) - if !p.expectPeek(token.LBRACE) { - return nil - } - expression.Block = p.parseBlockStatement() - return expression -} - -func (p *Parser) parseSwitchStatement() ast.Expression { - expression := &ast.SwitchExpression{Token: p.curToken} - - if !p.expectPeek(token.LPAREN) { - return nil - } - - p.nextToken() - expression.Value = p.parseExpression(LOWEST) - - if expression.Value == nil { - return nil - } - - if !p.expectPeek(token.RPAREN) { - return nil - } - - if !p.expectPeek(token.LBRACE) { - return nil - } - p.nextToken() - - for !p.curTokenIs(token.RBRACE) { - - if p.curTokenIs(token.EOF) { - msg := fmt.Sprintf("Mstari %d: Haukufunga ENDAPO (SWITCH)", p.curToken.Line) - p.errors = append(p.errors, msg) - return nil - } - tmp := &ast.CaseExpression{Token: p.curToken} - - if p.curTokenIs(token.DEFAULT) { - - tmp.Default = true - - } else if p.curTokenIs(token.CASE) { - - p.nextToken() - - if p.curTokenIs(token.DEFAULT) { - tmp.Default = true - } else { - tmp.Expr = append(tmp.Expr, p.parseExpression(LOWEST)) - for p.peekTokenIs(token.COMMA) { - p.nextToken() - p.nextToken() - tmp.Expr = append(tmp.Expr, p.parseExpression(LOWEST)) - } - } - } else { - msg := fmt.Sprintf("Mstari %d: Tulitegemea Kauli IKIWA (CASE) au KAWAIDA (DEFAULT) lakini tumepewa: %s", p.curToken.Line, p.curToken.Type) - p.errors = append(p.errors, msg) - return nil - } - - if !p.expectPeek(token.LBRACE) { - return nil - } - - tmp.Block = p.parseBlockStatement() - p.nextToken() - expression.Choices = append(expression.Choices, tmp) - } - - count := 0 - for _, c := range expression.Choices { - if c.Default { - count++ - } - } - if count > 1 { - msg := fmt.Sprintf("Kauli ENDAPO (SWITCH) hua na kauli 'KAWAIDA' (DEFAULT) moja tu! Wewe umeweka %d", count) - p.errors = append(p.errors, msg) - return nil - - } - return expression - -} diff --git a/src/parser/statements.go b/src/parser/statements.go new file mode 100644 index 0000000..c129c4f --- /dev/null +++ b/src/parser/statements.go @@ -0,0 +1,81 @@ +package parser + +import ( + "fmt" + + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/token" +) + +func (p *Parser) parseStatement() ast.Statement { + // Remember to add switch statements to the language + switch p.curToken.Type { + case token.LET: + return p.parseLetStatment() + case token.RETURN: + return p.parseReturnStatement() + case token.BREAK: + return p.parseBreak() + case token.CONTINUE: + return p.parseContinue() + default: + return p.parseExpressionStatement() + } +} + +func (p *Parser) parseLetStatment() *ast.LetStatement { + stmt := &ast.LetStatement{Token: p.curToken} + + if !p.expectPeek(token.IDENT) { + return nil + } + + stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + + if !p.expectPeek(token.ASSIGN) { + return nil + } + + p.nextToken() + + stmt.Value = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseReturnStatement() *ast.ReturnStatement { + stmt := &ast.ReturnStatement{Token: p.curToken} + p.nextToken() + + stmt.ReturnValue = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseBlockStatement() *ast.BlockStatement { + block := &ast.BlockStatement{Token: p.curToken} + block.Statements = []ast.Statement{} + + p.nextToken() + + for !p.curTokenIs(token.RBRACE) { + if p.curTokenIs(token.EOF) { + msg := fmt.Sprintf("Mstari %d: Hukufunga Mabano '}'", p.curToken.Line) + p.errors = append(p.errors, msg) + return nil + } + stmt := p.parseStatement() + block.Statements = append(block.Statements, stmt) + p.nextToken() + } + + return block +} diff --git a/src/parser/string.go b/src/parser/string.go new file mode 100644 index 0000000..cfd90f2 --- /dev/null +++ b/src/parser/string.go @@ -0,0 +1,9 @@ +package parser + +import ( + "github.com/AvicennaJr/Nuru/ast" +) + +func (p *Parser) parseStringLiteral() ast.Expression { + return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal} +} diff --git a/src/parser/switch.go b/src/parser/switch.go new file mode 100644 index 0000000..88ce460 --- /dev/null +++ b/src/parser/switch.go @@ -0,0 +1,89 @@ +package parser + +import ( + "fmt" + + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/token" +) + +func (p *Parser) parseSwitchStatement() ast.Expression { + expression := &ast.SwitchExpression{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + p.nextToken() + expression.Value = p.parseExpression(LOWEST) + + if expression.Value == nil { + return nil + } + + if !p.expectPeek(token.RPAREN) { + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + p.nextToken() + + for !p.curTokenIs(token.RBRACE) { + + if p.curTokenIs(token.EOF) { + msg := fmt.Sprintf("Mstari %d: Haukufunga ENDAPO (SWITCH)", p.curToken.Line) + p.errors = append(p.errors, msg) + return nil + } + tmp := &ast.CaseExpression{Token: p.curToken} + + if p.curTokenIs(token.DEFAULT) { + + tmp.Default = true + + } else if p.curTokenIs(token.CASE) { + + p.nextToken() + + if p.curTokenIs(token.DEFAULT) { + tmp.Default = true + } else { + tmp.Expr = append(tmp.Expr, p.parseExpression(LOWEST)) + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + tmp.Expr = append(tmp.Expr, p.parseExpression(LOWEST)) + } + } + } else { + msg := fmt.Sprintf("Mstari %d: Tulitegemea Kauli IKIWA (CASE) au KAWAIDA (DEFAULT) lakini tumepewa: %s", p.curToken.Line, p.curToken.Type) + p.errors = append(p.errors, msg) + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + tmp.Block = p.parseBlockStatement() + p.nextToken() + expression.Choices = append(expression.Choices, tmp) + } + + count := 0 + for _, c := range expression.Choices { + if c.Default { + count++ + } + } + if count > 1 { + msg := fmt.Sprintf("Kauli ENDAPO (SWITCH) hua na kauli 'KAWAIDA' (DEFAULT) moja tu! Wewe umeweka %d", count) + p.errors = append(p.errors, msg) + return nil + + } + return expression + +} diff --git a/src/parser/while.go b/src/parser/while.go new file mode 100644 index 0000000..3ae92fc --- /dev/null +++ b/src/parser/while.go @@ -0,0 +1,29 @@ +package parser + +import ( + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/token" +) + +func (p *Parser) parseWhileExpression() ast.Expression { + expression := &ast.WhileExpression{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + p.nextToken() + expression.Condition = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Consequence = p.parseBlockStatement() + + return expression +} From e8b416badbb0598913c1bb9e35f5295877100624 Mon Sep 17 00:00:00 2001 From: Hopertz Date: Tue, 3 Jan 2023 18:06:50 +0300 Subject: [PATCH 47/59] Refactor evaluator --- src/evaluator/bang.go | 16 + src/evaluator/block.go | 23 ++ src/evaluator/dict.go | 32 ++ src/evaluator/evaluator.go | 565 +----------------------------------- src/evaluator/forin.go | 29 ++ src/evaluator/identifier.go | 17 ++ src/evaluator/if.go | 22 ++ src/evaluator/in.go | 81 ++++++ src/evaluator/index.go | 44 +++ src/evaluator/infix.go | 247 ++++++++++++++++ src/evaluator/postfix.go | 40 +++ src/evaluator/prefix.go | 43 +++ src/evaluator/switch.go | 30 ++ src/evaluator/while.go | 24 ++ 14 files changed, 649 insertions(+), 564 deletions(-) create mode 100644 src/evaluator/bang.go create mode 100644 src/evaluator/block.go create mode 100644 src/evaluator/dict.go create mode 100644 src/evaluator/forin.go create mode 100644 src/evaluator/identifier.go create mode 100644 src/evaluator/if.go create mode 100644 src/evaluator/in.go create mode 100644 src/evaluator/index.go create mode 100644 src/evaluator/infix.go create mode 100644 src/evaluator/postfix.go create mode 100644 src/evaluator/prefix.go create mode 100644 src/evaluator/switch.go create mode 100644 src/evaluator/while.go diff --git a/src/evaluator/bang.go b/src/evaluator/bang.go new file mode 100644 index 0000000..de9b340 --- /dev/null +++ b/src/evaluator/bang.go @@ -0,0 +1,16 @@ +package evaluator + +import "github.com/AvicennaJr/Nuru/object" + +func evalBangOperatorExpression(right object.Object) object.Object { + switch right { + case TRUE: + return FALSE + case FALSE: + return TRUE + case NULL: + return TRUE + default: + return FALSE + } +} \ No newline at end of file diff --git a/src/evaluator/block.go b/src/evaluator/block.go new file mode 100644 index 0000000..a7c9447 --- /dev/null +++ b/src/evaluator/block.go @@ -0,0 +1,23 @@ +package evaluator + +import ( + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/object" +) + +func evalBlockStatement(block *ast.BlockStatement, env *object.Environment) object.Object { + var result object.Object + + for _, statment := range block.Statements { + result = Eval(statment, env) + + if result != nil { + rt := result.Type() + if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ || rt == object.CONTINUE_OBJ || rt == object.BREAK_OBJ { + return result + } + } + } + + return result +} diff --git a/src/evaluator/dict.go b/src/evaluator/dict.go new file mode 100644 index 0000000..05bbdcb --- /dev/null +++ b/src/evaluator/dict.go @@ -0,0 +1,32 @@ +package evaluator + +import ( + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/object" +) + +func evalDictLiteral(node *ast.DictLiteral, env *object.Environment) object.Object { + pairs := make(map[object.HashKey]object.DictPair) + + for keyNode, valueNode := range node.Pairs { + key := Eval(keyNode, env) + if isError(key) { + return key + } + + hashKey, ok := key.(object.Hashable) + if !ok { + return newError("Mstari %d: Hashing imeshindikana: %s", node.Token.Line, key.Type()) + } + + value := Eval(valueNode, env) + if isError(value) { + return value + } + + hashed := hashKey.HashKey() + pairs[hashed] = object.DictPair{Key: key, Value: value} + } + + return &object.Dict{Pairs: pairs} +} \ No newline at end of file diff --git a/src/evaluator/evaluator.go b/src/evaluator/evaluator.go index 6aedefb..a861aae 100644 --- a/src/evaluator/evaluator.go +++ b/src/evaluator/evaluator.go @@ -2,8 +2,6 @@ package evaluator import ( "fmt" - "math" - "strings" "github.com/AvicennaJr/Nuru/ast" "github.com/AvicennaJr/Nuru/object" @@ -220,328 +218,8 @@ func nativeBoolToBooleanObject(input bool) *object.Boolean { return FALSE } -func evalPrefixExpression(operator string, right object.Object, line int) object.Object { - switch operator { - case "!": - return evalBangOperatorExpression(right) - case "-": - return evalMinusPrefixOperatorExpression(right, line) - case "+": - return evalPlusPrefixOperatorExpression(right, line) - default: - return newError("Mstari %d: Operesheni haieleweki: %s%s", line, operator, right.Type()) - } -} - -func evalBangOperatorExpression(right object.Object) object.Object { - switch right { - case TRUE: - return FALSE - case FALSE: - return TRUE - case NULL: - return TRUE - default: - return FALSE - } -} - -func evalMinusPrefixOperatorExpression(right object.Object, line int) object.Object { - switch obj := right.(type) { - - case *object.Integer: - return &object.Integer{Value: -obj.Value} - - case *object.Float: - return &object.Float{Value: -obj.Value} - - default: - return newError("Mstari %d: Operesheni Haielweki: -%s", line, right.Type()) - } -} -func evalPlusPrefixOperatorExpression(right object.Object, line int) object.Object { - switch obj := right.(type) { - - case *object.Integer: - return &object.Integer{Value: obj.Value} - - case *object.Float: - return &object.Float{Value: obj.Value} - - default: - return newError("Mstari %d: Operesheni Haielweki: -%s", line, right.Type()) - } -} -func evalInfixExpression(operator string, left, right object.Object, line int) object.Object { - if left == nil { - return newError("Mstari %d: Umekosea hapa", line) - } - switch { - case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: - return evalStringInfixExpression(operator, left, right, line) - - case operator == "+" && left.Type() == object.DICT_OBJ && right.Type() == object.DICT_OBJ: - leftVal := left.(*object.Dict).Pairs - rightVal := right.(*object.Dict).Pairs - pairs := make(map[object.HashKey]object.DictPair) - for k, v := range leftVal { - pairs[k] = v - } - for k, v := range rightVal { - pairs[k] = v - } - return &object.Dict{Pairs: pairs} - - case operator == "+" && left.Type() == object.ARRAY_OBJ && right.Type() == object.ARRAY_OBJ: - leftVal := left.(*object.Array).Elements - rightVal := right.(*object.Array).Elements - elements := make([]object.Object, len(leftVal)+len(rightVal)) - elements = append(leftVal, rightVal...) - return &object.Array{Elements: elements} - - case operator == "*" && left.Type() == object.ARRAY_OBJ && right.Type() == object.INTEGER_OBJ: - leftVal := left.(*object.Array).Elements - rightVal := int(right.(*object.Integer).Value) - elements := leftVal - for i := rightVal; i > 1; i-- { - elements = append(elements, leftVal...) - } - return &object.Array{Elements: elements} - - case operator == "*" && left.Type() == object.INTEGER_OBJ && right.Type() == object.ARRAY_OBJ: - leftVal := int(left.(*object.Integer).Value) - rightVal := right.(*object.Array).Elements - elements := rightVal - for i := leftVal; i > 1; i-- { - elements = append(elements, rightVal...) - } - return &object.Array{Elements: elements} - - case operator == "*" && left.Type() == object.STRING_OBJ && right.Type() == object.INTEGER_OBJ: - leftVal := left.(*object.String).Value - rightVal := right.(*object.Integer).Value - return &object.String{Value: strings.Repeat(leftVal, int(rightVal))} - - case operator == "*" && left.Type() == object.INTEGER_OBJ && right.Type() == object.STRING_OBJ: - leftVal := left.(*object.Integer).Value - 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.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.FLOAT_OBJ && right.Type() == object.INTEGER_OBJ: - return evalFloatIntegerInfixExpression(operator, left, right, line) - - case operator == "ktk": - return evalInExpression(left, right, line) - - case operator == "==": - return nativeBoolToBooleanObject(left == right) - - case operator == "!=": - return nativeBoolToBooleanObject(left != right) - case left.Type() == object.BOOLEAN_OBJ && right.Type() == object.BOOLEAN_OBJ: - return evalBooleanInfixExpression(operator, left, right, line) - case left.Type() != right.Type(): - return newError("Mstari %d: Aina Hazilingani: %s %s %s", - line, left.Type(), operator, right.Type()) - default: - return newError("Mstari %d: Operesheni Haielweki: %s %s %s", - 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.Integer{Value: int64(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 Haielweki: %s %s %s", - line, left.Type(), operator, right.Type()) - } -} - -func evalFloatInfixExpression(operator string, left, right object.Object, line int) object.Object { - leftVal := left.(*object.Float).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 "**": - return &object.Float{Value: math.Pow(float64(leftVal), float64(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("Mstari %d: Operesheni Haielweki: %s %s %s", - line, left.Type(), operator, right.Type()) - } -} - -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 "<": - 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 Haielweki: %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 evalBooleanInfixExpression(operator string, left, right object.Object, line int) object.Object { - leftVal := left.(*object.Boolean).Value - rightVal := right.(*object.Boolean).Value - - switch operator { - case "&&": - return nativeBoolToBooleanObject(leftVal && rightVal) - case "||": - return nativeBoolToBooleanObject(leftVal || rightVal) - default: - return newError("Mstari %d: Operesheni Haielweki: %s %s %s", line, left.Type(), operator, right.Type()) - } -} - -func evalPostfixExpression(env *object.Environment, operator string, node *ast.PostfixExpression) object.Object { - val, ok := env.Get(node.Token.Literal) - if !ok { - return newError("Tumia KITAMBULISHI CHA NAMBA AU DESIMALI, sio %s", node.Token.Type) - } - switch operator { - case "++": - switch arg := val.(type) { - case *object.Integer: - v := arg.Value + 1 - return env.Set(node.Token.Literal, &object.Integer{Value: v}) - case *object.Float: - v := arg.Value + 1 - return env.Set(node.Token.Literal, &object.Float{Value: v}) - default: - return newError("Mstari %d: %s sio kitambulishi cha namba. Tumia '++' na kitambulishi cha namba au desimali.\nMfano:\tfanya i = 2; i++", node.Token.Line, node.Token.Literal) - - } - case "--": - switch arg := val.(type) { - case *object.Integer: - v := arg.Value - 1 - return env.Set(node.Token.Literal, &object.Integer{Value: v}) - case *object.Float: - v := arg.Value - 1 - return env.Set(node.Token.Literal, &object.Float{Value: v}) - default: - return newError("Mstari %d: %s sio kitambulishi cha namba. Tumia '--' na kitambulishi cha namba au desimali.\nMfano:\tfanya i = 2; i++", node.Token.Line, node.Token.Literal) - } - default: - return newError("Haifahamiki: %s", operator) - } -} - -func evalIfExpression(ie *ast.IfExpression, env *object.Environment) object.Object { - condition := Eval(ie.Condition, env) - - if isError(condition) { - return condition - } - - if isTruthy(condition) { - return Eval(ie.Consequence, env) - } else if ie.Alternative != nil { - return Eval(ie.Alternative, env) - } else { - return NULL - } -} func isTruthy(obj object.Object) bool { switch obj { @@ -556,22 +234,6 @@ func isTruthy(obj object.Object) bool { } } -func evalBlockStatement(block *ast.BlockStatement, env *object.Environment) object.Object { - var result object.Object - - for _, statment := range block.Statements { - result = Eval(statment, env) - - if result != nil { - rt := result.Type() - if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ || rt == object.CONTINUE_OBJ || rt == object.BREAK_OBJ { - return result - } - } - } - - return result -} func newError(format string, a ...interface{}) *object.Error { format = fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, format) @@ -586,16 +248,7 @@ func isError(obj object.Object) bool { return false } -func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object { - if val, ok := env.Get(node.Value); ok { - return val - } - if builtin, ok := builtins[node.Value]; ok { - return builtin - } - return newError("Mstari %d: Neno Halifahamiki: %s", node.Token.Line, node.Value) -} func evalExpressions(exps []ast.Expression, env *object.Environment) []object.Object { var result []object.Object @@ -648,107 +301,6 @@ func unwrapReturnValue(obj object.Object) object.Object { return obj } -func evalStringInfixExpression(operator string, left, right object.Object, line int) object.Object { - - leftVal := left.(*object.String).Value - rightVal := right.(*object.String).Value - - switch operator { - case "+": - return &object.String{Value: leftVal + rightVal} - case "==": - return nativeBoolToBooleanObject(leftVal == rightVal) - case "!=": - return nativeBoolToBooleanObject(leftVal != rightVal) - default: - return newError("Mstari %d: Operesheni Haielweki: %s %s %s", line, left.Type(), operator, right.Type()) - } -} - -func evalIndexExpression(left, index object.Object, line int) object.Object { - switch { - case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: - return evalArrayIndexExpression(left, index) - case left.Type() == object.ARRAY_OBJ && index.Type() != object.INTEGER_OBJ: - return newError("Mstari %d: Tafadhali tumia number, sio: %s", line, index.Type()) - case left.Type() == object.DICT_OBJ: - return evalDictIndexExpression(left, index, line) - default: - return newError("Mstari %d: Operesheni hii haiwezekani kwa: %s", line, left.Type()) - } -} - -func evalArrayIndexExpression(array, index object.Object) object.Object { - arrayObject := array.(*object.Array) - idx := index.(*object.Integer).Value - max := int64(len(arrayObject.Elements) - 1) - - if idx < 0 || idx > max { - return NULL - } - - return arrayObject.Elements[idx] -} - -func evalDictLiteral(node *ast.DictLiteral, env *object.Environment) object.Object { - pairs := make(map[object.HashKey]object.DictPair) - - for keyNode, valueNode := range node.Pairs { - key := Eval(keyNode, env) - if isError(key) { - return key - } - - hashKey, ok := key.(object.Hashable) - if !ok { - return newError("Mstari %d: Hashing imeshindikana: %s", node.Token.Line, key.Type()) - } - - value := Eval(valueNode, env) - if isError(value) { - return value - } - - hashed := hashKey.HashKey() - pairs[hashed] = object.DictPair{Key: key, Value: value} - } - - return &object.Dict{Pairs: pairs} -} - -func evalDictIndexExpression(dict, index object.Object, line int) object.Object { - dictObject := dict.(*object.Dict) - - key, ok := index.(object.Hashable) - if !ok { - return newError("Mstari %d: Samahani, %s haitumiki kama key", line, index.Type()) - } - - pair, ok := dictObject.Pairs[key.HashKey()] - if !ok { - return NULL - } - - return pair.Value -} - -func evalWhileExpression(we *ast.WhileExpression, env *object.Environment) object.Object { - condition := Eval(we.Condition, env) - if isError(condition) { - return condition - } - if isTruthy(condition) { - evaluated := Eval(we.Consequence, env) - if isError(evaluated) { - return evaluated - } - if evaluated != nil && evaluated.Type() == object.BREAK_OBJ { - return evaluated - } - evalWhileExpression(we, env) - } - return NULL -} func evalBreak(node *ast.Break) object.Object { return BREAK @@ -758,79 +310,7 @@ func evalContinue(node *ast.Continue) object.Object { return CONTINUE } -func evalInExpression(left, right object.Object, line int) object.Object { - switch right.(type) { - case *object.String: - return evalInStringExpression(left, right) - case *object.Array: - return evalInArrayExpression(left, right) - case *object.Dict: - return evalInDictExpression(left, right, line) - default: - return FALSE - } -} -func evalInStringExpression(left, right object.Object) object.Object { - if left.Type() != object.STRING_OBJ { - return FALSE - } - leftVal := left.(*object.String) - rightVal := right.(*object.String) - found := strings.Contains(rightVal.Value, leftVal.Value) - return nativeBoolToBooleanObject(found) -} - -func evalInDictExpression(left, right object.Object, line int) object.Object { - leftVal, ok := left.(object.Hashable) - if !ok { - return newError("Huwezi kutumia kama 'key': %s", left.Type()) - } - key := leftVal.HashKey() - rightVal := right.(*object.Dict).Pairs - _, ok = rightVal[key] - return nativeBoolToBooleanObject(ok) -} - -func evalInArrayExpression(left, right object.Object) object.Object { - rightVal := right.(*object.Array) - switch leftVal := left.(type) { - case *object.Null: - for _, v := range rightVal.Elements { - if v.Type() == object.NULL_OBJ { - return TRUE - } - } - case *object.String: - for _, v := range rightVal.Elements { - if v.Type() == object.STRING_OBJ { - elem := v.(*object.String) - if elem.Value == leftVal.Value { - return TRUE - } - } - } - case *object.Integer: - for _, v := range rightVal.Elements { - if v.Type() == object.INTEGER_OBJ { - elem := v.(*object.Integer) - if elem.Value == leftVal.Value { - return TRUE - } - } - } - case *object.Float: - for _, v := range rightVal.Elements { - if v.Type() == object.FLOAT_OBJ { - elem := v.(*object.Float) - if elem.Value == leftVal.Value { - return TRUE - } - } - } - } - return FALSE -} // func evalForExpression(fe *ast.For, env *object.Environment) object.Object { // obj, ok := env.Get(fe.Identifier) @@ -883,28 +363,7 @@ func evalInArrayExpression(left, right object.Object) object.Object { // return NULL // } -func evalForInExpression(fie *ast.ForIn, env *object.Environment, line int) object.Object { - iterable := Eval(fie.Iterable, env) - existingKeyIdentifier, okk := env.Get(fie.Key) // again, stay safe - existingValueIdentifier, okv := env.Get(fie.Value) - defer func() { // restore them later on - if okk { - env.Set(fie.Key, existingKeyIdentifier) - } - if okv { - env.Set(fie.Value, existingValueIdentifier) - } - }() - switch i := iterable.(type) { - case object.Iterable: - defer func() { - i.Reset() - }() - return loopIterable(i.Next, env, fie) - default: - return newError("Mstari %d: Huwezi kufanya operesheni hii na %s", line, i.Type()) - } -} + func loopIterable(next func() (object.Object, object.Object), env *object.Environment, fi *ast.ForIn) object.Object { k, v := next() @@ -932,26 +391,4 @@ func loopIterable(next func() (object.Object, object.Object), env *object.Enviro return NULL } -func evalSwitchStatement(se *ast.SwitchExpression, env *object.Environment) object.Object { - obj := Eval(se.Value, env) - for _, opt := range se.Choices { - if opt.Default { - continue - } - for _, val := range opt.Expr { - out := Eval(val, env) - if obj.Type() == out.Type() && obj.Inspect() == out.Inspect() { - blockOut := evalBlockStatement(opt.Block, env) - return blockOut - } - } - } - for _, opt := range se.Choices { - if opt.Default { - out := evalBlockStatement(opt.Block, env) - return out - } - } - return nil -} diff --git a/src/evaluator/forin.go b/src/evaluator/forin.go new file mode 100644 index 0000000..28db0f5 --- /dev/null +++ b/src/evaluator/forin.go @@ -0,0 +1,29 @@ +package evaluator + +import ( + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/object" +) + +func evalForInExpression(fie *ast.ForIn, env *object.Environment, line int) object.Object { + iterable := Eval(fie.Iterable, env) + existingKeyIdentifier, okk := env.Get(fie.Key) // again, stay safe + existingValueIdentifier, okv := env.Get(fie.Value) + defer func() { // restore them later on + if okk { + env.Set(fie.Key, existingKeyIdentifier) + } + if okv { + env.Set(fie.Value, existingValueIdentifier) + } + }() + switch i := iterable.(type) { + case object.Iterable: + defer func() { + i.Reset() + }() + return loopIterable(i.Next, env, fie) + default: + return newError("Mstari %d: Huwezi kufanya operesheni hii na %s", line, i.Type()) + } +} \ No newline at end of file diff --git a/src/evaluator/identifier.go b/src/evaluator/identifier.go new file mode 100644 index 0000000..7745a23 --- /dev/null +++ b/src/evaluator/identifier.go @@ -0,0 +1,17 @@ +package evaluator + +import ( + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/object" +) + +func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object { + if val, ok := env.Get(node.Value); ok { + return val + } + if builtin, ok := builtins[node.Value]; ok { + return builtin + } + + return newError("Mstari %d: Neno Halifahamiki: %s", node.Token.Line, node.Value) +} \ No newline at end of file diff --git a/src/evaluator/if.go b/src/evaluator/if.go new file mode 100644 index 0000000..890f24f --- /dev/null +++ b/src/evaluator/if.go @@ -0,0 +1,22 @@ +package evaluator + +import ( + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/object" +) + +func evalIfExpression(ie *ast.IfExpression, env *object.Environment) object.Object { + condition := Eval(ie.Condition, env) + + if isError(condition) { + return condition + } + + if isTruthy(condition) { + return Eval(ie.Consequence, env) + } else if ie.Alternative != nil { + return Eval(ie.Alternative, env) + } else { + return NULL + } +} \ No newline at end of file diff --git a/src/evaluator/in.go b/src/evaluator/in.go new file mode 100644 index 0000000..f81a0b9 --- /dev/null +++ b/src/evaluator/in.go @@ -0,0 +1,81 @@ +package evaluator + +import ( + "strings" + + "github.com/AvicennaJr/Nuru/object" +) + +func evalInExpression(left, right object.Object, line int) object.Object { + switch right.(type) { + case *object.String: + return evalInStringExpression(left, right) + case *object.Array: + return evalInArrayExpression(left, right) + case *object.Dict: + return evalInDictExpression(left, right, line) + default: + return FALSE + } +} + +func evalInStringExpression(left, right object.Object) object.Object { + if left.Type() != object.STRING_OBJ { + return FALSE + } + leftVal := left.(*object.String) + rightVal := right.(*object.String) + found := strings.Contains(rightVal.Value, leftVal.Value) + return nativeBoolToBooleanObject(found) +} + +func evalInDictExpression(left, right object.Object, line int) object.Object { + leftVal, ok := left.(object.Hashable) + if !ok { + return newError("Huwezi kutumia kama 'key': %s", left.Type()) + } + key := leftVal.HashKey() + rightVal := right.(*object.Dict).Pairs + _, ok = rightVal[key] + return nativeBoolToBooleanObject(ok) +} + +func evalInArrayExpression(left, right object.Object) object.Object { + rightVal := right.(*object.Array) + switch leftVal := left.(type) { + case *object.Null: + for _, v := range rightVal.Elements { + if v.Type() == object.NULL_OBJ { + return TRUE + } + } + case *object.String: + for _, v := range rightVal.Elements { + if v.Type() == object.STRING_OBJ { + elem := v.(*object.String) + if elem.Value == leftVal.Value { + return TRUE + } + } + } + case *object.Integer: + for _, v := range rightVal.Elements { + if v.Type() == object.INTEGER_OBJ { + elem := v.(*object.Integer) + if elem.Value == leftVal.Value { + return TRUE + } + } + } + case *object.Float: + for _, v := range rightVal.Elements { + if v.Type() == object.FLOAT_OBJ { + elem := v.(*object.Float) + if elem.Value == leftVal.Value { + return TRUE + } + } + } + } + return FALSE +} \ No newline at end of file diff --git a/src/evaluator/index.go b/src/evaluator/index.go new file mode 100644 index 0000000..07945f0 --- /dev/null +++ b/src/evaluator/index.go @@ -0,0 +1,44 @@ +package evaluator + +import "github.com/AvicennaJr/Nuru/object" + +func evalIndexExpression(left, index object.Object, line int) object.Object { + switch { + case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: + return evalArrayIndexExpression(left, index) + case left.Type() == object.ARRAY_OBJ && index.Type() != object.INTEGER_OBJ: + return newError("Mstari %d: Tafadhali tumia number, sio: %s", line, index.Type()) + case left.Type() == object.DICT_OBJ: + return evalDictIndexExpression(left, index, line) + default: + return newError("Mstari %d: Operesheni hii haiwezekani kwa: %s", line, left.Type()) + } +} + +func evalArrayIndexExpression(array, index object.Object) object.Object { + arrayObject := array.(*object.Array) + idx := index.(*object.Integer).Value + max := int64(len(arrayObject.Elements) - 1) + + if idx < 0 || idx > max { + return NULL + } + + return arrayObject.Elements[idx] +} + +func evalDictIndexExpression(dict, index object.Object, line int) object.Object { + dictObject := dict.(*object.Dict) + + key, ok := index.(object.Hashable) + if !ok { + return newError("Mstari %d: Samahani, %s haitumiki kama key", line, index.Type()) + } + + pair, ok := dictObject.Pairs[key.HashKey()] + if !ok { + return NULL + } + + return pair.Value +} \ No newline at end of file diff --git a/src/evaluator/infix.go b/src/evaluator/infix.go new file mode 100644 index 0000000..199ea6a --- /dev/null +++ b/src/evaluator/infix.go @@ -0,0 +1,247 @@ +package evaluator + +import ( + "math" + "strings" + + "github.com/AvicennaJr/Nuru/object" +) + +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 "<": + 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 Haielweki: %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 evalInfixExpression(operator string, left, right object.Object, line int) object.Object { + if left == nil { + return newError("Mstari %d: Umekosea hapa", line) + } + switch { + case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: + return evalStringInfixExpression(operator, left, right, line) + + case operator == "+" && left.Type() == object.DICT_OBJ && right.Type() == object.DICT_OBJ: + leftVal := left.(*object.Dict).Pairs + rightVal := right.(*object.Dict).Pairs + pairs := make(map[object.HashKey]object.DictPair) + for k, v := range leftVal { + pairs[k] = v + } + for k, v := range rightVal { + pairs[k] = v + } + return &object.Dict{Pairs: pairs} + + case operator == "+" && left.Type() == object.ARRAY_OBJ && right.Type() == object.ARRAY_OBJ: + leftVal := left.(*object.Array).Elements + rightVal := right.(*object.Array).Elements + elements := make([]object.Object, len(leftVal)+len(rightVal)) + elements = append(leftVal, rightVal...) + return &object.Array{Elements: elements} + + case operator == "*" && left.Type() == object.ARRAY_OBJ && right.Type() == object.INTEGER_OBJ: + leftVal := left.(*object.Array).Elements + rightVal := int(right.(*object.Integer).Value) + elements := leftVal + for i := rightVal; i > 1; i-- { + elements = append(elements, leftVal...) + } + return &object.Array{Elements: elements} + + case operator == "*" && left.Type() == object.INTEGER_OBJ && right.Type() == object.ARRAY_OBJ: + leftVal := int(left.(*object.Integer).Value) + rightVal := right.(*object.Array).Elements + elements := rightVal + for i := leftVal; i > 1; i-- { + elements = append(elements, rightVal...) + } + return &object.Array{Elements: elements} + + case operator == "*" && left.Type() == object.STRING_OBJ && right.Type() == object.INTEGER_OBJ: + leftVal := left.(*object.String).Value + rightVal := right.(*object.Integer).Value + return &object.String{Value: strings.Repeat(leftVal, int(rightVal))} + + case operator == "*" && left.Type() == object.INTEGER_OBJ && right.Type() == object.STRING_OBJ: + leftVal := left.(*object.Integer).Value + 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.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.FLOAT_OBJ && right.Type() == object.INTEGER_OBJ: + return evalFloatIntegerInfixExpression(operator, left, right, line) + + case operator == "ktk": + return evalInExpression(left, right, line) + + case operator == "==": + return nativeBoolToBooleanObject(left == right) + + case operator == "!=": + return nativeBoolToBooleanObject(left != right) + case left.Type() == object.BOOLEAN_OBJ && right.Type() == object.BOOLEAN_OBJ: + return evalBooleanInfixExpression(operator, left, right, line) + + case left.Type() != right.Type(): + return newError("Mstari %d: Aina Hazilingani: %s %s %s", + line, left.Type(), operator, right.Type()) + + default: + return newError("Mstari %d: Operesheni Haielweki: %s %s %s", + line, left.Type(), operator, right.Type()) + } +} + +func evalStringInfixExpression(operator string, left, right object.Object, line int) object.Object { + + leftVal := left.(*object.String).Value + rightVal := right.(*object.String).Value + + switch operator { + case "+": + return &object.String{Value: leftVal + rightVal} + case "==": + return nativeBoolToBooleanObject(leftVal == rightVal) + case "!=": + return nativeBoolToBooleanObject(leftVal != rightVal) + default: + return newError("Mstari %d: Operesheni Haielweki: %s %s %s", line, left.Type(), operator, right.Type()) + } +} + +func evalBooleanInfixExpression(operator string, left, right object.Object, line int) object.Object { + leftVal := left.(*object.Boolean).Value + rightVal := right.(*object.Boolean).Value + + switch operator { + case "&&": + return nativeBoolToBooleanObject(leftVal && rightVal) + case "||": + return nativeBoolToBooleanObject(leftVal || rightVal) + default: + return newError("Mstari %d: Operesheni Haielweki: %s %s %s", line, left.Type(), operator, right.Type()) + } +} + + +func evalFloatInfixExpression(operator string, left, right object.Object, line int) object.Object { + leftVal := left.(*object.Float).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 "**": + return &object.Float{Value: math.Pow(float64(leftVal), float64(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("Mstari %d: Operesheni Haielweki: %s %s %s", + 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.Integer{Value: int64(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 Haielweki: %s %s %s", + line, left.Type(), operator, right.Type()) + } +} \ No newline at end of file diff --git a/src/evaluator/postfix.go b/src/evaluator/postfix.go new file mode 100644 index 0000000..ea90a88 --- /dev/null +++ b/src/evaluator/postfix.go @@ -0,0 +1,40 @@ +package evaluator + +import ( + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/object" +) + +func evalPostfixExpression(env *object.Environment, operator string, node *ast.PostfixExpression) object.Object { + val, ok := env.Get(node.Token.Literal) + if !ok { + return newError("Tumia KITAMBULISHI CHA NAMBA AU DESIMALI, sio %s", node.Token.Type) + } + switch operator { + case "++": + switch arg := val.(type) { + case *object.Integer: + v := arg.Value + 1 + return env.Set(node.Token.Literal, &object.Integer{Value: v}) + case *object.Float: + v := arg.Value + 1 + return env.Set(node.Token.Literal, &object.Float{Value: v}) + default: + return newError("Mstari %d: %s sio kitambulishi cha namba. Tumia '++' na kitambulishi cha namba au desimali.\nMfano:\tfanya i = 2; i++", node.Token.Line, node.Token.Literal) + + } + case "--": + switch arg := val.(type) { + case *object.Integer: + v := arg.Value - 1 + return env.Set(node.Token.Literal, &object.Integer{Value: v}) + case *object.Float: + v := arg.Value - 1 + return env.Set(node.Token.Literal, &object.Float{Value: v}) + default: + return newError("Mstari %d: %s sio kitambulishi cha namba. Tumia '--' na kitambulishi cha namba au desimali.\nMfano:\tfanya i = 2; i++", node.Token.Line, node.Token.Literal) + } + default: + return newError("Haifahamiki: %s", operator) + } +} \ No newline at end of file diff --git a/src/evaluator/prefix.go b/src/evaluator/prefix.go new file mode 100644 index 0000000..cec053b --- /dev/null +++ b/src/evaluator/prefix.go @@ -0,0 +1,43 @@ +package evaluator + +import "github.com/AvicennaJr/Nuru/object" + +func evalMinusPrefixOperatorExpression(right object.Object, line int) object.Object { + switch obj := right.(type) { + + case *object.Integer: + return &object.Integer{Value: -obj.Value} + + case *object.Float: + return &object.Float{Value: -obj.Value} + + default: + return newError("Mstari %d: Operesheni Haielweki: -%s", line, right.Type()) + } +} +func evalPlusPrefixOperatorExpression(right object.Object, line int) object.Object { + switch obj := right.(type) { + + case *object.Integer: + return &object.Integer{Value: obj.Value} + + case *object.Float: + return &object.Float{Value: obj.Value} + + default: + return newError("Mstari %d: Operesheni Haielweki: -%s", line, right.Type()) + } +} + +func evalPrefixExpression(operator string, right object.Object, line int) object.Object { + switch operator { + case "!": + return evalBangOperatorExpression(right) + case "-": + return evalMinusPrefixOperatorExpression(right, line) + case "+": + return evalPlusPrefixOperatorExpression(right, line) + default: + return newError("Mstari %d: Operesheni haieleweki: %s%s", line, operator, right.Type()) + } +} diff --git a/src/evaluator/switch.go b/src/evaluator/switch.go new file mode 100644 index 0000000..53fe6f4 --- /dev/null +++ b/src/evaluator/switch.go @@ -0,0 +1,30 @@ +package evaluator + +import ( + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/object" +) + +func evalSwitchStatement(se *ast.SwitchExpression, env *object.Environment) object.Object { + obj := Eval(se.Value, env) + for _, opt := range se.Choices { + + if opt.Default { + continue + } + for _, val := range opt.Expr { + out := Eval(val, env) + if obj.Type() == out.Type() && obj.Inspect() == out.Inspect() { + blockOut := evalBlockStatement(opt.Block, env) + return blockOut + } + } + } + for _, opt := range se.Choices { + if opt.Default { + out := evalBlockStatement(opt.Block, env) + return out + } + } + return nil +} \ No newline at end of file diff --git a/src/evaluator/while.go b/src/evaluator/while.go new file mode 100644 index 0000000..bf40681 --- /dev/null +++ b/src/evaluator/while.go @@ -0,0 +1,24 @@ +package evaluator + +import ( + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/object" +) + +func evalWhileExpression(we *ast.WhileExpression, env *object.Environment) object.Object { + condition := Eval(we.Condition, env) + if isError(condition) { + return condition + } + if isTruthy(condition) { + evaluated := Eval(we.Consequence, env) + if isError(evaluated) { + return evaluated + } + if evaluated != nil && evaluated.Type() == object.BREAK_OBJ { + return evaluated + } + evalWhileExpression(we, env) + } + return NULL +} \ No newline at end of file From edf85ef7ad482bb9ef44fb7f38452b7e8fd9b043 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Thu, 5 Jan 2023 21:41:10 +0300 Subject: [PATCH 48/59] Fixed bugs in 'ktk' --- src/evaluator/dot.go | 33 ++++++++++++ src/evaluator/evaluator.go | 13 ----- src/evaluator/in.go | 4 +- src/evaluator/infix.go | 105 +++++++++++++++++++------------------ src/lexer/lexer.go | 2 + src/lexer/lexer_test.go | 4 +- src/parser/parser.go | 2 + src/token/token.go | 1 + 8 files changed, 97 insertions(+), 67 deletions(-) create mode 100644 src/evaluator/dot.go diff --git a/src/evaluator/dot.go b/src/evaluator/dot.go new file mode 100644 index 0000000..256e4d9 --- /dev/null +++ b/src/evaluator/dot.go @@ -0,0 +1,33 @@ +package evaluator + +import ( + "github.com/AvicennaJr/Nuru/object" +) + +func evalDotExpression(left, right object.Object, line int) object.Object { + if right.Type() != object.BUILTIN_OBJ { + return newError("Mstari %d: '%s' sio function sahihi", line, right.Inspect()) + } + switch left.(type) { + case *object.String: + return evalDotStringExpression(left, right, line) + case *object.Array: + return evalDotArrayExpression(left, right) + case *object.Dict: + return evalDotDictExpression(left, right, line) + default: + return FALSE + } +} + +func evalDotStringExpression(left, right object.Object, line int) object.Object { + return nil +} + +func evalDotDictExpression(left, right object.Object, line int) object.Object { + return nil +} + +func evalDotArrayExpression(left, right object.Object) object.Object { + return nil +} diff --git a/src/evaluator/evaluator.go b/src/evaluator/evaluator.go index a861aae..dabaa13 100644 --- a/src/evaluator/evaluator.go +++ b/src/evaluator/evaluator.go @@ -218,9 +218,6 @@ func nativeBoolToBooleanObject(input bool) *object.Boolean { return FALSE } - - - func isTruthy(obj object.Object) bool { switch obj { case NULL: @@ -234,7 +231,6 @@ func isTruthy(obj object.Object) bool { } } - func newError(format string, a ...interface{}) *object.Error { format = fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, format) return &object.Error{Message: fmt.Sprintf(format, a...)} @@ -248,8 +244,6 @@ func isError(obj object.Object) bool { return false } - - func evalExpressions(exps []ast.Expression, env *object.Environment) []object.Object { var result []object.Object @@ -301,7 +295,6 @@ func unwrapReturnValue(obj object.Object) object.Object { return obj } - func evalBreak(node *ast.Break) object.Object { return BREAK } @@ -310,8 +303,6 @@ func evalContinue(node *ast.Continue) object.Object { return CONTINUE } - - // func evalForExpression(fe *ast.For, env *object.Environment) object.Object { // obj, ok := env.Get(fe.Identifier) // defer func() { // stay safe and not reassign an existing variable @@ -363,8 +354,6 @@ func evalContinue(node *ast.Continue) object.Object { // return NULL // } - - func loopIterable(next func() (object.Object, object.Object), env *object.Environment, fi *ast.ForIn) object.Object { k, v := next() for k != nil && v != nil { @@ -390,5 +379,3 @@ func loopIterable(next func() (object.Object, object.Object), env *object.Enviro } return NULL } - - diff --git a/src/evaluator/in.go b/src/evaluator/in.go index f81a0b9..446b35c 100644 --- a/src/evaluator/in.go +++ b/src/evaluator/in.go @@ -32,7 +32,7 @@ func evalInStringExpression(left, right object.Object) object.Object { func evalInDictExpression(left, right object.Object, line int) object.Object { leftVal, ok := left.(object.Hashable) if !ok { - return newError("Huwezi kutumia kama 'key': %s", left.Type()) + return newError("Mstari %d: Huwezi kutumia kama 'key': %s", line, left.Type()) } key := leftVal.HashKey() rightVal := right.(*object.Dict).Pairs @@ -78,4 +78,4 @@ func evalInArrayExpression(left, right object.Object) object.Object { } } return FALSE -} \ No newline at end of file +} diff --git a/src/evaluator/infix.go b/src/evaluator/infix.go index 199ea6a..8560e15 100644 --- a/src/evaluator/infix.go +++ b/src/evaluator/infix.go @@ -7,57 +7,18 @@ import ( "github.com/AvicennaJr/Nuru/object" ) -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 "<": - 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 Haielweki: %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 evalInfixExpression(operator string, left, right object.Object, line int) object.Object { if left == nil { return newError("Mstari %d: Umekosea hapa", line) } switch { + + case operator == "ktk": + return evalInExpression(left, right, line) + + case operator == ".": + return evalDotExpression(left, right, line) + case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: return evalStringInfixExpression(operator, left, right, line) @@ -120,9 +81,6 @@ func evalInfixExpression(operator string, left, right object.Object, line int) o case left.Type() == object.FLOAT_OBJ && right.Type() == object.INTEGER_OBJ: return evalFloatIntegerInfixExpression(operator, left, right, line) - case operator == "ktk": - return evalInExpression(left, right, line) - case operator == "==": return nativeBoolToBooleanObject(left == right) @@ -141,6 +99,52 @@ 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 + 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 "<": + 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 Haielweki: %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 @@ -172,7 +176,6 @@ func evalBooleanInfixExpression(operator string, left, right object.Object, line } } - func evalFloatInfixExpression(operator string, left, right object.Object, line int) object.Object { leftVal := left.(*object.Float).Value rightVal := right.(*object.Float).Value @@ -244,4 +247,4 @@ func evalIntegerInfixExpression(operator string, left, right object.Object, line return newError("Mstari %d: Operesheni Haielweki: %s %s %s", line, left.Type(), operator, right.Type()) } -} \ No newline at end of file +} diff --git a/src/lexer/lexer.go b/src/lexer/lexer.go index 5afcecb..02b07db 100644 --- a/src/lexer/lexer.go +++ b/src/lexer/lexer.go @@ -142,6 +142,8 @@ func (l *Lexer) NextToken() token.Token { tok = newToken(token.RBRACKET, l.line, l.ch) case ':': tok = newToken(token.COLON, l.line, l.ch) + case '.': + tok = newToken(token.DOT, l.line, l.ch) case '&': if l.peekChar() == '&' { ch := l.ch diff --git a/src/lexer/lexer_test.go b/src/lexer/lexer_test.go index 74b3b42..d474cde 100644 --- a/src/lexer/lexer_test.go +++ b/src/lexer/lexer_test.go @@ -40,7 +40,8 @@ func TestNextToken(t *testing.T) { "bangi" "ba ngi" [1, 2]; - {"mambo": "vipi"}` + {"mambo": "vipi"} + . // testing dot` tests := []struct { expectedType token.TokenType @@ -132,6 +133,7 @@ func TestNextToken(t *testing.T) { {token.COLON, ":"}, {token.STRING, "vipi"}, {token.RBRACE, "}"}, + {token.DOT, "."}, {token.EOF, ""}, } diff --git a/src/parser/parser.go b/src/parser/parser.go index ae42caf..97eaf63 100644 --- a/src/parser/parser.go +++ b/src/parser/parser.go @@ -29,6 +29,7 @@ var precedences = map[token.TokenType]int{ token.AND: COND, token.OR: COND, token.IN: COND, + token.DOT: COND, token.ASSIGN: ASSIGN, token.EQ: EQUALS, token.NOT_EQ: EQUALS, @@ -135,6 +136,7 @@ func New(l *lexer.Lexer) *Parser { p.registerInfix(token.LBRACKET, p.parseIndexExpression) p.registerInfix(token.ASSIGN, p.parseAssignmentExpression) p.registerInfix(token.IN, p.parseInfixExpression) + p.registerInfix(token.DOT, p.parseInfixExpression) p.postfixParseFns = make(map[token.TokenType]postfixParseFn) p.registerPostfix(token.PLUS_PLUS, p.parsePostfixExpression) diff --git a/src/token/token.go b/src/token/token.go index bb950f7..1620fde 100644 --- a/src/token/token.go +++ b/src/token/token.go @@ -53,6 +53,7 @@ const ( LBRACKET = "[" RBRACKET = "]" COLON = ":" + DOT = "." // Keywords FUNCTION = "FUNCTION" From 114d27b5e7d55c4c452249e495189737c184e26d Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Thu, 5 Jan 2023 21:44:41 +0300 Subject: [PATCH 49/59] Revert "Fixed bugs in 'ktk'" This reverts commit edf85ef7ad482bb9ef44fb7f38452b7e8fd9b043. --- src/evaluator/dot.go | 33 ------------ src/evaluator/evaluator.go | 13 +++++ src/evaluator/in.go | 4 +- src/evaluator/infix.go | 105 ++++++++++++++++++------------------- src/lexer/lexer.go | 2 - src/lexer/lexer_test.go | 4 +- src/parser/parser.go | 2 - src/token/token.go | 1 - 8 files changed, 67 insertions(+), 97 deletions(-) delete mode 100644 src/evaluator/dot.go diff --git a/src/evaluator/dot.go b/src/evaluator/dot.go deleted file mode 100644 index 256e4d9..0000000 --- a/src/evaluator/dot.go +++ /dev/null @@ -1,33 +0,0 @@ -package evaluator - -import ( - "github.com/AvicennaJr/Nuru/object" -) - -func evalDotExpression(left, right object.Object, line int) object.Object { - if right.Type() != object.BUILTIN_OBJ { - return newError("Mstari %d: '%s' sio function sahihi", line, right.Inspect()) - } - switch left.(type) { - case *object.String: - return evalDotStringExpression(left, right, line) - case *object.Array: - return evalDotArrayExpression(left, right) - case *object.Dict: - return evalDotDictExpression(left, right, line) - default: - return FALSE - } -} - -func evalDotStringExpression(left, right object.Object, line int) object.Object { - return nil -} - -func evalDotDictExpression(left, right object.Object, line int) object.Object { - return nil -} - -func evalDotArrayExpression(left, right object.Object) object.Object { - return nil -} diff --git a/src/evaluator/evaluator.go b/src/evaluator/evaluator.go index dabaa13..a861aae 100644 --- a/src/evaluator/evaluator.go +++ b/src/evaluator/evaluator.go @@ -218,6 +218,9 @@ func nativeBoolToBooleanObject(input bool) *object.Boolean { return FALSE } + + + func isTruthy(obj object.Object) bool { switch obj { case NULL: @@ -231,6 +234,7 @@ func isTruthy(obj object.Object) bool { } } + func newError(format string, a ...interface{}) *object.Error { format = fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, format) return &object.Error{Message: fmt.Sprintf(format, a...)} @@ -244,6 +248,8 @@ func isError(obj object.Object) bool { return false } + + func evalExpressions(exps []ast.Expression, env *object.Environment) []object.Object { var result []object.Object @@ -295,6 +301,7 @@ func unwrapReturnValue(obj object.Object) object.Object { return obj } + func evalBreak(node *ast.Break) object.Object { return BREAK } @@ -303,6 +310,8 @@ func evalContinue(node *ast.Continue) object.Object { return CONTINUE } + + // func evalForExpression(fe *ast.For, env *object.Environment) object.Object { // obj, ok := env.Get(fe.Identifier) // defer func() { // stay safe and not reassign an existing variable @@ -354,6 +363,8 @@ func evalContinue(node *ast.Continue) object.Object { // return NULL // } + + func loopIterable(next func() (object.Object, object.Object), env *object.Environment, fi *ast.ForIn) object.Object { k, v := next() for k != nil && v != nil { @@ -379,3 +390,5 @@ func loopIterable(next func() (object.Object, object.Object), env *object.Enviro } return NULL } + + diff --git a/src/evaluator/in.go b/src/evaluator/in.go index 446b35c..f81a0b9 100644 --- a/src/evaluator/in.go +++ b/src/evaluator/in.go @@ -32,7 +32,7 @@ func evalInStringExpression(left, right object.Object) object.Object { func evalInDictExpression(left, right object.Object, line int) object.Object { leftVal, ok := left.(object.Hashable) if !ok { - return newError("Mstari %d: Huwezi kutumia kama 'key': %s", line, left.Type()) + return newError("Huwezi kutumia kama 'key': %s", left.Type()) } key := leftVal.HashKey() rightVal := right.(*object.Dict).Pairs @@ -78,4 +78,4 @@ func evalInArrayExpression(left, right object.Object) object.Object { } } return FALSE -} +} \ No newline at end of file diff --git a/src/evaluator/infix.go b/src/evaluator/infix.go index 8560e15..199ea6a 100644 --- a/src/evaluator/infix.go +++ b/src/evaluator/infix.go @@ -7,18 +7,57 @@ import ( "github.com/AvicennaJr/Nuru/object" ) +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 "<": + 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 Haielweki: %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 evalInfixExpression(operator string, left, right object.Object, line int) object.Object { if left == nil { return newError("Mstari %d: Umekosea hapa", line) } switch { - - case operator == "ktk": - return evalInExpression(left, right, line) - - case operator == ".": - return evalDotExpression(left, right, line) - case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: return evalStringInfixExpression(operator, left, right, line) @@ -81,6 +120,9 @@ func evalInfixExpression(operator string, left, right object.Object, line int) o case left.Type() == object.FLOAT_OBJ && right.Type() == object.INTEGER_OBJ: return evalFloatIntegerInfixExpression(operator, left, right, line) + case operator == "ktk": + return evalInExpression(left, right, line) + case operator == "==": return nativeBoolToBooleanObject(left == right) @@ -99,52 +141,6 @@ 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 - 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 "<": - 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 Haielweki: %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 @@ -176,6 +172,7 @@ func evalBooleanInfixExpression(operator string, left, right object.Object, line } } + func evalFloatInfixExpression(operator string, left, right object.Object, line int) object.Object { leftVal := left.(*object.Float).Value rightVal := right.(*object.Float).Value @@ -247,4 +244,4 @@ func evalIntegerInfixExpression(operator string, left, right object.Object, line return newError("Mstari %d: Operesheni Haielweki: %s %s %s", line, left.Type(), operator, right.Type()) } -} +} \ No newline at end of file diff --git a/src/lexer/lexer.go b/src/lexer/lexer.go index 02b07db..5afcecb 100644 --- a/src/lexer/lexer.go +++ b/src/lexer/lexer.go @@ -142,8 +142,6 @@ func (l *Lexer) NextToken() token.Token { tok = newToken(token.RBRACKET, l.line, l.ch) case ':': tok = newToken(token.COLON, l.line, l.ch) - case '.': - tok = newToken(token.DOT, l.line, l.ch) case '&': if l.peekChar() == '&' { ch := l.ch diff --git a/src/lexer/lexer_test.go b/src/lexer/lexer_test.go index d474cde..74b3b42 100644 --- a/src/lexer/lexer_test.go +++ b/src/lexer/lexer_test.go @@ -40,8 +40,7 @@ func TestNextToken(t *testing.T) { "bangi" "ba ngi" [1, 2]; - {"mambo": "vipi"} - . // testing dot` + {"mambo": "vipi"}` tests := []struct { expectedType token.TokenType @@ -133,7 +132,6 @@ func TestNextToken(t *testing.T) { {token.COLON, ":"}, {token.STRING, "vipi"}, {token.RBRACE, "}"}, - {token.DOT, "."}, {token.EOF, ""}, } diff --git a/src/parser/parser.go b/src/parser/parser.go index 97eaf63..ae42caf 100644 --- a/src/parser/parser.go +++ b/src/parser/parser.go @@ -29,7 +29,6 @@ var precedences = map[token.TokenType]int{ token.AND: COND, token.OR: COND, token.IN: COND, - token.DOT: COND, token.ASSIGN: ASSIGN, token.EQ: EQUALS, token.NOT_EQ: EQUALS, @@ -136,7 +135,6 @@ func New(l *lexer.Lexer) *Parser { p.registerInfix(token.LBRACKET, p.parseIndexExpression) p.registerInfix(token.ASSIGN, p.parseAssignmentExpression) p.registerInfix(token.IN, p.parseInfixExpression) - p.registerInfix(token.DOT, p.parseInfixExpression) p.postfixParseFns = make(map[token.TokenType]postfixParseFn) p.registerPostfix(token.PLUS_PLUS, p.parsePostfixExpression) diff --git a/src/token/token.go b/src/token/token.go index 1620fde..bb950f7 100644 --- a/src/token/token.go +++ b/src/token/token.go @@ -53,7 +53,6 @@ const ( LBRACKET = "[" RBRACKET = "]" COLON = ":" - DOT = "." // Keywords FUNCTION = "FUNCTION" From 207f5874b086a5634a55960065d6dee406b73928 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Thu, 5 Jan 2023 21:56:30 +0300 Subject: [PATCH 50/59] Fixed 'ktk' bug and add ability to parse '.' --- src/evaluator/dot.go | 33 ++++++++++++ src/evaluator/evaluator.go | 13 ----- src/evaluator/in.go | 4 +- src/evaluator/infix.go | 105 +++++++++++++++++++------------------ src/lexer/lexer.go | 2 + src/lexer/lexer_test.go | 4 +- src/parser/parser.go | 2 + src/token/token.go | 1 + 8 files changed, 97 insertions(+), 67 deletions(-) create mode 100644 src/evaluator/dot.go diff --git a/src/evaluator/dot.go b/src/evaluator/dot.go new file mode 100644 index 0000000..256e4d9 --- /dev/null +++ b/src/evaluator/dot.go @@ -0,0 +1,33 @@ +package evaluator + +import ( + "github.com/AvicennaJr/Nuru/object" +) + +func evalDotExpression(left, right object.Object, line int) object.Object { + if right.Type() != object.BUILTIN_OBJ { + return newError("Mstari %d: '%s' sio function sahihi", line, right.Inspect()) + } + switch left.(type) { + case *object.String: + return evalDotStringExpression(left, right, line) + case *object.Array: + return evalDotArrayExpression(left, right) + case *object.Dict: + return evalDotDictExpression(left, right, line) + default: + return FALSE + } +} + +func evalDotStringExpression(left, right object.Object, line int) object.Object { + return nil +} + +func evalDotDictExpression(left, right object.Object, line int) object.Object { + return nil +} + +func evalDotArrayExpression(left, right object.Object) object.Object { + return nil +} diff --git a/src/evaluator/evaluator.go b/src/evaluator/evaluator.go index a861aae..dabaa13 100644 --- a/src/evaluator/evaluator.go +++ b/src/evaluator/evaluator.go @@ -218,9 +218,6 @@ func nativeBoolToBooleanObject(input bool) *object.Boolean { return FALSE } - - - func isTruthy(obj object.Object) bool { switch obj { case NULL: @@ -234,7 +231,6 @@ func isTruthy(obj object.Object) bool { } } - func newError(format string, a ...interface{}) *object.Error { format = fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, format) return &object.Error{Message: fmt.Sprintf(format, a...)} @@ -248,8 +244,6 @@ func isError(obj object.Object) bool { return false } - - func evalExpressions(exps []ast.Expression, env *object.Environment) []object.Object { var result []object.Object @@ -301,7 +295,6 @@ func unwrapReturnValue(obj object.Object) object.Object { return obj } - func evalBreak(node *ast.Break) object.Object { return BREAK } @@ -310,8 +303,6 @@ func evalContinue(node *ast.Continue) object.Object { return CONTINUE } - - // func evalForExpression(fe *ast.For, env *object.Environment) object.Object { // obj, ok := env.Get(fe.Identifier) // defer func() { // stay safe and not reassign an existing variable @@ -363,8 +354,6 @@ func evalContinue(node *ast.Continue) object.Object { // return NULL // } - - func loopIterable(next func() (object.Object, object.Object), env *object.Environment, fi *ast.ForIn) object.Object { k, v := next() for k != nil && v != nil { @@ -390,5 +379,3 @@ func loopIterable(next func() (object.Object, object.Object), env *object.Enviro } return NULL } - - diff --git a/src/evaluator/in.go b/src/evaluator/in.go index f81a0b9..446b35c 100644 --- a/src/evaluator/in.go +++ b/src/evaluator/in.go @@ -32,7 +32,7 @@ func evalInStringExpression(left, right object.Object) object.Object { func evalInDictExpression(left, right object.Object, line int) object.Object { leftVal, ok := left.(object.Hashable) if !ok { - return newError("Huwezi kutumia kama 'key': %s", left.Type()) + return newError("Mstari %d: Huwezi kutumia kama 'key': %s", line, left.Type()) } key := leftVal.HashKey() rightVal := right.(*object.Dict).Pairs @@ -78,4 +78,4 @@ func evalInArrayExpression(left, right object.Object) object.Object { } } return FALSE -} \ No newline at end of file +} diff --git a/src/evaluator/infix.go b/src/evaluator/infix.go index 199ea6a..8560e15 100644 --- a/src/evaluator/infix.go +++ b/src/evaluator/infix.go @@ -7,57 +7,18 @@ import ( "github.com/AvicennaJr/Nuru/object" ) -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 "<": - 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 Haielweki: %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 evalInfixExpression(operator string, left, right object.Object, line int) object.Object { if left == nil { return newError("Mstari %d: Umekosea hapa", line) } switch { + + case operator == "ktk": + return evalInExpression(left, right, line) + + case operator == ".": + return evalDotExpression(left, right, line) + case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: return evalStringInfixExpression(operator, left, right, line) @@ -120,9 +81,6 @@ func evalInfixExpression(operator string, left, right object.Object, line int) o case left.Type() == object.FLOAT_OBJ && right.Type() == object.INTEGER_OBJ: return evalFloatIntegerInfixExpression(operator, left, right, line) - case operator == "ktk": - return evalInExpression(left, right, line) - case operator == "==": return nativeBoolToBooleanObject(left == right) @@ -141,6 +99,52 @@ 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 + 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 "<": + 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 Haielweki: %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 @@ -172,7 +176,6 @@ func evalBooleanInfixExpression(operator string, left, right object.Object, line } } - func evalFloatInfixExpression(operator string, left, right object.Object, line int) object.Object { leftVal := left.(*object.Float).Value rightVal := right.(*object.Float).Value @@ -244,4 +247,4 @@ func evalIntegerInfixExpression(operator string, left, right object.Object, line return newError("Mstari %d: Operesheni Haielweki: %s %s %s", line, left.Type(), operator, right.Type()) } -} \ No newline at end of file +} diff --git a/src/lexer/lexer.go b/src/lexer/lexer.go index 5afcecb..02b07db 100644 --- a/src/lexer/lexer.go +++ b/src/lexer/lexer.go @@ -142,6 +142,8 @@ func (l *Lexer) NextToken() token.Token { tok = newToken(token.RBRACKET, l.line, l.ch) case ':': tok = newToken(token.COLON, l.line, l.ch) + case '.': + tok = newToken(token.DOT, l.line, l.ch) case '&': if l.peekChar() == '&' { ch := l.ch diff --git a/src/lexer/lexer_test.go b/src/lexer/lexer_test.go index 74b3b42..bc11d38 100644 --- a/src/lexer/lexer_test.go +++ b/src/lexer/lexer_test.go @@ -40,7 +40,8 @@ func TestNextToken(t *testing.T) { "bangi" "ba ngi" [1, 2]; - {"mambo": "vipi"}` + {"mambo": "vipi"} + . // test dot` tests := []struct { expectedType token.TokenType @@ -132,6 +133,7 @@ func TestNextToken(t *testing.T) { {token.COLON, ":"}, {token.STRING, "vipi"}, {token.RBRACE, "}"}, + {token.DOT, "."}, {token.EOF, ""}, } diff --git a/src/parser/parser.go b/src/parser/parser.go index ae42caf..97eaf63 100644 --- a/src/parser/parser.go +++ b/src/parser/parser.go @@ -29,6 +29,7 @@ var precedences = map[token.TokenType]int{ token.AND: COND, token.OR: COND, token.IN: COND, + token.DOT: COND, token.ASSIGN: ASSIGN, token.EQ: EQUALS, token.NOT_EQ: EQUALS, @@ -135,6 +136,7 @@ func New(l *lexer.Lexer) *Parser { p.registerInfix(token.LBRACKET, p.parseIndexExpression) p.registerInfix(token.ASSIGN, p.parseAssignmentExpression) p.registerInfix(token.IN, p.parseInfixExpression) + p.registerInfix(token.DOT, p.parseInfixExpression) p.postfixParseFns = make(map[token.TokenType]postfixParseFn) p.registerPostfix(token.PLUS_PLUS, p.parsePostfixExpression) diff --git a/src/token/token.go b/src/token/token.go index bb950f7..1620fde 100644 --- a/src/token/token.go +++ b/src/token/token.go @@ -53,6 +53,7 @@ const ( LBRACKET = "[" RBRACKET = "]" COLON = ":" + DOT = "." // Keywords FUNCTION = "FUNCTION" From e32f32a0f3c5f1a6bb581cae6bcd8101ebddef6b Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Thu, 5 Jan 2023 23:47:16 +0300 Subject: [PATCH 51/59] Add ability to evaluate '.' --- src/evaluator/dot.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/evaluator/dot.go b/src/evaluator/dot.go index 256e4d9..3acb1bb 100644 --- a/src/evaluator/dot.go +++ b/src/evaluator/dot.go @@ -5,9 +5,9 @@ import ( ) func evalDotExpression(left, right object.Object, line int) object.Object { - if right.Type() != object.BUILTIN_OBJ { - return newError("Mstari %d: '%s' sio function sahihi", line, right.Inspect()) - } + // if right.Type() != object.BUILTIN_OBJ { + // return newError("Mstari %d: '%s' sio function sahihi", line, right.Inspect()) + // } switch left.(type) { case *object.String: return evalDotStringExpression(left, right, line) @@ -21,13 +21,24 @@ func evalDotExpression(left, right object.Object, line int) object.Object { } func evalDotStringExpression(left, right object.Object, line int) object.Object { + leftVal := left.(*object.String) + rightVal := right.(*object.String) + + switch rightVal.Inspect() { + case "idadi": + return &object.Integer{Value: int64(len(leftVal.Value))} + } return nil } func evalDotDictExpression(left, right object.Object, line int) object.Object { + // to do + return nil } func evalDotArrayExpression(left, right object.Object) object.Object { + // to do + return nil } From 564e82b126b1300549aeffb03ea35b8212627e85 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Fri, 6 Jan 2023 21:36:35 +0300 Subject: [PATCH 52/59] Added ability to 'properly' parse dot --- src/parser/dot.go | 18 ++++++++++++++++++ src/parser/parser.go | 7 ++++--- 2 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 src/parser/dot.go diff --git a/src/parser/dot.go b/src/parser/dot.go new file mode 100644 index 0000000..18086b8 --- /dev/null +++ b/src/parser/dot.go @@ -0,0 +1,18 @@ +package parser + +import ( + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/token" +) + +func (p *Parser) parseMethod(obj ast.Expression) ast.Expression { + precedence := p.curPrecedence() + exp := &ast.MethodExpression{Token: p.curToken, Object: obj} + p.nextToken() + exp.Method = p.parseExpression(precedence) + if !p.expectPeek(token.LPAREN) { + return nil + } + exp.Arguments = p.parseExpressionList(token.RPAREN) + return exp +} diff --git a/src/parser/parser.go b/src/parser/parser.go index 97eaf63..515397d 100644 --- a/src/parser/parser.go +++ b/src/parser/parser.go @@ -23,13 +23,13 @@ const ( PREFIX // -X OR !X CALL // myFunction(X) INDEX // Arrays + DOT // For methods ) var precedences = map[token.TokenType]int{ token.AND: COND, token.OR: COND, token.IN: COND, - token.DOT: COND, token.ASSIGN: ASSIGN, token.EQ: EQUALS, token.NOT_EQ: EQUALS, @@ -50,7 +50,8 @@ var precedences = map[token.TokenType]int{ token.MODULUS_ASSIGN: MODULUS, // token.BANG: PREFIX, token.LPAREN: CALL, - token.LBRACKET: INDEX, // Highest priority + token.LBRACKET: INDEX, + token.DOT: DOT, // Highest priority } type ( @@ -136,7 +137,7 @@ func New(l *lexer.Lexer) *Parser { p.registerInfix(token.LBRACKET, p.parseIndexExpression) p.registerInfix(token.ASSIGN, p.parseAssignmentExpression) p.registerInfix(token.IN, p.parseInfixExpression) - p.registerInfix(token.DOT, p.parseInfixExpression) + p.registerInfix(token.DOT, p.parseMethod) p.postfixParseFns = make(map[token.TokenType]postfixParseFn) p.registerPostfix(token.PLUS_PLUS, p.parsePostfixExpression) From 4c6e2d82ce7936b80cc0051828aaeb190eecbf35 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Fri, 6 Jan 2023 21:38:10 +0300 Subject: [PATCH 53/59] Separated objects --- src/object/array.go | 40 +++++++ src/object/bool.go | 26 +++++ src/object/break.go | 6 ++ src/object/builtin.go | 10 ++ src/object/continue.go | 6 ++ src/object/dict.go | 60 +++++++++++ src/object/error.go | 13 +++ src/object/float.go | 19 ++++ src/object/function.go | 33 ++++++ src/object/integer.go | 14 +++ src/object/null.go | 6 ++ src/object/object.go | 240 ++--------------------------------------- src/object/return.go | 8 ++ src/object/strings.go | 83 ++++++++++++++ 14 files changed, 330 insertions(+), 234 deletions(-) create mode 100644 src/object/array.go create mode 100644 src/object/bool.go create mode 100644 src/object/break.go create mode 100644 src/object/builtin.go create mode 100644 src/object/continue.go create mode 100644 src/object/dict.go create mode 100644 src/object/error.go create mode 100644 src/object/float.go create mode 100644 src/object/function.go create mode 100644 src/object/integer.go create mode 100644 src/object/null.go create mode 100644 src/object/return.go create mode 100644 src/object/strings.go diff --git a/src/object/array.go b/src/object/array.go new file mode 100644 index 0000000..027f63f --- /dev/null +++ b/src/object/array.go @@ -0,0 +1,40 @@ +package object + +import ( + "bytes" + "strings" +) + +type Array struct { + Elements []Object + offset int +} + +func (ao *Array) Type() ObjectType { return ARRAY_OBJ } +func (ao *Array) Inspect() string { + var out bytes.Buffer + + elements := []string{} + for _, e := range ao.Elements { + elements = append(elements, e.Inspect()) + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +func (ao *Array) Next() (Object, Object) { + idx := ao.offset + if len(ao.Elements) > idx { + ao.offset = idx + 1 + return &Integer{Value: int64(idx)}, ao.Elements[idx] + } + return nil, nil +} + +func (ao *Array) Reset() { + ao.offset = 0 +} diff --git a/src/object/bool.go b/src/object/bool.go new file mode 100644 index 0000000..8702d52 --- /dev/null +++ b/src/object/bool.go @@ -0,0 +1,26 @@ +package object + +type Boolean struct { + Value bool +} + +func (b *Boolean) Inspect() string { + if b.Value { + return "kweli" + } else { + return "sikweli" + } +} +func (b *Boolean) Type() ObjectType { return BOOLEAN_OBJ } + +func (b *Boolean) HashKey() HashKey { + var value uint64 + + if b.Value { + value = 1 + } else { + value = 0 + } + + return HashKey{Type: b.Type(), Value: value} +} diff --git a/src/object/break.go b/src/object/break.go new file mode 100644 index 0000000..047b17c --- /dev/null +++ b/src/object/break.go @@ -0,0 +1,6 @@ +package object + +type Break struct{} + +func (b *Break) Type() ObjectType { return BREAK_OBJ } +func (b *Break) Inspect() string { return "break" } diff --git a/src/object/builtin.go b/src/object/builtin.go new file mode 100644 index 0000000..b47c051 --- /dev/null +++ b/src/object/builtin.go @@ -0,0 +1,10 @@ +package object + +type BuiltinFunction func(args ...Object) Object + +type Builtin struct { + Fn BuiltinFunction +} + +func (b *Builtin) Inspect() string { return "builtin function" } +func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ } diff --git a/src/object/continue.go b/src/object/continue.go new file mode 100644 index 0000000..15c7355 --- /dev/null +++ b/src/object/continue.go @@ -0,0 +1,6 @@ +package object + +type Continue struct{} + +func (c *Continue) Type() ObjectType { return CONTINUE_OBJ } +func (c *Continue) Inspect() string { return "continue" } diff --git a/src/object/dict.go b/src/object/dict.go new file mode 100644 index 0000000..86ca482 --- /dev/null +++ b/src/object/dict.go @@ -0,0 +1,60 @@ +package object + +import ( + "bytes" + "fmt" + "sort" + "strings" +) + +type DictPair struct { + Key Object + Value Object +} + +type Dict struct { + Pairs map[HashKey]DictPair + offset int +} + +func (d *Dict) Type() ObjectType { return DICT_OBJ } +func (d *Dict) Inspect() string { + var out bytes.Buffer + + pairs := []string{} + + for _, pair := range d.Pairs { + pairs = append(pairs, fmt.Sprintf("%s: %s", pair.Key.Inspect(), pair.Value.Inspect())) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} + +func (d *Dict) Next() (Object, Object) { + idx := 0 + dict := make(map[string]DictPair) + var keys []string + for _, v := range d.Pairs { + dict[v.Key.Inspect()] = v + keys = append(keys, v.Key.Inspect()) + } + + sort.Strings(keys) + + for _, k := range keys { + if d.offset == idx { + d.offset += 1 + return dict[k].Key, dict[k].Value + } + idx += 1 + } + return nil, nil +} + +func (d *Dict) Reset() { + d.offset = 0 +} diff --git a/src/object/error.go b/src/object/error.go new file mode 100644 index 0000000..2bc65af --- /dev/null +++ b/src/object/error.go @@ -0,0 +1,13 @@ +package object + +import "fmt" + +type Error struct { + Message string +} + +func (e *Error) Inspect() string { + msg := fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, "Kosa: ") + return msg + e.Message +} +func (e *Error) Type() ObjectType { return ERROR_OBJ } diff --git a/src/object/float.go b/src/object/float.go new file mode 100644 index 0000000..a4b2293 --- /dev/null +++ b/src/object/float.go @@ -0,0 +1,19 @@ +package object + +import ( + "hash/fnv" + "strconv" +) + +type Float struct { + Value float64 +} + +func (f *Float) Inspect() string { return strconv.FormatFloat(f.Value, 'f', -1, 64) } +func (f *Float) Type() ObjectType { return FLOAT_OBJ } + +func (f *Float) HashKey() HashKey { + h := fnv.New64a() + h.Write([]byte(f.Inspect())) + return HashKey{Type: f.Type(), Value: h.Sum64()} +} diff --git a/src/object/function.go b/src/object/function.go new file mode 100644 index 0000000..aecaf8a --- /dev/null +++ b/src/object/function.go @@ -0,0 +1,33 @@ +package object + +import ( + "bytes" + "strings" + + "github.com/AvicennaJr/Nuru/ast" +) + +type Function struct { + Parameters []*ast.Identifier + Body *ast.BlockStatement + Env *Environment +} + +func (f *Function) Type() ObjectType { return FUNCTION_OBJ } +func (f *Function) Inspect() string { + var out bytes.Buffer + + params := []string{} + for _, p := range f.Parameters { + params = append(params, p.String()) + } + + out.WriteString("unda") + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") {\n") + out.WriteString(f.Body.String()) + out.WriteString("\n}") + + return out.String() +} diff --git a/src/object/integer.go b/src/object/integer.go new file mode 100644 index 0000000..87cb945 --- /dev/null +++ b/src/object/integer.go @@ -0,0 +1,14 @@ +package object + +import "fmt" + +type Integer struct { + Value int64 +} + +func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) } +func (i *Integer) Type() ObjectType { return INTEGER_OBJ } + +func (i *Integer) HashKey() HashKey { + return HashKey{Type: i.Type(), Value: uint64(i.Value)} +} diff --git a/src/object/null.go b/src/object/null.go new file mode 100644 index 0000000..1610994 --- /dev/null +++ b/src/object/null.go @@ -0,0 +1,6 @@ +package object + +type Null struct{} + +func (n *Null) Inspect() string { return "null" } +func (n *Null) Type() ObjectType { return NULL_OBJ } diff --git a/src/object/object.go b/src/object/object.go index 113341d..e1b8389 100644 --- a/src/object/object.go +++ b/src/object/object.go @@ -1,14 +1,7 @@ package object import ( - "bytes" "fmt" - "hash/fnv" - "sort" - "strconv" - "strings" - - "github.com/AvicennaJr/Nuru/ast" ) type ObjectType string @@ -27,6 +20,7 @@ const ( DICT_OBJ = "KAMUSI" CONTINUE_OBJ = "ENDELEA" BREAK_OBJ = "VUNJA" + FILE_OBJ = "FAILI" ) type Object interface { @@ -34,244 +28,22 @@ type Object interface { Inspect() string } -type Integer struct { - Value int64 -} - -func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) } -func (i *Integer) Type() ObjectType { return INTEGER_OBJ } - -type Float struct { - Value float64 -} - -func (f *Float) Inspect() string { return strconv.FormatFloat(f.Value, 'f', -1, 64) } -func (f *Float) Type() ObjectType { return FLOAT_OBJ } - -type Boolean struct { - Value bool -} - -func (b *Boolean) Inspect() string { - if b.Value { - return "kweli" - } else { - return "sikweli" - } -} -func (b *Boolean) Type() ObjectType { return BOOLEAN_OBJ } - -type Null struct{} - -func (n *Null) Inspect() string { return "null" } -func (n *Null) Type() ObjectType { return NULL_OBJ } - -type ReturnValue struct { - Value Object -} - -func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() } -func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ } - -type Error struct { - Message string -} - -func (e *Error) Inspect() string { - msg := fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, "Kosa: ") - return msg + e.Message -} -func (e *Error) Type() ObjectType { return ERROR_OBJ } - -type Function struct { - Parameters []*ast.Identifier - Body *ast.BlockStatement - Env *Environment -} - -func (f *Function) Type() ObjectType { return FUNCTION_OBJ } -func (f *Function) Inspect() string { - var out bytes.Buffer - - params := []string{} - for _, p := range f.Parameters { - params = append(params, p.String()) - } - - out.WriteString("unda") - out.WriteString("(") - out.WriteString(strings.Join(params, ", ")) - out.WriteString(") {\n") - out.WriteString(f.Body.String()) - out.WriteString("\n}") - - return out.String() -} - -type String struct { - Value string - offset int -} - -func (s *String) Inspect() string { return s.Value } -func (s *String) Type() ObjectType { return STRING_OBJ } -func (s *String) Next() (Object, Object) { - offset := s.offset - if len(s.Value) > offset { - s.offset = offset + 1 - return &Integer{Value: int64(offset)}, &String{Value: string(s.Value[offset])} - } - return nil, nil -} -func (s *String) Reset() { - s.offset = 0 -} - -type BuiltinFunction func(args ...Object) Object - -type Builtin struct { - Fn BuiltinFunction -} - -func (b *Builtin) Inspect() string { return "builtin function" } -func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ } - -type Array struct { - Elements []Object - offset int -} - -func (ao *Array) Type() ObjectType { return ARRAY_OBJ } -func (ao *Array) Inspect() string { - var out bytes.Buffer - - elements := []string{} - for _, e := range ao.Elements { - elements = append(elements, e.Inspect()) - } - - out.WriteString("[") - out.WriteString(strings.Join(elements, ", ")) - out.WriteString("]") - - return out.String() -} - -func (ao *Array) Next() (Object, Object) { - idx := ao.offset - if len(ao.Elements) > idx { - ao.offset = idx + 1 - return &Integer{Value: int64(idx)}, ao.Elements[idx] - } - return nil, nil -} - -func (ao *Array) Reset() { - ao.offset = 0 -} - type HashKey struct { Type ObjectType Value uint64 } -func (b *Boolean) HashKey() HashKey { - var value uint64 - - if b.Value { - value = 1 - } else { - value = 0 - } - - return HashKey{Type: b.Type(), Value: value} -} - -func (i *Integer) HashKey() HashKey { - return HashKey{Type: i.Type(), Value: uint64(i.Value)} -} - -func (f *Float) HashKey() HashKey { - h := fnv.New64a() - h.Write([]byte(f.Inspect())) - return HashKey{Type: f.Type(), Value: h.Sum64()} -} - -func (s *String) HashKey() HashKey { - h := fnv.New64a() - h.Write([]byte(s.Value)) - - return HashKey{Type: s.Type(), Value: h.Sum64()} -} - -type DictPair struct { - Key Object - Value Object -} - -type Dict struct { - Pairs map[HashKey]DictPair - offset int -} - -func (d *Dict) Type() ObjectType { return DICT_OBJ } -func (d *Dict) Inspect() string { - var out bytes.Buffer - - pairs := []string{} - - for _, pair := range d.Pairs { - pairs = append(pairs, fmt.Sprintf("%s: %s", pair.Key.Inspect(), pair.Value.Inspect())) - } - - out.WriteString("{") - out.WriteString(strings.Join(pairs, ", ")) - out.WriteString("}") - - return out.String() -} - -func (d *Dict) Next() (Object, Object) { - idx := 0 - dict := make(map[string]DictPair) - var keys []string - for _, v := range d.Pairs { - dict[v.Key.Inspect()] = v - keys = append(keys, v.Key.Inspect()) - } - - sort.Strings(keys) - - for _, k := range keys { - if d.offset == idx { - d.offset += 1 - return dict[k].Key, dict[k].Value - } - idx += 1 - } - return nil, nil -} - -func (d *Dict) Reset() { - d.offset = 0 -} - type Hashable interface { HashKey() HashKey } -type Continue struct{} - -func (c *Continue) Type() ObjectType { return CONTINUE_OBJ } -func (c *Continue) Inspect() string { return "continue" } - -type Break struct{} - -func (b *Break) Type() ObjectType { return BREAK_OBJ } -func (b *Break) Inspect() string { return "break" } - // Iterable interface for dicts, strings and arrays type Iterable interface { Next() (Object, Object) Reset() } + +func newError(format string, a ...interface{}) *Error { + format = fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, format) + return &Error{Message: fmt.Sprintf(format, a...)} +} diff --git a/src/object/return.go b/src/object/return.go new file mode 100644 index 0000000..42c7225 --- /dev/null +++ b/src/object/return.go @@ -0,0 +1,8 @@ +package object + +type ReturnValue struct { + Value Object +} + +func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() } +func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ } diff --git a/src/object/strings.go b/src/object/strings.go new file mode 100644 index 0000000..213fa8c --- /dev/null +++ b/src/object/strings.go @@ -0,0 +1,83 @@ +package object + +import ( + "hash/fnv" + "strings" +) + +type String struct { + Value string + offset int +} + +func (s *String) Inspect() string { return s.Value } +func (s *String) Type() ObjectType { return STRING_OBJ } +func (s *String) HashKey() HashKey { + h := fnv.New64a() + h.Write([]byte(s.Value)) + + return HashKey{Type: s.Type(), Value: h.Sum64()} +} +func (s *String) Next() (Object, Object) { + offset := s.offset + if len(s.Value) > offset { + s.offset = offset + 1 + return &Integer{Value: int64(offset)}, &String{Value: string(s.Value[offset])} + } + return nil, nil +} +func (s *String) Reset() { + s.offset = 0 +} +func (s *String) Method(method string, args []Object) Object { + switch method { + case "idadi": + return s.len(args) + case "herufikubwa": + return s.upper(args) + case "herufindogo": + return s.lower(args) + case "gawa": + return s.split(args) + default: + return newError("Samahani, function hii haitumiki na Strings (Neno)") + } +} + +func (s *String) len(args []Object) Object { + if len(args) != 0 { + return newError("Samahani, tunahitaji Hoja 0, wewe umeweka %d", len(args)) + } + return &Integer{Value: int64(len(s.Value))} +} + +func (s *String) upper(args []Object) Object { + if len(args) != 0 { + return newError("Samahani, tunahitaji Hoja 0, wewe umeweka %d", len(args)) + } + return &String{Value: strings.ToUpper(s.Value)} +} + +func (s *String) lower(args []Object) Object { + if len(args) != 0 { + return newError("Samahani, tunahitaji Hoja 0, wewe umeweka %d", len(args)) + } + return &String{Value: strings.ToLower(s.Value)} +} + +func (s *String) split(args []Object) Object { + if len(args) > 1 { + return newError("Samahani, tunahitaji Hoja 1 au 0, wewe umeweka %d", len(args)) + } + sep := " " + if len(args) == 1 { + sep = args[0].(*String).Value + } + parts := strings.Split(s.Value, sep) + length := len(parts) + elements := make([]Object, length) + for k, v := range parts { + elements[k] = &String{Value: v} + } + return &Array{Elements: elements} +} From 1b77573a3106894e80845c5b646c084e7feee70d Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Fri, 6 Jan 2023 21:39:01 +0300 Subject: [PATCH 54/59] Remove parsing 'dot' from infix.go --- src/evaluator/infix.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/evaluator/infix.go b/src/evaluator/infix.go index 8560e15..c20706a 100644 --- a/src/evaluator/infix.go +++ b/src/evaluator/infix.go @@ -16,9 +16,6 @@ func evalInfixExpression(operator string, left, right object.Object, line int) o case operator == "ktk": return evalInExpression(left, right, line) - case operator == ".": - return evalDotExpression(left, right, line) - case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: return evalStringInfixExpression(operator, left, right, line) @@ -37,8 +34,7 @@ func evalInfixExpression(operator string, left, right object.Object, line int) o case operator == "+" && left.Type() == object.ARRAY_OBJ && right.Type() == object.ARRAY_OBJ: leftVal := left.(*object.Array).Elements rightVal := right.(*object.Array).Elements - elements := make([]object.Object, len(leftVal)+len(rightVal)) - elements = append(leftVal, rightVal...) + elements := append(leftVal, rightVal...) return &object.Array{Elements: elements} case operator == "*" && left.Type() == object.ARRAY_OBJ && right.Type() == object.INTEGER_OBJ: From cc74e4c5687411816bd7441a09049ae26460d7b1 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Fri, 6 Jan 2023 21:39:36 +0300 Subject: [PATCH 55/59] Add builtin function 'fungua' to open files --- src/evaluator/builtins.go | 79 +++++++++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 20 deletions(-) diff --git a/src/evaluator/builtins.go b/src/evaluator/builtins.go index a658efe..fbf9781 100644 --- a/src/evaluator/builtins.go +++ b/src/evaluator/builtins.go @@ -37,32 +37,31 @@ var builtins = map[string]*object.Builtin{ switch arg := args[0].(type) { case *object.Array: - + var sums float64 - for _,num := range arg.Elements { - - if num.Type() != object.INTEGER_OBJ && num.Type() != object.FLOAT_OBJ{ - return newError("Samahani namba tu zinahitajika") - }else{ - if num.Type() == object.INTEGER_OBJ{ - no , _ := strconv.Atoi(num.Inspect()) - floatnum := float64(no) - sums += floatnum - }else if num.Type() == object.FLOAT_OBJ { - no , _ := strconv.ParseFloat(num.Inspect(), 64) - sums += no + for _, num := range arg.Elements { + + if num.Type() != object.INTEGER_OBJ && num.Type() != object.FLOAT_OBJ { + return newError("Samahani namba tu zinahitajika") + } else { + if num.Type() == object.INTEGER_OBJ { + no, _ := strconv.Atoi(num.Inspect()) + floatnum := float64(no) + sums += floatnum + } else if num.Type() == object.FLOAT_OBJ { + no, _ := strconv.ParseFloat(num.Inspect(), 64) + sums += no + } + } - - - } } - if math.Mod(sums,1) == 0 { - return &object.Integer{Value : int64(sums)} + if math.Mod(sums, 1) == 0 { + return &object.Integer{Value: int64(sums)} } - return &object.Float {Value: float64(sums)} - + return &object.Float{Value: float64(sums)} + default: return newError("Samahani, hii function haitumiki na %s", args[0].Type()) } @@ -157,4 +156,44 @@ var builtins = map[string]*object.Builtin{ return &object.String{Value: string(args[0].Type())} }, }, + "fungua": { + Fn: func(args ...object.Object) object.Object { + + if len(args) > 2 { + return newError("Samahani, Hatuhitaji hoja zaidi ya 2, wewe umeweka %d", len(args)) + } + filename := args[0].(*object.String).Value + mode := os.O_RDONLY + if len(args) == 2 { + fileMode := args[1].(*object.String).Value + switch fileMode { + case "r": + mode = os.O_RDONLY + // still buggy, will work on this soon + // case "w": + // mode = os.O_WRONLY + // err := os.Remove(filename) + // if err != nil { + // return &object.Null{} + // } + // case "a": + // mode = os.O_APPEND + default: + return newError("Tumeshindwa kufungua file na mode %s", fileMode) + } + } + file, err := os.OpenFile(filename, os.O_CREATE|mode, 0644) + if err != nil { + return &object.Null{} + } + var reader *bufio.Reader + var writer *bufio.Writer + if mode == os.O_RDONLY { + reader = bufio.NewReader(file) + } else { + writer = bufio.NewWriter(file) + } + return &object.File{Filename: filename, Reader: reader, Writer: writer, Handle: file} + }, + }, } From cbba5be06a570f5996891bdf6dd25c5ab61733c0 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Fri, 6 Jan 2023 21:40:22 +0300 Subject: [PATCH 56/59] Add method to ast for evaluating methods --- src/ast/ast.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/ast/ast.go b/src/ast/ast.go index 9505542..3f6e407 100644 --- a/src/ast/ast.go +++ b/src/ast/ast.go @@ -507,3 +507,22 @@ func (se *SwitchExpression) String() string { return out.String() } + +type MethodExpression struct { + Expression + Token token.Token + Object Expression + Method Expression + Arguments []Expression +} + +func (me *MethodExpression) expressionNode() {} +func (me *MethodExpression) TokenLiteral() string { return me.Token.Literal } +func (me *MethodExpression) String() string { + var out bytes.Buffer + out.WriteString(me.Object.String()) + out.WriteString(".") + out.WriteString(me.Method.String()) + + return out.String() +} From 87fe687e85c557461ccf0a992d2b87e4c8e14617 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Fri, 6 Jan 2023 21:40:57 +0300 Subject: [PATCH 57/59] Add file object with methods 'soma' and 'funga --- src/object/file.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/object/file.go diff --git a/src/object/file.go b/src/object/file.go new file mode 100644 index 0000000..7b3c744 --- /dev/null +++ b/src/object/file.go @@ -0,0 +1,45 @@ +package object + +import ( + "bufio" + "io" + "os" +) + +type File struct { + Filename string + Reader *bufio.Reader // To read the file + Writer *bufio.Writer // To write on the file + Handle *os.File // To handle the actual file (open, close etc) +} + +func (f *File) Type() ObjectType { return FILE_OBJ } +func (f *File) Inspect() string { return f.Filename } +func (f *File) Method(method string, args []Object) Object { + switch method { + case "soma": + return f.read(args) + case "funga": + return f.close(args) + } + return nil +} + +func (f *File) read(args []Object) Object { + if len(args) != 0 { + return newError("Samahani, tunahitaji Hoja 0, wewe umeweka %d", len(args)) + } + if f.Reader == nil { + return nil + } + txt, _ := io.ReadAll(f.Reader) + return &String{Value: string(txt)} +} + +func (f *File) close(args []Object) Object { + if len(args) != 0 { + return newError("Samahani, tunahitaji Hoja 0, wewe umeweka %d", len(args)) + } + _ = f.Handle.Close() + return &Null{} +} From c9a508f9bb40de9f1278ba164678bb0af8a3d803 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Fri, 6 Jan 2023 21:42:14 +0300 Subject: [PATCH 58/59] Add ability to evaluate methods --- src/evaluator/evaluator.go | 3 +++ src/evaluator/method.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 src/evaluator/method.go diff --git a/src/evaluator/evaluator.go b/src/evaluator/evaluator.go index dabaa13..b7bb4a5 100644 --- a/src/evaluator/evaluator.go +++ b/src/evaluator/evaluator.go @@ -81,6 +81,9 @@ func Eval(node ast.Node, env *object.Environment) object.Object { body := node.Body return &object.Function{Parameters: params, Env: env, Body: body} + case *ast.MethodExpression: + return evalMethodExpression(node, env) + case *ast.CallExpression: function := Eval(node.Function, env) if isError(function) { diff --git a/src/evaluator/method.go b/src/evaluator/method.go new file mode 100644 index 0000000..884ca85 --- /dev/null +++ b/src/evaluator/method.go @@ -0,0 +1,28 @@ +package evaluator + +import ( + "github.com/AvicennaJr/Nuru/ast" + "github.com/AvicennaJr/Nuru/object" +) + +func evalMethodExpression(node *ast.MethodExpression, env *object.Environment) object.Object { + obj := Eval(node.Object, env) + if isError(obj) { + return obj + } + args := evalExpressions(node.Arguments, env) + if len(args) == 1 && isError(args[0]) { + return args[0] + } + return applyMethod(obj, node.Method, args) +} + +func applyMethod(obj object.Object, method ast.Expression, args []object.Object) object.Object { + switch obj := obj.(type) { + case *object.String: + return obj.Method(method.(*ast.Identifier).Value, args) + case *object.File: + return obj.Method(method.(*ast.Identifier).Value, args) + } + return newError("Samahani, %s haina function '%s()'", obj.Inspect(), method.(*ast.Identifier).Value) +} From 90cf8cfd3089617d0c86b110e065947921b73ba6 Mon Sep 17 00:00:00 2001 From: AvicennaJr Date: Fri, 6 Jan 2023 21:43:20 +0300 Subject: [PATCH 59/59] Delete dot.go from evaluation since we now use method.go --- src/evaluator/dot.go | 44 -------------------------------------------- 1 file changed, 44 deletions(-) delete mode 100644 src/evaluator/dot.go diff --git a/src/evaluator/dot.go b/src/evaluator/dot.go deleted file mode 100644 index 3acb1bb..0000000 --- a/src/evaluator/dot.go +++ /dev/null @@ -1,44 +0,0 @@ -package evaluator - -import ( - "github.com/AvicennaJr/Nuru/object" -) - -func evalDotExpression(left, right object.Object, line int) object.Object { - // if right.Type() != object.BUILTIN_OBJ { - // return newError("Mstari %d: '%s' sio function sahihi", line, right.Inspect()) - // } - switch left.(type) { - case *object.String: - return evalDotStringExpression(left, right, line) - case *object.Array: - return evalDotArrayExpression(left, right) - case *object.Dict: - return evalDotDictExpression(left, right, line) - default: - return FALSE - } -} - -func evalDotStringExpression(left, right object.Object, line int) object.Object { - leftVal := left.(*object.String) - rightVal := right.(*object.String) - - switch rightVal.Inspect() { - case "idadi": - return &object.Integer{Value: int64(len(leftVal.Value))} - } - return nil -} - -func evalDotDictExpression(left, right object.Object, line int) object.Object { - // to do - - return nil -} - -func evalDotArrayExpression(left, right object.Object) object.Object { - // to do - - return nil -}