| /* |
| * 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"; |
| |
| function parse(program, origin, originKind, lineNumberOffset, text) |
| { |
| let lexer = new Lexer(origin, originKind, lineNumberOffset, text); |
| |
| // The hardest part of dealing with C-like languages is parsing variable declaration statements. |
| // Let's consider if this happens in WSL. Here are the valid statements in WSL that being with an |
| // identifier, if we assume that any expression can be a standalone statement. |
| // |
| // x; |
| // x <binop> y; |
| // x < y; |
| // x < y > z; |
| // x = y; |
| // x.f = y; |
| // \exp = y; |
| // x[42] = y; |
| // x(); |
| // x<y>(); |
| // x y; |
| // x<y> z; |
| // device x[] y; |
| // x[42] y; |
| // device x^ y; |
| // thread x^^ y; |
| // x^thread^thread y; |
| // x^device^thread y; |
| // |
| // This has two problem areas: |
| // |
| // - x<y>z can parse two different ways (as (x < y) > z or x<y> z). |
| // - x[42] could become either an assignment or a variable declaration. |
| // - x<y> could become either an assignment or a variable declaration. |
| // |
| // We solve the first problem by forbidding expressions as statements. The lack of function |
| // pointers means that we can still allow function call statements - unlike in C, those cannot |
| // have arbitrary expressions as the callee. The remaining two problems are solved by |
| // backtracking. In all other respects, this is a simple recursive descent parser. |
| |
| function genericConsume(callback, explanation) |
| { |
| let token = lexer.next(); |
| if (!token) |
| lexer.fail("Unexpected end of file"); |
| if (!callback(token)) |
| lexer.fail("Unexpected token: " + token.text + "; expected: " + explanation); |
| return token; |
| } |
| |
| function consume(...texts) |
| { |
| return genericConsume(token => texts.includes(token.text), texts); |
| } |
| |
| function consumeKind(kind) |
| { |
| return genericConsume(token => token.kind == kind, kind); |
| } |
| |
| function assertNext(...texts) |
| { |
| lexer.push(consume(...texts)); |
| } |
| |
| function genericTest(callback) |
| { |
| let token = lexer.peek(); |
| if (token && callback(token)) |
| return token; |
| return null; |
| } |
| |
| function test(...texts) |
| { |
| return genericTest(token => texts.includes(token.text)); |
| } |
| |
| function testKind(kind) |
| { |
| return genericTest(token => token.kind == kind); |
| } |
| |
| function tryConsume(...texts) |
| { |
| let result = test(...texts); |
| if (result) |
| lexer.next(); |
| return result; |
| } |
| |
| function tryConsumeKind(kind) |
| { |
| let result = testKind(kind); |
| if (result) |
| lexer.next(); |
| return result; |
| } |
| |
| function parseProtocolRef() |
| { |
| let protocolToken = consumeKind("identifier"); |
| return new ProtocolRef(protocolToken, protocolToken.text); |
| } |
| |
| function consumeEndOfTypeArgs() |
| { |
| let rightShift = tryConsume(">>"); |
| if (rightShift) |
| lexer.push(new LexerToken(lexer, rightShift, rightShift.index, rightShift.kind, ">")); |
| else |
| consume(">"); |
| } |
| |
| function parseTypeParameters() |
| { |
| if (!test("<")) |
| return []; |
| |
| let result = []; |
| consume("<"); |
| while (!test(">")) { |
| let constexpr = lexer.backtrackingScope(() => { |
| let type = parseType(); |
| let name = consumeKind("identifier"); |
| assertNext(",", ">", ">>"); |
| return new ConstexprTypeParameter(type.origin, name.text, type); |
| }); |
| if (constexpr) |
| result.push(constexpr); |
| else { |
| let name = consumeKind("identifier"); |
| let protocol = tryConsume(":") ? parseProtocolRef() : null; |
| result.push(new TypeVariable(name, name.text, protocol)); |
| } |
| if (!tryConsume(",")) |
| break; |
| } |
| consumeEndOfTypeArgs(); |
| return result; |
| } |
| |
| function parseTerm() |
| { |
| let token; |
| if (token = tryConsume("null")) |
| return new NullLiteral(token); |
| if (token = tryConsumeKind("identifier")) |
| return new VariableRef(token, token.text); |
| if (token = tryConsumeKind("intLiteral")) { |
| let intVersion = (+token.text) | 0; |
| if ("" + intVersion !== token.text) |
| lexer.fail("Integer literal is not an integer: " + token.text); |
| return new IntLiteral(token, intVersion); |
| } |
| if (token = tryConsumeKind("uintLiteral")) { |
| let uintVersion = token.text.substr(0, token.text.length - 1) >>> 0; |
| if (uintVersion + "u" !== token.text) |
| lexer.fail("Integer literal is not 32-bit unsigned integer: " + token.text); |
| return new UintLiteral(token, uintVersion); |
| } |
| if ((token = tryConsumeKind("intHexLiteral")) |
| || (token = tryConsumeKind("uintHexLiteral"))) { |
| let hexString = token.text.substr(2); |
| if (token.kind == "uintHexLiteral") |
| hexString = hexString.substr(0, hexString.length - 1); |
| if (!hexString.length) |
| throw new Error("Bad hex literal: " + token); |
| let intVersion = parseInt(hexString, 16); |
| if (token.kind == "intHexLiteral") |
| intVersion = intVersion | 0; |
| else |
| intVersion = intVersion >>> 0; |
| if (intVersion.toString(16) !== hexString) |
| lexer.fail("Hex integer literal is not an integer: " + token.text); |
| if (token.kind == "intHexLiteral") |
| return new IntLiteral(token, intVersion); |
| return new UintLiteral(token, intVersion >>> 0); |
| } |
| if (token = tryConsumeKind("doubleLiteral")) |
| return new DoubleLiteral(token, +token.text); |
| if (token = tryConsumeKind("floatLiteral")) { |
| let text = token.text; |
| let d = token.text.endsWith("d"); |
| let f = token.text.endsWith("f"); |
| if (d && f) |
| throw new Error("Literal cannot be both a double literal and a float literal."); |
| if (d || f) |
| text = text.substring(0, text.length - 1); |
| let value = parseFloat(text); |
| if (d) |
| return new DoubleLiteral(token, value); |
| return new FloatLiteral(token, Math.fround(value)); |
| } |
| if (token = tryConsume("true", "false")) |
| return new BoolLiteral(token, token.text == "true"); |
| // FIXME: Need support for other literals too. |
| consume("("); |
| let result = parseExpression(); |
| consume(")"); |
| return result; |
| } |
| |
| function parseConstexpr() |
| { |
| let token; |
| if (token = tryConsume("-")) |
| return new CallExpression(token, "operator" + token.text, [], [parseTerm()]); |
| let left = parseTerm(); |
| if (token = tryConsume(".")) |
| left = new DotExpression(token, left, consumeKind("identifier").text); |
| return left; |
| } |
| |
| function parseTypeArguments() |
| { |
| if (!test("<")) |
| return []; |
| |
| let result = []; |
| consume("<"); |
| while (!test(">")) { |
| // It's possible for a constexpr or type can syntactically overlap in the single |
| // identifier case. Let's consider the possibilities: |
| // |
| // T could be type or constexpr |
| // T[] only type |
| // T[42] only type (constexpr cannot do indexing) |
| // 42 only constexpr |
| // |
| // In the future we'll allow constexprs to do more things, and then we'll still have |
| // the problem that something of the form T[1][2][3]... can either be a type or a |
| // constexpr, and we can figure out in the checker which it is. |
| let typeOrVariableRef = lexer.backtrackingScope(() => { |
| let result = consumeKind("identifier"); |
| assertNext(",", ">", ">>"); |
| return new TypeOrVariableRef(result, result.text); |
| }); |
| if (typeOrVariableRef) |
| result.push(typeOrVariableRef); |
| else { |
| let constexpr = lexer.backtrackingScope(() => { |
| let result = parseConstexpr(); |
| assertNext(",", ">", ">>"); |
| return result; |
| }); |
| if (constexpr) |
| result.push(constexpr); |
| else |
| result.push(parseType()); |
| } |
| if (!tryConsume(",")) |
| break; |
| } |
| consumeEndOfTypeArgs(); |
| return result; |
| } |
| |
| function parseType() |
| { |
| let token; |
| let addressSpace; |
| let addressSpaceConsumed = false; |
| if (token = tryConsume(...addressSpaces)) |
| addressSpace = token.text; |
| |
| let name = consumeKind("identifier"); |
| let typeArguments = parseTypeArguments(); |
| let type = new TypeRef(name, name.text, typeArguments); |
| |
| function getAddressSpace() |
| { |
| addressSpaceConsumed = true; |
| if (addressSpace) |
| return addressSpace; |
| return consume(...addressSpaces).text; |
| } |
| |
| while (token = tryConsume("*", "[")) { |
| if (token.text == "*") { |
| type = new PtrType(token, getAddressSpace(), type); |
| continue; |
| } |
| |
| if (tryConsume("]")) { |
| type = new ArrayRefType(token, getAddressSpace(), type); |
| continue; |
| } |
| |
| type = new ArrayType(token, type, parseConstexpr()); |
| consume("]"); |
| } |
| |
| if (addressSpace && !addressSpaceConsumed) |
| lexer.fail("Address space specified for type that does not need address space"); |
| |
| return type; |
| } |
| |
| function parseTypeDef() |
| { |
| let origin = consume("typedef"); |
| let name = consumeKind("identifier").text; |
| let typeParameters = parseTypeParameters(); |
| consume("="); |
| let type = parseType(); |
| consume(";"); |
| return new TypeDef(origin, name, typeParameters, type); |
| } |
| |
| function genericParseLeft(texts, nextParser, constructor) |
| { |
| let left = nextParser(); |
| let token; |
| while (token = tryConsume(...texts)) |
| left = constructor(token, left, nextParser()); |
| return left; |
| } |
| |
| function parseLeftOperatorCall(texts, nextParser) |
| { |
| return genericParseLeft( |
| texts, nextParser, |
| (token, left, right) => |
| new CallExpression(token, "operator" + token.text, [], [left, right])); |
| } |
| |
| function parseCallExpression() |
| { |
| let name = consumeKind("identifier"); |
| let typeArguments = parseTypeArguments(); |
| consume("("); |
| let argumentList = []; |
| while (!test(")")) { |
| let argument = parsePossibleAssignment(); |
| argumentList.push(argument); |
| if (!tryConsume(",")) |
| break; |
| } |
| consume(")"); |
| let result = new CallExpression(name, name.text, typeArguments, argumentList); |
| return result; |
| } |
| |
| function isCallExpression() |
| { |
| return lexer.testScope(() => { |
| consumeKind("identifier"); |
| parseTypeArguments(); |
| consume("("); |
| }); |
| } |
| |
| function emitIncrement(token, old, extraArg) |
| { |
| let args = [old]; |
| if (extraArg) |
| args.push(extraArg); |
| |
| let name = "operator" + token.text; |
| if (/=$/.test(name)) |
| name = RegExp.leftContext; |
| |
| if (name == "operator") |
| throw new Error("Invalid name: " + name); |
| |
| return new CallExpression(token, name, [], args); |
| } |
| |
| function finishParsingPostIncrement(token, left) |
| { |
| let readModifyWrite = new ReadModifyWriteExpression(token, left); |
| readModifyWrite.newValueExp = emitIncrement(token, readModifyWrite.oldValueRef()); |
| readModifyWrite.resultExp = readModifyWrite.oldValueRef(); |
| return readModifyWrite; |
| } |
| |
| function parseSuffixOperator(left, acceptableOperators) |
| { |
| let token; |
| while (token = tryConsume(...acceptableOperators)) { |
| switch (token.text) { |
| case "++": |
| case "--": |
| return finishParsingPostIncrement(token, left); |
| case ".": |
| case "->": |
| if (token.text == "->") |
| left = new DereferenceExpression(token, left); |
| left = new DotExpression(token, left, consumeKind("identifier").text); |
| break; |
| case "[": { |
| let index = parseExpression(); |
| consume("]"); |
| left = new IndexExpression(token, left, index); |
| break; |
| } |
| default: |
| throw new Error("Bad token: " + token); |
| } |
| } |
| return left; |
| } |
| |
| function parsePossibleSuffix() |
| { |
| let acceptableOperators = ["++", "--", ".", "->", "["]; |
| let limitedOperators = [".", "->", "["]; |
| let left; |
| if (isCallExpression()) { |
| left = parseCallExpression(); |
| acceptableOperators = limitedOperators; |
| } else |
| left = parseTerm(); |
| |
| return parseSuffixOperator(left, acceptableOperators); |
| } |
| |
| function finishParsingPreIncrement(token, left, extraArg) |
| { |
| let readModifyWrite = new ReadModifyWriteExpression(token, left); |
| readModifyWrite.newValueExp = emitIncrement(token, readModifyWrite.oldValueRef(), extraArg); |
| readModifyWrite.resultExp = readModifyWrite.newValueRef(); |
| return readModifyWrite; |
| } |
| |
| function parsePreIncrement() |
| { |
| let token = consume("++", "--"); |
| let left = parsePossiblePrefix(); |
| return finishParsingPreIncrement(token, left); |
| } |
| |
| function parsePossiblePrefix() |
| { |
| let token; |
| if (test("++", "--")) |
| return parsePreIncrement(); |
| if (token = tryConsume("+", "-", "~")) |
| return new CallExpression(token, "operator" + token.text, [], [parsePossiblePrefix()]); |
| if (token = tryConsume("*")) |
| return new DereferenceExpression(token, parsePossiblePrefix()); |
| if (token = tryConsume("&")) |
| return new MakePtrExpression(token, parsePossiblePrefix()); |
| if (token = tryConsume("@")) |
| return new MakeArrayRefExpression(token, parsePossiblePrefix()); |
| if (token = tryConsume("!")) { |
| let remainder = parsePossiblePrefix(); |
| return new LogicalNot(token, new CallExpression(remainder.origin, "bool", [], [remainder])); |
| } |
| return parsePossibleSuffix(); |
| } |
| |
| function parsePossibleProduct() |
| { |
| return parseLeftOperatorCall(["*", "/", "%"], parsePossiblePrefix); |
| } |
| |
| function parsePossibleSum() |
| { |
| return parseLeftOperatorCall(["+", "-"], parsePossibleProduct); |
| } |
| |
| function parsePossibleShift() |
| { |
| return parseLeftOperatorCall(["<<", ">>"], parsePossibleSum); |
| } |
| |
| function parsePossibleRelationalInequality() |
| { |
| return parseLeftOperatorCall(["<", ">", "<=", ">="], parsePossibleShift); |
| } |
| |
| function parsePossibleRelationalEquality() |
| { |
| return genericParseLeft( |
| ["==", "!="], parsePossibleRelationalInequality, |
| (token, left, right) => { |
| let result = new CallExpression(token, "operator==", [], [left, right]); |
| if (token.text == "!=") |
| result = new LogicalNot(token, result); |
| return result; |
| }); |
| } |
| |
| function parsePossibleBitwiseAnd() |
| { |
| return parseLeftOperatorCall(["&"], parsePossibleRelationalEquality); |
| } |
| |
| function parsePossibleBitwiseXor() |
| { |
| return parseLeftOperatorCall(["^"], parsePossibleBitwiseAnd); |
| } |
| |
| function parsePossibleBitwiseOr() |
| { |
| return parseLeftOperatorCall(["|"], parsePossibleBitwiseXor); |
| } |
| |
| function parseLeftLogicalExpression(texts, nextParser) |
| { |
| return genericParseLeft( |
| texts, nextParser, |
| (token, left, right) => new LogicalExpression(token, token.text, new CallExpression(left.origin, "bool", [], [left]), new CallExpression(right.origin, "bool", [], [right]))); |
| } |
| |
| function parsePossibleLogicalAnd() |
| { |
| return parseLeftLogicalExpression(["&&"], parsePossibleBitwiseOr); |
| } |
| |
| function parsePossibleLogicalOr() |
| { |
| return parseLeftLogicalExpression(["||"], parsePossibleLogicalAnd); |
| } |
| |
| function parsePossibleTernaryConditional() |
| { |
| let predicate = parsePossibleLogicalOr(); |
| let operator = tryConsume("?"); |
| if (!operator) |
| return predicate; |
| return new TernaryExpression(operator, predicate, parsePossibleAssignment(), parsePossibleAssignment()); |
| } |
| |
| function parsePossibleAssignment(mode) |
| { |
| let lhs = parsePossibleTernaryConditional(); |
| let operator = tryConsume("=", "+=", "-=", "*=", "/=", "%=", "^=", "|=", "&="); |
| if (!operator) { |
| if (mode == "required") |
| lexer.fail("Expected assignment"); |
| return lhs; |
| } |
| if (operator.text == "=") |
| return new Assignment(operator, lhs, parsePossibleAssignment()); |
| return finishParsingPreIncrement(operator, lhs, parsePossibleAssignment()); |
| } |
| |
| function parseAssignment() |
| { |
| return parsePossibleAssignment("required"); |
| } |
| |
| function parsePostIncrement() |
| { |
| let left = parseSuffixOperator(parseTerm(), ".", "->", "["); |
| let token = consume("++", "--"); |
| return finishParsingPostIncrement(token, left); |
| } |
| |
| function parseEffectfulExpression() |
| { |
| if (isCallExpression()) |
| return parseCallExpression(); |
| let preIncrement = lexer.backtrackingScope(parsePreIncrement); |
| if (preIncrement) |
| return preIncrement; |
| let postIncrement = lexer.backtrackingScope(parsePostIncrement); |
| if (postIncrement) |
| return postIncrement; |
| return parseAssignment(); |
| } |
| |
| function genericParseCommaExpression(finalExpressionParser) |
| { |
| let list = []; |
| let origin = lexer.peek(); |
| if (!origin) |
| lexer.fail("Unexpected end of file"); |
| for (;;) { |
| let effectfulExpression = lexer.backtrackingScope(() => { |
| parseEffectfulExpression(); |
| consume(","); |
| }); |
| if (!effectfulExpression) { |
| let final = finalExpressionParser(); |
| list.push(final); |
| break; |
| } |
| list.push(effectfulExpression); |
| } |
| if (!list.length) |
| throw new Error("Length should never be zero"); |
| if (list.length == 1) |
| return list[0]; |
| return new CommaExpression(origin, list); |
| } |
| |
| function parseCommaExpression() |
| { |
| return genericParseCommaExpression(parsePossibleAssignment); |
| } |
| |
| function parseExpression() |
| { |
| return parseCommaExpression(); |
| } |
| |
| function parseEffectfulStatement() |
| { |
| let result = genericParseCommaExpression(parseEffectfulExpression); |
| consume(";"); |
| return result; |
| } |
| |
| function parseReturn() |
| { |
| let origin = consume("return"); |
| if (tryConsume(";")) |
| return new Return(origin, null); |
| let expression = parseExpression(); |
| consume(";"); |
| return new Return(origin, expression); |
| } |
| |
| function parseBreak() |
| { |
| let origin = consume("break"); |
| consume(";"); |
| return new Break(origin); |
| } |
| |
| function parseContinue() |
| { |
| let origin = consume("continue"); |
| consume(";"); |
| return new Continue(origin); |
| } |
| |
| function parseIfStatement() |
| { |
| let origin = consume("if"); |
| consume("("); |
| let conditional = parseExpression(); |
| consume(")"); |
| let body = parseStatement(); |
| let elseBody; |
| if (tryConsume("else")) |
| elseBody = parseStatement(); |
| return new IfStatement(origin, new CallExpression(conditional.origin, "bool", [], [conditional]), body, elseBody); |
| } |
| |
| function parseWhile() |
| { |
| let origin = consume("while"); |
| consume("("); |
| let conditional = parseExpression(); |
| consume(")"); |
| let body = parseStatement(); |
| return new WhileLoop(origin, new CallExpression(conditional.origin, "bool", [], [conditional]), body); |
| } |
| |
| function parseFor() |
| { |
| let origin = consume("for"); |
| consume("("); |
| let initialization; |
| if (tryConsume(";")) |
| initialization = undefined; |
| else { |
| initialization = lexer.backtrackingScope(parseVariableDecls); |
| if (!initialization) |
| initialization = parseEffectfulStatement(); |
| } |
| let condition = tryConsume(";"); |
| if (condition) |
| condition = undefined; |
| else { |
| condition = parseExpression(); |
| consume(";"); |
| condition = new CallExpression(condition.origin, "bool", [], [condition]); |
| } |
| let increment; |
| if (tryConsume(")")) |
| increment = undefined; |
| else { |
| increment = parseExpression(); |
| consume(")"); |
| } |
| let body = parseStatement(); |
| return new ForLoop(origin, initialization, condition, increment, body); |
| } |
| |
| function parseDo() |
| { |
| let origin = consume("do"); |
| let body = parseStatement(); |
| consume("while"); |
| consume("("); |
| let conditional = parseExpression(); |
| consume(")"); |
| return new DoWhileLoop(origin, body, new CallExpression(conditional.origin, "bool", [], [conditional])); |
| } |
| |
| function parseVariableDecls() |
| { |
| let type = parseType(); |
| let list = []; |
| do { |
| let name = consumeKind("identifier"); |
| let initializer = tryConsume("=") ? parseExpression() : null; |
| list.push(new VariableDecl(name, name.text, type, initializer)); |
| } while (consume(",", ";").text == ","); |
| return new CommaExpression(type.origin, list); |
| } |
| |
| function parseSwitchCase() |
| { |
| let token = consume("default", "case"); |
| let value; |
| if (token.text == "case") |
| value = parseConstexpr(); |
| consume(":"); |
| let body = parseBlockBody("}", "default", "case"); |
| return new SwitchCase(token, value, body); |
| } |
| |
| function parseSwitchStatement() |
| { |
| let origin = consume("switch"); |
| consume("("); |
| let value = parseExpression(); |
| consume(")"); |
| consume("{"); |
| let result = new SwitchStatement(origin, value); |
| while (!tryConsume("}")) |
| result.add(parseSwitchCase()); |
| return result; |
| } |
| |
| function parseStatement() |
| { |
| let token = lexer.peek(); |
| if (token.text == ";") { |
| lexer.next(); |
| return null; |
| } |
| if (token.text == "return") |
| return parseReturn(); |
| if (token.text == "break") |
| return parseBreak(); |
| if (token.text == "continue") |
| return parseContinue(); |
| if (token.text == "while") |
| return parseWhile(); |
| if (token.text == "do") |
| return parseDo(); |
| if (token.text == "for") |
| return parseFor(); |
| if (token.text == "if") |
| return parseIfStatement(); |
| if (token.text == "switch") |
| return parseSwitchStatement(); |
| if (token.text == "trap") { |
| let origin = consume("trap"); |
| consume(";"); |
| return new TrapStatement(origin); |
| } |
| if (token.text == "{") |
| return parseBlock(); |
| let variableDecl = lexer.backtrackingScope(parseVariableDecls); |
| if (variableDecl) |
| return variableDecl; |
| return parseEffectfulStatement(); |
| } |
| |
| function parseBlockBody(...terminators) |
| { |
| let block = new Block(origin); |
| while (!test(...terminators)) { |
| let statement = parseStatement(); |
| if (statement) |
| block.add(statement); |
| } |
| return block; |
| } |
| |
| function parseBlock() |
| { |
| let origin = consume("{"); |
| let block = parseBlockBody("}"); |
| consume("}"); |
| return block; |
| } |
| |
| function parseParameter() |
| { |
| let type = parseType(); |
| let name = tryConsumeKind("identifier"); |
| return new FuncParameter(type.origin, name ? name.text : null, type); |
| } |
| |
| function parseParameters() |
| { |
| consume("("); |
| let parameters = []; |
| while (!test(")")) { |
| parameters.push(parseParameter()); |
| if (!tryConsume(",")) |
| break; |
| } |
| consume(")"); |
| return parameters; |
| } |
| |
| function parseFuncName() |
| { |
| if (tryConsume("operator")) { |
| let token = consume("+", "-", "*", "/", "%", "^", "&", "|", "<", ">", "<=", ">=", "==", "++", "--", ".", "~", "<<", ">>", "["); |
| if (token.text == "&") { |
| if (tryConsume("[")) { |
| consume("]"); |
| return "operator&[]"; |
| } |
| if (tryConsume(".")) |
| return "operator&." + consumeKind("identifier").text; |
| return "operator&"; |
| } |
| if (token.text == ".") { |
| let result = "operator." + consumeKind("identifier").text; |
| if (tryConsume("=")) |
| result += "="; |
| return result; |
| } |
| if (token.text == "[") { |
| consume("]"); |
| let result = "operator[]"; |
| if (tryConsume("=")) |
| result += "="; |
| return result; |
| } |
| return "operator" + token.text; |
| } |
| return consumeKind("identifier").text; |
| } |
| |
| function parseFuncDecl() |
| { |
| let origin; |
| let returnType; |
| let name; |
| let typeParameters; |
| let isCast; |
| let shaderType; |
| let operatorToken = tryConsume("operator"); |
| if (operatorToken) { |
| origin = operatorToken; |
| typeParameters = parseTypeParameters(); |
| returnType = parseType(); |
| name = "operator cast"; |
| isCast = true; |
| } else { |
| shaderType = tryConsume("vertex", "fragment"); |
| returnType = parseType(); |
| if (shaderType) { |
| origin = shaderType; |
| shaderType = shaderType.text; |
| } else |
| origin = returnType.origin; |
| name = parseFuncName(); |
| typeParameters = parseTypeParameters(); |
| isCast = false; |
| } |
| let parameters = parseParameters(); |
| return new Func(origin, name, returnType, typeParameters, parameters, isCast, shaderType); |
| } |
| |
| function parseProtocolFuncDecl() |
| { |
| let func = parseFuncDecl(); |
| return new ProtocolFuncDecl(func.origin, func.name, func.returnType, func.typeParameters, func.parameters, func.isCast, func.shaderType); |
| } |
| |
| function parseFuncDef() |
| { |
| let func = parseFuncDecl(); |
| let body = parseBlock(); |
| return new FuncDef(func.origin, func.name, func.returnType, func.typeParameters, func.parameters, body, func.isCast, func.shaderType); |
| } |
| |
| function parseProtocolDecl() |
| { |
| let origin = consume("protocol"); |
| let name = consumeKind("identifier").text; |
| let result = new ProtocolDecl(origin, name); |
| if (tryConsume(":")) { |
| while (!test("{")) { |
| result.addExtends(parseProtocolRef()); |
| if (!tryConsume(",")) |
| break; |
| } |
| } |
| consume("{"); |
| while (!tryConsume("}")) { |
| result.add(parseProtocolFuncDecl()); |
| consume(";"); |
| } |
| return result; |
| } |
| |
| function parseField() |
| { |
| let type = parseType(); |
| let name = consumeKind("identifier"); |
| consume(";"); |
| return new Field(name, name.text, type); |
| } |
| |
| function parseStructType() |
| { |
| let origin = consume("struct"); |
| let name = consumeKind("identifier").text; |
| let typeParameters = parseTypeParameters(); |
| let result = new StructType(origin, name, typeParameters); |
| consume("{"); |
| while (!tryConsume("}")) |
| result.add(parseField()); |
| return result; |
| } |
| |
| function parseNativeFunc() |
| { |
| let func = parseFuncDecl(); |
| consume(";"); |
| return new NativeFunc(func.origin, func.name, func.returnType, func.typeParameters, func.parameters, func.isCast, func.shaderType); |
| } |
| |
| function parseNative() |
| { |
| let origin = consume("native"); |
| if (tryConsume("typedef")) { |
| let name = consumeKind("identifier"); |
| let parameters = parseTypeParameters(); |
| consume(";"); |
| return new NativeType(origin, name.text, parameters); |
| } |
| return parseNativeFunc(); |
| } |
| |
| function parseRestrictedFuncDef() |
| { |
| consume("restricted"); |
| let result; |
| if (tryConsume("native")) |
| result = parseNativeFunc(); |
| else |
| result = parseFuncDef(); |
| result.isRestricted = true; |
| return result; |
| } |
| |
| function parseEnumMember() |
| { |
| let name = consumeKind("identifier"); |
| let value = null; |
| if (tryConsume("=")) |
| value = parseConstexpr(); |
| return new EnumMember(name, name.text, value); |
| } |
| |
| function parseEnumType() |
| { |
| consume("enum"); |
| let name = consumeKind("identifier"); |
| let baseType; |
| if (tryConsume(":")) |
| baseType = parseType(); |
| else |
| baseType = new TypeRef(name, "int", []); |
| consume("{"); |
| let result = new EnumType(name, name.text, baseType); |
| while (!test("}")) { |
| result.add(parseEnumMember()); |
| if (!tryConsume(",")) |
| break; |
| } |
| consume("}"); |
| return result; |
| } |
| |
| for (;;) { |
| let token = lexer.peek(); |
| if (!token) |
| return; |
| if (token.text == ";") |
| lexer.next(); |
| else if (token.text == "typedef") |
| program.add(parseTypeDef()); |
| else if (originKind == "native" && token.text == "native") |
| program.add(parseNative()); |
| else if (originKind == "native" && token.text == "restricted") |
| program.add(parseRestrictedFuncDef()); |
| else if (token.text == "struct") |
| program.add(parseStructType()); |
| else if (token.text == "enum") |
| program.add(parseEnumType()); |
| else if (token.text == "protocol") |
| program.add(parseProtocolDecl()); |
| else |
| program.add(parseFuncDef()); |
| } |
| } |
| |