diff --git a/spec/ast.txt b/spec/ast.txt index 99cbf4aa1..a49dc3ef1 100644 --- a/spec/ast.txt +++ b/spec/ast.txt @@ -294,9 +294,14 @@ node JSString > JSToken { value: String, } +node JSInteger > JSToken { + value: Int, +} + node JSFromKeyword > JSToken; node JSReturnKeyword > JSToken; node JSTryKeyword > JSToken; +node JSFinallyKeyword > JSToken; node JSCatchKeyword > JSToken; node JSImportKeyword > JSToken; node JSAsKeyword > JSToken; @@ -377,6 +382,12 @@ node JSConditionalExpression > JSExpression { alternate: JSExpression, } +type JSValue = Int + +node JSLiteralExpression > JSExpression { + value: JSValue, +} + node JSReferenceExpression > JSExpression { name: String, } @@ -385,6 +396,17 @@ node JSSourceElement; node JSStatement > JSSourceElement; +node JSCatchBlock { + bindings: Option, + elements: Vec, +} + +node JSTryCatchStatement { + tryBlock: Vec, + catchBlock: Option, + finalBlock: Option>, +} + node JSExpressionStatement > JSStatement { expression: JSExpression, } @@ -395,6 +417,10 @@ node JSConditionalStatement > JSStatement { alternate: Vec, } +node JSReturnStatement > JSStatement { + value: Option, +} + node JSParameter { index: usize, bindings: JSPattern, diff --git a/src/ast.d.ts b/src/ast.d.ts index cd85c07fe..94182af46 100644 --- a/src/ast.d.ts +++ b/src/ast.d.ts @@ -75,59 +75,65 @@ export const enum SyntaxKind { JSOperator = 89, JSIdentifier = 90, JSString = 91, - JSFromKeyword = 92, - JSReturnKeyword = 93, - JSTryKeyword = 94, - JSCatchKeyword = 95, - JSImportKeyword = 96, - JSAsKeyword = 97, - JSConstKeyword = 98, - JSLetKeyword = 99, - JSExportKeyword = 100, - JSFunctionKeyword = 101, - JSWhileKeyword = 102, - JSForKeyword = 103, - JSCloseBrace = 104, - JSCloseBracket = 105, - JSCloseParen = 106, - JSOpenBrace = 107, - JSOpenBracket = 108, - JSOpenParen = 109, - JSSemi = 110, - JSComma = 111, - JSDot = 112, - JSDotDotDot = 113, - JSMulOp = 114, - JSAddOp = 115, - JSDivOp = 116, - JSSubOp = 117, - JSLtOp = 118, - JSGtOp = 119, - JSBOrOp = 120, - JSBXorOp = 121, - JSBAndOp = 122, - JSBNotOp = 123, - JSNotOp = 124, - JSBindPattern = 126, - JSConstantExpression = 128, - JSMemberExpression = 129, - JSCallExpression = 130, - JSBinaryExpression = 131, - JSUnaryExpression = 132, - JSNewExpression = 133, - JSSequenceExpression = 134, - JSConditionalExpression = 135, - JSReferenceExpression = 136, - JSExpressionStatement = 139, - JSConditionalStatement = 140, - JSParameter = 141, - JSImportStarBinding = 145, - JSImportAsBinding = 146, - JSImportDeclaration = 147, - JSFunctionDeclaration = 148, - JSArrowFunctionDeclaration = 149, - JSLetDeclaration = 150, - JSSourceFile = 151, + JSInteger = 92, + JSFromKeyword = 93, + JSReturnKeyword = 94, + JSTryKeyword = 95, + JSFinallyKeyword = 96, + JSCatchKeyword = 97, + JSImportKeyword = 98, + JSAsKeyword = 99, + JSConstKeyword = 100, + JSLetKeyword = 101, + JSExportKeyword = 102, + JSFunctionKeyword = 103, + JSWhileKeyword = 104, + JSForKeyword = 105, + JSCloseBrace = 106, + JSCloseBracket = 107, + JSCloseParen = 108, + JSOpenBrace = 109, + JSOpenBracket = 110, + JSOpenParen = 111, + JSSemi = 112, + JSComma = 113, + JSDot = 114, + JSDotDotDot = 115, + JSMulOp = 116, + JSAddOp = 117, + JSDivOp = 118, + JSSubOp = 119, + JSLtOp = 120, + JSGtOp = 121, + JSBOrOp = 122, + JSBXorOp = 123, + JSBAndOp = 124, + JSBNotOp = 125, + JSNotOp = 126, + JSBindPattern = 128, + JSConstantExpression = 130, + JSMemberExpression = 131, + JSCallExpression = 132, + JSBinaryExpression = 133, + JSUnaryExpression = 134, + JSNewExpression = 135, + JSSequenceExpression = 136, + JSConditionalExpression = 137, + JSLiteralExpression = 139, + JSReferenceExpression = 140, + JSCatchBlock = 143, + JSTryCatchStatement = 144, + JSExpressionStatement = 145, + JSConditionalStatement = 146, + JSReturnStatement = 147, + JSParameter = 148, + JSImportStarBinding = 152, + JSImportAsBinding = 153, + JSImportDeclaration = 154, + JSFunctionDeclaration = 155, + JSArrowFunctionDeclaration = 156, + JSLetDeclaration = 157, + JSSourceFile = 158, } @@ -650,9 +656,11 @@ export type JSToken | JSOperator | JSIdentifier | JSString + | JSInteger | JSFromKeyword | JSReturnKeyword | JSTryKeyword + | JSFinallyKeyword | JSCatchKeyword | JSImportKeyword | JSAsKeyword @@ -700,6 +708,11 @@ export interface JSString extends SyntaxBase { value: string; } +export interface JSInteger extends SyntaxBase { + kind: SyntaxKind.JSInteger; + value: bigint; +} + export interface JSFromKeyword extends SyntaxBase { kind: SyntaxKind.JSFromKeyword; } @@ -712,6 +725,10 @@ export interface JSTryKeyword extends SyntaxBase { kind: SyntaxKind.JSTryKeyword; } +export interface JSFinallyKeyword extends SyntaxBase { + kind: SyntaxKind.JSFinallyKeyword; +} + export interface JSCatchKeyword extends SyntaxBase { kind: SyntaxKind.JSCatchKeyword; } @@ -850,6 +867,7 @@ export type JSExpression | JSNewExpression | JSSequenceExpression | JSConditionalExpression + | JSLiteralExpression | JSReferenceExpression @@ -901,6 +919,11 @@ export interface JSConditionalExpression extends SyntaxBase { alternate: JSExpression; } +export interface JSLiteralExpression extends SyntaxBase { + kind: SyntaxKind.JSLiteralExpression; + value: JSValue; +} + export interface JSReferenceExpression extends SyntaxBase { kind: SyntaxKind.JSReferenceExpression; name: string; @@ -909,6 +932,7 @@ export interface JSReferenceExpression extends SyntaxBase { export type JSSourceElement = JSExpressionStatement | JSConditionalStatement + | JSReturnStatement | JSImportDeclaration | JSFunctionDeclaration | JSArrowFunctionDeclaration @@ -918,8 +942,22 @@ export type JSSourceElement export type JSStatement = JSExpressionStatement | JSConditionalStatement + | JSReturnStatement +export interface JSCatchBlock extends SyntaxBase { + kind: SyntaxKind.JSCatchBlock; + bindings: JSPattern | null; + elements: JSSourceElement[]; +} + +export interface JSTryCatchStatement extends SyntaxBase { + kind: SyntaxKind.JSTryCatchStatement; + tryBlock: JSSourceElement[]; + catchBlock: JSCatchBlock | null; + finalBlock: JSSourceElement[] | null; +} + export interface JSExpressionStatement extends SyntaxBase { kind: SyntaxKind.JSExpressionStatement; expression: JSExpression; @@ -932,6 +970,11 @@ export interface JSConditionalStatement extends SyntaxBase { alternate: JSStatement[]; } +export interface JSReturnStatement extends SyntaxBase { + kind: SyntaxKind.JSReturnStatement; + value: JSExpression | null; +} + export interface JSParameter extends SyntaxBase { kind: SyntaxKind.JSParameter; index: number; @@ -1074,9 +1117,11 @@ export type JSSyntax = JSOperator | JSIdentifier | JSString + | JSInteger | JSFromKeyword | JSReturnKeyword | JSTryKeyword + | JSFinallyKeyword | JSCatchKeyword | JSImportKeyword | JSAsKeyword @@ -1116,9 +1161,13 @@ export type JSSyntax | JSNewExpression | JSSequenceExpression | JSConditionalExpression + | JSLiteralExpression | JSReferenceExpression + | JSCatchBlock + | JSTryCatchStatement | JSExpressionStatement | JSConditionalStatement + | JSReturnStatement | JSParameter | JSImportStarBinding | JSImportAsBinding @@ -1205,9 +1254,11 @@ export type Syntax | JSOperator | JSIdentifier | JSString + | JSInteger | JSFromKeyword | JSReturnKeyword | JSTryKeyword + | JSFinallyKeyword | JSCatchKeyword | JSImportKeyword | JSAsKeyword @@ -1247,9 +1298,13 @@ export type Syntax | JSNewExpression | JSSequenceExpression | JSConditionalExpression + | JSLiteralExpression | JSReferenceExpression + | JSCatchBlock + | JSTryCatchStatement | JSExpressionStatement | JSConditionalStatement + | JSReturnStatement | JSParameter | JSImportStarBinding | JSImportAsBinding @@ -1337,9 +1392,11 @@ export function createBoltRecordDeclaration(modifiers: BoltDeclarationModifiers, export function createJSOperator(text: string, span?: TextSpan | null): JSOperator; export function createJSIdentifier(text: string, span?: TextSpan | null): JSIdentifier; export function createJSString(value: string, span?: TextSpan | null): JSString; +export function createJSInteger(value: bigint, span?: TextSpan | null): JSInteger; export function createJSFromKeyword(span?: TextSpan | null): JSFromKeyword; export function createJSReturnKeyword(span?: TextSpan | null): JSReturnKeyword; export function createJSTryKeyword(span?: TextSpan | null): JSTryKeyword; +export function createJSFinallyKeyword(span?: TextSpan | null): JSFinallyKeyword; export function createJSCatchKeyword(span?: TextSpan | null): JSCatchKeyword; export function createJSImportKeyword(span?: TextSpan | null): JSImportKeyword; export function createJSAsKeyword(span?: TextSpan | null): JSAsKeyword; @@ -1379,9 +1436,13 @@ export function createJSUnaryExpression(operator: JSOperator, operand: JSExpress export function createJSNewExpression(target: JSExpression, arguments: JSExpression[], span?: TextSpan | null): JSNewExpression; export function createJSSequenceExpression(expressions: JSExpression[], span?: TextSpan | null): JSSequenceExpression; export function createJSConditionalExpression(test: JSExpression, consequent: JSExpression, alternate: JSExpression, span?: TextSpan | null): JSConditionalExpression; +export function createJSLiteralExpression(value: JSValue, span?: TextSpan | null): JSLiteralExpression; export function createJSReferenceExpression(name: string, span?: TextSpan | null): JSReferenceExpression; +export function createJSCatchBlock(bindings: JSPattern | null, elements: JSSourceElement[], span?: TextSpan | null): JSCatchBlock; +export function createJSTryCatchStatement(tryBlock: JSSourceElement[], catchBlock: JSCatchBlock | null, finalBlock: JSSourceElement[] | null, span?: TextSpan | null): JSTryCatchStatement; export function createJSExpressionStatement(expression: JSExpression, span?: TextSpan | null): JSExpressionStatement; export function createJSConditionalStatement(test: JSExpression, consequent: JSStatement[], alternate: JSStatement[], span?: TextSpan | null): JSConditionalStatement; +export function createJSReturnStatement(value: JSExpression | null, span?: TextSpan | null): JSReturnStatement; export function createJSParameter(index: number, bindings: JSPattern, defaultValue: JSExpression | null, span?: TextSpan | null): JSParameter; export function createJSImportStarBinding(local: JSIdentifier, span?: TextSpan | null): JSImportStarBinding; export function createJSImportAsBinding(remote: JSIdentifier, local: JSIdentifier | null, span?: TextSpan | null): JSImportAsBinding; @@ -1478,9 +1539,11 @@ export function isJSToken(value: any): value is JSToken; export function isJSOperator(value: any): value is JSOperator; export function isJSIdentifier(value: any): value is JSIdentifier; export function isJSString(value: any): value is JSString; +export function isJSInteger(value: any): value is JSInteger; export function isJSFromKeyword(value: any): value is JSFromKeyword; export function isJSReturnKeyword(value: any): value is JSReturnKeyword; export function isJSTryKeyword(value: any): value is JSTryKeyword; +export function isJSFinallyKeyword(value: any): value is JSFinallyKeyword; export function isJSCatchKeyword(value: any): value is JSCatchKeyword; export function isJSImportKeyword(value: any): value is JSImportKeyword; export function isJSAsKeyword(value: any): value is JSAsKeyword; @@ -1522,11 +1585,15 @@ export function isJSUnaryExpression(value: any): value is JSUnaryExpression; export function isJSNewExpression(value: any): value is JSNewExpression; export function isJSSequenceExpression(value: any): value is JSSequenceExpression; export function isJSConditionalExpression(value: any): value is JSConditionalExpression; +export function isJSLiteralExpression(value: any): value is JSLiteralExpression; export function isJSReferenceExpression(value: any): value is JSReferenceExpression; export function isJSSourceElement(value: any): value is JSSourceElement; export function isJSStatement(value: any): value is JSStatement; +export function isJSCatchBlock(value: any): value is JSCatchBlock; +export function isJSTryCatchStatement(value: any): value is JSTryCatchStatement; export function isJSExpressionStatement(value: any): value is JSExpressionStatement; export function isJSConditionalStatement(value: any): value is JSConditionalStatement; +export function isJSReturnStatement(value: any): value is JSReturnStatement; export function isJSParameter(value: any): value is JSParameter; export function isJSDeclaration(value: any): value is JSDeclaration; export function isJSImportBinding(value: any): value is JSImportBinding; diff --git a/src/foreign/js/parser.ts b/src/foreign/js/parser.ts index 9cb1aec2e..9c139652f 100644 --- a/src/foreign/js/parser.ts +++ b/src/foreign/js/parser.ts @@ -19,11 +19,33 @@ import { JSString, createJSImportDeclaration, createJSImportStarBinding, - createJSImportAsBinding + createJSImportAsBinding, + JSReturnKeyword, + createJSReturnStatement, + JSReturnStatement, + createJSTryCatchStatement, + JSPattern, + createJSLiteralExpression, } from "../../ast" export type JSTokenStream = Stream; +const T0_EXPRESSION = [ + SyntaxKind.JSIdentifier, + SyntaxKind.JSString, + SyntaxKind.JSAddOp, + SyntaxKind.JSSubOp, + SyntaxKind.JSNotOp, + SyntaxKind.JSBNotOp, +]; + +const T0_STATEMENT = [ + ...T0_EXPRESSION, + SyntaxKind.JSReturnKeyword, + SyntaxKind.JSTryKeyword, + SyntaxKind.JSForKeyword, +]; + const T0_DECLARATION = [ SyntaxKind.JSConstKeyword, SyntaxKind.JSLetKeyword, @@ -34,6 +56,11 @@ const T0_DECLARATION = [ export class JSParser { + public parseJSPattern(tokens: JSTokenStream): JSPattern { + // TODO + tokens.get(); + } + public parseJSReferenceExpression(tokens: JSTokenStream): JSReferenceExpression { const t0 = tokens.get(); assertToken(t0, SyntaxKind.JSIdentifier); @@ -46,6 +73,11 @@ export class JSParser { const t0 = tokens.peek(); if (t0.kind === SyntaxKind.JSIdentifier) { return this.parseJSReferenceExpression(tokens); + } else if (t0.kind === SyntaxKind.JSInteger) { + tokens.get(); + const result = createJSLiteralExpression(t0.value); + setOrigNodeRange(result, t0, t0); + return result; } else { throw new ParseError(t0, [SyntaxKind.JSIdentifier]); } @@ -56,7 +88,11 @@ export class JSParser { let result = this.parsePrimitiveJSExpression(tokens); while (true) { const t1 = tokens.peek(); - if (t1.kind === SyntaxKind.JSCloseBrace || t1.kind === SyntaxKind.JSCloseParen || t1.kind === SyntaxKind.JSCloseBracket || t1.kind === SyntaxKind.JSSemi) { + if (t1.kind === SyntaxKind.JSCloseBrace + || t1.kind === SyntaxKind.JSCloseParen + || t1.kind === SyntaxKind.JSCloseBracket + || t1.kind === SyntaxKind.JSComma + || t1.kind === SyntaxKind.JSSemi) { break; } if (t1.kind === SyntaxKind.JSDot) { @@ -105,8 +141,79 @@ export class JSParser { return result; } + public parseJSReturnStatement(tokens: JSTokenStream): JSReturnStatement { + let value = null; + const t0 = tokens.get(); + assertToken(t0, SyntaxKind.JSReturnKeyword); + const t1 = tokens.peek(); + if (T0_EXPRESSION.indexOf(t1.kind) !== -1) { + value = this.parseJSExpression(tokens); + } + const result = createJSReturnStatement(value) + setOrigNodeRange(result, t0, value !== null ? value : t0); + return result; + } + + public parseJSTryCatchStatement(tokens: JSTokenStream): JSTryCatchStatement { + + let catchBlock = null; + let finallyBlock = null; + + let lastToken: JSToken; + + const t0 = tokens.get(); + assertToken(t0, SyntaxKind.JSTryKeyword); + const t1 = tokens.get(); + assertToken(t1, SyntaxKind.JSOpenBrace); + const tryBlock = this.parseJSSourceElementList(tokens); + const t3 = tokens.get(); + assertToken(t3, SyntaxKind.JSCloseBrace); + + let t4 = tokens.peek(); + if (t4.kind === SyntaxKind.JSCatchKeyword) { + tokens.get(); + const t5 = tokens.get(); + let bindings = null; + if (t5.kind === SyntaxKind.JSOpenParen) { + bindings = this.parseJSPattern(tokens); + const t6 = tokens.get(); + assertToken(t6, SyntaxKind.JSCloseParen); + } + const t7 = tokens.get(); + assertToken(t7, SyntaxKind.JSOpenBrace); + const elements = this.parseJSSourceElementList(tokens); + const t8 = tokens.get(); + assertToken(t8, SyntaxKind.JSCloseBrace); + lastToken = t8 + t4 = tokens.peek(); + } + + if (t4.kind === SyntaxKind.JSFinallyKeyword) { + tokens.get(); + const t7 = tokens.get(); + assertToken(t7, SyntaxKind.JSOpenBrace); + finallyBlock = this.parseJSSourceElementList(tokens); + const t8 = tokens.get(); + assertToken(t8, SyntaxKind.JSCloseBrace); + lastToken = t8 + } + + const result = createJSTryCatchStatement(tryBlock, catchBlock, finallyBlock) + setOrigNodeRange(result, t0, lastToken!); + return result; + } + public parseJSStatement(tokens: JSTokenStream): JSStatement { - return this.parseJSExpressionStatement(tokens); + const t0 = tokens.peek(); + if (t0.kind === SyntaxKind.JSReturnKeyword) { + return this.parseJSReturnStatement(tokens); + } else if (t0.kind === SyntaxKind.JSTryKeyword) { + return this.parseJSTryCatchStatement(tokens); + } else if (T0_EXPRESSION.indexOf(t0.kind) !== -1) { + return this.parseJSExpressionStatement(tokens); + } else { + throw new ParseError(t0, T0_STATEMENT); + } } public parseImportDeclaration(tokens: JSTokenStream): JSImportDeclaration { @@ -179,7 +286,7 @@ export class JSParser { const elements: JSSourceElement[] = []; while (true) { const t0 = tokens.peek(); - if (t0.kind === SyntaxKind.EndOfFile) { + if (t0.kind === SyntaxKind.EndOfFile || t0.kind === SyntaxKind.JSCloseBrace) { break; } if (t0.kind === SyntaxKind.JSSemi) { diff --git a/src/foreign/js/scanner.ts b/src/foreign/js/scanner.ts index 12095a259..835368868 100644 --- a/src/foreign/js/scanner.ts +++ b/src/foreign/js/scanner.ts @@ -41,10 +41,12 @@ import { createJSCatchKeyword, createJSFromKeyword, createJSString, + createJSTryKeyword, + createJSInteger, } from "../../ast" function isWhiteSpace(ch: string): boolean { - return /[\u0009\u000B\u000C\u0020\u00A0\u000B\uFEFF\p{Zs}]/.test(ch) + return /[\u0009\u000B\u000C\u0020\u00A0\u000B\uFEFF\p{Zs}]/u.test(ch) } function isLineTerminator(ch: string): boolean { @@ -287,6 +289,12 @@ export class JSScanner { } } + if (c0 === '0') { + this.getChar(); + const endPos = this.currPos.clone(); + return createJSInteger(0, new TextSpan(this.file, startPos, endPos)); + } + if (isOperator(c0)) { const text = this.takeWhile(isOperator) const span = new TextSpan(this.file, startPos, this.currPos.clone()); @@ -322,6 +330,7 @@ export class JSScanner { const span = new TextSpan(this.file, startPos, endPos); switch (name) { case 'return': return createJSReturnKeyword(span); + case 'try': return createJSTryKeyword(span); case 'catch': return createJSCatchKeyword(span); case 'from': return createJSFromKeyword(span); case 'let': return createJSLetKeyword(span); diff --git a/src/util.ts b/src/util.ts index 262239221..edb4c4139 100644 --- a/src/util.ts +++ b/src/util.ts @@ -223,6 +223,7 @@ export function getFileStem(filepath: string): string { export function describeKind(kind: SyntaxKind): string { switch (kind) { case SyntaxKind.JSIdentifier: + return "a JavaScript identifier" case SyntaxKind.BoltIdentifier: return "an identifier" case SyntaxKind.BoltOperator: @@ -331,6 +332,10 @@ export function describeKind(kind: SyntaxKind): string { return "a JavaScript string" case SyntaxKind.JSReturnKeyword: return "'return'"; + case SyntaxKind.JSForKeyword: + return "'for'"; + case SyntaxKind.JSTryKeyword: + return "'try'"; default: throw new Error(`failed to describe ${kindToString(kind)}`) }