diff --git a/monkey2/go.mod b/monkey2/go.mod new file mode 100644 index 0000000..d5ceb30 --- /dev/null +++ b/monkey2/go.mod @@ -0,0 +1,3 @@ +module gitea.paas.celticinfo.fr/oabrivard/monkeylang/monkey2 + +go 1.21.0 diff --git a/monkey2/lexer/lexer.go b/monkey2/lexer/lexer.go index 12669ea..7bffd17 100644 --- a/monkey2/lexer/lexer.go +++ b/monkey2/lexer/lexer.go @@ -33,7 +33,14 @@ func (l *Lexer) NextToken() token.Token { case '+': tok = newToken(token.PLUS, l.ch) case '-': - tok = newToken(token.MINUS, l.ch) + if l.peekChar() == '>' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.Token{Type: token.LAMBDA, Literal: literal} + } else { + tok = newToken(token.MINUS, l.ch) + } case '!': if l.peekChar() == '=' { ch := l.ch diff --git a/monkey2/lexer/lexer_test.go b/monkey2/lexer/lexer_test.go index 891ca96..ce0d562 100644 --- a/monkey2/lexer/lexer_test.go +++ b/monkey2/lexer/lexer_test.go @@ -14,6 +14,8 @@ let add = fn(x, y) { x + y; }; +let addlambda = fn(x,y) -> x + y; + let result = add(five, ten); !-/*5; 5 < 10 > 5; @@ -59,6 +61,20 @@ if (5 < 10) { {token.RBRACE, "}"}, {token.SEMICOLON, ";"}, {token.LET, "let"}, + {token.IDENT, "addlambda"}, + {token.ASSIGN, "="}, + {token.FUNCTION, "fn"}, + {token.LPAREN, "("}, + {token.IDENT, "x"}, + {token.COMMA, ","}, + {token.IDENT, "y"}, + {token.RPAREN, ")"}, + {token.LAMBDA, "->"}, + {token.IDENT, "x"}, + {token.PLUS, "+"}, + {token.IDENT, "y"}, + {token.SEMICOLON, ";"}, + {token.LET, "let"}, {token.IDENT, "result"}, {token.ASSIGN, "="}, {token.IDENT, "add"}, diff --git a/monkey2/parser/parser.go b/monkey2/parser/parser.go index dbb0c5c..28b5521 100644 --- a/monkey2/parser/parser.go +++ b/monkey2/parser/parser.go @@ -357,11 +357,28 @@ func (p *Parser) parseFunctionLiteral() ast.Expression { lit.Parameters = p.parseFunctionParameters() - if !p.expectPeek(token.LBRACE) { - return nil - } + if p.peekTokenIs(token.LAMBDA) { + // parse fn() with lambda syntax + p.nextToken() + p.nextToken() + + block := &ast.BlockStatement{Token: p.curToken} + block.Statements = []ast.Statement{} + + exp := p.parseExpressionStatement() + if exp != nil { + block.Statements = append(block.Statements, exp) + } - lit.Body = p.parseBlockStatement() + lit.Body = block + } else { + // parse fn() with bloc statement syntax + if !p.expectPeek(token.LBRACE) { + return nil + } + + lit.Body = p.parseBlockStatement() + } return lit } diff --git a/monkey2/parser/parser_test.go b/monkey2/parser/parser_test.go index 392d1f5..9daa95b 100644 --- a/monkey2/parser/parser_test.go +++ b/monkey2/parser/parser_test.go @@ -503,50 +503,58 @@ func TestIfElseExpression(t *testing.T) { } func TestFunctionLiteralParsing(t *testing.T) { - input := `fn(x, y) { x + y; }` + tests := []struct { + input string + }{ + {"fn(x, y) { x + y; }"}, + {"fn(x, y) -> x + y;"}, + } - l := lexer.New(input) - p := New(l) - program := p.ParseProgram() - checkParserErrors(t, p) + for _, tt := range tests { - if len(program.Statements) != 1 { - t.Fatalf("program.Body does not contain %d statements. got=%d\n", - 1, len(program.Statements)) - } + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) - stmt, ok := program.Statements[0].(*ast.ExpressionStatement) - if !ok { - t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", - program.Statements[0]) - } + if len(program.Statements) != 1 { + t.Fatalf("program.Body does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } - function, ok := stmt.Expression.(*ast.FunctionLiteral) - if !ok { - t.Fatalf("stmt.Expression is not ast.FunctionLiteral. got=%T", - stmt.Expression) - } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } - if len(function.Parameters) != 2 { - t.Fatalf("function literal parameters wrong. want 2, got=%d\n", - len(function.Parameters)) - } + function, ok := stmt.Expression.(*ast.FunctionLiteral) + if !ok { + t.Fatalf("stmt.Expression is not ast.FunctionLiteral. got=%T", + stmt.Expression) + } - testLiteralExpression(t, function.Parameters[0], "x") - testLiteralExpression(t, function.Parameters[1], "y") + if len(function.Parameters) != 2 { + t.Fatalf("function literal parameters wrong. want 2, got=%d\n", + len(function.Parameters)) + } - if len(function.Body.Statements) != 1 { - t.Fatalf("function.Body.Statements has not 1 statements. got=%d\n", - len(function.Body.Statements)) - } + testLiteralExpression(t, function.Parameters[0], "x") + testLiteralExpression(t, function.Parameters[1], "y") - 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]) - } + if len(function.Body.Statements) != 1 { + t.Fatalf("function.Body.Statements has not 1 statements. got=%d\n", + len(function.Body.Statements)) + } - testInfixExpression(t, bodyStmt.Expression, "x", "+", "y") + 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) { diff --git a/monkey2/token/token.go b/monkey2/token/token.go index 12158fa..029a392 100644 --- a/monkey2/token/token.go +++ b/monkey2/token/token.go @@ -33,6 +33,8 @@ const ( LBRACE = "{" RBRACE = "}" + LAMBDA = "->" + // Keywords FUNCTION = "FUNCTION" LET = "LET"