UNPKG

psl-parser

Version:

A parser for the Profile Scripting Language

380 lines 15.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const fs = require("fs-extra"); const path = require("path"); const parser_1 = require("./parser"); const tokenizer_1 = require("./tokenizer"); exports.dummyPosition = new tokenizer_1.Position(0, 0); class ParsedDocFinder { constructor(parsedDocument, paths, getWorkspaceDocumentText) { this.hierarchy = []; this.parsedDocument = parsedDocument; this.paths = paths; if (getWorkspaceDocumentText) this.getWorkspaceDocumentText = getWorkspaceDocumentText; this.procName = path.basename(this.paths.routine).split('.')[0]; } async resolveResult(callTokens) { let finder = this; if (callTokens.length === 1) { const result = await finder.searchParser(callTokens[0]); // check for core class or tables if (!result) { const pslClsNames = await getPslClsNames(this.paths.corePsl); if (pslClsNames.indexOf(callTokens[0].value) >= 0) { finder = await finder.newFinder(callTokens[0].value); return { fsPath: finder.paths.routine, }; } const tableName = callTokens[0].value.replace('Record', ''); const tableLocation = path.join(this.paths.table, tableName.toLowerCase(), tableName.toUpperCase() + '.TBL'); const tableLocationExists = await fs.pathExists(tableLocation); if (tableLocationExists) { return { fsPath: tableLocation, }; } else if (callTokens[0] === this.parsedDocument.extending) { finder = await finder.newFinder(callTokens[0].value); return { fsPath: finder.paths.routine, }; } else if (callTokens[0].value === 'this' || callTokens[0].value === this.procName) { return { fsPath: this.paths.routine, }; } } // handle static types else if (result.member.types[0] === callTokens[0]) { finder = await finder.newFinder(result.member.id.value); return { fsPath: finder.paths.routine, }; } return result; } else { let result; for (let index = 0; index < callTokens.length; index++) { const token = callTokens[index]; if (index === 0) { // handle core class const pslClsNames = await getPslClsNames(this.paths.corePsl); if (pslClsNames.indexOf(token.value) >= 0) { finder = await finder.newFinder(token.value); continue; } // skip over 'this' else if (token.value === 'this' || token.value === this.procName) { result = { fsPath: this.paths.routine, }; continue; } else { result = await finder.searchParser(token); } } if (!result || (result.fsPath === this.paths.routine && !result.member)) { result = await finder.searchInDocument(token.value); } if (!result) return; if (!callTokens[index + 1]) return result; let type = result.member.types[0].value; if (type === 'void') type = 'Primitive'; // TODO whack hack finder = await finder.newFinder(type); result = undefined; } } } async newFinder(routineName) { if (routineName.startsWith('Record') && routineName !== 'Record') { const tableName = routineName.replace('Record', ''); const tableDirectory = path.join(this.paths.table, tableName.toLowerCase()); const tableLocationExists = await fs.pathExists(tableDirectory); if (!tableLocationExists) return; const columns = (await fs.readdir(tableDirectory)).filter(file => file.endsWith('.COL')).map(col => { const colName = col.replace(`${tableName}-`, '').replace('.COL', ''); const ret = { id: new tokenizer_1.Token(1 /* Alphanumeric */, colName, exports.dummyPosition), memberClass: parser_1.MemberClass.column, modifiers: [], types: [new tokenizer_1.Token(1 /* Alphanumeric */, 'String', exports.dummyPosition)], }; return ret; }); const parsedDocument = { comments: [], declarations: [], extending: new tokenizer_1.Token(1 /* Alphanumeric */, 'Record', exports.dummyPosition), pslPackage: '', methods: [], properties: columns, tokens: [], }; const newPaths = Object.create(this.paths); newPaths.routine = tableDirectory; return new ParsedDocFinder(parsedDocument, newPaths, this.getWorkspaceDocumentText); } const pathsWithoutExtensions = this.paths.projectPsl.map(pslPath => path.join(pslPath, routineName)); for (const pathWithoutExtension of pathsWithoutExtensions) { for (const extension of ['.PROC', '.psl', '.PSL']) { const possiblePath = pathWithoutExtension + extension; const routineText = await this.getWorkspaceDocumentText(possiblePath); if (!routineText) continue; const newPaths = Object.create(this.paths); newPaths.routine = possiblePath; return new ParsedDocFinder(parser_1.parseText(routineText), newPaths, this.getWorkspaceDocumentText); } } } /** * Search the parsed document and parents for a particular member */ async searchParser(queriedToken) { const activeMethod = this.findActiveMethod(queriedToken); if (activeMethod) { const variable = this.searchInMethod(activeMethod, queriedToken); if (variable) return { member: variable, fsPath: this.paths.routine }; } return this.searchInDocument(queriedToken.value); } async searchInDocument(queriedId) { let foundProperty; if (path.relative(this.paths.routine, this.paths.table) === '..') { foundProperty = this.parsedDocument.properties.find(p => p.id.value.toLowerCase() === queriedId.toLowerCase()); if (foundProperty) { const tableName = path.basename(this.paths.routine).toUpperCase(); return { fsPath: path.join(this.paths.routine, `${tableName}-${foundProperty.id.value}.COL`), member: foundProperty, }; } } foundProperty = this.parsedDocument.properties.find(p => p.id.value === queriedId); if (foundProperty) return { member: foundProperty, fsPath: this.paths.routine }; const foundMethod = this.parsedDocument.methods.find(p => p.id.value === queriedId); if (foundMethod) return { member: foundMethod, fsPath: this.paths.routine }; if (this.parsedDocument.extending) { const parentRoutineName = this.parsedDocument.extending.value; if (this.hierarchy.indexOf(parentRoutineName) > -1) return; const parentFinder = await this.searchForParent(parentRoutineName); if (!parentFinder) return; return parentFinder.searchInDocument(queriedId); } } async findAllInDocument(results) { if (!results) results = []; const addToResults = (result) => { if (!results.find(r => r.member.id.value === result.member.id.value)) { results.push(result); } }; if (path.relative(this.paths.routine, this.paths.table) === '..') { this.parsedDocument.properties.forEach(property => { const tableName = path.basename(this.paths.routine).toUpperCase(); addToResults({ member: property, fsPath: path.join(this.paths.routine, `${tableName}-${property.id.value}.COL`) }); }); } this.parsedDocument.properties.forEach(property => { addToResults({ member: property, fsPath: this.paths.routine }); }); this.parsedDocument.methods.forEach(method => { addToResults({ member: method, fsPath: this.paths.routine }); }); if (this.parsedDocument.extending) { const parentRoutineName = this.parsedDocument.extending.value; if (this.hierarchy.indexOf(parentRoutineName) > -1) return results; const parentFinder = await this.searchForParent(parentRoutineName); if (!parentFinder) return results; return parentFinder.findAllInDocument(results); } return results; } async searchForParent(parentRoutineName) { const parentFinder = await this.newFinder(parentRoutineName); if (!parentFinder) return; parentFinder.hierarchy = this.hierarchy.concat(this.paths.routine); return parentFinder; } searchInMethod(activeMethod, queriedToken) { for (const variable of activeMethod.declarations.reverse()) { if (queriedToken.position.line < variable.id.position.line) continue; if (queriedToken.value === variable.id.value) return variable; } for (const parameter of activeMethod.parameters) { if (queriedToken.value === parameter.id.value) return parameter; } } findActiveMethod(queriedToken) { const methods = this.parsedDocument.methods.filter(method => queriedToken.position.line >= method.id.position.line); if (methods) return methods[methods.length - 1]; } async getWorkspaceDocumentText(fsPath) { return fs.readFile(fsPath).then(b => b.toString()).catch(() => ''); } } exports.ParsedDocFinder = ParsedDocFinder; async function getPslClsNames(dir) { try { const names = await fs.readdir(dir); return names.map(name => name.split('.')[0]); } catch (_a) { return []; } } /** * Get the tokens on the line of position, as well as the specific index of the token at position */ function searchTokens(tokens, position) { const tokensOnLine = tokens.filter(t => t.position.line === position.line); if (tokensOnLine.length === 0) return undefined; const index = tokensOnLine.findIndex(t => { if (t.isNewLine() || t.isSpace() || t.isTab()) return; const start = t.position; const end = { line: t.position.line, character: t.position.character + t.value.length }; return isBetween(start, position, end); }); return { tokensOnLine, index }; } exports.searchTokens = searchTokens; function isBetween(lb, t, ub) { return lb.line <= t.line && lb.character <= t.character && ub.line >= t.line && ub.character >= t.character; } function getCallTokens(tokensOnLine, index) { const ret = []; let current = getChildNode(tokensOnLine, index); if (!current) return ret; while (current.parent && current.token) { ret.unshift(current.token); current = current.parent; } if (current.token) ret.unshift(current.token); return ret; } exports.getCallTokens = getCallTokens; function getChildNode(tokensOnLine, index) { const currentToken = tokensOnLine[index]; if (!currentToken) return { token: undefined }; const previousToken = tokensOnLine[index - 1]; const nextToken = tokensOnLine[index + 1]; let routine = false; if (previousToken) { let newIndex = -1; if (currentToken.isPeriod()) { newIndex = resolve(tokensOnLine.slice(0, index)); } else if (previousToken.isCaret()) { routine = true; } else if (currentToken.isAlphanumeric() && previousToken.isPeriod()) { newIndex = resolve(tokensOnLine.slice(0, index - 1)); } if (newIndex >= 0) { const parent = getChildNode(tokensOnLine, newIndex); return { parent, token: currentToken }; } } if (nextToken && nextToken.isCaret()) { const routineToken = tokensOnLine[index + 2]; if (!routineToken) return undefined; return { parent: { token: routineToken, routine: true }, token: currentToken }; } if (currentToken.isAlphanumeric()) { return { token: currentToken, routine }; } return undefined; } function resolve(tokens) { const length = tokens.length; let parenCount = 0; if (length === 0) return -1; if (tokens[length - 1].isAlphanumeric()) return length - 1; for (let index = tokens.length - 1; index >= 0; index--) { const token = tokens[index]; if (token.isCloseParen()) parenCount++; else if (token.isOpenParen()) parenCount--; if (parenCount === 0) { if (index > 0 && tokens[index - 1].isAlphanumeric()) return index - 1; else return -1; } } return -1; } exports.resolve = resolve; function findCallable(tokensOnLine, index) { const callables = []; for (let tokenBufferIndex = 0; tokenBufferIndex <= index; tokenBufferIndex++) { const token = tokensOnLine[tokenBufferIndex]; if (!tokenBufferIndex && !token.isTab() && !token.isSpace()) return; if (token.isOpenParen()) { callables.push({ tokenBufferIndex: tokenBufferIndex - 1, parameterIndex: 0 }); } else if (token.isCloseParen()) { if (callables.length) callables.pop(); else return; } else if (token.isComma() && callables.length) { callables[callables.length - 1].parameterIndex += 1; } } if (!callables.length) return; const activeCallable = callables[callables.length - 1]; return { callTokens: getCallTokens(tokensOnLine, activeCallable.tokenBufferIndex), parameterIndex: activeCallable.parameterIndex, }; } exports.findCallable = findCallable; function getLineAfter(method) { return method.closeParen ? method.closeParen.position.line + 1 : method.id.position.line + 1; } exports.getLineAfter = getLineAfter; function getCommentsOnLine(parsedDocument, lineNumber) { return parsedDocument.comments.filter(t => { return t.position.line === lineNumber; }); } exports.getCommentsOnLine = getCommentsOnLine; //# sourceMappingURL=utilities.js.map