1// Copyright 2019 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import { TokenType } from "./token.js"; 16import * as AST from "./ast.js"; 17 18export default class Parser { 19 /** 20 * @param {Hash} The SPIR-V grammar 21 * @param {Lexer} The lexer 22 * @return {AST} Attempts to build an AST from the tokens returned by the 23 * given lexer 24 */ 25 constructor(grammar, lexer) { 26 this.grammar_ = grammar; 27 this.lexer_ = lexer; 28 29 this.peek_ = []; 30 this.error_ = ""; 31 } 32 33 get error() { return this.error_; } 34 35 next() { 36 return this.peek_.shift() || this.lexer_.next(); 37 } 38 39 peek(idx) { 40 while (this.peek_.length <= idx) { 41 this.peek_.push(this.lexer_.next()); 42 } 43 return this.peek_[idx]; 44 } 45 46 /** 47 * Executes the parser. 48 * 49 * @return {AST|undefined} returns a parsed AST on success or undefined 50 * on error. The error message can be retrieved by 51 * calling error(). 52 */ 53 parse() { 54 let ast = new AST.Module(); 55 for(;;) { 56 let token = this.next(); 57 if (token === TokenType.kError) { 58 this.error_ = token.line() + ": " + token.data(); 59 return undefined; 60 } 61 if (token.type === TokenType.kEOF) 62 break; 63 64 let result_id = undefined; 65 if (token.type === TokenType.kResultId) { 66 result_id = token; 67 68 token = this.next(); 69 if (token.type !== TokenType.kEqual) { 70 this.error_ = token.line + ": expected = after result id"; 71 return undefined; 72 } 73 74 token = this.next(); 75 } 76 77 if (token.type !== TokenType.kOp) { 78 this.error_ = token.line + ": expected Op got " + token.type; 79 return undefined; 80 } 81 82 let name = token.data.name; 83 let data = this.getInstructionData(name); 84 let operands = []; 85 let result_type = undefined; 86 87 for (let operand of data.operands) { 88 if (operand.kind === "IdResult") { 89 if (result_id === undefined) { 90 this.error_ = token.line + ": expected result id"; 91 return undefined; 92 } 93 let o = new AST.Operand(ast, result_id.data.name, "result_id", 94 result_id.data.val, []); 95 if (o === undefined) { 96 return undefined; 97 } 98 operands.push(o); 99 } else { 100 if (operand.quantifier === "?") { 101 if (this.nextIsNewInstr()) { 102 break; 103 } 104 } else if (operand.quantifier === "*") { 105 while (!this.nextIsNewInstr()) { 106 let o = this.extractOperand(ast, result_type, operand); 107 if (o === undefined) { 108 return undefined; 109 } 110 operands.push(o); 111 } 112 break; 113 } 114 115 let o = this.extractOperand(ast, result_type, operand); 116 if (o === undefined) { 117 return undefined; 118 } 119 120 // Store the result type away so we can use it for context dependent 121 // numbers if needed. 122 if (operand.kind === "IdResultType") { 123 result_type = ast.getType(o.name()); 124 } 125 126 operands.push(o); 127 } 128 } 129 130 // Verify only GLSL extended instructions are used 131 if (name === "OpExtInstImport" && operands[1].value() !== "GLSL.std.450") { 132 this.error_ = token.line + ": Only GLSL.std.450 external instructions supported"; 133 return undefined; 134 } 135 136 let inst = new AST.Instruction(name, data.opcode, operands); 137 138 ast.addInstruction(inst); 139 } 140 return ast; 141 } 142 143 getInstructionData(name) { 144 return this.grammar_["instructions"][name]; 145 } 146 147 nextIsNewInstr() { 148 let n0 = this.peek(0); 149 if (n0.type === TokenType.kOp || n0.type === TokenType.kEOF) { 150 return true; 151 } 152 153 let n1 = this.peek(1); 154 if (n1.type === TokenType.kEOF) { 155 return false; 156 } 157 if (n0.type === TokenType.kResultId && n1.type === TokenType.kEqual) 158 return true; 159 160 return false; 161 } 162 163 extractOperand(ast, result_type, data) { 164 let t = this.next(); 165 166 let name = undefined; 167 let kind = undefined; 168 let value = undefined; 169 let params = []; 170 171 // TODO(dsinclair): There are a bunch of missing types here. See 172 // https://github.com/KhronosGroup/SPIRV-Tools/blob/master/source/text.cpp#L210 173 // 174 // LiteralSpecConstantOpInteger 175 // PairLiteralIntegerIdRef 176 // PairIdRefLiteralInteger 177 // PairIdRefIdRef 178 if (data.kind === "IdResult" || data.kind === "IdRef" 179 || data.kind === "IdResultType" || data.kind === "IdScope" 180 || data.kind === "IdMemorySemantics") { 181 if (t.type !== TokenType.kResultId) { 182 this.error_ = t.line + ": expected result id"; 183 return undefined; 184 } 185 186 name = t.data.name; 187 kind = "result_id"; 188 value = t.data.val; 189 } else if (data.kind === "LiteralString") { 190 if (t.type !== TokenType.kStringLiteral) { 191 this.error_ = t.line + ": expected string not found"; 192 return undefined; 193 } 194 195 name = t.data; 196 kind = "string"; 197 value = t.data; 198 } else if (data.kind === "LiteralInteger") { 199 if (t.type !== TokenType.kIntegerLiteral) { 200 this.error_ = t.line + ": expected integer not found"; 201 return undefined; 202 } 203 204 name = "" + t.data; 205 kind = t.type; 206 value = t.data; 207 } else if (data.kind === "LiteralContextDependentNumber") { 208 if (result_type === undefined) { 209 this.error_ = t.line + 210 ": missing result type for context dependent number"; 211 return undefined; 212 } 213 if (t.type !== TokenType.kIntegerLiteral 214 && t.type !== TokenType.kFloatLiteral) { 215 this.error_ = t.line + ": expected number not found"; 216 return undefined; 217 } 218 219 name = "" + t.data; 220 kind = result_type.type; 221 value = t.data; 222 223 } else if (data.kind === "LiteralExtInstInteger") { 224 if (t.type !== TokenType.kIdentifier) { 225 this.error_ = t.line + ": expected instruction identifier"; 226 return undefined; 227 } 228 229 if (this.grammar_.ext[t.data] === undefined) { 230 this.error_ = t.line + `: unable to find extended instruction (${t.data})`; 231 return undefined; 232 } 233 234 name = t.data; 235 kind = "integer"; 236 value = this.grammar_.ext[t.data]; 237 238 } else { 239 let d = this.grammar_.operand_kinds[data.kind]; 240 if (d === undefined) { 241 this.error_ = t.line + ": expected " + data.kind + " not found"; 242 return undefined; 243 } 244 245 let val = d.values[t.data]["value"]; 246 let names = [t.data]; 247 if (d.type === "BitEnum") { 248 for(;;) { 249 let tmp = this.peek(0); 250 if (tmp.type !== TokenType.kPipe) { 251 break; 252 } 253 254 this.next(); // skip pipe 255 tmp = this.next(); 256 257 if (tmp.type !== TokenType.kIdentifier) { 258 this.error_ = tmp.line() + ": expected identifier"; 259 return undefined; 260 } 261 262 val |= d.values[tmp.data]["value"]; 263 names.push(tmp.data); 264 } 265 } 266 267 name = names.join("|"); 268 kind = d.type; 269 value = val; 270 271 for (const op_name of names) { 272 if (d.values[op_name]['params'] === undefined) { 273 continue; 274 } 275 276 for (const param of d.values[op_name]["params"]) { 277 params.push(this.extractOperand(ast, result_type, { kind: param })); 278 } 279 } 280 } 281 return new AST.Operand(ast, name, kind, value, params); 282 } 283} 284