From be1204545c7f1b5fb01e47024580910327d8bd20 Mon Sep 17 00:00:00 2001 From: odino Date: Tue, 28 Jul 2020 18:10:33 +0400 Subject: [PATCH] Parsing negative numbers a-la ruby, closes #383 and #382 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This bugfix introduces negative numbers into our parser: before, -10 would be parsed as (minus, number) whereas right now it's going to be parsed as (number) only. This fixes a bug with precedences, where `-1.str()` would be parsed as (-, (1, str)), leading to first calling the str method on the positive number, then applying the minus. Before: ``` ⧐ -1.234.str() ERROR: unknown operator: -STRING [1:1] -1.234.str() ``` After: ``` ⧐ -1.234.str() -1.234 ``` If a space is found between the minus and the number, the old parsing mechanism still applies. I found inspiration in Ruby, where: ``` $ - 1.to_s() -@: undefined method `-@' for "1":String $-1.to_s() -1 $ -10.3.floor() -11 $ - 10.3.floor() -10 ``` --- VERSION | 2 +- evaluator/builtin_functions_test.go | 2 ++ lexer/lexer.go | 7 +++++++ lexer/lexer_test.go | 12 ++++++++++++ parser/parser.go | 1 - parser/parser_test.go | 6 +++++- 6 files changed, 27 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index c043eea7..b1b25a5f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.2.1 +2.2.2 diff --git a/evaluator/builtin_functions_test.go b/evaluator/builtin_functions_test.go index 60d84bb2..b711bed5 100644 --- a/evaluator/builtin_functions_test.go +++ b/evaluator/builtin_functions_test.go @@ -553,6 +553,8 @@ func TestBetween(t *testing.T) { {`1.between(0, 0.9)`, false}, {`1.between(1, 0)`, "arguments to between(min, max) must satisfy min < max (1 < 0 given)"}, {`1.between(1, 2)`, true}, + {`-1.between(-10, 0)`, true}, + {`-1.between(-10, -2)`, false}, } testBuiltinFunction(tests, t) diff --git a/lexer/lexer.go b/lexer/lexer.go index 5af54878..43659a38 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -107,6 +107,13 @@ func (l *Lexer) NextToken() token.Token { tok.Position = l.position tok.Literal = "-=" l.readChar() + } else if isDigit(l.peekChar()) { + tok.Position = l.position + l.readChar() + literal, kind := l.readNumber() + tok.Type = kind + tok.Literal = "-" + literal + return tok } else { tok = l.newToken(token.MINUS) } diff --git a/lexer/lexer_test.go b/lexer/lexer_test.go index a32a609c..1e1b331a 100644 --- a/lexer/lexer_test.go +++ b/lexer/lexer_test.go @@ -9,6 +9,9 @@ import ( func TestNextToken(t *testing.T) { input := `five = 5; ten = 10; +-10; +- 10; +1 - 2; add = f(x, y) { x + y; }; @@ -127,6 +130,15 @@ f hello(x, y) { {token.ASSIGN, "="}, {token.NUMBER, "10"}, {token.SEMICOLON, ";"}, + {token.NUMBER, "-10"}, + {token.SEMICOLON, ";"}, + {token.MINUS, "-"}, + {token.NUMBER, "10"}, + {token.SEMICOLON, ";"}, + {token.NUMBER, "1"}, + {token.MINUS, "-"}, + {token.NUMBER, "2"}, + {token.SEMICOLON, ";"}, {token.IDENT, "add"}, {token.ASSIGN, "="}, {token.FUNCTION, "f"}, diff --git a/parser/parser.go b/parser/parser.go index 152d10c9..c0abbf6d 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -469,7 +469,6 @@ func (p *Parser) parsePrefixExpression() ast.Expression { } p.nextToken() - expression.Right = p.parseExpression(PREFIX) return expression diff --git a/parser/parser_test.go b/parser/parser_test.go index 345154d2..a0fccdb9 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -171,7 +171,7 @@ func TestParsingPrefixExpressions(t *testing.T) { value interface{} }{ {"!5;", "!", 5}, - {"-15;", "-", 15}, + {"- 15;", "-", 15}, {"!foobar;", "!", "foobar"}, {"-foobar;", "-", "foobar"}, {"!true;", "!", true}, @@ -310,6 +310,10 @@ func TestOperatorPrecedenceParsing(t *testing.T) { }, { "3 + 4; -5 * 5", + "(3 + 4)(-5 * 5)", + }, + { + "3 + 4; - 5 * 5", "(3 + 4)((-5) * 5)", }, {