lib/parser.js (2,126 lines of code) (raw):

'use strict'; const { Tag, tip } = require('./tag'); const { Parser: BaseParser } = require('@jacksontian/skyline'); class Parser extends BaseParser { constructor(lexer) { super(lexer); this.comments = new Map(); this.notes = new Map(); this.index = 0; } move() { do { this.look = this.lexer.scan(); this.look.index = ++this.index; if (this.look.tag === Tag.COMMENT) { this.comments.set(this.look.index, this.look); } if (this.look.tag === Tag.NOTE) { this.notes.set(this.look.index, this.note()); } } while (this.look.tag === Tag.COMMENT); } tagTip(tag) { return tip(tag); } isID() { if (this.is(Tag.ID) || this.is(Tag.NEW) || this.is(Tag.EXTENDS) || this.is(Tag.SUPER) || this.is(Tag.TYPE) || this.is(Tag.NUMBER) || this.is(Tag.RPC)) { return true; } return false; } matchID() { if (this.isID()) { this.move(); } else { this.error(`Expect ${this.tagTip(Tag.ID)}, but ${this.tokenTip(this.look)}`); } } getIndex() { return this.look.index; } program() { this.move(); return this.module(); } note(){ const begin = this.getIndex(); const note = this.look; this.match(Tag.NOTE); this.match('('); const arg = this.expr(); this.match(')'); let end = this.getIndex(); if (this.is(';')) { end = this.getIndex(); this.move(); } return { type: 'note', note, arg, loc: { start: note.loc.start, end: this.lexer.loc() }, tokenRange: [begin, end] }; } extends() { const begin = this.getIndex(); this.match(Tag.EXTENDS); const alias = this.look; let end = this.getIndex(); this.match(Tag.ID); // for A.B if (this.look.tag === '.') { const path = [alias]; while (this.look.tag === '.') { this.move(); const id = this.look; path.push(id); this.match(Tag.ID); } return { type: 'subModel_or_moduleModel', path: path }; } if (this.is(';')) { end = this.getIndex(); this.move(); } alias.tokenRange = [begin, end]; return alias; } module() { var annotation; if (this.is(Tag.ANNOTATION)) { annotation = this.look; this.move(); } const imports = []; while (this.is(Tag.IMPORT)) { const begin = this.getIndex(); this.move(); let innerPath; if(this.is(Tag.STRING)) { innerPath = this.look.string; this.move(); } let alias = this.look; let end = this.getIndex(); this.match(Tag.ID); if(innerPath) { alias.innerPath = innerPath; } // import A.B as AB if (this.look.tag === '.') { this.move(); const mainModule = alias.lexeme; alias = this.look; end = this.getIndex(); const module = alias.lexeme; this.match(Tag.ID); if(this.isWord(Tag.ID, 'as')) { this.move(); alias = this.look; this.match(Tag.ID); end = this.getIndex(); } alias.mainModule = mainModule; alias.module = module; } imports.push(alias); if (this.is(';')) { end = this.getIndex(); this.move(); } alias.tokenRange = [begin, end]; } let extendOn; if (this.is(Tag.EXTENDS)) { extendOn = this.extends(); } return { annotation: annotation, imports: imports, extends: extendOn, type: 'module', moduleBody: this.moduleBody(), comments: this.comments, notes: this.notes, }; } moduleBody() { // moduleBody = "{" { const | type | model | exception | api | function } "}" const nodes = []; while (this.look.tag) { let node; let annotation; if (this.is(Tag.ANNOTATION)) { annotation = this.look; this.move(); } if (this.is(Tag.CONST)) { node = this.const(); } else if (this.isWord(Tag.ID, 'typedef')) { node = this.typedef(); } else if (this.isWord(Tag.ID, 'model')) { node = this.model(); } else if (this.isWord(Tag.ID, 'exception')) { node = this.exception(); } else if (this.isWord(Tag.ID, 'enum')) { node = this.enum(); } else if (this.isWord(Tag.ID, 'api')) { node = this.api(); } else if (this.is(Tag.RPC)) { node = this.rpc(); } else if (this.isWord(Tag.ID, 'type')) { node = this.type(); } else if (this.isWord(Tag.ID, 'init')) { node = this.init(); } else if (this.is(Tag.STATIC) || this.isWord(Tag.ID, 'async') || this.isWord(Tag.ID, 'function')) { node = this.fun(); } else { this.error('expect "const", "type", "model", "function", "init" or "api"'); } node.annotation = annotation; nodes.push(node); } return { type: 'moduleBody', nodes: nodes }; } // rpc = [ Annotation ] "rpc" rpcName "(" [ params ] ")" returnType rpcBody rpc() { this.match(Tag.RPC); const rpcName = this.look; this.match(Tag.ID); this.match('('); const params = this.params(); this.match(')'); this.match(':'); const returnType = this.baseType(); const rpcBody = this.object(); return { type: 'rpc', rpcName: rpcName, params: params, returnType: returnType, rpcBody: rpcBody, }; } baseType() { if (this.look.tag === '[') { const start = this.look; this.move(); const t = this.baseType(); const end = this.look; this.match(']'); return { type: 'array', subType: t, loc: { start: start.loc.start, end: end.loc.end }, }; } if (this.isWord(Tag.TYPE, 'map')) { let t = this.look; this.move(); this.match('['); const keyType = this.baseType(); this.match(']'); const valueType = this.baseType(); return { loc: { start: t.loc.start, end: valueType.loc.end }, type: 'map', keyType: keyType, valueType: valueType }; } if (this.isWord(Tag.TYPE, 'entry')) { let t = this.look; this.move(); this.match('['); const valueType = this.baseType(); this.match(']'); return { loc: { start: t.loc.start, end: valueType.loc.end }, type: 'entry', valueType: valueType }; } if (this.isWord(Tag.TYPE, 'iterator') || this.isWord(Tag.TYPE, 'asyncIterator')) { let t = this.look; this.move(); this.match('['); const valueType = this.baseType(); this.match(']'); return { loc: { start: t.loc.start, end: valueType.loc.end }, type: t.lexeme, valueType: valueType }; } if (this.is(Tag.ID)) { var t = this.look; this.move(); // for A.B if (this.look.tag === '.') { const path = [t]; let id; while (this.look.tag === '.') { this.move(); id = this.look; path.push(id); this.match(Tag.ID); } return { type: 'subModel_or_moduleModel', path: path, loc: { start: t.loc.start, end: id.loc.end }, }; } return t; } if (this.is(Tag.TYPE)) { let t = this.look; this.move(); return t; } this.error(`expect base type, model id or array form`); } init() { const begin = this.getIndex(); var start = this.look.loc; this.matchWord(Tag.ID, 'init'); this.match('('); const params = this.params(); this.match(')'); var initBody; let endToken; if (this.is('{')) { initBody = this.blockStmts(); endToken = initBody.tokenRange[1]; } else if (this.is(';')) { endToken = this.getIndex(); this.move(); } var end = this.look.loc; return { type: 'init', loc: { start: start.start, end: end.end }, params: params, initBody: initBody, tokenRange: [begin, endToken] }; } type() { const begin = this.getIndex(); this.matchWord(Tag.ID, 'type'); const vid = this.look; this.match(Tag.VID); this.match('='); const value = this.baseType(); const end = this.getIndex(); if (this.look.tag === ';') { this.move(); } return { type: 'type', vid: vid, value: value, tokenRange: [begin, end] }; } typedef() { const begin = this.getIndex(); this.matchWord(Tag.ID, 'typedef'); const value = this.look; this.match(Tag.ID); const end = this.getIndex(); if (this.look.tag === ';') { this.move(); } return { type: 'typedef', value: value, tokenRange: [begin, end] }; } const() { // const = "const" ID "=" constant ";" const begin = this.getIndex(); this.match(Tag.CONST); const constName = this.look; this.match(Tag.ID); this.match('='); const constValue = this.look; if (this.look.tag === Tag.STRING || this.look.tag === Tag.NUMBER || this.look.tag === Tag.BOOL) { this.move(); } else { this.error('const value must be STRING/NUMBER/BOOLEAN'); } const end = this.getIndex(); this.match(';'); return { type: 'const', constName, constValue, tokenRange: [begin, end] }; } model() { // model = "model" modelName "=" modelBody const begin = this.getIndex(); this.matchWord(Tag.ID, 'model'); const modelName = this.look; this.match(Tag.ID); let extendOn; if (this.is(Tag.EXTENDS)) { extendOn = this.extends(); } // 可选的 = if (this.look.tag === '=') { this.move(); } const body = this.modelBody(); let end = body.tokenRange[1]; if (this.is(';')) { // 可选的 ; end = this.getIndex(); this.move(); } return { type: 'model', modelName: modelName, extendOn: extendOn, modelBody: body, tokenRange: [begin, end] }; } modelBody() { // modelBody = "{" [ modelFields ] "}" // modelFields = modelField { "," modelField } [","] const begin = this.getIndex(); this.match('{'); const nodes = []; if (this.is('}')) { const end = this.getIndex(); this.move(); return { type: 'modelBody', nodes: nodes, tokenRange: [begin, end] }; } var node = this.modelField(); nodes.push(node); while (!this.is('}')) { if (this.is(',')) { this.move(); if (this.is('}')) { // only one fields break; } let node = this.modelField(); nodes.push(node); } else { this.error('expect ","'); } } const end = this.getIndex(); this.match('}'); return { type: 'modelBody', nodes: nodes, tokenRange: [begin, end] }; } modelField() { // modelField = fieldName ["?"] ":" fieldValue [ attrs ] var required = true; let fieldName = this.look; const begin = this.getIndex(); if (this.is(Tag.ID)) { this.move(); } else if ( this.isID()) { fieldName.tag = Tag.ID; this.move(); } else { this.error(`only id is allowed`); } if (this.look.tag === '?') { required = false; this.move(); } this.match(':'); const fieldValue = this.fieldValue(); const attrs = this.attrs(); const end = this.getIndex(); return { type: 'modelField', fieldName: fieldName, required: required, fieldValue: fieldValue, attrs: attrs, tokenRange: [begin, end] }; } enum() { // enum = "enum" enumName ":" enumType enumBody const begin = this.getIndex(); this.matchWord(Tag.ID, 'enum'); const enumName = this.look; this.match(Tag.ID); this.match(':'); const enumType = this.look; this.match(Tag.TYPE); const body = this.enumBody(); let end = body.tokenRange[1]; if (this.is(';')) { // 可选的 ; end = this.getIndex(); this.move(); } return { type: 'enum', enumName: enumName, enumType: enumType, enumBody: body, tokenRange: [begin, end] }; } enumBody() { // enumBody = "{" [ enumFields ] "}" const begin = this.getIndex(); this.match('{'); const nodes = []; if (this.is('}')) { const end = this.getIndex(); this.move(); return { type: 'modelBody', nodes: nodes, tokenRange: [begin, end] }; } var node = this.enumField(); nodes.push(node); while (!this.is('}')) { if (this.is(',')) { this.move(); if (this.is('}')) { // only one fields break; } let node = this.enumField(); nodes.push(node); } else { this.error('expect ","'); } } const end = this.getIndex(); this.match('}'); return { type: 'enumBody', nodes: nodes, tokenRange: [begin, end] }; } enumField() { // enumField = fieldName enumAttrs let fieldName = this.look; const begin = this.getIndex(); if (this.is(Tag.ID)) { this.move(); } else if (this.is(Tag.TYPE) || this.is(Tag.NEW)) { fieldName.tag = Tag.ID; this.move(); } else { this.error(`only id is allowed`); } const attrs = this.enumAttrs(); const end = this.getIndex(); return { type: 'enumField', fieldName: fieldName, enumAttrs: attrs, tokenRange: [begin, end] }; } exception() { // exception = "exception" exceptionName exceptionBody const begin = this.getIndex(); this.matchWord(Tag.ID, 'exception'); const exceptionName = this.look; this.match(Tag.ID); let extendOn; if (this.is(Tag.EXTENDS)) { extendOn = this.extends(); } // 可选的 = if (this.look.tag === '=') { this.move(); } const body = this.exceptionBody(); let end = body.tokenRange[1]; if (this.is(';')) { // 可选的 ; end = this.getIndex(); this.move(); } return { type: 'exception', exceptionName: exceptionName, extendOn: extendOn, exceptionBody: body, tokenRange: [begin, end] }; } exceptionBody() { // exceptionBody = "{" [ exceptionFields ] "}" const begin = this.getIndex(); this.match('{'); const nodes = []; if (this.is('}')) { const end = this.getIndex(); this.move(); return { type: 'exceptionBody', nodes: nodes, tokenRange: [begin, end] }; } var node = this.exceptionField(); nodes.push(node); while (!this.is('}')) { if (this.is(',')) { this.move(); if (this.is('}')) { // only one fields break; } let node = this.exceptionField(); nodes.push(node); } else { this.error('expect ","'); } } const end = this.getIndex(); this.match('}'); return { type: 'exceptionBody', nodes: nodes, tokenRange: [begin, end] }; } exceptionField() { var required = true; let fieldName = this.look; const begin = this.getIndex(); if (this.is(Tag.ID)) { this.move(); } else if ( this.isID()) { fieldName.tag = Tag.ID; this.move(); } else { this.error(`only id is allowed`); } if (this.look.tag === '?') { required = false; this.move(); } this.match(':'); const fieldValue = this.fieldValue(); const attrs = this.attrs(); const end = this.getIndex(); return { type: 'exceptionField', fieldName: fieldName, required: required, fieldValue: fieldValue, attrs: attrs, tokenRange: [begin, end] }; } fieldValue() { // fieldValue = ( type | arrayType | modelBody | mapType ) // attrs = "(" attr { "," attr } ")" // attr = attrName "=" constant // type = "string" | "number" | "boolean" // mapType = "map" "[" type "]" type if (this.look.tag === '{') { return this.modelBody(); } if (this.look.tag === '[') { const node = { type: 'fieldType', fieldType: 'array', }; this.move(); if (this.look.tag === '[') { node.fieldItemType = this.fieldValue(); } else if (this.look.tag === Tag.TYPE || this.look.tag === Tag.ID) { const type = this.baseType(); node.fieldItemType = type; } else if (this.look.tag === '{') { node.fieldItemType = this.modelBody(); } else { this.error('expect type or model name'); } this.match(']'); return node; } if (this.look.tag === Tag.TYPE) { const type = this.look; this.move(); if (type.lexeme === 'map') { this.match('['); const keyType = this.baseType(); this.match(']'); const valueType = this.baseType(); return { type: 'fieldType', fieldType: type.lexeme, keyType: keyType, valueType: valueType }; } if (type.lexeme === 'entry') { this.match('['); const valueType = this.baseType(); this.match(']'); return { type: 'fieldType', fieldType: type.lexeme, valueType: valueType }; } if (type.lexeme === 'iterator' || type.lexeme === 'asyncIterator') { this.match('['); const valueType = this.baseType(); this.match(']'); return { type: 'fieldType', fieldType: type.lexeme, valueType: valueType, }; } return { type: 'fieldType', fieldType: type.lexeme }; } if (this.look.tag === Tag.ID) { const model = this.look; this.move(); // for A.B if (this.look.tag === '.') { const path = [model]; while (this.look.tag === '.') { this.move(); const id = this.look; path.push(id); this.match(Tag.ID); } return { type: 'fieldType', fieldType: { type: 'subModel_or_moduleModel', path: path } }; } return { type: 'fieldType', fieldType: model }; } this.error('expect "{", "[", "string", "number", "map", ID'); } attrs() { var attrs = []; if (this.look.tag !== '(') { return attrs; } this.match('('); attrs.push(this.attr()); while (this.look.tag !== ')') { this.match(','); attrs.push(this.attr()); } this.match(')'); return attrs; } enumAttrs() { var attrs = []; this.match('('); attrs.push(this.enumAttr()); while (this.look.tag !== ')') { this.match(','); attrs.push(this.enumAttr()); } this.match(')'); return attrs; } attr() { const attrName = this.look; this.match(Tag.ID); this.match('='); const attrValue = this.look; if (attrValue.tag === Tag.STRING || attrValue.tag === Tag.NUMBER || attrValue.tag === Tag.BOOL) { this.move(); } else { this.error('expect string, number, bool'); } return { type: 'attr', attrName: attrName, attrValue: attrValue }; } enumAttr() { const attrName = this.look; this.match(Tag.ID); this.match('='); let attrValue; if (this.is(Tag.STRING)) { attrValue = this.string(); } else if (this.is(Tag.NUMBER)) { attrValue = this.number(); } else { this.error('expect string or number'); } return { type: 'enumAttr', attrName: attrName, attrValue: attrValue }; } api() { // api = "api" apiName "(" [ params ] ")" [returnType] apiBody [ returns ] const begin = this.getIndex(); this.matchWord(Tag.ID, 'api'); const apiName = this.look; this.match(Tag.ID); this.match('('); const params = this.params(); this.match(')'); this.match(':'); const returnType = this.baseType(); const apiBody = this.apiBody(); let end = apiBody.tokenRange[1]; var returnBody; if (this.isWord(Tag.ID, 'returns')) { // 可选的 this.move(); returnBody = this.returnBody(); end = returnBody.tokenRange[1]; } var runtimeBody; if (this.isWord(Tag.ID, 'runtime')) { this.move(); runtimeBody = this.object(); end = runtimeBody.tokenRange[1]; } return { type: 'api', apiName: apiName, params: params, returnType: returnType, apiBody: apiBody, returns: returnBody, runtimeBody: runtimeBody, tokenRange: [begin, end] }; } params() { if (this.look.tag === ')') { return { type: 'params', params: [] }; } var params = []; var param = this.param(); params.push(param); while (this.look.tag !== ')') { this.match(','); params.push(this.param()); } return { type: 'params', params: params }; } param() { const paramName = this.look; this.match(Tag.ID); this.match(':'); const paramType = this.baseType(); var defaultValue = null; if (this.look.tag === '=') { this.move(); defaultValue = this.look; this.match(Tag.STRING); } return { type: 'param', paramName: paramName, paramType: paramType, defaultValue: defaultValue }; } apiBody() { var stmts = this.blockStmts(); return { type: 'apiBody', stmts: stmts, tokenRange: [ stmts.tokenRange[0], stmts.tokenRange[1] ] }; } group() { // group = "(" expr ")" const begin = this.getIndex(); var start = this.lexer.loc(); this.match('('); var expr = this.expr(); this.match(')'); const end = this.getIndex(); return { type: 'group', expr: expr, loc: { start: start, end: this.lexer.loc(), }, tokenRange: [begin, end] }; } array() { // array = "[" [ arrayItems ] "]" // arrayItems = expr { "," expr } const begin = this.getIndex(); var start = this.lexer.loc(); this.match('['); var items = []; if (this.look.tag === ']') { const end = this.getIndex(); this.move(); return { type: 'array', items: items, tokenRange: [begin, end] }; } var item = this.expr(); items.push(item); while (this.look.tag !== ']') { if (this.look.tag === ',') { this.move(); let item = this.expr(); items.push(item); } else { this.error('expect ","'); } } const end = this.getIndex(); this.match(']'); return { type: 'array', items: items, loc: { start: start, end: this.lexer.loc() }, tokenRange: [begin, end] }; } object() { // object = "{" [ objectFields ] "}" // objectFields = objectField { "," objectField } [","] const begin = this.getIndex(); var start = this.lexer.loc(); this.match('{'); var fields = []; if (this.look.tag === '}') { const end = this.getIndex(); this.move(); return { type: 'object', fields: fields, loc: { start: start, end: this.lexer.loc() }, tokenRange: [begin, end] }; } var field = this.objectField(); fields.push(field); while (this.look.tag !== '}') { if (this.look.tag === ',') { this.move(); if (this.look.tag === '}') { // only one fields break; } let field = this.objectField(); fields.push(field); } else { this.error('expect ","'); } } const end = this.getIndex(); this.match('}'); return { type: 'object', fields: fields, loc: { start: start, end: this.lexer.loc() }, tokenRange: [begin, end] }; } fieldName() { let fieldName = this.look; while(this.isID() || this.is('-')) { this.move(); const id = this.look; if(this.is(Tag.NUMBER)) { fieldName.lexeme += id.value; } else if(this.isID()){ fieldName.lexeme += id.lexeme; } else if(this.is('-')){ fieldName.lexeme += '-'; } else { return fieldName; } } return fieldName; } objectField() { // objectField = objectFieldName "=" expr const begin = this.getIndex(); if(this.is(Tag.STRING)) { const fieldName = this.look; this.move(); this.match('='); const expr = this.expr(); const end = this.getIndex(); return { type: 'objectField', fieldName: fieldName, expr: expr, tokenRange: [begin, end] }; } if (this.isID()) { const fieldName = this.fieldName(); this.match('='); const expr = this.expr(); const end = this.getIndex(); return { type: 'objectField', fieldName: fieldName, expr: expr, tokenRange: [begin, end] }; } if (this.look.tag === '.') { this.move(); this.match('.'); this.match('.'); let expr = this.expr(); const end = this.getIndex(); return { type: 'expandField', expr: expr, tokenRange: [begin, end] }; } this.error('expect "..." or ID'); } args() { const args = []; if (this.look.tag === ')') { return args; } var arg = this.expr(); args.push(arg); while (this.look.tag !== ')') { this.match(','); args.push(this.expr()); } return args; } string() { var str = this.look; this.match(Tag.STRING); return { type: 'string', value: str, loc: str.loc }; } number() { var str = this.look; this.match(Tag.NUMBER); return { type: 'number', value: str, loc: str.loc }; } bool() { var v = this.look; this.match(Tag.BOOL); return { type: 'boolean', value: v.lexeme === 'true', loc: v.loc }; } template() { var elements = []; var loc = { start: this.look.loc.start, }; elements.push({ type: 'element', value: this.look }); var last = this.look; this.move(); if (last.tag === Tag.TEMPLATE && last.tail === true) { loc.end = last.loc.end; return { type: 'template_string', elements: elements, loc }; } for (; ;) { if (this.look.tag === Tag.TEMPLATE) { var current = this.look; elements.push({ type: 'element', value: this.look }); this.move(); if (current.tail === true) { loc.end = current.loc.end; break; } } else { var expr = this.expr(); elements.push({ type: 'expr', expr: expr }); } } return { type: 'template_string', elements: elements, loc }; } construct() { this.move(); const id = this.look; this.match(Tag.ID); if (this.look.tag === '(') { this.move(); const args = this.args(); this.match(')'); return { type: 'construct', aliasId: id, args: args }; } const propertyPath = []; while (this.look.tag === '.') { this.move(); const id = this.look; propertyPath.push(id); this.match(Tag.ID); } let object = null; if (this.look.tag === '{') { object = this.object(); } return { type: 'construct_model', aliasId: id, propertyPath, object: object }; } idThings() { var id = this.look; this.matchID(); // id++ if (this.is(Tag.INCREMENT)) { this.move(); return { type: 'increment', position: 'backend', expr: { type: 'variable', id: id, loc: id.loc }, loc: { start: id.loc.start, end: this.look.loc.end } }; } // id-- if (this.is(Tag.DECREMENT)) { this.move(); return { type: 'decrement', position: 'backend', expr: { type: 'variable', id: id, loc: id.loc }, loc: { start: id.loc.start, end: this.look.loc.end } }; } // id = xxx if (this.look.tag === '=') { this.move(); let expr = this.expr(); return { type: 'assign', left: { type: 'variable', id: id, }, expr }; } // id() if (this.look.tag === '(') { this.move(); const args = this.args(); this.match(')'); return { type: 'call', left: { type: 'method_call', id: id }, args, loc: { start: id.loc.start, end: this.lexer.loc() } }; } if (this.look.tag === '[') { this.move(); let accessKey = this.expr(); this.match(']'); // @id.x[] return this.mapAccess({ type: 'map_access', id: id, accessKey: accessKey, loc: { start: id.loc.start, end: this.lexer.loc() } }); } // id.x = xxx, id.x(), id.x if (this.look.tag === '.') { return this.properties(id); } return { type: 'variable', id: id, loc: id.loc }; } properties(id){ const propertyPath = []; while (this.look.tag === '.') { this.move(); var prop = this.look; this.matchID(); propertyPath.push(prop); } // id.x() if (this.look.tag === '(') { this.move(); const args = this.args(); this.match(')'); // Module.staticCall() or module.instanceCall() return { type: 'call', left: { type: 'static_or_instance_call', id: id, propertyPath: propertyPath }, args: args, loc: { start: id.loc.start, end: this.lexer.loc() }, }; } if (this.look.tag === '[') { this.move(); let accessKey = this.expr(); this.match(']'); // @id.x[] return this.mapAccess({ type: 'map_access', id: id, propertyPath: propertyPath, accessKey: accessKey, loc: { start: id.loc.start, end: this.lexer.loc() } }); } // id.x = xxx if (this.look.tag === '=') { this.move(); let expr = this.expr(); return { type: 'assign', left: { type: 'property', id, propertyPath }, expr: expr }; } // id.x return { type: 'property_access', id: id, propertyPath: propertyPath, loc: { start: id.loc.start, end: this.lexer.loc() } }; } mapAccess(mapAccessAst) { if (this.look.tag === '=') { this.move(); let expr = this.expr(); return { type: 'assign', left: mapAccessAst, expr: expr }; } return mapAccessAst; } vidThings() { var vid = this.look; this.match(Tag.VID); // @id = xxx if (this.look.tag === '=') { this.move(); var expr = this.expr(); return { type: 'assign', left: { type: 'virtualVariable', vid: vid }, expr: expr, loc: { start: vid.loc.start, end: this.lexer.loc() } }; } // @vid.x = xxx, @id.x(), @id.x if (this.look.tag === '.') { const propertyPath = []; while (this.look.tag === '.') { this.move(); var prop = this.look; this.match(Tag.ID); propertyPath.push(prop); } // id.x() if (this.look.tag === '(') { this.move(); const args = this.args(); this.match(')'); // Module.staticCall() or module.instanceCall() return { type: 'call', left: { type: 'static_or_instance_call', id: vid, propertyPath: propertyPath }, args: args, loc: { start: vid.loc.start, end: this.lexer.loc() }, }; } // id.x = xxx if (this.look.tag === '=') { this.move(); let expr = this.expr(); return { type: 'assign', left: { type: 'property', id: vid, propertyPath }, expr: expr }; } if (this.look.tag === '[') { this.move(); let accessKey = this.expr(); this.match(']'); // @id.x[] return this.mapAccess({ type: 'map_access', id: vid, propertyPath: propertyPath, accessKey: accessKey, loc: { start: vid.loc.start, end: this.lexer.loc() } }); } // id.x return { type: 'property_access', id: vid, propertyPath: propertyPath, loc: { start: vid.loc.start, end: this.lexer.loc() } }; } if (this.look.tag === '[') { this.move(); let accessKey = this.expr(); this.match(']'); // @id.x[] return this.mapAccess({ type: 'map_access', id: vid, accessKey: accessKey, loc: { start: vid.loc.start, end: this.lexer.loc() } }); } return { type: 'virtualVariable', vid: vid, loc: vid.loc }; } exprItem() { if (this.look.tag === '!') { return this.notExpr(); } if (this.look.tag === Tag.INCREMENT) { return this.increment(); } if (this.look.tag === Tag.DECREMENT) { return this.decrement(); } if (this.look.tag === Tag.STRING) { return this.string(); } if (this.look.tag === Tag.NUMBER) { return this.number(); } if (this.look.tag === Tag.BOOL) { return this.bool(); } if (this.look.tag === Tag.NULL) { this.move(); return { type: 'null' }; } if (this.look.tag === Tag.NEW) { return this.construct(); } if (this.look.tag === Tag.ID) { return this.idThings(); } if (this.look.tag === Tag.VID) { return this.vidThings(); } if (this.look.tag === '{') { return this.object(); } if (this.look.tag === '[') { return this.array(); } if (this.look.tag === '(') { return this.group(); } if (this.look.tag === Tag.TEMPLATE) { return this.template(); } if (this.look.tag === Tag.SUPER) { return this.superCall(); } this.error('expect valid expression'); } superCall() { var start = this.lexer.loc(); this.match(Tag.SUPER); this.match('('); const args = this.args(); this.match(')'); return { type: 'super', args: args, loc: { start: start, end: this.look.loc.end } }; } increment() { var start = this.lexer.loc(); this.match(Tag.INCREMENT); const expr = this.exprItem(); return { type: 'increment', position: 'front', expr: expr, loc: { start: start, end: this.look.loc.end } }; } decrement() { var start = this.lexer.loc(); this.match(Tag.DECREMENT); const expr = this.exprItem(); return { type: 'decrement', position: 'front', expr: expr, loc: { start: start, end: this.look.loc.end } }; } notExpr() { var start = this.lexer.loc(); this.match('!'); const expr = this.exprItem(); return { type: 'not', expr: expr, loc: { start: start, end: this.look.loc.end } }; } expr() { const begin = this.getIndex(); const item = this.exprItem(); let end = this.getIndex(); if (this.is('.')) { return this.properties(item); } if (this.is('+')) { this.move(); const right = this.expr(); return { type: 'add', left: item, right: right, loc: { start: item.loc.start, end: this.look.loc.end }, tokenRange: [begin, right.tokenRange[1]] }; } if (this.is('-')) { this.move(); const right = this.expr(); return { type: 'subtract', left: item, right: right, loc: { start: item.loc.start, end: this.look.loc.end }, tokenRange: [begin, right.tokenRange[1]] }; } if (this.is('*')) { this.move(); const right = this.expr(); return { type: 'multi', left: item, right: right, loc: { start: item.loc.start, end: this.look.loc.end }, tokenRange: [begin, right.tokenRange[1]] }; } if (this.is('/')) { this.move(); const right = this.expr(); return { type: 'div', left: item, right: right, loc: { start: item.loc.start, end: this.look.loc.end }, tokenRange: [begin, right.tokenRange[1]] }; } if (this.is(Tag.EQ)) { this.move(); const right = this.expr(); return { type: 'eq', left: item, right: right, loc: { start: item.loc.start, end: this.look.loc.end }, tokenRange: [begin, right.tokenRange[1]] }; } if (this.is(Tag.NEQ)) { this.move(); const right = this.expr(); return { type: 'neq', left: item, right: right, loc: { start: item.loc.start, end: this.look.loc.end }, tokenRange: [begin, right.tokenRange[1]] }; } if (this.is(Tag.GT)) { this.move(); const right = this.expr(); return { type: 'gt', left: item, right: right, loc: { start: item.loc.start, end: this.look.loc.end }, tokenRange: [begin, right.tokenRange[1]] }; } if (this.is(Tag.GTE)) { this.move(); const right = this.expr(); return { type: 'gte', left: item, right: right, loc: { start: item.loc.start, end: this.look.loc.end }, tokenRange: [begin, right.tokenRange[1]] }; } if (this.is(Tag.LTE)) { this.move(); const right = this.expr(); return { type: 'lte', left: item, right: right, loc: { start: item.loc.start, end: this.look.loc.end }, tokenRange: [begin, right.tokenRange[1]] }; } if (this.is(Tag.LT)) { this.move(); const right = this.expr(); return { type: 'lt', left: item, right: right, loc: { start: item.loc.start, end: this.look.loc.end }, tokenRange: [begin, right.tokenRange[1]] }; } if (this.is(Tag.AND)) { this.move(); const right = this.expr(); return { type: 'and', left: item, right: right, loc: { start: item.loc.start, end: this.look.loc.end }, tokenRange: [begin, right.tokenRange[1]] }; } if (this.is(Tag.OR)) { this.move(); const right = this.expr(); return { type: 'or', left: item, right: right, loc: { start: item.loc.start, end: this.look.loc.end }, tokenRange: [begin, right.tokenRange[1]] }; } if (this.is(Tag.INCREMENT)) { this.move(); return { type: 'increment', position: 'backend', expr: item, loc: { start: item.loc.start, end: this.look.loc.end }, tokenRange: [begin, end] }; } if (this.is(Tag.DECREMENT)) { this.move(); return { type: 'decrement', position: 'backend', expr: item, loc: { start: item.loc.start, end: this.look.loc.end }, tokenRange: [begin, end] }; } item.tokenRange = [begin, end]; return item; } returnBody() { var start = this.lexer.loc(); const stmts = this.blockStmts(); return { type: 'returnBody', loc: { start: start, end: this.lexer.loc() }, stmts: stmts, tokenRange: [stmts.tokenRange[0], stmts.tokenRange[1]] }; } stmts() { if (this.look.tag === '}') { return { type: 'stmts', stmts: [] }; } const stmts = []; var stmt = this.stmt(); stmts.push(stmt); while (this.look.tag !== '}') { let stmt = this.stmt(); stmts.push(stmt); } return { type: 'stmts', stmts: stmts }; } blockStmts() { const begin = this.getIndex(); this.match('{'); var stmts = this.stmts(); const end = this.getIndex(); this.match('}'); stmts.tokenRange = [begin, end]; return stmts; } ifStmt() { const begin = this.getIndex(); this.match(Tag.IF); this.match('('); var condition = this.expr(); this.match(')'); var stmts = this.blockStmts(); var end = stmts.tokenRange[1]; var elseStmts; var elseIfs = []; while (this.look.tag === Tag.ELSE) { this.move(); if (this.look.tag === Tag.IF) { this.move(); this.match('('); let condition = this.expr(); this.match(')'); let stmts = this.blockStmts(); end = stmts.tokenRange[1]; elseIfs.push({ type: 'elseif', condition: condition, stmts: stmts }); } else if (this.look.tag === '{') { elseStmts = this.blockStmts(); end = elseStmts.tokenRange[1]; break; } else { this.error('expect "if" or "{"'); } } return { type: 'if', condition: condition, stmts: stmts, elseIfs: elseIfs, elseStmts: elseStmts, tokenRange: [begin, end] }; } throwStmt() { const begin = this.getIndex(); this.match(Tag.THROW); let expr, end; if(this.look.tag === '{'){ expr = this.object(); end = expr.tokenRange[1]; } else if(this.look.tag === Tag.NEW) { expr = this.construct(); end = expr.object.tokenRange[1]; } else { this.error('Unexpected expr: expect a exception or object.'); } if (this.look.tag === ';') { end = this.getIndex(); this.move(); } return { type: 'throw', expr: expr, tokenRange: [begin, end] }; } tryStmt() { const begin = this.getIndex(); this.match(Tag.TRY); let tryStmts = this.blockStmts(); const catchBlocks = []; let catchStmts = null; let id = null; let finallyStmts = null; let end; if (this.look.tag === Tag.CATCH || this.look.tag === Tag.FINALLY) { while(this.look.tag === Tag.CATCH) { this.move(); this.match('('); const catchId = this.look; this.match(Tag.ID); if(this.is(')')) { // compatible with v1 // only can be last catch block this.move(); id = catchId; catchStmts = this.blockStmts(); catchBlocks.push({ id: catchId, catchStmts, }); end = catchStmts.tokenRange[1]; break; } this.match(':'); catchId.type = this.baseType(); this.match(')'); catchStmts = this.blockStmts(); catchBlocks.push({ id: catchId, catchStmts, }); end = catchStmts.tokenRange[1]; } if (this.look.tag === Tag.FINALLY) { this.move(); finallyStmts = this.blockStmts(); end = finallyStmts.tokenRange[1]; } } else { this.error('"try" expect "catch" or "finally"'); } return { type: 'try', tryBlock: tryStmts, catchId: id, catchBlock: catchStmts, catchBlocks: catchBlocks, finallyBlock: finallyStmts, tokenRange: [begin, end] }; } whileStmt() { const begin = this.getIndex(); this.match(Tag.WHILE); this.match('('); const expr = this.expr(); this.match(')'); const stmts = this.blockStmts(); const end = stmts.tokenRange[1]; return { type: 'while', condition: expr, stmts: stmts, tokenRange: [begin, end] }; } forStmt() { const begin = this.getIndex(); this.match(Tag.FOR); this.match('('); this.match(Tag.VAR); var id = this.look; this.match(Tag.ID); this.match(':'); const expr = this.expr(); this.match(')'); const stmts = this.blockStmts(); const end = stmts.tokenRange[1]; return { type: 'for', id: id, list: expr, stmts: stmts, tokenRange: [begin, end] }; } breakStmt() { const begin = this.getIndex(); this.match(Tag.BREAK); const end = this.getIndex(); this.match(';'); return { type: 'break', tokenRange: [begin, end] }; } retryStmt() { const token = this.look; const begin = this.getIndex(); this.matchWord(Tag.ID, 'retry'); const end = this.getIndex(); this.match(';'); return { type: 'retry', loc: token.loc, tokenRange: [begin, end] }; } stmt() { if (this.look.tag === Tag.IF) { return this.ifStmt(); } if (this.look.tag === Tag.WHILE) { return this.whileStmt(); } if (this.look.tag === Tag.FOR) { return this.forStmt(); } if (this.look.tag === Tag.TRY) { return this.tryStmt(); } if (this.isWord(Tag.ID, 'retry')) { return this.retryStmt(); } if (this.look.tag === Tag.BREAK) { return this.breakStmt(); } if (this.look.tag === Tag.RETURN) { return this.returnStmt(); } if (this.look.tag === Tag.YIELD) { return this.yieldStmt(); } if (this.look.tag === Tag.THROW) { return this.throwStmt(); } if (this.look.tag === Tag.VAR) { return this.declare(); } let expr = this.expr(); this.match(';'); return expr; } returnStmt() { const begin = this.getIndex(); var returnToken = this.look; this.move(); let end = this.getIndex(); if (this.is(';')) { this.move(); return { type: 'return', loc: { start: returnToken.loc.start, end: this.look.loc.end }, tokenRange: [begin, end] }; } let expr = this.expr(); end = this.getIndex(); this.match(';'); return { type: 'return', expr: expr, loc: { start: returnToken.loc.start, end: this.look.loc.end }, tokenRange: [begin, end] }; } yieldStmt() { const begin = this.getIndex(); var yieldToken = this.look; this.move(); let end = this.getIndex(); if (this.is(';')) { this.move(); return { type: 'yield', loc: { start: yieldToken.loc.start, end: this.look.loc.end }, tokenRange: [begin, end] }; } let expr = this.expr(); end = this.getIndex(); this.match(';'); return { type: 'yield', expr: expr, loc: { start: yieldToken.loc.start, end: this.look.loc.end }, tokenRange: [begin, end] }; } declare() { const begin = this.getIndex(); this.match(Tag.VAR); let id = this.look; this.matchID(); let expectedType; if (this.look.tag === ':') { this.move(); expectedType = this.baseType(); } this.match('='); let expr = this.expr(); const end = this.getIndex(); this.match(';'); return { type: 'declare', id: id, expr: expr, expectedType, tokenRange: [begin, end] }; } fun() { // function = [ Annotation ] ["static"] "funtion" apiName "(" [ params ] ")" returnType functionBody const begin = this.getIndex(); let isStatic = false; let hasThrow = false; if (this.is(Tag.STATIC)) { isStatic = true; this.move(); } let isAsync = false; if (this.isWord(Tag.ID, 'async')) { isAsync = true; this.move(); } this.matchWord(Tag.ID, 'function'); const functionName = this.look; this.match(Tag.ID); this.match('('); const params = this.params(); this.match(')'); if (this.isWord(Tag.ID, 'throws')) { hasThrow = true; this.move(); } this.match(':'); const returnType = this.baseType(); let functionBody = null; let end = this.getIndex(); if (this.look.tag === '{') { functionBody = this.functionBody(); end = functionBody.tokenRange[1]; } else if (this.look.tag === ';') { this.move(); } return { type: 'function', isStatic: isStatic, isAsync: isAsync, hasThrow: hasThrow, functionName: functionName, params: params, returnType: returnType, functionBody: functionBody, tokenRange: [begin, end] }; } functionBody() { var start = this.lexer.loc(); const stmts = this.blockStmts(); return { type: 'functionBody', loc: { start: start, end: this.lexer.loc() }, stmts: stmts, tokenRange: [stmts.tokenRange[0], stmts.tokenRange[1]] }; } } module.exports = Parser;