• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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