blob: ccf872a110400328a9929c8ac6eb16490041a021 [file] [log] [blame]
// 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);
}
}