| // Copyright 2019 Google LLC | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| import { TokenType } from "./token.js"; | |
| import * as AST from "./ast.js"; | |
| export default class Parser { | |
| /** | |
| * @param {Hash} The SPIR-V grammar | |
| * @param {Lexer} The lexer | |
| * @return {AST} Attempts to build an AST from the tokens returned by the | |
| * given lexer | |
| */ | |
| constructor(grammar, lexer) { | |
| this.grammar_ = grammar; | |
| this.lexer_ = lexer; | |
| this.peek_ = []; | |
| this.error_ = ""; | |
| } | |
| get error() { return this.error_; } | |
| next() { | |
| return this.peek_.shift() || this.lexer_.next(); | |
| } | |
| peek(idx) { | |
| while (this.peek_.length <= idx) { | |
| this.peek_.push(this.lexer_.next()); | |
| } | |
| return this.peek_[idx]; | |
| } | |
| /** | |
| * Executes the parser. | |
| * | |
| * @return {AST|undefined} returns a parsed AST on success or undefined | |
| * on error. The error message can be retrieved by | |
| * calling error(). | |
| */ | |
| parse() { | |
| let ast = new AST.Module(); | |
| for(;;) { | |
| let token = this.next(); | |
| if (token === TokenType.kError) { | |
| this.error_ = token.line() + ": " + token.data(); | |
| return undefined; | |
| } | |
| if (token.type === TokenType.kEOF) | |
| break; | |
| let result_id = undefined; | |
| if (token.type === TokenType.kResultId) { | |
| result_id = token; | |
| token = this.next(); | |
| if (token.type !== TokenType.kEqual) { | |
| this.error_ = token.line + ": expected = after result id"; | |
| return undefined; | |
| } | |
| token = this.next(); | |
| } | |
| if (token.type !== TokenType.kOp) { | |
| this.error_ = token.line + ": expected Op got " + token.type; | |
| return undefined; | |
| } | |
| let name = token.data.name; | |
| let data = this.getInstructionData(name); | |
| let operands = []; | |
| let result_type = undefined; | |
| for (let operand of data.operands) { | |
| if (operand.kind === "IdResult") { | |
| if (result_id === undefined) { | |
| this.error_ = token.line + ": expected result id"; | |
| return undefined; | |
| } | |
| let o = new AST.Operand(ast, result_id.data.name, "result_id", | |
| result_id.data.val, []); | |
| if (o === undefined) { | |
| return undefined; | |
| } | |
| operands.push(o); | |
| } else { | |
| if (operand.quantifier === "?") { | |
| if (this.nextIsNewInstr()) { | |
| break; | |
| } | |
| } else if (operand.quantifier === "*") { | |
| while (!this.nextIsNewInstr()) { | |
| let o = this.extractOperand(ast, result_type, operand); | |
| if (o === undefined) { | |
| return undefined; | |
| } | |
| operands.push(o); | |
| } | |
| break; | |
| } | |
| let o = this.extractOperand(ast, result_type, operand); | |
| if (o === undefined) { | |
| return undefined; | |
| } | |
| // Store the result type away so we can use it for context dependent | |
| // numbers if needed. | |
| if (operand.kind === "IdResultType") { | |
| result_type = ast.getType(o.name()); | |
| } | |
| operands.push(o); | |
| } | |
| } | |
| // Verify only GLSL extended instructions are used | |
| if (name === "OpExtInstImport" && operands[1].value() !== "GLSL.std.450") { | |
| this.error_ = token.line + ": Only GLSL.std.450 external instructions supported"; | |
| return undefined; | |
| } | |
| let inst = new AST.Instruction(name, data.opcode, operands); | |
| ast.addInstruction(inst); | |
| } | |
| return ast; | |
| } | |
| getInstructionData(name) { | |
| return this.grammar_["instructions"][name]; | |
| } | |
| nextIsNewInstr() { | |
| let n0 = this.peek(0); | |
| if (n0.type === TokenType.kOp || n0.type === TokenType.kEOF) { | |
| return true; | |
| } | |
| let n1 = this.peek(1); | |
| if (n1.type === TokenType.kEOF) { | |
| return false; | |
| } | |
| if (n0.type === TokenType.kResultId && n1.type === TokenType.kEqual) | |
| return true; | |
| return false; | |
| } | |
| extractOperand(ast, result_type, data) { | |
| let t = this.next(); | |
| let name = undefined; | |
| let kind = undefined; | |
| let value = undefined; | |
| let params = []; | |
| // TODO(dsinclair): There are a bunch of missing types here. See | |
| // https://github.com/KhronosGroup/SPIRV-Tools/blob/master/source/text.cpp#L210 | |
| // | |
| // LiteralSpecConstantOpInteger | |
| // PairLiteralIntegerIdRef | |
| // PairIdRefLiteralInteger | |
| // PairIdRefIdRef | |
| if (data.kind === "IdResult" || data.kind === "IdRef" | |
| || data.kind === "IdResultType" || data.kind === "IdScope" | |
| || data.kind === "IdMemorySemantics") { | |
| if (t.type !== TokenType.kResultId) { | |
| this.error_ = t.line + ": expected result id"; | |
| return undefined; | |
| } | |
| name = t.data.name; | |
| kind = "result_id"; | |
| value = t.data.val; | |
| } else if (data.kind === "LiteralString") { | |
| if (t.type !== TokenType.kStringLiteral) { | |
| this.error_ = t.line + ": expected string not found"; | |
| return undefined; | |
| } | |
| name = t.data; | |
| kind = "string"; | |
| value = t.data; | |
| } else if (data.kind === "LiteralInteger") { | |
| if (t.type !== TokenType.kIntegerLiteral) { | |
| this.error_ = t.line + ": expected integer not found"; | |
| return undefined; | |
| } | |
| name = "" + t.data; | |
| kind = t.type; | |
| value = t.data; | |
| } else if (data.kind === "LiteralContextDependentNumber") { | |
| if (result_type === undefined) { | |
| this.error_ = t.line + | |
| ": missing result type for context dependent number"; | |
| return undefined; | |
| } | |
| if (t.type !== TokenType.kIntegerLiteral | |
| && t.type !== TokenType.kFloatLiteral) { | |
| this.error_ = t.line + ": expected number not found"; | |
| return undefined; | |
| } | |
| name = "" + t.data; | |
| kind = result_type.type; | |
| value = t.data; | |
| } else if (data.kind === "LiteralExtInstInteger") { | |
| if (t.type !== TokenType.kIdentifier) { | |
| this.error_ = t.line + ": expected instruction identifier"; | |
| return undefined; | |
| } | |
| if (this.grammar_.ext[t.data] === undefined) { | |
| this.error_ = t.line + `: unable to find extended instruction (${t.data})`; | |
| return undefined; | |
| } | |
| name = t.data; | |
| kind = "integer"; | |
| value = this.grammar_.ext[t.data]; | |
| } else { | |
| let d = this.grammar_.operand_kinds[data.kind]; | |
| if (d === undefined) { | |
| this.error_ = t.line + ": expected " + data.kind + " not found"; | |
| return undefined; | |
| } | |
| let val = d.values[t.data]["value"]; | |
| let names = [t.data]; | |
| if (d.type === "BitEnum") { | |
| for(;;) { | |
| let tmp = this.peek(0); | |
| if (tmp.type !== TokenType.kPipe) { | |
| break; | |
| } | |
| this.next(); // skip pipe | |
| tmp = this.next(); | |
| if (tmp.type !== TokenType.kIdentifier) { | |
| this.error_ = tmp.line() + ": expected identifier"; | |
| return undefined; | |
| } | |
| val |= d.values[tmp.data]["value"]; | |
| names.push(tmp.data); | |
| } | |
| } | |
| name = names.join("|"); | |
| kind = d.type; | |
| value = val; | |
| for (const op_name of names) { | |
| if (d.values[op_name]['params'] === undefined) { | |
| continue; | |
| } | |
| for (const param of d.values[op_name]["params"]) { | |
| params.push(this.extractOperand(ast, result_type, { kind: param })); | |
| } | |
| } | |
| } | |
| return new AST.Operand(ast, name, kind, value, params); | |
| } | |
| } |