Skip to content

Commit

Permalink
Added parser support for import statements.
Browse files Browse the repository at this point in the history
  • Loading branch information
TwitchBronBron committed Apr 22, 2020
1 parent 634f22e commit f94c43a
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 24 deletions.
10 changes: 5 additions & 5 deletions src/DiagnosticMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@ export let DiagnosticMessages = {
code: 1023,
severity: DiagnosticSeverity.Error
}),
______UNUSED: () => ({
message: '',
importStatementMustBeDeclaredAtTopOfFile: () => ({
message: `'import' statement must be declared at the top of the file`,
code: 1024,
severity: DiagnosticSeverity.Error
}),
Expand Down Expand Up @@ -287,8 +287,8 @@ export let DiagnosticMessages = {
code: 1054,
severity: DiagnosticSeverity.Error
}),
expectedStringLiteralAfterLibraryKeyword: () => ({
message: `Missing string literal after 'library' keyword`,
expectedStringLiteralAfterKeyword: (keyword: string) => ({
message: `Missing string literal after '${keyword}' keyword`,
code: 1055,
severity: DiagnosticSeverity.Error
}),
Expand All @@ -298,7 +298,7 @@ export let DiagnosticMessages = {
severity: DiagnosticSeverity.Error
}),
libraryStatementMustBeDeclaredAtTopOfFile: () => ({
message: `Library statements must be declared at the top of the file`,
message: `'library' statement must be declared at the top of the file`,
code: 1057,
severity: DiagnosticSeverity.Error
}),
Expand Down
13 changes: 13 additions & 0 deletions src/files/BrsFile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,19 @@ describe('BrsFile', () => {
expect(file.getDiagnostics()).to.be.lengthOf(0);
});

it('adds error for library statements NOT at top of file', async () => {
await file.parse(`
sub main()
end sub
import "file.brs"
`);
expect(
file.getDiagnostics().map(x => x.message)
).to.eql([
DiagnosticMessages.importStatementMustBeDeclaredAtTopOfFile().message
]);
});

it('supports library imports', async () => {
await file.parse(`
Library "v30/bslCore.brs"
Expand Down
39 changes: 26 additions & 13 deletions src/files/BrsFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { FunctionParameter } from '../brsTypes';
import { Lexer, Token, TokenKind, Identifier, AllowedLocalIdentifiers } from '../lexer';
import { Parser, ParseMode } from '../parser';
import { AALiteralExpression, DottedGetExpression, FunctionExpression, LiteralExpression, CallExpression, VariableExpression, NewExpression, Expression } from '../parser/Expression';
import { AssignmentStatement, CommentStatement, FunctionStatement, IfStatement, LibraryStatement, Body, NamespaceStatement } from '../parser/Statement';
import { AssignmentStatement, CommentStatement, FunctionStatement, IfStatement, LibraryStatement, Body, NamespaceStatement, ImportStatement } from '../parser/Statement';
import { Program } from '../Program';
import { BrsType } from '../types/BrsType';
import { DynamicType } from '../types/DynamicType';
Expand Down Expand Up @@ -197,36 +197,49 @@ export class BrsFile {
}

public ensureLibraryCallsAreAtTopOfFile() {
let topOfFileLibraryStatements = [] as LibraryStatement[];
let topOfFileIncludeStatements = [] as Array<LibraryStatement | ImportStatement>;

for (let stmt of this.ast.statements) {
//skip comments
if (stmt instanceof CommentStatement) {
continue;
}
//if we found a non-library statement, this statement is not at the top of the file
if (stmt instanceof LibraryStatement) {
topOfFileLibraryStatements.push(stmt);
if (stmt instanceof LibraryStatement || stmt instanceof ImportStatement) {
topOfFileIncludeStatements.push(stmt);
} else {
//break out of the loop, we found all of our library statements
break;
}
}
let libraryStatementSearchResults = this.findAllInstances(LibraryStatement);
for (let result of libraryStatementSearchResults) {
let includeStatementSearchResults = util.findAllDeep<LibraryStatement | ImportStatement>(this.ast.statements, (item) => {
if (item instanceof LibraryStatement || item instanceof ImportStatement) {
return true;
} else {
return false;
}
});
for (let result of includeStatementSearchResults) {
//if this statement is not one of the top-of-file statements,
//then add a diagnostic explaining that it is invalid
if (!topOfFileLibraryStatements.includes(result.value)) {
this.diagnostics.push({
...DiagnosticMessages.libraryStatementMustBeDeclaredAtTopOfFile(),
range: result.value.range,
file: this
});
if (!topOfFileIncludeStatements.includes(result.value)) {
if (result.value instanceof LibraryStatement) {
this.diagnostics.push({
...DiagnosticMessages.libraryStatementMustBeDeclaredAtTopOfFile(),
range: result.value.range,
file: this
});
} else if (result.value instanceof ImportStatement) {
this.diagnostics.push({
...DiagnosticMessages.importStatementMustBeDeclaredAtTopOfFile(),
range: result.value.range,
file: this
});
}
}
}
}


/**
* Loop through all of the class statements and add them to `this.classStatements`
*/
Expand Down
6 changes: 6 additions & 0 deletions src/lexer/Lexer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ describe('lexer', () => {
TokenKind.Eof
]);
});

it('recognizes the import token', () => {
let { tokens } = Lexer.scan('import');
expect(tokens[0].kind).to.eql(TokenKind.Import);
});

it('recognizes library token', () => {
let { tokens } = Lexer.scan('library');
expect(tokens[0].kind).to.eql(TokenKind.Library);
Expand Down
10 changes: 7 additions & 3 deletions src/lexer/TokenKind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export enum TokenKind {
As = 'As',
New = 'New',
Override = 'Override',
Import = 'Import',


//comments
Expand Down Expand Up @@ -270,7 +271,8 @@ export const Keywords: { [key: string]: TokenKind } = {
override: TokenKind.Override,
namespace: TokenKind.Namespace,
endnamespace: TokenKind.EndNamespace,
'end namespace': TokenKind.EndNamespace
'end namespace': TokenKind.EndNamespace,
import: TokenKind.Import
};

/** Set of all keywords that end blocks. */
Expand Down Expand Up @@ -364,7 +366,8 @@ export const AllowedProperties = [
TokenKind.New,
TokenKind.Override,
TokenKind.Namespace,
TokenKind.EndNamespace
TokenKind.EndNamespace,
TokenKind.Import
];

/** List of TokenKind that are allowed as local var identifiers. */
Expand Down Expand Up @@ -392,7 +395,8 @@ export const AllowedLocalIdentifiers = [
TokenKind.New,
TokenKind.Override,
TokenKind.Namespace,
TokenKind.EndNamespace
TokenKind.EndNamespace,
TokenKind.Import
];

/**
Expand Down
33 changes: 32 additions & 1 deletion src/parser/Parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect } from 'chai';
import { Lexer, ReservedWords } from '../lexer';
import { DottedGetExpression, XmlAttributeGetExpression } from './Expression';
import { Parser, ParseMode } from './Parser';
import { PrintStatement, AssignmentStatement, FunctionStatement, NamespaceStatement } from './Statement';
import { PrintStatement, AssignmentStatement, FunctionStatement, NamespaceStatement, ImportStatement } from './Statement';
import { Range } from 'vscode-languageserver';
import { DiagnosticMessages } from '../DiagnosticMessages';

Expand Down Expand Up @@ -472,6 +472,37 @@ describe('parser', () => {
}
});
});

describe('import keyword', () => {

it('parses without errors', () => {
let { statements, diagnostics } = parse(`
import "somePath"
`, ParseMode.BrighterScript);
expect(diagnostics[0]?.message).not.to.exist;
expect(statements[0]).to.be.instanceof(ImportStatement);
});

it('catches import statements used in brightscript files', () => {
let { statements, diagnostics } = parse(`
import "somePath"
`, ParseMode.BrightScript);
expect(diagnostics[0]?.message).to.eql(
DiagnosticMessages.bsFeatureNotSupportedInBrsFiles('import statements').message
);
expect(statements[0]).to.be.instanceof(ImportStatement);
});

it('catchs missing file path', () => {
let { statements, diagnostics } = parse(`
import
`, ParseMode.BrighterScript);
expect(diagnostics[0]?.message).to.equal(
DiagnosticMessages.expectedStringLiteralAfterKeyword('import').message
);
expect(statements[0]).to.be.instanceof(ImportStatement);
});
});
});

function parse(text: string, mode?: ParseMode) {
Expand Down
28 changes: 26 additions & 2 deletions src/parser/Parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ import {
GotoStatement,
StopStatement,
NamespaceStatement,
Body
Body,
ImportStatement
} from './Statement';
import { DiagnosticMessages, DiagnosticInfo } from '../DiagnosticMessages';
import { util } from '../util';
Expand Down Expand Up @@ -669,6 +670,10 @@ export class Parser {
return this.libraryStatement();
}

if (this.check(TokenKind.Import)) {
return this.importStatement();
}

if (this.check(TokenKind.Stop)) {
return this.stopStatement();
}
Expand Down Expand Up @@ -1041,7 +1046,7 @@ export class Parser {
library: this.advance(),
//grab the next token only if it's a string
filePath: this.tryConsume(
DiagnosticMessages.expectedStringLiteralAfterLibraryKeyword(),
DiagnosticMessages.expectedStringLiteralAfterKeyword('library'),
TokenKind.StringLiteral
)
});
Expand All @@ -1054,6 +1059,25 @@ export class Parser {
return libStatement;
}

private importStatement() {
this.warnIfNotBrighterScriptMode('import statements');
let importStatement = new ImportStatement(
this.advance(),
//grab the next token only if it's a string
this.tryConsume(
DiagnosticMessages.expectedStringLiteralAfterKeyword('import'),
TokenKind.StringLiteral
)
);

//consume all tokens until the end of the line
this.flagUntil(TokenKind.Newline, TokenKind.Eof, TokenKind.Colon, TokenKind.Comment);

//consume to the next newline, eof, or colon
while (this.match(TokenKind.Newline, TokenKind.Eof, TokenKind.Colon)) { }
return importStatement;
}

private ifStatement(): IfStatement {
const ifToken = this.advance();
const startingRange = ifToken.range;
Expand Down
27 changes: 27 additions & 0 deletions src/parser/Statement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -845,3 +845,30 @@ export class NamespaceStatement implements Statement {
return this.body.transpile(state);
}
}

export class ImportStatement implements Statement {
constructor(
readonly importToken: Token,
readonly filePath: Token
) {
this.range = Range.create(
importToken.range.start,
(filePath ?? importToken).range.end
);
}

public range: Range;

transpile(state: TranspileState) {
//The xml files are responsible for adding the additional script imports, but
//add the import statement as a comment just for debugging purposes
return [
new SourceNode(
this.range.start.line + 1,
this.range.start.character,
state.file.pathAbsolute,
`'${this.importToken.text} "${this.filePath.text}"`
)
];
}
}

0 comments on commit f94c43a

Please sign in to comment.