Skip to content

Commit

Permalink
fix(parser): Allow complex expressions on RHS of set statements (#191)
Browse files Browse the repository at this point in the history
The parser was a little bit too greedy when attempting to detect set
statements, which I should have noticed with the weird workarounds in
that function.  Now that we're _not_ trying to read an entire expression
as the left-hand side of a set statement (e.g.  `foo + bar *= 3`), that
complication can be removed.

Arbitrarily complex expressions are now supported on the right-hand side
of a set statement, e.g. `foo.bar = true and false or 3 > 4`

fixes #156
  • Loading branch information
sjbarag committed Mar 9, 2019
1 parent f34cc8e commit 94dcefc
Show file tree
Hide file tree
Showing 6 changed files with 340 additions and 112 deletions.
70 changes: 27 additions & 43 deletions src/parser/Parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -632,50 +632,34 @@ export class Parser {
);
}

let expr = expression();

let left: Expr.Expression;
let operator: Token;
let right: Expr.Expression;

if (expr instanceof Expr.Binary) {
// Simple assignments (e.g. `foo.bar = "baz"`) are parsed as binary expressions
// because of the ambiguity of the `=` operator without context
left = expr.left;
operator = expr.token;
right = expr.right;
} else if (check(...assignmentOperators)) {
// Otherwise it appears to be a set statement
left = expr;
operator = advance();
let parsedRhs = expression();

// De-sugar assignment operators into binary expressions that operate on the
// left- and right-hand sides of the operator
right = new Expr.Binary(left, operator, parsedRhs);
} else {
return _expressionStatement();
}


// Create a dotted or indexed "set" based on the left-hand side's type
if (left instanceof Expr.IndexedGet) {
consume("Expected newline or ':' after indexed 'set' statement", Lexeme.Newline, Lexeme.Colon, Lexeme.Eof);

return new Stmt.IndexedSet(
left.obj,
left.index,
right,
left.closingSquare,
);
} else if (left instanceof Expr.DottedGet) {
consume("Expected newline or ':' after dotted 'set' statement", Lexeme.Newline, Lexeme.Colon, Lexeme.Eof);
let expr = call();
if (check(...assignmentOperators) && !(expr instanceof Expr.Call)) {
let left = expr;
let operator = advance();
let right = expression();


// Create a dotted or indexed "set" based on the left-hand side's type
if (left instanceof Expr.IndexedGet) {
consume("Expected newline or ':' after indexed 'set' statement", Lexeme.Newline, Lexeme.Colon, Lexeme.Eof);

return new Stmt.IndexedSet(
left.obj,
left.index,
operator.kind === Lexeme.Equal ? right : new Expr.Binary(left, operator, right),
left.closingSquare,
);
} else if (left instanceof Expr.DottedGet) {
consume("Expected newline or ':' after dotted 'set' statement", Lexeme.Newline, Lexeme.Colon, Lexeme.Eof);

return new Stmt.DottedSet(
left.obj,
left.name,
right,
);
return new Stmt.DottedSet(
left.obj,
left.name,
operator.kind === Lexeme.Equal ? right : new Expr.Binary(left, operator, right),
);
} else {
return _expressionStatement();
}
} else {
return _expressionStatement();
}
Expand Down
5 changes: 4 additions & 1 deletion test/e2e/Iterables.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ describe("end to end iterables", () => {
"3",

// modify twoDimensional.secondLayer.level to sanity-check *= and friends
"6"
"6",

// add `false` via expression to `empty`
"false"
]);
});
});
Expand Down
6 changes: 5 additions & 1 deletion test/e2e/resources/associative-arrays.brs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ twoDimensional.secondLayer.thirdLayer = { level: 3 }

print twoDimensional.secondLayer.thirdLayer.level

' modify the top for an as
' modify the top for a silly example
twoDimensional.secondLayer.level *= 3
print twoDimensional.secondLayer.level

' add property to `empty` to be really silly
empty.isEmpty = oneDimensional.isOneDimensional and false
print empty.isEmpty
2 changes: 1 addition & 1 deletion test/parser/expression/Function.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ describe("parser", () => {
it("allows sub expressions in call arguments", () => {
const { statements, errors } = parser.parse([
identifier("acceptsCallback"),
{ kind: Lexeme.LeftParen, text: "(", line: 1 },
token(Lexeme.LeftParen, "("),
token(Lexeme.Newline, "\\n"),

token(Lexeme.Function, "function"),
Expand Down
43 changes: 41 additions & 2 deletions test/parser/statement/Set.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe("parser indexed assignment", () => {
});

describe("dotted", () => {
test("assignment", () => {
it("assigns anonymous functions", () => {
let { statements, errors } = parser.parse([
identifier("foo"),
token(Lexeme.Dot, "."),
Expand All @@ -31,6 +31,25 @@ describe("parser indexed assignment", () => {
expect(statements).toMatchSnapshot();
});

it("assigns boolean expressions", () => {
let { statements, errors } = parser.parse([
identifier("foo"),
token(Lexeme.Dot, "."),
identifier("bar"),
token(Lexeme.Equal, "="),
token(Lexeme.True, "true"),
token(Lexeme.And, "and"),
token(Lexeme.False, "false"),
token(Lexeme.Newline, "\\n"),
EOF
]);

expect(errors).toEqual([])
expect(statements).toBeDefined();
expect(statements).not.toBeNull();
expect(statements).toMatchSnapshot();
});

test("assignment operator", () => {
let { statements, errors } = parser.parse([
identifier("foo"),
Expand All @@ -50,7 +69,7 @@ describe("parser indexed assignment", () => {
});

describe("bracketed", () => {
it("assignment", () => {
it("assigns anonymous functions", () => {
let { statements, errors } = parser.parse([
identifier("someArray"),
token(Lexeme.LeftSquare, "["),
Expand All @@ -71,6 +90,26 @@ describe("parser indexed assignment", () => {
expect(statements).toMatchSnapshot();
});

it("assigns boolean expressions", () => {
let { statements, errors } = parser.parse([
identifier("someArray"),
token(Lexeme.LeftSquare, "["),
token(Lexeme.Integer, "0", new Int32(0)),
token(Lexeme.RightSquare, "]"),
token(Lexeme.Equal, "="),
token(Lexeme.True, "true"),
token(Lexeme.And, "and"),
token(Lexeme.False, "false"),
token(Lexeme.Newline, "\\n"),
EOF
]);

expect(errors).toEqual([])
expect(statements).toBeDefined();
expect(statements).not.toBeNull();
expect(statements).toMatchSnapshot();
});

it("assignment operator", () => {
let { statements, errors } = parser.parse([
identifier("someArray"),
Expand Down
Loading

0 comments on commit 94dcefc

Please sign in to comment.