UNPKG

psl-parser

Version:

A parser for the Profile Scripting Language

651 lines 23.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const fs = require("fs"); const statementParser_1 = require("./statementParser"); const tokenizer_1 = require("./tokenizer"); const utilities_1 = require("./utilities"); /** * Used for checking the type of Member at runtime */ var MemberClass; (function (MemberClass) { MemberClass[MemberClass["method"] = 1] = "method"; MemberClass[MemberClass["parameter"] = 2] = "parameter"; MemberClass[MemberClass["property"] = 3] = "property"; MemberClass[MemberClass["declaration"] = 4] = "declaration"; MemberClass[MemberClass["column"] = 5] = "column"; MemberClass[MemberClass["table"] = 6] = "table"; MemberClass[MemberClass["proc"] = 7] = "proc"; })(MemberClass = exports.MemberClass || (exports.MemberClass = {})); // tslint:disable-next-line:class-name class _Method { constructor() { this.types = []; this.modifiers = []; this.parameters = []; this.line = -1; this.declarations = []; this.endLine = -1; this.memberClass = MemberClass.method; this.documentation = ''; this.statements = []; } } // tslint:disable-next-line:class-name class _Parameter { constructor() { this.modifiers = []; this.req = false; this.ret = false; this.literal = false; this.memberClass = MemberClass.parameter; } } const NON_METHOD_KEYWORDS = [ 'do', 'set', 'if', 'for', 'while', ]; exports.NON_TYPE_MODIFIERS = [ 'public', 'static', 'private', ]; function parseText(sourceText) { const parser = new Parser(); return parser.parseDocument(sourceText); } exports.parseText = parseText; function parseFile(sourcePath) { return new Promise((resolve, reject) => { fs.readFile(sourcePath, (err, data) => { if (err) { reject(err); } else { const parser = new Parser(); resolve(parser.parseDocument(data.toString())); } }); }); } exports.parseFile = parseFile; class Parser { constructor(tokenizer) { this.methods = []; this.properties = []; this.declarations = []; this.tokens = []; this.comments = []; if (tokenizer) this.tokenizer = tokenizer; } parseDocument(documentText) { this.tokenizer = tokenizer_1.getTokens(documentText); while (this.next()) { if (this.activeToken.isAlphanumeric() || this.activeToken.isMinusSign()) { const method = this.parseMethod(); if (!method) continue; this.methods.push(method); this.activeMethod = method; } else if (this.activeToken.isTab() || this.activeToken.isSpace()) { const lineNumber = this.activeToken.position.line; const tokenBuffer = this.loadTokenBuffer(); const propertyDef = this.lookForPropertyDef(tokenBuffer); if (propertyDef) { if (propertyDef.id) this.properties.push(propertyDef); this.activeProperty = propertyDef; continue; } const typeDec = this.lookForTypeDeclaration(tokenBuffer); if (typeDec.length > 0) { const activeDeclarations = this.activeMethod ? this.activeMethod.declarations : this.declarations; for (const dec of typeDec) activeDeclarations.push(dec); continue; } const extending = this.checkForExtends(tokenBuffer); if (extending) this.extending = extending; const pslPackage = this.checkForPSLPackage(tokenBuffer); if (pslPackage) this.pslPackage = pslPackage; if (this.activeMethod && this.activeMethod.batch && this.activeMethod.id.value === 'REVHIST') { continue; } const statements = this.parseStatementsOnLine(tokenBuffer); if (statements && this.activeMethod) this.activeMethod.statements = this.activeMethod.statements.concat(statements); if (this.activeProperty && this.activeProperty.id.position.line + 1 === lineNumber) { const documentation = this.checkForDocumentation(tokenBuffer); if (documentation) this.activeProperty.documentation = documentation; } else if (this.activeMethod && utilities_1.getLineAfter(this.activeMethod) === lineNumber) { const documentation = this.checkForDocumentation(tokenBuffer); if (documentation) this.activeMethod.documentation = documentation; } } else if (this.activeToken.isNewLine()) continue; else this.throwAwayTokensTil(13 /* NewLine */); } return { comments: this.comments, declarations: this.declarations, extending: this.extending, pslPackage: this.pslPackage, methods: this.methods, properties: this.properties, tokens: this.tokens, }; } next() { this.activeToken = this.tokenizer.next().value; if (this.activeToken) { this.tokens.push(this.activeToken); if (this.activeToken.isLineComment() || this.activeToken.isBlockComment()) { this.comments.push(this.activeToken); } } return this.activeToken !== undefined; } checkForDocumentation(tokenBuffer) { let i = 0; while (i < tokenBuffer.length) { const token = tokenBuffer[i]; if (token.isTab() || token.isSpace()) { i++; continue; } if (token.isBlockCommentInit() && tokenBuffer[i + 1] && tokenBuffer[i + 1].isBlockComment()) { return tokenBuffer[i + 1].value; } return ''; } } lookForTypeDeclaration(tokenBuffer) { let i = 0; const tokens = []; while (i < tokenBuffer.length) { const token = tokenBuffer[i]; if (token.isTab() || token.isSpace()) { i++; continue; } if (token.isAlphanumeric() && token.value === 'type') { for (let j = i + 1; j < tokenBuffer.length; j++) { const loadToken = tokenBuffer[j]; if (loadToken.isSpace() || loadToken.isTab()) continue; // if (loadToken.isEqualSign()) break; tokens.push(loadToken); } } else if (token.isAlphanumeric() && token.value === 'catch') { for (let j = i + 1; j < tokenBuffer.length; j++) { const loadToken = tokenBuffer[j]; if (loadToken.isSpace() || loadToken.isTab()) continue; // if (loadToken.isEqualSign()) break; tokens.push(new tokenizer_1.Token(1 /* Alphanumeric */, 'Error', { character: 0, line: 0 })); tokens.push(loadToken); break; } } break; } const memberClass = MemberClass.declaration; const declarations = []; let type; let tokenIndex = 0; let id; let hasType; const modifiers = []; while (tokenIndex < tokens.length) { const token = tokens[tokenIndex]; tokenIndex++; if (this.isDeclarationKeyword(token)) { modifiers.push(token); continue; } if (!hasType) { if (token.type !== 1 /* Alphanumeric */) break; if (token.value === 'static') { modifiers.push(token); hasType = true; } else { type = token; hasType = true; } continue; } else if (token.isAlphanumeric()) { id = token; if (hasType && !type) type = token; // declarations.push({types: [type], identifier}); } else if (token.isEqualSign()) { tokenIndex = this.skipToNextDeclaration(tokens, tokenIndex); if (id && type) declarations.push({ types: [type], id, memberClass, modifiers }); id = undefined; } else if (token.isOpenParen()) { const types = []; const myIdentifier = tokens[tokenIndex - 2]; while (tokenIndex < tokens.length) { const arrayTypeToken = tokens[tokenIndex]; tokenIndex++; if (arrayTypeToken.isOpenParen()) continue; else if (arrayTypeToken.isAlphanumeric()) { types.push(arrayTypeToken); } else if (arrayTypeToken.isComma()) { continue; } else if (arrayTypeToken.isCloseParen()) { if (type) declarations.push({ id: myIdentifier, types: [type].concat(types), memberClass, modifiers }); id = undefined; break; } } } // Cheating!! // else if (token.isPercentSign()) continue; else if (token.isComma()) { if (id && type) declarations.push({ types: [type], id, memberClass, modifiers }); id = undefined; continue; } else if (token.value === '\r') continue; else if (token.isBlockComment()) continue; else if (token.isBlockCommentInit()) continue; else if (token.isBlockCommentTerm()) continue; else if (token.isNewLine()) { if (id && type) declarations.push({ types: [type], id, memberClass, modifiers }); id = undefined; break; } else break; } if (id && type) declarations.push({ types: [type], id, memberClass, modifiers }); return declarations; } checkForExtends(tokenBuffer) { let i = 0; let classDef = false; let extending = false; let equals = false; while (i < tokenBuffer.length) { const token = tokenBuffer[i]; if (token.isTab() || token.isSpace()) { i++; continue; } else if (token.isNumberSign() && !classDef) { const nextToken = tokenBuffer[i + 1]; if (!nextToken) return; if (nextToken.value === 'CLASSDEF') { classDef = true; i += 2; } else break; } else if (token.value === 'extends' && !extending) { extending = true; i++; } else if (token.isEqualSign() && !equals) { equals = true; i++; } else if (token.isAlphanumeric() && classDef && extending && equals) { return token; } else { i++; } } return; } checkForPSLPackage(tokenBuffer) { let i = 0; let foundPackageToken = false; let fullPackage = ''; while (i < tokenBuffer.length) { const token = tokenBuffer[i]; if (token.isTab() || token.isSpace()) { i++; continue; } else if (token.isNumberSign() && !foundPackageToken) { const nextToken = tokenBuffer[i + 1]; if (!nextToken) return; if (nextToken.value === 'PACKAGE') { foundPackageToken = true; i += 2; } else break; } else if (token.isAlphanumeric() && foundPackageToken) { // TODO: Maybe this should return an ordered list of tokens? if (fullPackage === '') { fullPackage = token.value; } else { fullPackage += ('.' + token.value); } i++; } else { i++; } } if (fullPackage !== '') { return fullPackage; } return; } skipToNextDeclaration(identifiers, tokenIndex) { let parenStack = 0; while (tokenIndex < identifiers.length) { const token = identifiers[tokenIndex]; tokenIndex++; if (token.isOpenParen()) { parenStack++; } else if (token.isCloseParen()) { parenStack--; } else if (token.isComma() && parenStack === 0) { break; } } return tokenIndex; } isDeclarationKeyword(token) { if (token.type !== 1 /* Alphanumeric */) return false; const keywords = ['public', 'private', 'new', 'literal']; return keywords.indexOf(token.value) !== -1; } throwAwayTokensTil(type) { while (this.next() && this.activeToken.type !== type) ; } loadTokenBuffer() { const tokenBuffer = []; while (this.next() && this.activeToken.type !== 13 /* NewLine */) { tokenBuffer.push(this.activeToken); } return tokenBuffer; } lookForPropertyDef(tokenBuffer) { let i = 0; // TODO better loop while (i < tokenBuffer.length) { let token = tokenBuffer[i]; if (token.isTab() || token.isSpace()) { i++; continue; } if (token.isNumberSign()) { token = tokenBuffer[i + 1]; if (token && token.value === 'PROPERTYDEF') { const tokens = tokenBuffer.filter(t => { if (t.isNumberSign()) return false; if (t.value === 'PROPERTYDEF') return false; return t.type !== 32 /* Space */ && t.type !== 11 /* Tab */; }); const classTypes = []; const classIndex = tokens.findIndex(t => t.value === 'class'); if (tokens[classIndex + 1] && tokens[classIndex + 1].value === '=' && tokens[classIndex + 2] && tokens[classIndex + 2].isAlphanumeric()) { classTypes.push(tokens[classIndex + 2]); } return { id: tokens[0], memberClass: MemberClass.property, modifiers: this.findPropertyModifiers(tokens.slice(1)), types: classTypes, }; } else { break; } } else { break; } } return; } findPropertyModifiers(tokens) { return tokens.filter(t => { return t.value === 'private' || t.value === 'literal' || t.value === 'public'; }); } parseMethod() { let batchLabel = false; const method = new _Method(); do { if (!this.activeToken) continue; if (this.activeToken.isTab() || this.activeToken.isSpace()) continue; else if (this.activeToken.isNewLine()) break; else if (this.activeToken.isOpenParen()) { const processed = this.processParameters(method); if (!processed) return undefined; method.parameters = processed; break; } else if (this.activeToken.isAlphanumeric() || this.activeToken.isNumeric()) { if (batchLabel) { method.modifiers.push(this.activeToken); method.batch = true; break; } if (method.line === -1) { method.line = this.activeToken.position.line; } method.modifiers.push(this.activeToken); } else if (this.activeToken.isMinusSign()) { batchLabel = true; continue; } else if (this.activeToken.isLineCommentInit() || this.activeToken.isLineComment() || this.activeToken.isBlockCommentInit() || this.activeToken.isBlockComment() || this.activeToken.isBlockCommentTerm()) { continue; } else if (this.activeToken.value === '\r') continue; else if (this.activeToken.isCloseParen()) { if (!method.closeParen) { method.closeParen = this.activeToken; } } else { this.throwAwayTokensTil(13 /* NewLine */); if (method.modifiers.length > 1) { break; } return undefined; } } while (this.next()); return this.finalizeMethod(method); } finalizeMethod(method) { for (const keyword of NON_METHOD_KEYWORDS) { const index = method.modifiers.map(i => i.value).indexOf(keyword); if (index > -1 && index < method.modifiers.length - 1) { method.modifiers = [method.modifiers[0]]; method.parameters = []; } } // better way... method.id = method.modifiers.pop(); if (this.activeMethod) { this.activeMethod.endLine = method.id.position.line - 1; } const lastModifier = method.modifiers[method.modifiers.length - 1]; if (lastModifier && exports.NON_TYPE_MODIFIERS.indexOf(lastModifier.value) < 0) { method.types = [method.modifiers.pop()]; } this.activeMethod = method; return method; } processParameters(method) { const args = []; let param; let open = false; while (this.next()) { if (this.activeToken.isTab() || this.activeToken.isSpace() || this.activeToken.isNewLine()) continue; else if (this.activeToken.isOpenParen()) { open = true; if (!param) return undefined; if (param.types.length === 1 && !param.id) { param.id = param.types[0]; param.types[0] = this.getDummy(); } const objectArgs = this.processObjectArgs(); if (!objectArgs) return undefined; param.types = param.types.concat(objectArgs); continue; } else if (this.activeToken.isCloseParen()) { open = false; method.closeParen = this.activeToken; if (!param) break; if (param.types.length === 1 && !param.id) { param.id = param.types[0]; param.types[0] = this.getDummy(); } args.push(param); break; } else if (this.activeToken.isAlphanumeric()) { if (!param) param = new _Parameter(); // let value = this.activeToken.value; if (this.activeToken.value === 'req') { param.modifiers.push(this.activeToken); param.req = true; } else if (this.activeToken.value === 'ret') { param.modifiers.push(this.activeToken); param.ret = true; } else if (this.activeToken.value === 'literal') { param.modifiers.push(this.activeToken); param.literal = true; } else if (!param.types) param.types = [this.activeToken]; else { param.id = this.activeToken; } } else if (this.activeToken.isLineComment()) { if (param) { param.comment = this.activeToken; } else if (args.length >= 1) { args[args.length - 1].comment = this.activeToken; } } else if (this.activeToken.isComma()) { if (!param) return undefined; if (param.types.length === 1 && !param.id) { param.id = param.types[0]; param.types[0] = this.getDummy(); } args.push(param); param = undefined; } } if (open) return undefined; return args; } processObjectArgs() { const types = []; let found = false; while (this.next()) { const dummy = this.getDummy(); if (this.activeToken.isTab() || this.activeToken.isSpace()) continue; else if (this.activeToken.isCloseParen()) { if (types.length === 0) types.push(dummy); return types; } else if (this.activeToken.isAlphanumeric()) { if (!found) { types.push(this.activeToken); found = true; } else return undefined; } else if (this.activeToken.isComma()) { if (!found) { if (types.length === 0) { types.push(dummy); } types.push(dummy); } found = false; continue; } } return undefined; } parseStatementsOnLine(tokenBuffer) { const statementParser = new statementParser_1.StatementParser(tokenBuffer); try { return statementParser.parseLine(); } catch (_a) { return []; } } getDummy() { return new tokenizer_1.Token(-1 /* Undefined */, '', this.activeToken.position); } } //# sourceMappingURL=parser.js.map