| /* |
| * Copyright (C) 2017 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| "use strict"; |
| |
| const GlobalAnnotation = "global"; |
| const LocalAnnotation = "local"; |
| |
| class SourceFile |
| { |
| constructor(fileName) |
| { |
| this._fileName = fileName; |
| let fileNumber = SourceFile.fileNames.indexOf(fileName); |
| if (fileNumber == -1) { |
| SourceFile.fileNames.push(fileName); |
| fileNumber = SourceFile.fileNames.length; |
| } else |
| fileNumber++; // File numbers are 1 based |
| |
| this._fileNumber = fileNumber; |
| } |
| |
| get name() |
| { |
| return this._fileName; |
| } |
| |
| get fileNumber() |
| { |
| return this._fileNumber; |
| } |
| } |
| |
| SourceFile.fileNames = []; |
| |
| class CodeOrigin |
| { |
| constructor(sourceFile, lineNumber) |
| { |
| this._sourceFile = sourceFile; |
| this._lineNumber = lineNumber; |
| } |
| |
| fileName() |
| { |
| return this._sourceFile.name; |
| } |
| |
| debugDirective() |
| { |
| return emitWinAsm ? undefined : "\".loc " + this._sourceFile.fileNumber + " " + this._lineNumber + "\\n\""; |
| } |
| |
| toString() |
| { |
| return this.fileName() + ":" + this._lineNumber; |
| } |
| |
| get lineNumber() |
| { |
| return this._lineNumber; |
| } |
| } |
| |
| class IncludeFile |
| { |
| constructor(moduleName, defaultDir) |
| { |
| this._fileName = moduleName + ".asm"; |
| } |
| |
| toString() |
| { |
| return fileName; |
| } |
| |
| get fileName() |
| { |
| return this._fileName; |
| } |
| } |
| |
| IncludeFile.includeDirs = []; |
| |
| |
| class Token |
| { |
| constructor(codeOrigin, string) |
| { |
| this._codeOrigin = codeOrigin; |
| this._string = string; |
| } |
| |
| isEqualTo(other) |
| { |
| if (other instanceof Token) |
| return this.string === other.string; |
| |
| return this.string === other; |
| } |
| |
| isNotEqualTo(other) |
| { |
| return !this.isEqualTo(other); |
| } |
| |
| get string() |
| { |
| return this._string; |
| } |
| |
| get codeOrigin() |
| { |
| return this._codeOrigin; |
| } |
| |
| toString() |
| { |
| return "" + this._string + "\" at " + this._codeOrigin; |
| } |
| |
| parseError(comment) |
| { |
| if (!comment || !comment.length) |
| throw "Parse error: " + this; |
| |
| throw "Parse error: " + this + ": " + comment; |
| } |
| } |
| |
| class Annotation |
| { |
| constructor(codeOrigin, type, string) |
| { |
| this.codeOrigin = codeOrigin; |
| this.type = type; |
| this.string = string; |
| } |
| |
| get codeOrigin() |
| { |
| return this.codeOrigin; |
| } |
| |
| get type() |
| { |
| return this.type; |
| } |
| |
| get string() |
| { |
| return this.string; |
| } |
| } |
| |
| |
| // The lexer. Takes a string and returns an array of tokens. |
| |
| function lex(str, file) |
| { |
| function scanRegExp(source, regexp) |
| { |
| return source.match(regexp); |
| } |
| |
| let result = []; |
| let lineNumber = 1; |
| let annotation = null; |
| let annotationType; |
| let whitespaceFound = false; |
| |
| while (str.length) |
| { |
| let tokenMatch; |
| let annotation; |
| let annotationType; |
| |
| if (tokenMatch = scanRegExp(str, /^#([^\n]*)/)) |
| ; // comment, ignore |
| else if (tokenMatch = scanRegExp(str, /^\/\/\ ?([^\n]*)/)) { |
| // annotation |
| annotation = tokenMatch[0]; |
| annotationType = whitespaceFound ? LocalAnnotation : GlobalAnnotation; |
| } else if (tokenMatch = scanRegExp(str, /^\n/)) { |
| /* We've found a '\n'. Emit the last comment recorded if appropriate: |
| * We need to parse annotations regardless of whether the backend does |
| * anything with them or not. This is because the C++ backend may make |
| * use of this for its cloopDo debugging utility even if |
| * enableInstrAnnotations is not enabled. |
| */ |
| if (annotation) { |
| result.push(new Annotation(new CodeOrigin(file, lineNumber), |
| annotationType, annotation)); |
| annotation = null; |
| } |
| result.push(new Token(new CodeOrigin(file, lineNumber), tokenMatch[0])); |
| lineNumber++; |
| } else if (tokenMatch = scanRegExp(str, /^[a-zA-Z]([a-zA-Z0-9_.]*)/)) |
| result.push(new Token(new CodeOrigin(file, lineNumber), tokenMatch[0])); |
| else if (tokenMatch = scanRegExp(str, /^\.([a-zA-Z0-9_]*)/)) |
| result.push(new Token(new CodeOrigin(file, lineNumber), tokenMatch[0])); |
| else if (tokenMatch = scanRegExp(str, /^_([a-zA-Z0-9_]*)/)) |
| result.push(new Token(new CodeOrigin(file, lineNumber), tokenMatch[0])); |
| else if (tokenMatch = scanRegExp(str, /^([ \t]+)/)) { |
| // whitespace, ignore |
| whitespaceFound = true; |
| str = str.slice(tokenMatch[0].length); |
| continue; |
| } else if (tokenMatch = scanRegExp(str, /^0x([0-9a-fA-F]+)/)) |
| result.push(new Token(new CodeOrigin(file, lineNumber), Number.parseInt(tokenMatch[1], 16))); |
| else if (tokenMatch = scanRegExp(str, /^0([0-7]+)/)) |
| result.push(new Token(new CodeOrigin(file, lineNumber), Number.parseInt(tokenMatch[1], 8))); |
| else if (tokenMatch = scanRegExp(str, /^([0-9]+)/)) |
| result.push(new Token(new CodeOrigin(file, lineNumber), tokenMatch[0])); |
| else if (tokenMatch = scanRegExp(str, /^::/)) |
| result.push(new Token(new CodeOrigin(file, lineNumber), tokenMatch[0])); |
| else if (tokenMatch = scanRegExp(str, /^[:,\(\)\[\]=\+\-~\|&^*]/)) |
| result.push(new Token(new CodeOrigin(file, lineNumber), tokenMatch[0])); |
| else if (tokenMatch = scanRegExp(str, /^\".*\"/)) |
| result.push(new Token(new CodeOrigin(file, lineNumber), tokenMatch[0])); |
| else |
| throw "Lexer error at " + (new CodeOrigin(file, lineNumber)) + ", unexpected sequence " + str.slice(0, 20); |
| |
| whitespaceFound = false; |
| str = str.slice(tokenMatch[0].length); |
| } |
| |
| return result; |
| } |
| |
| // Token identification. |
| |
| function isRegister(token) |
| { |
| registerPattern.test(token.string); |
| } |
| |
| function isInstruction(token) |
| { |
| return instructionSet.has(token.string); |
| } |
| |
| function isKeyword(token) |
| { |
| return /^((true)|(false)|(if)|(then)|(else)|(elsif)|(end)|(and)|(or)|(not)|(global)|(macro)|(const)|(constexpr)|(sizeof)|(error)|(include))$/.test(token.string) |
| || isRegister(token) |
| || isInstruction(token); |
| } |
| |
| function isIdentifier(token) |
| { |
| return /^[a-zA-Z]([a-zA-Z0-9_.]*)$/.test(token.string) && !isKeyword(token); |
| } |
| |
| function isLabel(token) |
| { |
| let tokenString; |
| if (token instanceof Token) |
| tokenString = token.string; |
| else |
| tokenString = token; |
| |
| return /^_([a-zA-Z0-9_]*)$/.test(tokenString); |
| } |
| |
| function isLocalLabel(token) |
| { |
| let tokenString; |
| if (token instanceof Token) |
| tokenString = token.string; |
| else |
| tokenString = token; |
| |
| return /^\.([a-zA-Z0-9_]*)$/.test(tokenString); |
| } |
| |
| function isVariable(token) |
| { |
| return isIdentifier(token) || isRegister(token); |
| } |
| |
| function isInteger(token) |
| { |
| return /^[0-9]/.test(token.string); |
| } |
| |
| function isString(token) |
| { |
| return /^".*"/.test(token); |
| } |
| |
| |
| // The parser. Takes an array of tokens and returns an AST. Methods |
| // other than parse(tokens) are not for public consumption. |
| |
| class Parser |
| { |
| constructor(data, fileName) |
| { |
| this.tokens = lex(data, fileName); |
| this.idx = 0; |
| this.annotation = null; |
| } |
| |
| parseError(comment) |
| { |
| if (this.tokens[this.idx]) |
| this.tokens[this.idx].parseError(comment); |
| else { |
| if (!comment.length) |
| throw "Parse error at end of file"; |
| |
| throw "Parse error at end of file: " + comment; |
| } |
| } |
| |
| consume(regexp) |
| { |
| if (regexp) { |
| if (!regexp.test(this.tokens[this.idx].string)) |
| this.parseError(); |
| } else if (this.idx != this.tokens.length) |
| this.parseError(); |
| |
| this.idx++; |
| } |
| |
| skipNewLine() |
| { |
| while (this.tokens[this.idx].isEqualTo("\n")) |
| this.idx++; |
| } |
| |
| parsePredicateAtom() |
| { |
| if (this.tokens[this.idx].isEqualTo("not")) { |
| let codeOrigin = this.tokens[this.idx].codeOrigin; |
| this.idx++; |
| return new Not(codeOrigin, this.parsePredicateAtom()); |
| } |
| if (this.tokens[this.idx].isEqualTo("(")) { |
| this.idx++; |
| skipNewLine(); |
| let result = this.parsePredicate(); |
| if (this.tokens[this.idx].isNotEqualTo(")")) |
| parseError(); |
| this.idx++; |
| return result; |
| } |
| if (this.tokens[this.idx].isEqualTo("true")) { |
| let result = True.instance(); |
| this.idx++; |
| return result; |
| } |
| if (this.tokens[this.idx].isEqualTo("false")) { |
| let result = False.instance(); |
| this.idx++; |
| return result; |
| } |
| if (isIdentifier(this.tokens[this.idx])) { |
| let result = Setting.forName(this.tokens[this.idx].codeOrigin, this.tokens[this.idx].string); |
| this.idx++; |
| return result; |
| } |
| |
| this.parseError(); |
| } |
| |
| parsePredicateAnd() |
| { |
| let result = this.parsePredicateAtom(); |
| while (this.tokens[this.idx].isEqualTo("and")) { |
| let codeOrigin = this.tokens[this.idx].codeOrigin; |
| this.idx++; |
| this.skipNewLine(); |
| let right = this.parsePredicateAtom(); |
| result = new And(codeOrigin, result, right); |
| } |
| |
| return result; |
| } |
| |
| parsePredicate() |
| { |
| // some examples of precedence: |
| // not a and b -> (not a) and b |
| // a and b or c -> (a and b) or c |
| // a or b and c -> a or (b and c) |
| |
| let result = this.parsePredicateAnd(); |
| while (this.tokens[this.idx].isEqualTo("or")) { |
| let codeOrigin = this.tokens[this.idx].codeOrigin; |
| this.idx++; |
| this.skipNewLine(); |
| let right = this.parsePredicateAnd(); |
| result = new Or(codeOrigin, result, right); |
| } |
| |
| return result; |
| } |
| |
| parseVariable() |
| { |
| let result; |
| if (isRegister(this.tokens[this.idx])) { |
| if (fprPattern.test(this.tokens[this.idx])) |
| result = FPRegisterID.forName(this.tokens[this.idx].codeOrigin, this.tokens[this.idx].string); |
| else |
| result = RegisterID.forName(this.tokens[this.idx].codeOrigin, this.tokens[this.idx].string); |
| } else if (isIdentifier(this.tokens[this.idx])) |
| result = Variable.forName(this.tokens[this.idx].codeOrigin, this.tokens[this.idx].string); |
| else |
| this.parseError(); |
| |
| this.idx++; |
| return result; |
| } |
| |
| parseAddress(offset) |
| { |
| if (this.tokens[this.idx].isNotEqualTo("[")) |
| this.parseError(); |
| |
| let codeOrigin = this.tokens[this.idx].codeOrigin; |
| let result; |
| |
| // Three possibilities: |
| // [] -> AbsoluteAddress |
| // [a] -> Address |
| // [a,b] -> BaseIndex with scale = 1 |
| // [a,b,c] -> BaseIndex |
| |
| this.idx++; |
| if (this.tokens[this.idx].isEqualTo("]")) { |
| this.idx++; |
| return new AbsoluteAddress(codeOrigin, offset); |
| } |
| let a = this.parseVariable(); |
| if (this.tokens[this.idx].isEqualTo("]")) |
| result = new Address(codeOrigin, a, offset); |
| else |
| { |
| if (this.tokens[this.idx].isNotEqualTo(",")) |
| this.parseError(); |
| this.idx++; |
| let b = this.parseVariable(); |
| if (this.tokens[this.idx].isEqualTo("]")) |
| result = new BaseIndex(codeOrigin, a, b, 1, offset); |
| else { |
| if (this.tokens[this.idx].isNotEqualTo(",")) |
| this.parseError(); |
| this.idx++; |
| if (!["1", "2", "4", "8"].includes(this.tokens[this.idx].string)) |
| this.parseError(); |
| let c = Number.parseInt(this.tokens[this.idx]); |
| this.idx++; |
| if (this.tokens[this.idx].isNotEqualTo("]")) |
| this.parseError(); |
| result = new BaseIndex(codeOrigin, a, b, c, offset); |
| } |
| } |
| this.idx++; |
| return result; |
| } |
| |
| parseColonColon() |
| { |
| this.skipNewLine(); |
| let firstToken = this.tokens[this.idx]; |
| let codeOrigin = this.tokens[this.idx].codeOrigin; |
| if (!isIdentifier(this.tokens[this.idx])) |
| this.parseError(); |
| let names = [this.tokens[this.idx].string]; |
| this.idx++; |
| while (this.tokens[this.idx].isEqualTo("::")) { |
| this.idx++; |
| if (!isIdentifier(this.tokens[this.idx])) |
| this.parseError(); |
| names.push(this.tokens[this.idx].string); |
| this.idx++; |
| } |
| if (!names.length) |
| firstToken.parseError(); |
| return { codeOrigin: codeOrigin, names: names }; |
| } |
| |
| parseTextInParens() |
| { |
| this.skipNewLine(); |
| let codeOrigin = this.tokens[this.idx].codeOrigin; |
| if (this.tokens[this.idx].isNotEqualTo("(")) |
| throw "Missing \"(\" at " + codeOrigin; |
| this.idx++; |
| // need at least one item |
| if (this.tokens[this.idx].isEqualTo(")")) |
| throw "No items in list at " + codeOrigin; |
| let numEnclosedParens = 0; |
| let text = []; |
| while (this.tokens[this.idx].isNotEqualTo(")") || numEnclosedParens > 0) { |
| if (this.tokens[this.idx].isEqualTo("(")) |
| numEnclosedParens++; |
| else if (this.tokens[this.idx].isEqualTo(")")) |
| numEnclosedParens--; |
| |
| text.push(this.tokens[this.idx].string); |
| this.idx++; |
| } |
| this.idx++; |
| return { codeOrigin: codeOrigin, text: text }; |
| } |
| |
| parseExpressionAtom() |
| { |
| let result; |
| this.skipNewLine(); |
| if (this.tokens[this.idx].isEqualTo("-")) { |
| this.idx++; |
| return new NegImmediate(this.tokens[this.idx - 1].codeOrigin, this.parseExpressionAtom()); |
| } |
| if (this.tokens[this.idx].isEqualTo("~")) { |
| this.idx++; |
| return new BitnotImmediate(this.tokens[this.idx - 1].codeOrigin, this.parseExpressionAtom()); |
| } |
| if (this.tokens[this.idx].isEqualTo("(")) { |
| this.idx++; |
| result = this.parseExpression(); |
| if (this.tokens[this.idx].isNotEqualTo(")")) |
| this.parseError(); |
| this.idx++; |
| return result; |
| } |
| if (isInteger(this.tokens[this.idx])) { |
| result = new Immediate(this.tokens[this.idx].codeOrigin, Number.parseInt(this.tokens[this.idx])); |
| this.idx++; |
| return result; |
| } |
| if (isString(this.tokens[this.idx])) { |
| result = new StringLiteral(this.tokens[this.idx].codeOrigin, this.tokens[this.idx].string); |
| this.idx++; |
| return result; |
| } |
| if (isIdentifier(this.tokens[this.idx])) { |
| let {codeOrigin, names} = this.parseColonColon(); |
| if (names.length > 1) |
| return StructOffset.forField(codeOrigin, names.slice(0, -1).join('::'), names[names.length - 1]); |
| |
| return Variable.forName(codeOrigin, names[0]); |
| } |
| if (isRegister(this.tokens[this.idx])) |
| return this.parseVariable(); |
| if (this.tokens[this.idx].isEqualTo("sizeof")) { |
| this.idx++; |
| let {codeOrigin, names} = this.parseColonColon(); |
| return Sizeof.forName(codeOrigin, names.join("::")); |
| } |
| if (this.tokens[this.idx].isEqualTo("constexpr")) { |
| this.idx++; |
| this.skipNewLine(); |
| let codeOrigin; |
| let text; |
| let names; |
| if (this.tokens[this.idx].isEqualTo("(")) { |
| ({ codeOrigin, text } = this.parseTextInParens()); |
| text = text.join(""); |
| } else { |
| ({ codeOrigin, names } = this.parseColonColon()); |
| text = names.join("::"); |
| } |
| return ConstExpr.forName(codeOrigin, text); |
| } |
| if (isLabel(this.tokens[this.idx])) { |
| result = new LabelReference(this.tokens[this.idx].codeOrigin, Label.forName(this.tokens[this.idx].codeOrigin, this.tokens[this.idx].string)); |
| this.idx++; |
| return result; |
| } |
| if (isLocalLabel(this.tokens[this.idx])) { |
| result = new LocalLabelReference(this.tokens[this.idx].codeOrigin, LocalLabel.forName(this.tokens[this.idx].codeOrigin, this.tokens[this.idx].string)); |
| this.idx++; |
| return result; |
| } |
| this.parseError(); |
| } |
| |
| parseExpressionMul() |
| { |
| this.skipNewLine(); |
| let result = this.parseExpressionAtom(); |
| while (this.tokens[this.idx].isEqualTo("*")) { |
| if (this.tokens[this.idx].isEqualTo("*")) { |
| this.idx++; |
| result = new MulImmediates(this.tokens[this.idx - 1].codeOrigin, result, this.parseExpressionAtom()); |
| } else |
| throw "Invalid token " + this.tokens[this.idx] + " in multiply expression"; |
| } |
| return result; |
| } |
| |
| couldBeExpression() |
| { |
| return this.tokens[this.idx].isEqualTo("-") || this.tokens[this.idx].isEqualTo("~") || this.tokens[this.idx].isEqualTo("constexpr") || this.tokens[this.idx].isEqualTo("sizeof") || isInteger(this.tokens[this.idx]) || isString(this.tokens[this.idx]) || isVariable(this.tokens[this.idx]) || this.tokens[this.idx].isEqualTo("("); |
| } |
| |
| parseExpressionAdd() |
| { |
| this.skipNewLine(); |
| let result = this.parseExpressionMul(); |
| while (this.tokens[this.idx].isEqualTo("+") || this.tokens[this.idx].isEqualTo("-")) { |
| if (this.tokens[this.idx].isEqualTo("+")) { |
| this.idx++; |
| result = new AddImmediates(this.tokens[this.idx - 1].codeOrigin, result, this.parseExpressionMul()); |
| } else if (this.tokens[this.idx].isEqualTo("-")) { |
| this.idx++; |
| result = new SubImmediates(this.tokens[this.idx - 1].codeOrigin, result, this.parseExpressionMul()); |
| } else |
| throw "Invalid token " + this.tokens[this.idx] + " in addition expression"; |
| } |
| return result; |
| } |
| |
| parseExpressionAnd() |
| { |
| this.skipNewLine(); |
| let result = this.parseExpressionAdd(); |
| while (this.tokens[this.idx].isEqualTo("&")) { |
| this.idx++; |
| result = new AndImmediates(this.tokens[this.idx - 1].codeOrigin, result, this.parseExpressionAdd()); |
| } |
| return result; |
| } |
| |
| parseExpression() |
| { |
| this.skipNewLine(); |
| let result = this.parseExpressionAnd(); |
| while (this.tokens[this.idx].isEqualTo("|") || this.tokens[this.idx].isEqualTo("^")) { |
| if (this.tokens[this.idx].isEqualTo("|")) { |
| this.idx++; |
| result = new OrImmediates(this.tokens[this.idx - 1].codeOrigin, result, this.parseExpressionAnd()); |
| } else if (this.tokens[this.idx].isEqualTo("^")) { |
| this.idx++; |
| result = new XorImmediates(this.tokens[this.idx - 1].codeOrigin, result, this.parseExpressionAnd()); |
| } else |
| throw "Invalid token " + this.tokens[this.idx] + " in expression"; |
| } |
| return result; |
| } |
| |
| parseOperand(comment) |
| { |
| this.skipNewLine(); |
| if (this.couldBeExpression()) { |
| let expr = this.parseExpression(); |
| if (this.tokens[this.idx].isEqualTo("[")) |
| return this.parseAddress(expr); |
| |
| return expr; |
| } |
| if (this.tokens[this.idx].isEqualTo("[")) |
| return this.parseAddress(new Immediate(this.tokens[this.idx].codeOrigin, 0)); |
| if (isLabel(this.tokens[this.idx])) { |
| let result = new LabelReference(this.tokens[this.idx].codeOrigin, Label.forName(this.tokens[this.idx].codeOrigin, this.tokens[this.idx].string)); |
| this.idx++; |
| return result; |
| } |
| if (isLocalLabel(this.tokens[this.idx])) { |
| let result = new LocalLabelReference(this.tokens[this.idx].codeOrigin, LocalLabel.forName(this.tokens[this.idx].codeOrigin, this.tokens[this.idx].string)); |
| this.idx++; |
| return result; |
| } |
| |
| this.parseError(comment); |
| } |
| |
| parseMacroVariables() |
| { |
| this.skipNewLine(); |
| this.consume(/^\($/); |
| let variables = []; |
| while (true) { |
| this.skipNewLine(); |
| if (this.tokens[this.idx].isEqualTo(")")) { |
| this.idx++; |
| break |
| } else if (isIdentifier(this.tokens[this.idx])) { |
| variables.push(Variable.forName(this.tokens[this.idx].codeOrigin, this.tokens[this.idx].string)); |
| this.idx++; |
| this.skipNewLine(); |
| if (this.tokens[this.idx].isEqualTo(")")) { |
| this.idx++; |
| break; |
| } else if (this.tokens[this.idx].isEqualTo(",")) |
| this.idx++; |
| else |
| this.parseError(); |
| } else |
| this.parseError(); |
| } |
| return variables; |
| } |
| |
| parseSequence(final, comment) |
| { |
| let firstCodeOrigin = this.tokens[this.idx].codeOrigin; |
| let list = []; |
| while (true) { |
| if ((this.idx == this.tokens.length && !final) || (final && final.test(this.tokens[this.idx].string))) |
| break; |
| else if (this.tokens[this.idx] instanceof Annotation) { |
| // This is the only place where we can encounter a global |
| // annotation, and hence need to be able to distinguish between |
| // them. |
| // globalAnnotations are the ones that start from column 0. All |
| // others are considered localAnnotations. The only reason to |
| // distinguish between them is so that we can format the output |
| // nicely as one would expect. |
| |
| let codeOrigin = this.tokens[this.idx].codeOrigin; |
| let annotationOpcode = (this.tokens[this.idx].type == GlobalAnnotation) ? "globalAnnotation" : "localAnnotation"; |
| list.push(new Instruction(codeOrigin, annotationOpcode, [], this.tokens[this.idx].string)); |
| this.annotation = null; |
| this.idx += 2 // Consume the newline as well. |
| } else if (this.tokens[this.idx].isEqualTo("\n")) { |
| // ignore |
| this.idx++; |
| } else if (this.tokens[this.idx].isEqualTo("const")) { |
| this.idx++; |
| if (!isVariable(this.tokens[this.idx])) |
| this.parseError(); |
| let variable = Variable.forName(this.tokens[this.idx].codeOrigin, this.tokens[this.idx].string); |
| this.idx++; |
| if (this.tokens[this.idx].isNotEqualTo("=")) |
| this.parseError(); |
| this.idx++; |
| let value = this.parseOperand("while inside of const " + variable.name); |
| list.push(new ConstDecl(this.tokens[this.idx].codeOrigin, variable, value)); |
| } else if (this.tokens[this.idx].isEqualTo("error")) { |
| list.push(new Error(this.tokens[this.idx].codeOrigin)); |
| this.idx++; |
| } else if (this.tokens[this.idx].isEqualTo("if")) { |
| let codeOrigin = this.tokens[this.idx].codeOrigin; |
| this.idx++; |
| this.skipNewLine(); |
| let predicate = this.parsePredicate(); |
| this.consume(/^((then)|(\n))$/); |
| this.skipNewLine(); |
| let ifThenElse = new IfThenElse(codeOrigin, predicate, this.parseSequence(/^((else)|(end)|(elsif))$/, "while inside of \"if " + predicate.dump() + "\"")); |
| list.push(ifThenElse); |
| while (this.tokens[this.idx].isEqualTo("elsif")) { |
| codeOrigin = this.tokens[this.idx].codeOrigin; |
| this.idx++; |
| this.skipNewLine(); |
| predicate = this.parsePredicate(); |
| this.consume(/^((then)|(\n))$/); |
| this.skipNewLine(); |
| let elseCase = new IfThenElse(codeOrigin, predicate, this.parseSequence(/^((else)|(end)|(elsif))$/, "while inside of \"if " + predicate.dump() + "\"")); |
| ifThenElse.elseCase = elseCase; |
| ifThenElse = elseCase; |
| } |
| if (this.tokens[this.idx].isEqualTo("else")) { |
| this.idx++; |
| ifThenElse.elseCase = this.parseSequence(/^end$/, "while inside of else case for \"if " + predicate.dump() + "\""); |
| this.idx++; |
| } else { |
| if (this.tokens[this.idx].isNotEqualTo("end")) |
| this.parseError(); |
| this.idx++; |
| } |
| } else if (this.tokens[this.idx].isEqualTo("macro")) { |
| let codeOrigin = this.tokens[this.idx].codeOrigin; |
| this.idx++; |
| this.skipNewLine(); |
| if (!isIdentifier(this.tokens[this.idx])) |
| this.parseError(); |
| let name = this.tokens[this.idx].string; |
| this.idx++; |
| let variables = this.parseMacroVariables(); |
| let body = this.parseSequence(/^end$/, "while inside of macro " + name); |
| this.idx++; |
| list.push(new Macro(codeOrigin, name, variables, body)); |
| } else if (this.tokens[this.idx].isEqualTo("global")) { |
| let codeOrigin = this.tokens[this.idx].codeOrigin; |
| this.idx++; |
| this.skipNewLine(); |
| if (!isLabel(this.tokens[this.idx])) |
| this.parseError(); |
| let name = this.tokens[this.idx].string; |
| this.idx++; |
| Label.setAsGlobal(codeOrigin, name); |
| } else if (isInstruction(this.tokens[this.idx])) { |
| let codeOrigin = this.tokens[this.idx].codeOrigin; |
| let name = this.tokens[this.idx].string; |
| this.idx++; |
| if ((!final && this.idx == this.tokens.size) || (final && final.test(this.tokens[this.idx]))) { |
| // Zero operand instruction, and it's the last one. |
| list.push(new Instruction(codeOrigin, name, [], this.annotation)); |
| this.annotation = null; |
| break; |
| } else if (this.tokens[this.idx] instanceof Annotation) { |
| list.push(new Instruction(codeOrigin, name, [], this.tokens[this.idx].string)); |
| this.annotation = null; |
| this.idx += 2 // Consume the newline as well. |
| } else if (this.tokens[this.idx].isEqualTo("\n")) { |
| // Zero operand instruction. |
| list.push(new Instruction(codeOrigin, name, [], this.annotation)); |
| this.annotation = null; |
| this.idx++; |
| } else { |
| // It's definitely an instruction, and it has at least one operand. |
| let operands = []; |
| let endOfSequence = false; |
| while (true) { |
| operands.push(this.parseOperand("while inside of instruction " + name)); |
| if ((!final && this.idx == this.tokens.size) || (final && final.test(this.tokens[this.idx].string))) { |
| // The end of the instruction and of the sequence. |
| endOfSequence = true; |
| break; |
| } else if (this.tokens[this.idx].isEqualTo(",")) { |
| // Has another operand. |
| this.idx++; |
| } else if (this.tokens[this.idx] instanceof Annotation) { |
| this.annotation = this.tokens[this.idx].string; |
| this.idx += 2; // Consume the newline as well. |
| break; |
| } else if (this.tokens[this.idx].isEqualTo("\n")) { |
| // The end of the instruction. |
| this.idx++; |
| break; |
| } else |
| this.parseError("Expected a comma, newline, or " + final + " after " + operands[operands.length - 1].dump()); |
| } |
| list.push(new Instruction(codeOrigin, name, operands, this.annotation)); |
| this.annotation = null; |
| if (endOfSequence) |
| break; |
| } |
| } else if (isIdentifier(this.tokens[this.idx])) { |
| // Check for potential macro invocation: |
| let codeOrigin = this.tokens[this.idx].codeOrigin; |
| let name = this.tokens[this.idx].string; |
| this.idx++; |
| if (this.tokens[this.idx].isEqualTo("(")) { |
| // Macro invocation. |
| this.idx++; |
| let operands = []; |
| this.skipNewLine(); |
| if (this.tokens[this.idx].isEqualTo(")")) |
| this.idx++; |
| else { |
| while (true) { |
| this.skipNewLine(); |
| if (this.tokens[this.idx].isEqualTo("macro")) { |
| // It's a macro lambda! |
| let codeOriginInner = this.tokens[this.idx].codeOrigin; |
| this.idx++; |
| let variables = this.parseMacroVariables(); |
| let body = this.parseSequence(/^end$/, "while inside of anonymous macro passed as argument to " + name); |
| this.idx++; |
| operands.push(new Macro(codeOriginInner, "", variables, body)); |
| } else |
| operands.push(this.parseOperand("while inside of macro call to " + name)); |
| |
| this.skipNewLine(); |
| if (this.tokens[this.idx].isEqualTo(")")) { |
| this.idx++; |
| break |
| } else if (this.tokens[this.idx].isEqualTo(",")) |
| this.idx++; |
| else |
| this.parseError("Unexpected " + this.tokens[this.idx].string + " while parsing invocation of macro " + name); |
| |
| } |
| } |
| // Check if there's a trailing annotation after the macro invoke: |
| if (this.tokens[this.idx] instanceof Annotation) { |
| this.annotation = this.tokens[this.idx].string; |
| this.idx += 2 // Consume the newline as well. |
| } |
| list.push(new MacroCall(codeOrigin, name, operands, this.annotation)); |
| this.annotation = null; |
| } else |
| this.parseError("Expected \"(\" after " + name); |
| } else if (isLabel(this.tokens[this.idx]) || isLocalLabel(this.tokens[this.idx])) { |
| let codeOrigin = this.tokens[this.idx].codeOrigin; |
| let name = this.tokens[this.idx].string; |
| this.idx++; |
| if (this.tokens[this.idx].isNotEqualTo(":")) |
| this.parseError(); |
| // It's a label. |
| if (isLabel(name)) |
| list.push(Label.forName(codeOrigin, name, true)); |
| else |
| list.push(LocalLabel.forName(codeOrigin, name)); |
| |
| this.idx++; |
| } else if (this.tokens[this.idx].isEqualTo("include")) { |
| this.idx++; |
| if (!isIdentifier(this.tokens[this.idx])) |
| this.parseError(); |
| let moduleName = this.tokens[this.idx].string; |
| let fileName = new IncludeFile(moduleName, this.tokens[this.idx].codeOrigin.fileName.dirname).fileName; |
| this.idx++; |
| list.push(parse(fileName)); |
| } else |
| this.parseError("Expecting terminal " + final + " " + comment); |
| } |
| return new Sequence(firstCodeOrigin, list); |
| } |
| |
| parseIncludes(final, comment) |
| { |
| let firstCodeOrigin = this.tokens[this.idx].codeOrigin; |
| let fileList = []; |
| fileList.push(this.tokens[this.idx].codeOrigin.fileName); |
| while (true) { |
| if ((this.idx == this.tokens.length && !final) || (final && final.test(this.tokens[this.idx]))) |
| break; |
| else if (this.tokens[this.idx].isEqualTo("include")) { |
| this.idx++; |
| if (!isIdentifier(this.tokens[this.idx])) |
| this.parseError(); |
| let moduleName = this.tokens[this.idx].string; |
| let fileName = new IncludeFile(moduleName, this.tokens[this.idx].codeOrigin.fileName.dirname).fileName; |
| this.idx++; |
| |
| fileList.push(fileName); |
| } else |
| this.idx++; |
| } |
| |
| return fileList; |
| } |
| } |
| |
| function parseData(data, fileName) |
| { |
| let parser = new Parser(data, new SourceFile(fileName)); |
| return parser.parseSequence(null, ""); |
| } |
| |
| function parse(fileName) |
| { |
| return parseData(File.open(fileName).read(), fileName); |
| } |