From 97d8d0c8e7df66c4f122a4852503891738abf9cd Mon Sep 17 00:00:00 2001
From: odino
Date: Tue, 30 Mar 2021 19:00:55 +0400
Subject: [PATCH 1/2] Fix for parsing prefixes, closes #409
The way ABS used to parse prefixes (eg. -5) was kind of wonky:
we would dump in a token the whole prefix + number (-5) vs
parsing them separately and build the AST according to where
the minus token is found. This change makes it so -5 is actually
`(-)(5)`.
Problems with the previous approach:
* inconsistent behavior between `x = 5; -x` and `-5`
* `-5` would be treated differently than `- 5`
* code such as `1 -1` would be parsed as `(1); (-1)`, producing 2
statements. The return value would be -1 rather than 0
In general, I looked around and the common behavior is to read
tokens separately and, while parsing, figure out, based on their
position, what precedence they should have. This is what this PR
fundamentally does.
For example:
* when `-` is a prefix (`-2`), it should have the highest precedence over
any other operator. For example, `-2.clamp(0, 1)` should interpret as
`(-2).clamp(0, 1)`
* when `-` is not a prefix, it can fallback to its usual precedence (eg.
less than `*` or `/`)
Thie behavior is also consistent with other, languages, take ruby as an
example:
```ruby
irb(main):011:0> -1.clamp(0, 10)
=> 0
```
One way in which ABS now deviates with other implementations
is that, since we do not bother reading whitespaces etc in our lexer
`-1.clamp(0, 10)` and `- 1.clamp(0, 10)` are exactly the same (while
in ruby, for example, the latter is intepreted as `(-)(1.clamp(0,
10))`). I think for now this is a reasonable compromise, but I'm open
to change this behavior in subsequent versions of ABS.
This PR also fixes a couple additional issues:
* `+2` is now a valid number, earlier we only considered `-` as the only
valid prefix for numbers
* there could be instances where an infinite loop would be triggered
because of ABS parsing `x +y` as `x; +y;` where the return value is just
`+y`. A for loop written like this would have triggered the bug: `decr = f(x) {x -1}; for x = 0; x > -10; x = decr(x)`. It would only happen on for loops where our looper is decrementing
---
evaluator/evaluator.go | 10 ++++++++++
evaluator/evaluator_test.go | 21 ++++++++++++++++++++-
lexer/lexer.go | 7 -------
lexer/lexer_test.go | 3 ++-
parser/parser.go | 20 ++++++++++++++++----
parser/parser_test.go | 2 +-
6 files changed, 49 insertions(+), 14 deletions(-)
diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go
index b5c23a20..0d3a4787 100644
--- a/evaluator/evaluator.go
+++ b/evaluator/evaluator.go
@@ -506,6 +506,8 @@ func evalPrefixExpression(tok token.Token, operator string, right object.Object)
return evalBangOperatorExpression(right)
case "-":
return evalMinusPrefixOperatorExpression(tok, right)
+ case "+":
+ return evalPlusPrefixOperatorExpression(tok, right)
case "~":
return evalTildePrefixOperatorExpression(tok, right)
default:
@@ -601,6 +603,14 @@ func evalMinusPrefixOperatorExpression(tok token.Token, right object.Object) obj
return &object.Number{Value: -value}
}
+func evalPlusPrefixOperatorExpression(tok token.Token, right object.Object) object.Object {
+ if right.Type() != object.NUMBER_OBJ {
+ return newError(tok, "unknown operator: +%s", right.Type())
+ }
+
+ return right
+}
+
func evalNumberInfixExpression(
tok token.Token, operator string,
left, right object.Object,
diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go
index 0101358d..93d90367 100644
--- a/evaluator/evaluator_test.go
+++ b/evaluator/evaluator_test.go
@@ -110,6 +110,24 @@ func TestEvalNumberExpression(t *testing.T) {
{"a = 5; a **= 2; a", 25},
{"a = 5; a %= 3; a", 2},
{"a = 0; a += 1 + 1; a", 2},
+ {"2", 2},
+ {"-2", -2},
+ {"+2", 2},
+ {"+-2", -2},
+ {"+-+-+-2", -2},
+ {"2 + 1", 3},
+ {"2 +1", 3},
+ {"2 - 1", 1},
+ {"2 -1", 1},
+ {"+2", 2},
+ {"+2 +2", 4},
+ {" +2 + 2", 4},
+ {"+2 + 2", 4},
+ {"+ 2 + 2", 4},
+ {"-2 + 2", 0},
+ {"- 2 + 2", 0},
+ {"-2 +-2", -4},
+ {"x = 1; x+1", 2},
}
for _, tt := range tests {
@@ -296,7 +314,8 @@ func TestForExpressions(t *testing.T) {
{`x = 0; for k = 0; k < 11; k = k + 1 { if k < 10 { continue; }; x += k }; x`, 10},
{"a = 0; for x = 0; x < 10; x = x + 1 { a = a + 1}; a", 10},
{"a = 0; for x = 0; x < y; x = x + 1 { a = a + 1}; a", "identifier not found: y"},
- {"a = 0; increment = f(x) {x+1}; for x = 0; x < 10; x = increment(x) { a = a + 1}; a", 10},
+ {"a = 0; increment = f(x) { x+1 }; for x = 0; x < 10; x = increment(x) { a = a + 1}; a", 10},
+ {"a = 0; decrement = f(x) { x-1 }; for x = 0; x > -10; x = decrement(x) { a = a + 1}; a", 10},
{`a = 0; for k = 0; k < 10; k = k + 1 { a = a + 1}; k`, "identifier not found: k"},
{`k = 100; for k = 0; k < 10; k = k { k = k + 1}; k`, 100},
{`k = 100; for k = y; k < 10; k = k { k = 9 }; k`, "identifier not found: y"},
diff --git a/lexer/lexer.go b/lexer/lexer.go
index 9c18a4a3..bf875465 100644
--- a/lexer/lexer.go
+++ b/lexer/lexer.go
@@ -107,13 +107,6 @@ 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 5e7e317b..3207d94f 100644
--- a/lexer/lexer_test.go
+++ b/lexer/lexer_test.go
@@ -130,7 +130,8 @@ f hello(x, y) {
{token.ASSIGN, "="},
{token.NUMBER, "10"},
{token.SEMICOLON, ";"},
- {token.NUMBER, "-10"},
+ {token.MINUS, "-"},
+ {token.NUMBER, "10"},
{token.SEMICOLON, ";"},
{token.MINUS, "-"},
{token.NUMBER, "10"},
diff --git a/parser/parser.go b/parser/parser.go
index 3fbcb0c4..3ebe5347 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -24,6 +24,7 @@ const (
INDEX // array[index]
QUESTION // some?.function() or some?.property
DOT // some.function() or some.property
+ HIGHEST // special preference for -x or +y
)
var precedences = map[token.TokenType]int{
@@ -97,6 +98,7 @@ func New(l *lexer.Lexer) *Parser {
p.registerPrefix(token.STRING, p.ParseStringLiteral)
p.registerPrefix(token.NULL, p.ParseNullLiteral)
p.registerPrefix(token.BANG, p.parsePrefixExpression)
+ p.registerPrefix(token.PLUS, p.parsePrefixExpression)
p.registerPrefix(token.MINUS, p.parsePrefixExpression)
p.registerPrefix(token.TILDE, p.parsePrefixExpression)
p.registerPrefix(token.TRUE, p.ParseBoolean)
@@ -115,10 +117,10 @@ func New(l *lexer.Lexer) *Parser {
p.registerPrefix(token.AT, p.parseDecorator)
p.infixParseFns = make(map[token.TokenType]infixParseFn)
- p.registerInfix(token.QUESTION, p.parseQuestionExpression)
- p.registerInfix(token.DOT, p.parseDottedExpression)
p.registerInfix(token.PLUS, p.parseInfixExpression)
p.registerInfix(token.MINUS, p.parseInfixExpression)
+ p.registerInfix(token.QUESTION, p.parseQuestionExpression)
+ p.registerInfix(token.DOT, p.parseDottedExpression)
p.registerInfix(token.SLASH, p.parseInfixExpression)
p.registerInfix(token.EXPONENT, p.parseInfixExpression)
p.registerInfix(token.MODULO, p.parseInfixExpression)
@@ -380,6 +382,7 @@ func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement {
func (p *Parser) parseExpression(precedence int) ast.Expression {
prefix := p.prefixParseFns[p.curToken.Type]
+
if prefix == nil {
p.noPrefixParseFnError(p.curToken)
return nil
@@ -465,9 +468,18 @@ func (p *Parser) parsePrefixExpression() ast.Expression {
Token: p.curToken,
Operator: p.curToken.Literal,
}
-
+ precedence := PREFIX
+
+ // When +- are used as prefixes, we want them to have
+ // the highest priority, so that -5.clamp(4, 5) is read
+ // as (-5).clamp(4, 5) = 4 instead of
+ // -(5.clamp(4,5)) = -5
+ if p.curTokenIs(token.PLUS) || p.curTokenIs(token.MINUS) {
+ precedence = HIGHEST
+ }
p.nextToken()
- expression.Right = p.parseExpression(PREFIX)
+
+ expression.Right = p.parseExpression(precedence)
return expression
}
diff --git a/parser/parser_test.go b/parser/parser_test.go
index a0fccdb9..b0eefbbb 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -310,7 +310,7 @@ func TestOperatorPrecedenceParsing(t *testing.T) {
},
{
"3 + 4; -5 * 5",
- "(3 + 4)(-5 * 5)",
+ "(3 + 4)((-5) * 5)",
},
{
"3 + 4; - 5 * 5",
From 90d940fc08f81f6594b5550077e1c8de80d8742f Mon Sep 17 00:00:00 2001
From: odino
Date: Tue, 30 Mar 2021 20:06:06 +0400
Subject: [PATCH 2/2] prepared to release
---
README.md | 2 +-
VERSION | 2 +-
docs/README.md | 2 +-
docs/abs.wasm | Bin 5608258 -> 5576933 bytes
js/js.go | 59 +++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 62 insertions(+), 3 deletions(-)
mode change 100644 => 100755 docs/abs.wasm
create mode 100644 js/js.go
diff --git a/README.md b/README.md
index b83a8025..15498be1 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
-
+
diff --git a/VERSION b/VERSION
index 005119ba..8e8299dc 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.4.1
+2.4.2
diff --git a/docs/README.md b/docs/README.md
index 3539f04e..06d54636 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -5,7 +5,7 @@
-
+
diff --git a/docs/abs.wasm b/docs/abs.wasm
old mode 100644
new mode 100755
index 622789a7b0db40b62f88289b395645192179c190..b461c4992b517c5ac5eb82f05e2ee77b689bbaac
GIT binary patch
literal 5576933
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