psl-parser
Version:
A parser for the Profile Scripting Language
380 lines • 15.5 kB
JavaScript
"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