UNPKG

@syntaxs/compiler

Version:

Compiler used to compile Syntax Script projects.

670 lines (669 loc) 35.7 kB
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 = {}));