// 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); } }