Skip to content

Commit

Permalink
Finds and includes more deeply embedded expressions (#696)
Browse files Browse the repository at this point in the history
* Finds and includes more deeply embedded expressions

* Removed commented out code

* Update src/parser/Parser.ts

Co-authored-by: Bronley Plumb <bronley@gmail.com>

* Update src/parser/Expression.ts

Co-authored-by: Bronley Plumb <bronley@gmail.com>

* Updated tests to use direct string parsing

* Fixed some function comments

Co-authored-by: Bronley Plumb <bronley@gmail.com>
  • Loading branch information
markwpearce and TwitchBronBron committed Sep 23, 2022
1 parent 0805c1f commit 63b53bf
Show file tree
Hide file tree
Showing 9 changed files with 482 additions and 114 deletions.
26 changes: 26 additions & 0 deletions src/files/BrsFile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1311,6 +1311,32 @@ describe('BrsFile', () => {
expect(file.functionCalls[1].nameRange).to.eql(Range.create(5, 20, 5, 23));
});

it('finds function calls that are unfinished', () => {
let file = new BrsFile('absolute_path/file.brs', 'relative_path/file.brs', program);
file.parse(`
function DoA()
DoB("a"
end function
function DoB(a as string)
DoC(
end function
`);
expectDiagnostics(file.parser.diagnostics, [
DiagnosticMessages.expectedRightParenAfterFunctionCallArguments(),
DiagnosticMessages.expectedNewlineOrColon(),
DiagnosticMessages.unexpectedToken('end function'),
DiagnosticMessages.expectedRightParenAfterFunctionCallArguments(),
DiagnosticMessages.expectedNewlineOrColon()
]);
expect(file.functionCalls.length).to.equal(2);

expect(file.functionCalls[0].range).to.eql(Range.create(2, 20, 2, 27));
expect(file.functionCalls[0].nameRange).to.eql(Range.create(2, 20, 2, 23));

expect(file.functionCalls[1].range).to.eql(Range.create(5, 20, 5, 24));
expect(file.functionCalls[1].nameRange).to.eql(Range.create(5, 20, 5, 23));
});

it('sanitizes brs errors', () => {
let file = new BrsFile('absolute_path/file.brs', 'relative_path/file.brs', program);
file.parse(`
Expand Down
2 changes: 1 addition & 1 deletion src/files/BrsFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,7 @@ export class BrsFile {
}
}
let functionCall: FunctionCall = {
range: util.createRangeFromPositions(expression.range.start, expression.closingParen.range.end),
range: expression.range,
functionScope: this.getFunctionScopeAtPosition(callee.range.start),
file: this,
name: functionName,
Expand Down
35 changes: 20 additions & 15 deletions src/parser/Expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export class CallExpression extends Expression {
readonly args: Expression[]
) {
super();
this.range = util.createRangeFromPositions(this.callee.range.start, this.closingParen.range.end);
this.range = util.createBoundingRange(this.callee, this.openingParen, ...args, this.closingParen);
}

public readonly range: Range;
Expand All @@ -88,9 +88,11 @@ export class CallExpression extends Expression {
let arg = this.args[i];
result.push(...arg.transpile(state));
}
result.push(
state.transpileToken(this.closingParen)
);
if (this.closingParen) {
result.push(
state.transpileToken(this.closingParen)
);
}
return result;
}

Expand Down Expand Up @@ -429,8 +431,8 @@ export class IndexedGetExpression extends Expression {
...this.obj.transpile(state),
this.questionDotToken ? state.transpileToken(this.questionDotToken) : '',
state.transpileToken(this.openingSquare),
...this.index.transpile(state),
state.transpileToken(this.closingSquare)
...(this.index?.transpile(state) ?? []),
this.closingSquare ? state.transpileToken(this.closingSquare) : ''
];
}

Expand Down Expand Up @@ -546,7 +548,7 @@ export class ArrayLiteralExpression extends Expression {
readonly hasSpread = false
) {
super();
this.range = util.createRangeFromPositions(this.open.range.start, this.close.range.end);
this.range = util.createBoundingRange(this.open, ...this.elements, this.close);
}

public readonly range: Range;
Expand Down Expand Up @@ -591,10 +593,11 @@ export class ArrayLiteralExpression extends Expression {
result.push('\n');
result.push(state.indent());
}

result.push(
state.transpileToken(this.close)
);
if (this.close) {
result.push(
state.transpileToken(this.close)
);
}
return result;
}

Expand Down Expand Up @@ -637,7 +640,7 @@ export class AALiteralExpression extends Expression {
readonly close: Token
) {
super();
this.range = util.createRangeFromPositions(this.open.range.start, this.close.range.end);
this.range = util.createBoundingRange(this.open, ...this.elements, this.close);
}

public readonly range: Range;
Expand Down Expand Up @@ -704,9 +707,11 @@ export class AALiteralExpression extends Expression {
result.push(state.indent());
}
//close curly
result.push(
state.transpileToken(this.close)
);
if (this.close) {
result.push(
state.transpileToken(this.close)
);
}
return result;
}

Expand Down
166 changes: 102 additions & 64 deletions src/parser/Parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2357,12 +2357,17 @@ export class Parser {
private indexedGet(expr: Expression) {
let openingSquare = this.previous();
let questionDotToken = this.getMatchingTokenAtOffset(-2, TokenKind.QuestionDot);
let index: Expression;
let closingSquare: Token;
while (this.match(TokenKind.Newline)) { }

let index = this.expression();
try {
index = this.expression();
} catch (error) {
this.rethrowNonDiagnosticError(error);
}

while (this.match(TokenKind.Newline)) { }
let closingSquare = this.consume(
closingSquare = this.tryConsume(
DiagnosticMessages.expectedRightSquareBraceAfterArrayOrObjectIndex(),
TokenKind.RightSquareBracket
);
Expand Down Expand Up @@ -2429,11 +2434,14 @@ export class Parser {
expr = this.indexedGet(expr);
} else {
let dot = this.previous();
let name = this.consume(
let name = this.tryConsume(
DiagnosticMessages.expectedPropertyNameAfterPeriod(),
TokenKind.Identifier,
...AllowedProperties
);
if (!name) {
break;
}

// force it into an identifier so the AST makes some sense
name.kind = TokenKind.Identifier;
Expand All @@ -2444,15 +2452,17 @@ export class Parser {

} else if (this.checkAny(TokenKind.At, TokenKind.QuestionAt)) {
let dot = this.advance();
let name = this.consume(
let name = this.tryConsume(
DiagnosticMessages.expectedAttributeNameAfterAtSymbol(),
TokenKind.Identifier,
...AllowedProperties
);

// force it into an identifier so the AST makes some sense
name.kind = TokenKind.Identifier;

if (!name) {
break;
}
expr = new XmlAttributeGetExpression(expr, name as Identifier, dot);
//only allow a single `@` expression
break;
Expand Down Expand Up @@ -2483,13 +2493,19 @@ export class Parser {
});
throw this.lastDiagnosticAsError();
}
args.push(this.expression());
try {
args.push(this.expression());
} catch (error) {
this.rethrowNonDiagnosticError(error);
// we were unable to get an expression, so don't continue
break;
}
} while (this.match(TokenKind.Comma));
}

while (this.match(TokenKind.Newline)) { }

const closingParen = this.consume(
const closingParen = this.tryConsume(
DiagnosticMessages.expectedRightParenAfterFunctionCallArguments(),
TokenKind.RightParen
);
Expand Down Expand Up @@ -2614,34 +2630,39 @@ export class Parser {

while (this.match(TokenKind.Newline)) {
}
let closingSquare: Token;

if (!this.match(TokenKind.RightSquareBracket)) {
elements.push(this.expression());
try {
elements.push(this.expression());

while (this.matchAny(TokenKind.Comma, TokenKind.Newline, TokenKind.Comment)) {
if (this.checkPrevious(TokenKind.Comment) || this.check(TokenKind.Comment)) {
let comment = this.check(TokenKind.Comment) ? this.advance() : this.previous();
elements.push(new CommentStatement([comment]));
}
while (this.match(TokenKind.Newline)) {
while (this.matchAny(TokenKind.Comma, TokenKind.Newline, TokenKind.Comment)) {
if (this.checkPrevious(TokenKind.Comment) || this.check(TokenKind.Comment)) {
let comment = this.check(TokenKind.Comment) ? this.advance() : this.previous();
elements.push(new CommentStatement([comment]));
}
while (this.match(TokenKind.Newline)) {

}
}

if (this.check(TokenKind.RightSquareBracket)) {
break;
}
if (this.check(TokenKind.RightSquareBracket)) {
break;
}

elements.push(this.expression());
elements.push(this.expression());
}
} catch (error: any) {
this.rethrowNonDiagnosticError(error);
}

this.consume(
closingSquare = this.tryConsume(
DiagnosticMessages.unmatchedLeftSquareBraceAfterArrayLiteral(),
TokenKind.RightSquareBracket
);
} else {
closingSquare = this.previous();
}

let closingSquare = this.previous();

//this.consume("Expected newline or ':' after array literal", TokenKind.Newline, TokenKind.Colon, TokenKind.Eof);
return new ArrayLiteralExpression(elements, openingSquare, closingSquare);
}
Expand Down Expand Up @@ -2677,47 +2698,14 @@ export class Parser {
};

while (this.match(TokenKind.Newline)) { }

let closingBrace: Token;
if (!this.match(TokenKind.RightCurlyBrace)) {
let lastAAMember: AAMemberExpression;
if (this.check(TokenKind.Comment)) {
lastAAMember = null;
members.push(new CommentStatement([this.advance()]));
} else {
let k = key();
let expr = this.expression();
lastAAMember = new AAMemberExpression(
k.keyToken,
k.colonToken,
expr
);
members.push(lastAAMember);
}

while (this.matchAny(TokenKind.Comma, TokenKind.Newline, TokenKind.Colon, TokenKind.Comment)) {
// collect comma at end of expression
if (lastAAMember && this.checkPrevious(TokenKind.Comma)) {
lastAAMember.commaToken = this.previous();
}

//check for comment at the end of the current line
if (this.check(TokenKind.Comment) || this.checkPrevious(TokenKind.Comment)) {
let token = this.checkPrevious(TokenKind.Comment) ? this.previous() : this.advance();
members.push(new CommentStatement([token]));
try {
if (this.check(TokenKind.Comment)) {
lastAAMember = null;
members.push(new CommentStatement([this.advance()]));
} else {
this.consumeStatementSeparators(true);

//check for a comment on its own line
if (this.check(TokenKind.Comment) || this.checkPrevious(TokenKind.Comment)) {
let token = this.checkPrevious(TokenKind.Comment) ? this.previous() : this.advance();
lastAAMember = null;
members.push(new CommentStatement([token]));
continue;
}

if (this.check(TokenKind.RightCurlyBrace)) {
break;
}
let k = key();
let expr = this.expression();
lastAAMember = new AAMemberExpression(
Expand All @@ -2727,16 +2715,53 @@ export class Parser {
);
members.push(lastAAMember);
}

while (this.matchAny(TokenKind.Comma, TokenKind.Newline, TokenKind.Colon, TokenKind.Comment)) {
// collect comma at end of expression
if (lastAAMember && this.checkPrevious(TokenKind.Comma)) {
lastAAMember.commaToken = this.previous();
}

//check for comment at the end of the current line
if (this.check(TokenKind.Comment) || this.checkPrevious(TokenKind.Comment)) {
let token = this.checkPrevious(TokenKind.Comment) ? this.previous() : this.advance();
members.push(new CommentStatement([token]));
} else {
this.consumeStatementSeparators(true);

//check for a comment on its own line
if (this.check(TokenKind.Comment) || this.checkPrevious(TokenKind.Comment)) {
let token = this.checkPrevious(TokenKind.Comment) ? this.previous() : this.advance();
lastAAMember = null;
members.push(new CommentStatement([token]));
continue;
}

if (this.check(TokenKind.RightCurlyBrace)) {
break;
}
let k = key();
let expr = this.expression();
lastAAMember = new AAMemberExpression(
k.keyToken,
k.colonToken,
expr
);
members.push(lastAAMember);
}
}
} catch (error: any) {
this.rethrowNonDiagnosticError(error);
}

this.consume(
closingBrace = this.tryConsume(
DiagnosticMessages.unmatchedLeftCurlyAfterAALiteral(),
TokenKind.RightCurlyBrace
);
} else {
closingBrace = this.previous();
}

let closingBrace = this.previous();

const aaExpr = new AALiteralExpression(members, openingBrace, closingBrace);
this.addPropertyHints(aaExpr);
return aaExpr;
Expand Down Expand Up @@ -2911,6 +2936,19 @@ export class Parser {
return this.tokens[this.current - 1];
}

/**
* Sometimes we catch an error that is a diagnostic.
* If that's the case, we want to continue parsing.
* Otherwise, re-throw the error
*
* @param error error caught in a try/catch
*/
private rethrowNonDiagnosticError(error) {
if (!error.isDiagnostic) {
throw error;
}
}

/**
* Get the token that is {offset} indexes away from {this.current}
* @param offset the number of index steps away from current index to fetch
Expand Down
Loading

0 comments on commit 63b53bf

Please sign in to comment.