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