@syntaxs/compiler
Version:
Compiler used to compile Syntax Script projects.
670 lines (669 loc) • 35.7 kB
JavaScript
import { CompilerError, NodeType, TokenType, statementIsA } from './types.js';
import { CodeActionKind } from 'lsp-types';
import { dictionary } from './dictionary/dictionary.js';
import levenshtein from 'js-levenshtein';
import { subRange } from './diagnostic.js';
const caf = {
mk: (keyword, program, range, filePath) => {
const existingKeywords = program.body
.filter(r => statementIsA(r, NodeType.Keyword))
.map(r => r)
.map(r => r.word)
.sort(a => levenshtein(keyword, a.value));
return existingKeywords.map(word => {
return {
title: `Replace with '${word}'`,
kind: CodeActionKind.QuickFix,
edit: {
changes: {
[filePath]: [{
range: subRange(range),
newText: word.value
}]
}
}
};
});
}
};
export var syxparser;
(function (syxparser) {
//#
//# STATEMENT PARSERS
//#
/**
* Parses an import statement. Parameters are related to the environment of {@link syxparser.parseStatement} or {@link sysparser.parseStatement}.
* @returns Parsed node.
*/
function parseImportStatement(put, token) {
const ex = parseExpression(false, false);
if (!statementIsA(ex, NodeType.String))
throw new CompilerError(ex.range, 'Expected file path after import statement.', syxparser.filePath);
if (at().type !== TokenType.Semicolon)
throw new CompilerError(at().range, `Expected ';' after import statement, found '${at().value}'.`, syxparser.filePath);
syxparser.tokens.shift();
return node({ type: NodeType.Import, path: ex, range: combineTwo(token, ex.range), modifiers: [] }, put);
}
syxparser.parseImportStatement = parseImportStatement;
/**
* Parses an import statement. Parameters are related to the environment of {@link syxparser.parseStatement} or {@link sysparser.parseStatement}.
* @returns Parsed node.
*/
function parseRuleStatement(token, put) {
const ruleExpr = parseExpression(false, false);
if (!statementIsA(ruleExpr, NodeType.String))
throw new CompilerError(ruleExpr.range, `Expected rule name as string after 'rule', found ${ruleExpr.value}.`, syxparser.filePath);
if (at().value !== ':')
throw new CompilerError(at().range, `Expected \':\' after rule name, found ${at().value}.`, syxparser.filePath);
syxparser.tokens.shift();
if (!dictionary.Rules.some(r => r.name === ruleExpr.value))
throw new CompilerError(ruleExpr.range, `Unknown rule '${ruleExpr.value}'.`, syxparser.filePath);
const rule = dictionary.Rules.find(r => r.name === ruleExpr.value);
if (rule.type === 'boolean') {
const boolEx = parseExpression(false, false, true);
if (!(statementIsA(boolEx, NodeType.Identifier) && dictionary.RuleTypeRegexes.boolean.test(boolEx.value)))
throw new CompilerError(boolEx.range, `Rule '${rule.name}' requires a boolean value, found '${boolEx.value}'.`, syxparser.filePath);
if (at().type !== TokenType.Semicolon)
throw new CompilerError(at().range, `Expected semicolon after rule statement, found '${at().value}'.`, syxparser.filePath);
syxparser.tokens.shift();
return node({ type: NodeType.Rule, rule: ruleExpr, value: boolEx.value, range: combineTwo(token, boolEx.range), modifiers: [] }, put);
}
else if (rule.type === 'keyword') {
const keyEx = parseExpression(false, false, true);
if (!statementIsA(keyEx, NodeType.String))
throw new CompilerError(keyEx.range, 'Excepted keyword.', syxparser.filePath);
if (!syxparser.program.body.some(s => statementIsA(s, NodeType.Keyword) && s.word.value === keyEx.value))
throw new CompilerError(keyEx.range, `Can't find keyword '${keyEx.value}'.`, syxparser.filePath, caf.mk(keyEx.value, syxparser.program, keyEx.range, syxparser.filePath));
if (at().type !== TokenType.Semicolon)
throw new CompilerError(at().range, `Expected semicolon after rule statement, found ${at().value}.`, syxparser.filePath);
syxparser.tokens.shift();
return node({ type: NodeType.Rule, rule: ruleExpr, value: keyEx.value, range: combineTwo(token, keyEx.range), modifiers: [] }, put);
}
}
syxparser.parseRuleStatement = parseRuleStatement;
/**
* Parses a keyword statement. Parameters are related to the environment of {@link syxparser.parseStatement} or {@link sysparser.parseStatement}.
* @returns Parsed node.
*/
function parseKeywordStatement(put, token) {
const ex = parseExpression(false, false, true);
if (!statementIsA(ex, NodeType.Identifier))
throw new CompilerError(ex.range, 'Expected identifier after keyword statement.', syxparser.filePath);
if (at().type !== TokenType.Semicolon)
throw new CompilerError(at().range, `Expected ';' after statement, found '${at().value}'.`, syxparser.filePath);
syxparser.tokens.shift(); // skip semicolon
return node({ type: NodeType.Keyword, word: ex, range: combineTwo(token, ex.range), modifiers: [] }, put);
}
syxparser.parseKeywordStatement = parseKeywordStatement;
/**
* Parses an export statement. Parameters are related to the environment of {@link syxparser.parseStatement} or {@link sysparser.parseStatement}.
* @returns Parsed node.
*/
function parseExportStatement(token, put) {
const stmt = parseStatement(false);
stmt.range = combineTwo(token, stmt.range);
stmt.modifiers.push(token);
return node(stmt, put);
}
syxparser.parseExportStatement = parseExportStatement;
/**
* Parses a function statement. Parameters are related to the environment of {@link syxparser.parseStatement} or {@link sysparser.parseStatement}.
* @returns Parsed node.
*/
function parseFunctionStatement(token, put) {
const statement = { type: NodeType.Function, arguments: [], name: { type: NodeType.Identifier, modifiers: [], value: '', range: defaultRange }, body: [], range: defaultRange, modifiers: [] };
if (at().type !== TokenType.Identifier)
throw new CompilerError(at().range, `Expected identifier after function statement, found '${at().value}'.`, syxparser.filePath);
statement.name = { type: NodeType.Identifier, modifiers: [], range: at().range, value: at().value };
syxparser.tokens.shift();
while (at().type !== TokenType.OpenBrace) {
const expr = parseExpression(false, false);
if (!statementIsA(expr, NodeType.PrimitiveType))
throw new CompilerError(expr.range, `Expected argument types after function name, found ${expr.value}.`, syxparser.filePath);
statement.arguments.push(expr);
}
const braceExpr = parseExpression(false);
if (!statementIsA(braceExpr, NodeType.Brace))
throw new CompilerError(braceExpr.range, 'Function statement requires braces.', syxparser.filePath);
braceExpr.body.forEach(s => { if (!([NodeType.Compile, NodeType.Imports].includes(s.type)))
throw new CompilerError(s.range, 'Statement not allowed inside a function statement.', syxparser.filePath); });
statement.body = braceExpr.body;
statement.range = combineTwo(token, braceExpr.range);
return node(statement, put);
}
syxparser.parseFunctionStatement = parseFunctionStatement;
/**
* Parses an imports statement. Parameters are related to the environment of {@link syxparser.parseStatement} or {@link sysparser.parseStatement}.
* @returns Parsed node.
*/
function parseImportsStatement(token, put) {
const statement = { type: NodeType.Imports, formats: [], module: { type: NodeType.String, modifiers: [], range: defaultRange, value: '' }, range: defaultRange, modifiers: [] };
if (at().type !== TokenType.OpenParen)
throw new CompilerError(at().range, 'Imports statement require parens.', syxparser.filePath);
syxparser.tokens.shift(); // skip OpenParen
while (at().type !== TokenType.CloseParen) {
const t = syxparser.tokens.shift();
if (t.type === TokenType.Comma && at().type !== TokenType.Identifier)
throw new CompilerError(t.range, 'Expected identifier after comma.', syxparser.filePath);
else if (t.type === TokenType.Comma && statement.formats.length === 0)
throw new CompilerError(t.range, 'Can\'t start with comma.', syxparser.filePath);
else if (t.type === TokenType.Comma) { }
else if (t.type === TokenType.Identifier)
statement.formats.push({ type: NodeType.Identifier, modifiers: [], range: t.range, value: t.value });
else
throw new CompilerError(t.range, `Expected comma or identifier, found '${t.value}'.`, syxparser.filePath);
}
syxparser.tokens.shift(); // skip CloseParen
if (statement.formats.length === 0)
throw new CompilerError(token.range, 'At least one file type is required.', syxparser.filePath);
const moduleExpr = parseExpression(false, false);
if (!statementIsA(moduleExpr, NodeType.String))
throw new CompilerError(moduleExpr.range, `Expected string after parens of imports statement, found '${moduleExpr.value}'.`, syxparser.filePath);
statement.module = moduleExpr;
statement.range = combineTwo(token, moduleExpr.range);
if (at().type !== TokenType.Semicolon)
throw new CompilerError(at().range, `Expected ';' after imports statement, found '${at().value}'.`, syxparser.filePath);
syxparser.tokens.shift();
return node(statement, put);
}
syxparser.parseImportsStatement = parseImportsStatement;
/**
* Parses a compile statement. Parameters are related to the environment of {@link syxparser.parseStatement} or {@link sysparser.parseStatement}.
* @returns Parsed node.
*/
function parseCompileStatement(token, put) {
const statement = { type: NodeType.Compile, formats: [], body: [], range: defaultRange, modifiers: [] };
if (at().type !== TokenType.OpenParen)
throw new CompilerError(at().range, 'Compile statement require parens.', syxparser.filePath);
syxparser.tokens.shift(); // skip OpenParen
while (at().type !== TokenType.CloseParen) {
const t = syxparser.tokens.shift();
if (t.type === TokenType.Comma && at().type !== TokenType.Identifier)
throw new CompilerError(t.range, 'Expected identifier after comma.', syxparser.filePath);
else if (t.type === TokenType.Comma && statement.formats.length === 0)
throw new CompilerError(t.range, 'Can\'t start with comma.', syxparser.filePath);
else if (t.type === TokenType.Comma) { }
else if (t.type === TokenType.Identifier)
statement.formats.push({ type: NodeType.Identifier, modifiers: [], range: t.range, value: t.value });
else
throw new CompilerError(t.range, `Expected comma or identifier, found '${t.value}'.`, syxparser.filePath);
}
syxparser.tokens.shift(); // skip CloseParen
if (statement.formats.length === 0)
throw new CompilerError(token.range, 'At least one file type is required.', syxparser.filePath);
while (at().type !== TokenType.Semicolon) {
const expr = parseExpression(false, false);
statement.body.push(expr);
statement.range = combineTwo(token, expr.range);
}
syxparser.tokens.shift();
return node(statement, put);
}
syxparser.parseCompileStatement = parseCompileStatement;
/**
* Parses an operator statement. Parameters are related to the environment of {@link syxparser.parseStatement} or {@link sysparser.parseStatement}.
* @returns Parsed node.
*/
function parseOperatorStatement(token, put) {
const statement = { type: NodeType.Operator, regex: [], body: [], range: defaultRange, modifiers: [] };
while (at().type !== TokenType.OpenBrace) {
const ex = parseExpression(false);
statement.regex.push(ex);
}
const braceExpr = parseExpression(false);
if (!statementIsA(braceExpr, NodeType.Brace))
throw new CompilerError(braceExpr.range, 'Expected braces after operator regex.', syxparser.filePath);
braceExpr.body.forEach(s => { if (!([NodeType.Compile, NodeType.Imports].includes(s.type)))
throw new CompilerError(s.range, 'Statement not allowed inside of operator statement.'); }, syxparser.filePath);
statement.body = braceExpr.body;
statement.range = combineTwo(token, braceExpr.range);
return node(statement, put);
}
syxparser.parseOperatorStatement = parseOperatorStatement;
/**
* Parses an operator statement. Parameters are related to the environment of {@link syxparser.parseStatement} or {@link sysparser.parseStatement}.
* @returns Parsed node.
*/
function parseGlobalStatement(token, put) {
const stmt = { type: NodeType.Global, range: token.range, body: [], modifiers: [], name: { type: NodeType.Identifier, modifiers: [], range: defaultRange, value: '' } };
if (at().type !== TokenType.Identifier)
throw new CompilerError(at().range, `Expected identifier after function statement, found '${at().value}'.`, syxparser.filePath);
const { range, value } = syxparser.tokens.shift();
stmt.name = { modifiers: [], type: NodeType.Identifier, range, value };
const braceExpr = parseExpression(false, false, false);
if (!statementIsA(braceExpr, NodeType.Brace))
throw new CompilerError(braceExpr.range, 'Expected braces after global name.', syxparser.filePath);
stmt.body = braceExpr.body;
stmt.range = combineTwo(token, braceExpr.range);
return node(stmt, put);
}
syxparser.parseGlobalStatement = parseGlobalStatement;
//#
//# EXPRESSION PARSERS
//#
/**
* Parses a single quote string expression. Parameters are related to the environment of {@link syxparser.parseExpression} or {@link sysparser.parseExpression}.
* @returns Parsed node.
*/
function parseSingleQuotedString(put) {
let s = '';
const { range } = at();
syxparser.tokens.shift();
while (at().type !== TokenType.SingleQuote) {
const _t = syxparser.tokens.shift();
if (_t.type === TokenType.EndOfFile)
throw new CompilerError(combineTwo(range, { start: { line: 0, character: 0 }, end: { character: range.end.character + s.length, line: range.end.line } }), 'Strings must be closed.', syxparser.filePath);
s += _t.value;
}
return node({ type: NodeType.String, value: s, range: combineTwo(range, syxparser.tokens.shift()), modifiers: [] }, put);
}
syxparser.parseSingleQuotedString = parseSingleQuotedString;
/**
* Parses a double quote string expression. Parameters are related to the environment of {@link syxparser.parseExpression} or {@link sysparser.parseExpression}.
* @returns Parsed node.
*/
function parseDoubleQuotedString(put) {
let s = '';
const { range } = at();
syxparser.tokens.shift();
while (at().type !== TokenType.DoubleQuote) {
const _t = syxparser.tokens.shift();
if (_t.type === TokenType.EndOfFile)
throw new CompilerError(combineTwo(range, { start: { line: 0, character: 0 }, end: { character: range.end.character + s.length, line: range.end.line } }), 'Strings must be closed.', syxparser.filePath);
s += _t.value;
}
return node({ type: NodeType.String, value: s, range: combineTwo(range, syxparser.tokens.shift()), modifiers: [] }, put);
}
syxparser.parseDoubleQuotedString = parseDoubleQuotedString;
/**
* Parses a primitive type expression. Parameters are related to the environment of {@link syxparser.parseExpression} or {@link sysparser.parseExpression}.
* @returns Parsed node.
*/
function parsePrimitiveType(primitiveTypes, put) {
const newToken = at(1);
if (newToken.type !== TokenType.Identifier)
throw new CompilerError(newToken.range, `Expected identifier after '<', found '${newToken.value}'.`, syxparser.filePath);
if (!primitiveTypes.test(newToken.value))
throw new CompilerError(newToken.range, `Expected primitive type identifier after '<', found '${newToken.value}'`, syxparser.filePath);
if (at(2).type !== TokenType.CloseDiamond)
throw new CompilerError(at(2).range, `Expected '>' after primitive type identifier, found '${at(2).value}'`, syxparser.filePath);
const t = syxparser.tokens.shift();
syxparser.tokens.shift();
return node({ type: NodeType.PrimitiveType, value: newToken.value, range: combineTwo(t, syxparser.tokens.shift()), modifiers: [] }, put);
}
syxparser.parsePrimitiveType = parsePrimitiveType;
/**
* Parses a whitespace identifier expression. Parameters are related to the environment of {@link syxparser.parseExpression} or {@link sysparser.parseExpression}.
* @returns Parsed node.
*/
function parseWhitespaceIdentifier(put) {
const { range } = syxparser.tokens.shift();
return node({ type: NodeType.WhitespaceIdentifier, value: '+s', range, modifiers: [] }, put);
}
syxparser.parseWhitespaceIdentifier = parseWhitespaceIdentifier;
/**
* Parses a brace expression. Parameters are related to the environment of {@link syxparser.parseExpression} or {@link sysparser.parseExpression}.
* @returns Parsed node.
*/
function parseBraceExpression(put, dr) {
const { range } = syxparser.tokens.shift();
const expr = { type: NodeType.Brace, body: [], value: '{', range: dr, modifiers: [] };
while (at().type !== TokenType.CloseBrace) {
const stmt = parseStatement(false);
expr.body.push(stmt);
}
expr.range = combineTwo(range, syxparser.tokens.shift());
return node(expr, put);
}
syxparser.parseBraceExpression = parseBraceExpression;
/**
* Parses a square expression. Parameters are related to the environment of {@link syxparser.parseExpression} or {@link sysparser.parseExpression}.
* @returns Parsed node.
*/
function parseSquareExpression(put, dr) {
const { range } = syxparser.tokens.shift();
const expr = { type: NodeType.Square, body: [], value: '[', range: dr, modifiers: [] };
while (at().type !== TokenType.CloseSquare) {
const stmt = parseStatement(false);
expr.body.push(stmt);
}
expr.range = combineTwo(range, syxparser.tokens.shift());
return node(expr, put);
}
syxparser.parseSquareExpression = parseSquareExpression;
/**
* Parses a paren expression. Parameters are related to the environment of {@link syxparser.parseExpression} or {@link sysparser.parseExpression}.
* @returns Parsed node.
*/
function parseParenExpression(put, dr) {
const { range } = syxparser.tokens.shift();
const expr = { type: NodeType.Paren, body: [], value: '(', range: dr, modifiers: [] };
while (at().type !== TokenType.CloseParen) {
const stmt = parseStatement(false);
expr.body.push(stmt);
}
expr.range = combineTwo(range, syxparser.tokens.shift());
return node(expr, put);
}
syxparser.parseParenExpression = parseParenExpression;
/**
* Parses a primitive variable expression. Parameters are related to the environment of {@link syxparser.parseExpression} or {@link sysparser.parseExpression}.
* @returns Parsed node.
*/
function parsePrimitiveVariable(put) {
if (at(2).type !== TokenType.IntNumber)
throw new CompilerError(at(2).range, `Expected index after ${at().value} variable, found ${at(2).value}.`, syxparser.filePath);
const id = syxparser.tokens.shift(); // id
syxparser.tokens.shift(); // sep
const index = syxparser.tokens.shift(); // index
const expr = { index: parseInt(index.value), type: NodeType.Variable, value: id.value, range: combineTwo(id, index), modifiers: [] };
return node(expr, put);
}
syxparser.parsePrimitiveVariable = parsePrimitiveVariable;
/**
* Determintes whether the parser can keep parsing tokens.
* @returns Whether there is a token left.
* @author efekos
* @version 1.0.0
* @since 0.0.1-alpha
*/
function canGo() {
return syxparser.tokens[0].type !== TokenType.EndOfFile;
}
/**
* Parses the token list given into statements and expressions.
* @param {Token[]} t Token list to parse.
* @param {string} _filePath Path of the file that is being parsed.
* @returns Main {@link ProgramStatement} containing all other statements.
* @author efekos
* @version 1.0.4
* @since 0.0.2-alpha
*/
function parseTokens(t, _filePath) {
syxparser.tokens = t;
const eof = t.find(r => r.type === TokenType.EndOfFile);
syxparser.program = { body: [], type: NodeType.Program, range: { end: eof.range.end, start: { line: 0, character: 0 } }, modifiers: [] };
syxparser.filePath = _filePath;
while (canGo()) {
parseStatement();
}
return syxparser.program;
}
syxparser.parseTokens = parseTokens;
/**
* Returns the token at given index. Alias for `tokens[i]`.
* @param {number} i Token index. Defaults to 0.
* @returns The token at given index.
* @author efekos
* @version 1.0.0
* @since 0.0.1-alpha
*/
function at(i = 0) {
return syxparser.tokens[i];
}
syxparser.at = at;
const defaultRange = { end: { line: 0, character: 0 }, start: { character: 0, line: 0 } };
/**
* Combines the start of first range with the end of second range to create a range.
* @param starterRange Start range.
* @param enderRange End range.
* @author efekos
* @since 0.0.1-alpha
* @version 1.0.1
*/
function combineTwo(starter, ender) {
return { start: ('range' in starter ? starter.range : starter).start, end: ('range' in ender ? ender.range : ender).end };
}
syxparser.combineTwo = combineTwo;
/**
* Parses a statement from the most recent token. Will call {@link parseExpression} if no statement is present.
* @param {boolean} put Whether the result should be added to the program statement.
* @returns A node that is either a statement or an expression if a statement wasn't present.
* @author efekos
* @version 1.1.0
* @since 0.0.2-alpha
*/
function parseStatement(put = true) {
if (keywords.includes(at().type)) {
const token = at();
syxparser.tokens.shift();
switch (token.type) {
case TokenType.ImportKeyword: return parseImportStatement(put, token);
case TokenType.OperatorKeyword: return parseOperatorStatement(token, put);
case TokenType.CompileKeyword: return parseCompileStatement(token, put);
case TokenType.ExportKeyword: return parseExportStatement(token, put);
case TokenType.ImportsKeyword: return parseImportsStatement(token, put);
case TokenType.FunctionKeyword: return parseFunctionStatement(token, put);
case TokenType.KeywordKeyword: return parseKeywordStatement(put, token);
case TokenType.RuleKeyword: return parseRuleStatement(token, put);
case TokenType.GlobalKeyword: return parseGlobalStatement(token, put);
}
}
else
parseExpression();
}
syxparser.parseStatement = parseStatement;
/**
* An alias function to handle different cases of calling {@link parseStatement} and {@link parseExpression}.
* @param {T} node The node.
* @param {boolean} put Whether the node should be added to current program.
* @returns The node.
* @author efekos
* @version 1.0.0
* @since 0.0.1-alpha
*/
function node(node, put) {
if (put)
syxparser.program.body.push(node);
return node;
}
syxparser.node = node;
/**
* Parses the most recent expression at the token list. Goes to {@link parseStatement} if a keyword is found.
* @param {boolean} put Whether the result should be put into current program.
* @param {boolean} statements Whether statements should be allowed. Function will stop if a keyword found with this value set to `true`.
* @param {boolean} expectIdentifier Whether identifiers should be allowed. Unknown identifiers will stop the function with this value set to `false`, returning the identifier as a {@link StringExpression} otherwise.
* @returns The parsed node.
* @author efekos
* @version 1.1.0
* @since 0.0.2-alpha
*/
function parseExpression(put = true, statements = true, expectIdentifier = false) {
const tt = at().type;
switch (tt) {
case TokenType.SingleQuote: return parseSingleQuotedString(put);
case TokenType.DoubleQuote: return parseDoubleQuotedString(put);
case TokenType.OpenDiamond: return parsePrimitiveType(primitiveTypes, put);
case TokenType.WhitespaceIdentifier: return parseWhitespaceIdentifier(put);
case TokenType.OpenBrace: return parseBraceExpression(put, defaultRange);
case TokenType.OpenSquare: return parseSquareExpression(put, defaultRange);
case TokenType.OpenParen: return parseParenExpression(put, defaultRange);
case TokenType.Identifier:
if (at(1).type === TokenType.VarSeperator)
return parsePrimitiveVariable(put);
else if (keywords.includes(tt)) {
if (!statements)
throw new CompilerError(at().range, 'Statement not allowed here.', syxparser.filePath);
return parseStatement();
}
else if (expectIdentifier) {
const { value, range } = syxparser.tokens.shift();
return node({ type: NodeType.Identifier, value, range, modifiers: [] }, put);
}
}
throw new CompilerError(at().range, `Unexpected expression: '${at().value}'`, syxparser.filePath);
}
syxparser.parseExpression = parseExpression;
const primitiveTypes = /^(int|string|boolean|decimal)$/;
const keywords = [TokenType.ImportKeyword, TokenType.ExportKeyword, TokenType.CompileKeyword, TokenType.OperatorKeyword, TokenType.ImportsKeyword, TokenType.GlobalKeyword, TokenType.FunctionKeyword, TokenType.KeywordKeyword, TokenType.RuleKeyword];
})(syxparser || (syxparser = {}));
export var sysparser;
(function (sysparser) {
//#
//# STATEMENT PARSERS
//#
/**
* Parses an import statement. Parameters are related to the environment of {@link syxparser.parseStatement} or {@link sysparser.parseStatement}.
* @returns Parsed node.
*/
function parseImportStatement(put, token) {
const ex = parseExpression(false, false);
if (!statementIsA(ex, NodeType.String))
throw new CompilerError(ex.range, 'Expected file path after import statement.', sysparser.filePath);
if (at().type !== TokenType.Semicolon)
throw new CompilerError(at().range, `Expected ';' after import statement, found '${at().value}'.`, sysparser.filePath);
sysparser.tokens.shift();
return node({ type: NodeType.Import, path: ex, range: combineTwo(token, ex.range), modifiers: [] }, put);
}
sysparser.parseImportStatement = parseImportStatement;
//#
//# EXPRESSION PARSERS
//#
/**
* Parses a single quote string expression. Parameters are related to the environment of {@link syxparser.parseExpression} or {@link sysparser.parseExpression}.
* @returns Parsed node.
*/
function parseSingleQuotedString(put) {
let s = '';
const { range } = at();
sysparser.tokens.shift();
while (at().type !== TokenType.SingleQuote) {
const _t = sysparser.tokens.shift();
if (_t.type === TokenType.EndOfFile)
throw new CompilerError(combineTwo(range, { start: { line: 0, character: 0 }, end: { character: range.end.character + s.length, line: range.end.line } }), 'Strings must be closed.', sysparser.filePath);
s += _t.value;
}
return node({ type: NodeType.String, value: s, range: combineTwo(range, sysparser.tokens.shift()), modifiers: [] }, put);
}
sysparser.parseSingleQuotedString = parseSingleQuotedString;
/**
* Parses a double quote string expression. Parameters are related to the environment of {@link syxparser.parseExpression} or {@link sysparser.parseExpression}.
* @returns Parsed node.
*/
function parseDoubleQuotedString(put) {
let s = '';
const { range } = at();
sysparser.tokens.shift();
while (at().type !== TokenType.DoubleQuote) {
const _t = sysparser.tokens.shift();
if (_t.type === TokenType.EndOfFile)
throw new CompilerError(combineTwo(range, { start: { line: 0, character: 0 }, end: { character: range.end.character + s.length, line: range.end.line } }), 'Strings must be closed.', sysparser.filePath);
s += _t.value;
}
return node({ type: NodeType.String, value: s, range: combineTwo(range, sysparser.tokens.shift()), modifiers: [] }, put);
}
sysparser.parseDoubleQuotedString = parseDoubleQuotedString;
/**
* Determintes whether the parser can keep parsing tokens.
* @returns Whether there is a token left.
* @author efekos
* @version 1.0.0
* @since 0.0.1-alpha
*/
function canGo() {
return sysparser.tokens[0].type !== TokenType.EndOfFile;
}
/**
* Combines the start of first range with the end of second range to create a range.
* @param starterRange Start range.
* @param enderRange End range.
* @author efekos
* @since 0.0.1-alpha
* @version 1.0.1
*/
function combineTwo(starter, ender) {
return { start: ('range' in starter ? starter.range : starter).start, end: ('range' in ender ? ender.range : ender).end };
}
sysparser.combineTwo = combineTwo;
/**
* Parses the token list given into statements and expressions.
* @param {Token[]} t Token list to parse.
* @returns Main {@link ProgramStatement} containing all other statements.
* @author efekos
* @version 1.0.3
* @since 0.0.2-alpha
*/
function parseTokens(t, _filePath) {
sysparser.tokens = t;
const eof = t.find(r => r.type === TokenType.EndOfFile);
sysparser.program = { body: [], type: NodeType.Program, range: { start: { character: 0, line: 0 }, end: eof.range.end }, modifiers: [] };
sysparser.filePath = _filePath;
while (canGo()) {
parseStatement();
}
return sysparser.program;
}
sysparser.parseTokens = parseTokens;
/**
* Returns the token at given index. Alias for `tokens[i]`.
* @param {number} i Token index. Defaults to 0.
* @returns The token at given index.
* @author efekos
* @version 1.0.0
* @since 0.0.1-alpha
*/
function at(i = 0) {
return sysparser.tokens[i];
}
sysparser.at = at;
/**
* Parses a statement from the most recent token. Will call {@link parseExpression} if no statement is present.
* @param {boolean} put Whether the result should be added to the program statement.
* @returns A node that is either a statement or an expression if a statement wasn't present.
* @author efekos
* @version 1.0.6
* @since 0.0.1-alpha
*/
function parseStatement(put = true) {
if (keywords.includes(at().type)) {
const token = at();
sysparser.tokens.shift();
if (token.type === TokenType.ImportKeyword)
return parseImportStatement(put, token);
}
else
parseExpression();
}
sysparser.parseStatement = parseStatement;
/**
* An alias function to handle different cases of calling {@link parseStatement} and {@link parseExpression}.
* @param {Node} node The node.
* @param {boolean} put Whether the node should be added to current program.
* @returns The node.
* @author efekos
* @version 1.0.0
* @since 0.0.1-alpha
*/
function node(node, put) {
if (put)
sysparser.program.body.push(node);
return node;
}
sysparser.node = node;
/**
* Parses the most recent expression at the token list. Goes to {@link parseStatement} if a keyword is found.
* @param {boolean} put Whether the result should be put into current program.
* @param {boolean} statements Whether statements should be allowed. Function will stop if a keyword found with this value set to `true`.
* @param {boolean} expectIdentifier Whether identifiers should be allowed. Unknown identifiers will stop the function with this value set to `false`, returning the identifier as a {@link StringExpression} otherwise.
* @returns The parsed node.
* @author efekos
* @version 1.0.6
* @since 0.0.1-alpha
*/
function parseExpression(put = true, statements = true) {
const tt = at().type;
if (tt === TokenType.SingleQuote)
return parseSingleQuotedString(put);
else if (tt === TokenType.DoubleQuote)
return parseDoubleQuotedString(put);
else if (keywords.includes(tt)) {
if (!statements)
throw new CompilerError(at().range, 'Statements are not allowed here.', sysparser.filePath);
return parseStatement();
}
else
throw new CompilerError(at().range, `Unexpected expression: '${at().value}'`, sysparser.filePath);
}
sysparser.parseExpression = parseExpression;
const keywords = [TokenType.ImportKeyword];
})(sysparser || (sysparser = {}));