This change includes a fix for an issue we filed against spirv-tools: https://github.com/KhronosGroup/SPIRV-Tools/issues/4371 So, it should reduce the number of extraneous matrix copies in finalized GLSL code. I did quick size total of all filamat files (*) before and after this update, it went from 11.3 MB to 11.1 MB. (*) All backends are enabled, includes built-in Filament shaders and gltfio ubershaders.
819 lines
20 KiB
Go
819 lines
20 KiB
Go
// Copyright (C) 2019 Google Inc.
|
|
//
|
|
// 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.
|
|
|
|
// Package parser implements a SPIR-V assembly parser.
|
|
package parser
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"strings"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
|
|
"github.com/KhronosGroup/SPIRV-Tools/utils/vscode/src/schema"
|
|
)
|
|
|
|
// Type is an enumerator of token types.
|
|
type Type int
|
|
|
|
// Type enumerators
|
|
const (
|
|
Ident Type = iota // Foo
|
|
PIdent // %32, %foo
|
|
Integer
|
|
Float
|
|
String
|
|
Operator
|
|
Comment
|
|
Newline
|
|
)
|
|
|
|
func (t Type) String() string {
|
|
switch t {
|
|
case Ident:
|
|
return "Ident"
|
|
case PIdent:
|
|
return "PIdent"
|
|
case Integer:
|
|
return "Integer"
|
|
case Float:
|
|
return "Float"
|
|
case String:
|
|
return "String"
|
|
case Operator:
|
|
return "Operator"
|
|
case Comment:
|
|
return "Comment"
|
|
default:
|
|
return "<unknown>"
|
|
}
|
|
}
|
|
|
|
// Token represents a single lexed token.
|
|
type Token struct {
|
|
Type Type
|
|
Range Range
|
|
}
|
|
|
|
func (t Token) String() string { return fmt.Sprintf("{%v %v}", t.Type, t.Range) }
|
|
|
|
// Text returns the tokens text from the source.
|
|
func (t Token) Text(lines []string) string { return t.Range.Text(lines) }
|
|
|
|
// Range represents an interval in a text file.
|
|
type Range struct {
|
|
Start Position
|
|
End Position
|
|
}
|
|
|
|
func (r Range) String() string { return fmt.Sprintf("[%v %v]", r.Start, r.End) }
|
|
|
|
// Text returns the text for the given Range in the provided lines.
|
|
func (r Range) Text(lines []string) string {
|
|
sl, sc := r.Start.Line-1, r.Start.Column-1
|
|
if sl < 0 || sc < 0 || sl > len(lines) || sc > len(lines[sl]) {
|
|
return fmt.Sprintf("<invalid start position %v>", r.Start)
|
|
}
|
|
el, ec := r.End.Line-1, r.End.Column-1
|
|
if el < 0 || ec < 0 || el > len(lines) || ec > len(lines[sl]) {
|
|
return fmt.Sprintf("<invalid end position %v>", r.End)
|
|
}
|
|
|
|
sb := strings.Builder{}
|
|
if sl != el {
|
|
sb.WriteString(lines[sl][sc:])
|
|
for l := sl + 1; l < el; l++ {
|
|
sb.WriteString(lines[l])
|
|
}
|
|
sb.WriteString(lines[el][:ec])
|
|
} else {
|
|
sb.WriteString(lines[sl][sc:ec])
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
// Contains returns true if p is in r.
|
|
func (r Range) Contains(p Position) bool {
|
|
return !(p.LessThan(r.Start) || p.GreaterThan(r.End))
|
|
}
|
|
|
|
func (r *Range) grow(o Range) {
|
|
if !r.Start.IsValid() || o.Start.LessThan(r.Start) {
|
|
r.Start = o.Start
|
|
}
|
|
if !r.End.IsValid() || o.End.GreaterThan(r.End) {
|
|
r.End = o.End
|
|
}
|
|
}
|
|
|
|
// Position holds a line and column position in a text file.
|
|
type Position struct {
|
|
Line, Column int
|
|
}
|
|
|
|
func (p Position) String() string { return fmt.Sprintf("%v:%v", p.Line, p.Column) }
|
|
|
|
// IsValid returns true if the position has a line and column greater than 1.
|
|
func (p Position) IsValid() bool { return p.Line > 0 && p.Column > 0 }
|
|
|
|
// LessThan returns true iff o is before p.
|
|
func (p Position) LessThan(o Position) bool {
|
|
switch {
|
|
case !p.IsValid() || !o.IsValid():
|
|
return false
|
|
case p.Line < o.Line:
|
|
return true
|
|
case p.Line > o.Line:
|
|
return false
|
|
case p.Column < o.Column:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// GreaterThan returns true iff o is greater than p.
|
|
func (p Position) GreaterThan(o Position) bool {
|
|
switch {
|
|
case !p.IsValid() || !o.IsValid():
|
|
return false
|
|
case p.Line > o.Line:
|
|
return true
|
|
case p.Line < o.Line:
|
|
return false
|
|
case p.Column > o.Column:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
type lexer struct {
|
|
source string
|
|
lexerState
|
|
diags []Diagnostic
|
|
e error
|
|
}
|
|
|
|
type lexerState struct {
|
|
offset int // byte offset in source
|
|
toks []*Token // all the lexed tokens
|
|
pos Position // current position
|
|
}
|
|
|
|
// err appends an fmt.Printf style error into l.diags for the given token.
|
|
func (l *lexer) err(tok *Token, msg string, args ...interface{}) {
|
|
rng := Range{}
|
|
if tok != nil {
|
|
rng = tok.Range
|
|
}
|
|
l.diags = append(l.diags, Diagnostic{
|
|
Range: rng,
|
|
Severity: SeverityError,
|
|
Message: fmt.Sprintf(msg, args...),
|
|
})
|
|
}
|
|
|
|
// next returns the next rune, or io.EOF if the last rune has already been
|
|
// consumed.
|
|
func (l *lexer) next() rune {
|
|
if l.offset >= len(l.source) {
|
|
l.e = io.EOF
|
|
return 0
|
|
}
|
|
r, n := utf8.DecodeRuneInString(l.source[l.offset:])
|
|
l.offset += n
|
|
if n == 0 {
|
|
l.e = io.EOF
|
|
return 0
|
|
}
|
|
if r == '\n' {
|
|
l.pos.Line++
|
|
l.pos.Column = 1
|
|
} else {
|
|
l.pos.Column++
|
|
}
|
|
return r
|
|
}
|
|
|
|
// save returns the current lexerState.
|
|
func (l *lexer) save() lexerState {
|
|
return l.lexerState
|
|
}
|
|
|
|
// restore restores the current lexer state with s.
|
|
func (l *lexer) restore(s lexerState) {
|
|
l.lexerState = s
|
|
}
|
|
|
|
// pident processes the PIdent token at the current position.
|
|
// The lexer *must* know the next token is a PIdent before calling.
|
|
func (l *lexer) pident() {
|
|
tok := &Token{Type: PIdent, Range: Range{Start: l.pos, End: l.pos}}
|
|
if r := l.next(); r != '%' {
|
|
log.Fatalf("lexer expected '%%', got '%v'", r)
|
|
return
|
|
}
|
|
for l.e == nil {
|
|
s := l.save()
|
|
r := l.next()
|
|
if !isAlphaNumeric(r) && r != '_' {
|
|
l.restore(s)
|
|
break
|
|
}
|
|
}
|
|
tok.Range.End = l.pos
|
|
l.toks = append(l.toks, tok)
|
|
}
|
|
|
|
// numberOrIdent processes the Ident, Float or Integer token at the current
|
|
// position.
|
|
func (l *lexer) numberOrIdent() {
|
|
const Unknown Type = -1
|
|
tok := &Token{Type: Unknown, Range: Range{Start: l.pos, End: l.pos}}
|
|
loop:
|
|
for l.e == nil {
|
|
s := l.save()
|
|
r := l.next()
|
|
switch {
|
|
case r == '-', r == '+', isNumeric(r):
|
|
continue
|
|
case isAlpha(r), r == '_':
|
|
switch tok.Type {
|
|
case Unknown:
|
|
tok.Type = Ident
|
|
case Float, Integer:
|
|
l.err(tok, "invalid number")
|
|
return
|
|
}
|
|
case r == '.':
|
|
switch tok.Type {
|
|
case Unknown:
|
|
tok.Type = Float
|
|
default:
|
|
l.restore(s)
|
|
break loop
|
|
}
|
|
default:
|
|
if tok.Type == Unknown {
|
|
tok.Type = Integer
|
|
}
|
|
l.restore(s)
|
|
break loop
|
|
}
|
|
}
|
|
tok.Range.End = l.pos
|
|
l.toks = append(l.toks, tok)
|
|
}
|
|
|
|
// string processes the String token at the current position.
|
|
// The lexer *must* know the next token is a String before calling.
|
|
func (l *lexer) string() {
|
|
tok := &Token{Type: String, Range: Range{Start: l.pos, End: l.pos}}
|
|
if r := l.next(); r != '"' {
|
|
log.Fatalf("lexer expected '\"', got '%v'", r)
|
|
return
|
|
}
|
|
escape := false
|
|
for l.e == nil {
|
|
switch l.next() {
|
|
case '"':
|
|
if !escape {
|
|
tok.Range.End = l.pos
|
|
l.toks = append(l.toks, tok)
|
|
return
|
|
}
|
|
case '\\':
|
|
escape = !escape
|
|
default:
|
|
escape = false
|
|
}
|
|
}
|
|
}
|
|
|
|
// operator processes the Operator token at the current position.
|
|
// The lexer *must* know the next token is a Operator before calling.
|
|
func (l *lexer) operator() {
|
|
tok := &Token{Type: Operator, Range: Range{Start: l.pos, End: l.pos}}
|
|
for l.e == nil {
|
|
switch l.next() {
|
|
case '=', '|':
|
|
tok.Range.End = l.pos
|
|
l.toks = append(l.toks, tok)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// lineComment processes the Comment token at the current position.
|
|
// The lexer *must* know the next token is a Comment before calling.
|
|
func (l *lexer) lineComment() {
|
|
tok := &Token{Type: Comment, Range: Range{Start: l.pos, End: l.pos}}
|
|
if r := l.next(); r != ';' {
|
|
log.Fatalf("lexer expected ';', got '%v'", r)
|
|
return
|
|
}
|
|
for l.e == nil {
|
|
s := l.save()
|
|
switch l.next() {
|
|
case '\n':
|
|
l.restore(s)
|
|
tok.Range.End = l.pos
|
|
l.toks = append(l.toks, tok)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// newline processes the Newline token at the current position.
|
|
// The lexer *must* know the next token is a Newline before calling.
|
|
func (l *lexer) newline() {
|
|
tok := &Token{Type: Newline, Range: Range{Start: l.pos, End: l.pos}}
|
|
if r := l.next(); r != '\n' {
|
|
log.Fatalf("lexer expected '\n', got '%v'", r)
|
|
return
|
|
}
|
|
tok.Range.End = l.pos
|
|
l.toks = append(l.toks, tok)
|
|
}
|
|
|
|
// lex returns all the tokens and diagnostics after lexing source.
|
|
func lex(source string) ([]*Token, []Diagnostic, error) {
|
|
l := lexer{source: source, lexerState: lexerState{pos: Position{1, 1}}}
|
|
|
|
lastPos := Position{}
|
|
for l.e == nil {
|
|
// Integrity check that the parser is making progress
|
|
if l.pos == lastPos {
|
|
log.Panicf("Parsing stuck at %v", l.pos)
|
|
}
|
|
lastPos = l.pos
|
|
|
|
s := l.save()
|
|
r := l.next()
|
|
switch {
|
|
case r == '%':
|
|
l.restore(s)
|
|
l.pident()
|
|
case r == '+' || r == '-' || r == '_' || isAlphaNumeric(r):
|
|
l.restore(s)
|
|
l.numberOrIdent()
|
|
case r == '"':
|
|
l.restore(s)
|
|
l.string()
|
|
case r == '=', r == '|':
|
|
l.restore(s)
|
|
l.operator()
|
|
case r == ';':
|
|
l.restore(s)
|
|
l.lineComment()
|
|
case r == '\n':
|
|
l.restore(s)
|
|
l.newline()
|
|
}
|
|
}
|
|
if l.e != nil && l.e != io.EOF {
|
|
return nil, nil, l.e
|
|
}
|
|
return l.toks, l.diags, nil
|
|
}
|
|
|
|
func isNumeric(r rune) bool { return unicode.IsDigit(r) }
|
|
func isAlpha(r rune) bool { return unicode.IsLetter(r) }
|
|
func isAlphaNumeric(r rune) bool { return isAlpha(r) || isNumeric(r) }
|
|
|
|
type parser struct {
|
|
lines []string // all source lines
|
|
toks []*Token // all tokens
|
|
diags []Diagnostic // parser emitted diagnostics
|
|
idents map[string]*Identifier // identifiers by name
|
|
mappings map[*Token]interface{} // tokens to semantic map
|
|
extInstImports map[string]schema.OpcodeMap // extension imports by identifier
|
|
insts []*Instruction // all instructions
|
|
}
|
|
|
|
func (p *parser) parse() error {
|
|
for i := 0; i < len(p.toks); {
|
|
if p.newline(i) || p.comment(i) {
|
|
i++
|
|
continue
|
|
}
|
|
if n := p.instruction(i); n > 0 {
|
|
i += n
|
|
} else {
|
|
p.unexpected(i)
|
|
i++
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// instruction parses the instruction starting at the i'th token.
|
|
func (p *parser) instruction(i int) (n int) {
|
|
inst := &Instruction{}
|
|
|
|
switch {
|
|
case p.opcode(i) != nil:
|
|
inst.Opcode = p.opcode(i)
|
|
inst.Tokens = []*Token{p.tok(i)}
|
|
p.mappings[p.tok(i)] = inst
|
|
n++
|
|
case p.opcode(i+2) != nil: // try '%id' '='
|
|
inst.Result, inst.Opcode = p.pident(i), p.opcode(i+2)
|
|
if inst.Result == nil || p.operator(i+1) != "=" {
|
|
return 0
|
|
}
|
|
n += 3
|
|
inst.Tokens = []*Token{p.tok(i), p.tok(i + 1), p.tok(i + 2)}
|
|
p.mappings[p.tok(i+2)] = inst
|
|
default:
|
|
return
|
|
}
|
|
|
|
expectsResult := len(inst.Opcode.Operands) > 0 && IsResult(inst.Opcode.Operands[0].Kind)
|
|
operands := inst.Opcode.Operands
|
|
switch {
|
|
case inst.Result != nil && !expectsResult:
|
|
p.err(inst.Result, "'%s' does not have a result", inst.Opcode.Opname)
|
|
return
|
|
case inst.Result == nil && expectsResult:
|
|
p.err(p.tok(i), "'%s' expects a result", inst.Opcode.Opname)
|
|
return
|
|
case inst.Result != nil && expectsResult:
|
|
// Check the result is of the correct type
|
|
o := inst.Opcode.Operands[0]
|
|
p.operand(o.Name, o.Kind, i, false)
|
|
operands = operands[1:]
|
|
p.addIdentDef(inst.Result.Text(p.lines), inst, p.tok(i))
|
|
}
|
|
|
|
processOperand := func(o schema.Operand) bool {
|
|
if p.newline(i + n) {
|
|
return false
|
|
}
|
|
|
|
switch o.Quantifier {
|
|
case schema.Once:
|
|
if op, c := p.operand(o.Name, o.Kind, i+n, false); op != nil {
|
|
inst.Tokens = append(inst.Tokens, op.Tokens...)
|
|
n += c
|
|
}
|
|
case schema.ZeroOrOnce:
|
|
if op, c := p.operand(o.Name, o.Kind, i+n, true); op != nil {
|
|
inst.Tokens = append(inst.Tokens, op.Tokens...)
|
|
n += c
|
|
}
|
|
case schema.ZeroOrMany:
|
|
for !p.newline(i + n) {
|
|
if op, c := p.operand(o.Name, o.Kind, i+n, true); op != nil {
|
|
inst.Tokens = append(inst.Tokens, op.Tokens...)
|
|
n += c
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
for _, o := range operands {
|
|
if !processOperand(o) {
|
|
break
|
|
}
|
|
|
|
if inst.Opcode == schema.OpExtInst && n == 4 {
|
|
extImportTok, extNameTok := p.tok(i+n), p.tok(i+n+1)
|
|
extImport := extImportTok.Text(p.lines)
|
|
if extOpcodes, ok := p.extInstImports[extImport]; ok {
|
|
extName := extNameTok.Text(p.lines)
|
|
if extOpcode, ok := extOpcodes[extName]; ok {
|
|
n += 2 // skip ext import, ext name
|
|
for _, o := range extOpcode.Operands {
|
|
if !processOperand(o) {
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
p.err(extNameTok, "Unknown extension opcode '%s'", extName)
|
|
}
|
|
} else {
|
|
p.err(extImportTok, "Expected identifier to OpExtInstImport")
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, t := range inst.Tokens {
|
|
inst.Range.grow(t.Range)
|
|
}
|
|
|
|
p.insts = append(p.insts, inst)
|
|
|
|
if inst.Opcode == schema.OpExtInstImport && len(inst.Tokens) >= 4 {
|
|
// Instruction is a OpExtInstImport. Keep track of this.
|
|
extTok := inst.Tokens[3]
|
|
extName := strings.Trim(extTok.Text(p.lines), `"`)
|
|
extOpcodes, ok := schema.ExtOpcodes[extName]
|
|
if !ok {
|
|
p.err(extTok, "Unknown extension '%s'", extName)
|
|
}
|
|
extImport := inst.Result.Text(p.lines)
|
|
p.extInstImports[extImport] = extOpcodes
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// operand parses the operand with the name n, kind k, starting at the i'th
|
|
// token.
|
|
func (p *parser) operand(n string, k *schema.OperandKind, i int, optional bool) (*Operand, int) {
|
|
tok := p.tok(i)
|
|
if tok == nil {
|
|
return nil, 0
|
|
}
|
|
|
|
op := &Operand{
|
|
Name: n,
|
|
Kind: k,
|
|
Tokens: []*Token{tok},
|
|
}
|
|
p.mappings[tok] = op
|
|
|
|
switch k.Category {
|
|
case schema.OperandCategoryBitEnum, schema.OperandCategoryValueEnum:
|
|
s := tok.Text(p.lines)
|
|
for _, e := range k.Enumerants {
|
|
if e.Enumerant == s {
|
|
count := 1
|
|
for _, param := range e.Parameters {
|
|
p, c := p.operand(param.Name, param.Kind, i+count, false)
|
|
if p != nil {
|
|
op.Tokens = append(op.Tokens, p.Tokens...)
|
|
op.Parameters = append(op.Parameters, p)
|
|
}
|
|
count += c
|
|
}
|
|
|
|
// Handle bitfield '|' chains
|
|
if p.tok(i+count).Text(p.lines) == "|" {
|
|
count++ // '|'
|
|
p, c := p.operand(n, k, i+count, false)
|
|
if p != nil {
|
|
op.Tokens = append(op.Tokens, p.Tokens...)
|
|
op.Parameters = append(op.Parameters, p)
|
|
}
|
|
count += c
|
|
}
|
|
|
|
return op, count
|
|
}
|
|
}
|
|
if !optional {
|
|
p.err(p.tok(i), "invalid operand value '%s'", s)
|
|
}
|
|
|
|
return nil, 0
|
|
|
|
case schema.OperandCategoryID:
|
|
id := p.pident(i)
|
|
if id != nil {
|
|
p.addIdentRef(p.tok(i))
|
|
return op, 1
|
|
}
|
|
if !optional {
|
|
p.err(p.tok(i), "operand requires id, got '%s'", tok.Text(p.lines))
|
|
}
|
|
return nil, 0
|
|
|
|
case schema.OperandCategoryLiteral:
|
|
switch tok.Type {
|
|
case String, Integer, Float, Ident:
|
|
return op, 1
|
|
}
|
|
if !optional {
|
|
p.err(p.tok(i), "operand requires literal, got '%s'", tok.Text(p.lines))
|
|
}
|
|
return nil, 0
|
|
|
|
case schema.OperandCategoryComposite:
|
|
n := 1
|
|
for _, b := range k.Bases {
|
|
o, c := p.operand(b.Kind, b, i+n, optional)
|
|
if o != nil {
|
|
op.Tokens = append(op.Tokens, o.Tokens...)
|
|
}
|
|
n += c
|
|
}
|
|
return op, n
|
|
|
|
default:
|
|
p.err(p.tok(i), "OperandKind '%s' has unexpected category '%s'", k.Kind, k.Category)
|
|
return nil, 0
|
|
}
|
|
}
|
|
|
|
// tok returns the i'th token, or nil if i is out of bounds.
|
|
func (p *parser) tok(i int) *Token {
|
|
if i < 0 || i >= len(p.toks) {
|
|
return nil
|
|
}
|
|
return p.toks[i]
|
|
}
|
|
|
|
// opcode returns the schema.Opcode for the i'th token, or nil if the i'th token
|
|
// does not represent an opcode.
|
|
func (p *parser) opcode(i int) *schema.Opcode {
|
|
if tok := p.ident(i); tok != nil {
|
|
name := tok.Text(p.lines)
|
|
if inst, found := schema.Opcodes[name]; found {
|
|
return inst
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// operator returns the operator for the i'th token, or and empty string if the
|
|
// i'th token is not an operator.
|
|
func (p *parser) operator(i int) string {
|
|
if tok := p.tok(i); tok != nil && tok.Type == Operator {
|
|
return tok.Text(p.lines)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// ident returns the i'th token if it is an Ident, otherwise nil.
|
|
func (p *parser) ident(i int) *Token {
|
|
if tok := p.tok(i); tok != nil && tok.Type == Ident {
|
|
return tok
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// pident returns the i'th token if it is an PIdent, otherwise nil.
|
|
func (p *parser) pident(i int) *Token {
|
|
if tok := p.tok(i); tok != nil && tok.Type == PIdent {
|
|
return tok
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// comment returns true if the i'th token is a Comment, otherwise false.
|
|
func (p *parser) comment(i int) bool {
|
|
if tok := p.tok(i); tok != nil && tok.Type == Comment {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// newline returns true if the i'th token is a Newline, otherwise false.
|
|
func (p *parser) newline(i int) bool {
|
|
if tok := p.tok(i); tok != nil && tok.Type == Newline {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// unexpected emits an 'unexpected token error' for the i'th token.
|
|
func (p *parser) unexpected(i int) {
|
|
p.err(p.toks[i], "syntax error: unexpected '%s'", p.toks[i].Text(p.lines))
|
|
}
|
|
|
|
// addIdentDef records the token definition for the instruction inst with the
|
|
// given id.
|
|
func (p *parser) addIdentDef(id string, inst *Instruction, def *Token) {
|
|
i, existing := p.idents[id]
|
|
if !existing {
|
|
i = &Identifier{}
|
|
p.idents[id] = i
|
|
}
|
|
if i.Definition == nil {
|
|
i.Definition = inst
|
|
} else {
|
|
p.err(def, "id '%v' redeclared", id)
|
|
}
|
|
}
|
|
|
|
// addIdentRef adds a identifier reference for the token ref.
|
|
func (p *parser) addIdentRef(ref *Token) {
|
|
id := ref.Text(p.lines)
|
|
i, existing := p.idents[id]
|
|
if !existing {
|
|
i = &Identifier{}
|
|
p.idents[id] = i
|
|
}
|
|
i.References = append(i.References, ref)
|
|
}
|
|
|
|
// err appends an fmt.Printf style error into l.diags for the given token.
|
|
func (p *parser) err(tok *Token, msg string, args ...interface{}) {
|
|
rng := Range{}
|
|
if tok != nil {
|
|
rng = tok.Range
|
|
}
|
|
p.diags = append(p.diags, Diagnostic{
|
|
Range: rng,
|
|
Severity: SeverityError,
|
|
Message: fmt.Sprintf(msg, args...),
|
|
})
|
|
}
|
|
|
|
// Parse parses the SPIR-V assembly string source, returning the parse results.
|
|
func Parse(source string) (Results, error) {
|
|
toks, diags, err := lex(source)
|
|
if err != nil {
|
|
return Results{}, err
|
|
}
|
|
lines := strings.SplitAfter(source, "\n")
|
|
p := parser{
|
|
lines: lines,
|
|
toks: toks,
|
|
idents: map[string]*Identifier{},
|
|
mappings: map[*Token]interface{}{},
|
|
extInstImports: map[string]schema.OpcodeMap{},
|
|
}
|
|
if err := p.parse(); err != nil {
|
|
return Results{}, err
|
|
}
|
|
diags = append(diags, p.diags...)
|
|
return Results{
|
|
Lines: lines,
|
|
Tokens: toks,
|
|
Diagnostics: p.diags,
|
|
Identifiers: p.idents,
|
|
Mappings: p.mappings,
|
|
}, nil
|
|
}
|
|
|
|
// IsResult returns true if k is used to store the result of an instruction.
|
|
func IsResult(k *schema.OperandKind) bool {
|
|
switch k {
|
|
case schema.OperandKindIdResult, schema.OperandKindIdResultType:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Results holds the output of Parse().
|
|
type Results struct {
|
|
Lines []string
|
|
Tokens []*Token
|
|
Diagnostics []Diagnostic
|
|
Identifiers map[string]*Identifier // identifiers by name
|
|
Mappings map[*Token]interface{} // tokens to semantic map
|
|
}
|
|
|
|
// Instruction describes a single instruction instance
|
|
type Instruction struct {
|
|
Tokens []*Token // all the tokens that make up the instruction
|
|
Result *Token // the token that represents the result of the instruction, or nil
|
|
Operands []*Operand // the operands of the instruction
|
|
Range Range // the textual range of the instruction
|
|
Opcode *schema.Opcode // the opcode for the instruction
|
|
}
|
|
|
|
// Operand describes a single operand instance
|
|
type Operand struct {
|
|
Name string // name of the operand
|
|
Kind *schema.OperandKind // kind of the operand
|
|
Tokens []*Token // all the tokens that make up the operand
|
|
Parameters []*Operand // all the parameters for the operand
|
|
}
|
|
|
|
// Identifier describes a single, unique SPIR-V identifier (i.e. %32)
|
|
type Identifier struct {
|
|
Definition *Instruction // where the identifier was defined
|
|
References []*Token // all the places the identifier was referenced
|
|
}
|
|
|
|
// Severity is an enumerator of diagnostic severities
|
|
type Severity int
|
|
|
|
// Severity levels
|
|
const (
|
|
SeverityError Severity = iota
|
|
SeverityWarning
|
|
SeverityInformation
|
|
SeverityHint
|
|
)
|
|
|
|
// Diagnostic holds a single diagnostic message that was generated while
|
|
// parsing.
|
|
type Diagnostic struct {
|
|
Range Range
|
|
Severity Severity
|
|
Message string
|
|
}
|