tools: Shuffle 'intrinsic-gen' tooling

Rename to 'gen', so that more templating can be added without having a confusing name.

Can now be run with './tools/run gen'

Move the bulk of the intrinsic-gen logic to `tools/src/tint/intrinsic`

Change-Id: I750989a5aa86272c10c2ad37adffe7def11c61f2
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/97141
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
This commit is contained in:
Ben Clayton
2022-07-26 15:46:44 +00:00
committed by Dawn LUCI CQ
parent 62c58a076c
commit cde5009be3
28 changed files with 305 additions and 322 deletions

View File

@@ -0,0 +1,350 @@
// Copyright 2021 The Tint Authors.
//
// 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 ast defines AST nodes that are produced by the Tint intrinsic
// definition parser
package ast
import (
"fmt"
"strings"
"dawn.googlesource.com/dawn/tools/src/tint/intrinsic/tok"
)
// AST is the parsed syntax tree of the intrinsic definition file
type AST struct {
Enums []EnumDecl
Types []TypeDecl
Matchers []MatcherDecl
Builtins []IntrinsicDecl
Constructors []IntrinsicDecl
Converters []IntrinsicDecl
Operators []IntrinsicDecl
}
func (a AST) String() string {
sb := strings.Builder{}
for _, e := range a.Enums {
fmt.Fprintf(&sb, "%v", e)
fmt.Fprintln(&sb)
}
for _, p := range a.Types {
fmt.Fprintf(&sb, "%v", p)
fmt.Fprintln(&sb)
}
for _, m := range a.Matchers {
fmt.Fprintf(&sb, "%v", m)
fmt.Fprintln(&sb)
}
for _, b := range a.Builtins {
fmt.Fprintf(&sb, "%v", b)
fmt.Fprintln(&sb)
}
for _, o := range a.Constructors {
fmt.Fprintf(&sb, "%v", o)
fmt.Fprintln(&sb)
}
for _, o := range a.Converters {
fmt.Fprintf(&sb, "%v", o)
fmt.Fprintln(&sb)
}
for _, o := range a.Operators {
fmt.Fprintf(&sb, "%v", o)
fmt.Fprintln(&sb)
}
return sb.String()
}
// EnumDecl describes an enumerator
type EnumDecl struct {
Source tok.Source
Name string
Entries []EnumEntry
}
// Format implements the fmt.Formatter interface
func (e EnumDecl) Format(w fmt.State, verb rune) {
fmt.Fprintf(w, "enum %v {\n", e.Name)
for _, e := range e.Entries {
fmt.Fprintf(w, " %v\n", e)
}
fmt.Fprintf(w, "}\n")
}
// EnumEntry describes an entry in a enumerator
type EnumEntry struct {
Source tok.Source
Name string
Attributes Attributes
}
// Format implements the fmt.Formatter interface
func (e EnumEntry) Format(w fmt.State, verb rune) {
if len(e.Attributes) > 0 {
fmt.Fprintf(w, "%v %v", e.Attributes, e.Name)
} else {
fmt.Fprint(w, e.Name)
}
}
// MatcherDecl describes a matcher declaration
type MatcherDecl struct {
Source tok.Source
Name string
Options MatcherOptions
}
// Format implements the fmt.Formatter interface
func (m MatcherDecl) Format(w fmt.State, verb rune) {
fmt.Fprintf(w, "match %v", m.Name)
fmt.Fprintf(w, ": ")
m.Options.Format(w, verb)
}
// IntrinsicKind is either a Builtin, Operator, Constructor or Converter
type IntrinsicKind string
const (
// Builtin is a builtin function (max, fract, etc).
// Declared with 'fn'.
Builtin IntrinsicKind = "builtin"
// Operator is a unary or binary operator.
// Declared with 'op'.
Operator IntrinsicKind = "operator"
// Constructor is a type constructor function.
// Declared with 'ctor'.
Constructor IntrinsicKind = "constructor"
// Converter is a type conversion function.
// Declared with 'conv'.
Converter IntrinsicKind = "converter"
)
// IntrinsicDecl describes a builtin or operator declaration
type IntrinsicDecl struct {
Source tok.Source
Kind IntrinsicKind
Name string
Attributes Attributes
TemplateParams TemplateParams
Parameters Parameters
ReturnType *TemplatedName
}
// Format implements the fmt.Formatter interface
func (i IntrinsicDecl) Format(w fmt.State, verb rune) {
switch i.Kind {
case Builtin:
fmt.Fprintf(w, "fn ")
case Operator:
fmt.Fprintf(w, "op ")
case Constructor:
fmt.Fprintf(w, "ctor ")
case Converter:
fmt.Fprintf(w, "conv ")
}
fmt.Fprintf(w, "%v", i.Name)
i.TemplateParams.Format(w, verb)
i.Parameters.Format(w, verb)
if i.ReturnType != nil {
fmt.Fprintf(w, " -> ")
i.ReturnType.Format(w, verb)
}
}
// Parameters is a list of parameter
type Parameters []Parameter
// Format implements the fmt.Formatter interface
func (l Parameters) Format(w fmt.State, verb rune) {
fmt.Fprintf(w, "(")
for i, p := range l {
if i > 0 {
fmt.Fprintf(w, ", ")
}
p.Attributes.Format(w, verb)
p.Format(w, verb)
}
fmt.Fprintf(w, ")")
}
// Parameter describes a single parameter of a function
type Parameter struct {
Source tok.Source
Attributes Attributes
Name string // Optional
Type TemplatedName
}
// Format implements the fmt.Formatter interface
func (p Parameter) Format(w fmt.State, verb rune) {
if p.Name != "" {
fmt.Fprintf(w, "%v: ", p.Name)
}
p.Type.Format(w, verb)
}
// MatcherOptions is a list of TemplatedName
type MatcherOptions TemplatedNames
// Format implements the fmt.Formatter interface
func (o MatcherOptions) Format(w fmt.State, verb rune) {
for i, mo := range o {
if i > 0 {
fmt.Fprintf(w, " | ")
}
mo.Format(w, verb)
}
}
// TemplatedNames is a list of TemplatedName
// Example:
// a<b>, c<d, e>
type TemplatedNames []TemplatedName
// Format implements the fmt.Formatter interface
func (l TemplatedNames) Format(w fmt.State, verb rune) {
for i, n := range l {
if i > 0 {
fmt.Fprintf(w, ", ")
}
n.Format(w, verb)
}
}
// TemplatedName is an identifier with optional templated arguments
// Example:
// vec<N, T>
type TemplatedName struct {
Source tok.Source
Name string
TemplateArgs TemplatedNames
}
// Format implements the fmt.Formatter interface
func (t TemplatedName) Format(w fmt.State, verb rune) {
fmt.Fprintf(w, "%v", t.Name)
if len(t.TemplateArgs) > 0 {
fmt.Fprintf(w, "<")
t.TemplateArgs.Format(w, verb)
fmt.Fprintf(w, ">")
}
}
// TypeDecl describes a type declaration
type TypeDecl struct {
Source tok.Source
Attributes Attributes
Name string
TemplateParams TemplateParams
}
// Format implements the fmt.Formatter interface
func (p TypeDecl) Format(w fmt.State, verb rune) {
if len(p.Attributes) > 0 {
p.Attributes.Format(w, verb)
fmt.Fprintf(w, " type %v", p.Name)
}
fmt.Fprintf(w, "type %v", p.Name)
p.TemplateParams.Format(w, verb)
}
// TemplateParams is a list of TemplateParam
// Example:
// <A, B : TyB>
type TemplateParams []TemplateParam
// Format implements the fmt.Formatter interface
func (p TemplateParams) Format(w fmt.State, verb rune) {
if len(p) > 0 {
fmt.Fprintf(w, "<")
for i, tp := range p {
if i > 0 {
fmt.Fprintf(w, ", ")
}
tp.Format(w, verb)
}
fmt.Fprintf(w, ">")
}
}
// TemplateParam describes a template parameter with optional type
// Example:
// <Name>
// <Name: Type>
type TemplateParam struct {
Source tok.Source
Name string
Type TemplatedName // Optional
}
// Format implements the fmt.Formatter interface
func (t TemplateParam) Format(w fmt.State, verb rune) {
fmt.Fprintf(w, "%v", t.Name)
if t.Type.Name != "" {
fmt.Fprintf(w, " : ")
t.Type.Format(w, verb)
}
}
// Attributes is a list of Attribute
// Example:
// [[a(x), b(y)]]
type Attributes []Attribute
// Format implements the fmt.Formatter interface
func (l Attributes) Format(w fmt.State, verb rune) {
for _, d := range l {
fmt.Fprint(w, "@")
d.Format(w, verb)
fmt.Fprint(w, " ")
}
}
// Take looks up the attribute with the given name. If the attribute is found
// it is removed from the Attributes list and returned, otherwise nil is
// returned and the Attributes are not altered.
func (l *Attributes) Take(name string) *Attribute {
for i, a := range *l {
if a.Name == name {
*l = append((*l)[:i], (*l)[i+1:]...)
return &a
}
}
return nil
}
// Attribute describes a single attribute
// Example:
// @a(x)
type Attribute struct {
Source tok.Source
Name string
Values []string
}
// Format implements the fmt.Formatter interface
func (d Attribute) Format(w fmt.State, verb rune) {
fmt.Fprintf(w, "%v", d.Name)
if len(d.Values) > 0 {
fmt.Fprintf(w, "(")
for i, v := range d.Values {
if i > 0 {
fmt.Fprint(w, ", ")
}
fmt.Fprintf(w, "%v", v)
}
fmt.Fprintf(w, ")")
}
}

View File

@@ -0,0 +1,465 @@
// Copyright 2021 The Tint Authors.
//
// 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 gen holds types and helpers for generating templated code from the
/// intrinsic.def file.
///
/// Used by tools/src/cmd/gen/main.go
package gen
import (
"fmt"
"strings"
"dawn.googlesource.com/dawn/tools/src/list"
"dawn.googlesource.com/dawn/tools/src/lut"
"dawn.googlesource.com/dawn/tools/src/tint/intrinsic/sem"
)
// IntrinsicTable holds data specific to the intrinsic_table.inl.tmpl template
type IntrinsicTable struct {
// The semantic info
Sem *sem.Sem
// TMatchers are all the sem.TemplateType, sem.Type and sem.TypeMatchers.
// These are all implemented by classes deriving from tint::TypeMatcher
TMatchers []sem.Named
TMatcherIndex map[sem.Named]int // [object -> index] in TMatcher
// NMatchers are all the sem.TemplateNumber and sem.EnumMatchers.
// These are all implemented by classes deriving from tint::NumberMatcher
NMatchers []sem.Named
NMatcherIndex map[sem.Named]int // [object -> index] in NMatchers
MatcherIndices []int // kMatcherIndices table content
TemplateTypes []TemplateType // kTemplateTypes table content
TemplateNumbers []TemplateNumber // kTemplateNumbers table content
Parameters []Parameter // kParameters table content
Overloads []Overload // kOverloads table content
Builtins []Intrinsic // kBuiltins table content
UnaryOperators []Intrinsic // kUnaryOperators table content
BinaryOperators []Intrinsic // kBinaryOperators table content
ConstructorsAndConverters []Intrinsic // kConstructorsAndConverters table content
}
// TemplateType is used to create the C++ TemplateTypeInfo structure
type TemplateType struct {
// Name of the template type (e.g. 'T')
Name string
// Optional type matcher constraint.
// Either an index in Matchers::type, or -1
MatcherIndex int
}
// TemplateNumber is used to create the C++ TemplateNumberInfo structure
type TemplateNumber struct {
// Name of the template number (e.g. 'N')
Name string
// Optional type matcher constraint.
// Either an index in Matchers::type, or -1
MatcherIndex int
}
// Parameter is used to create the C++ ParameterInfo structure
type Parameter struct {
// The parameter usage (parameter name)
Usage string
// Index into IntrinsicTable.MatcherIndices, beginning the list of matchers
// required to match the parameter type. The matcher indices index
// into IntrinsicTable::TMatchers and / or IntrinsicTable::NMatchers.
// These indices are consumed by the matchers themselves.
// The first index is always a TypeMatcher.
MatcherIndicesOffset *int
}
// Overload is used to create the C++ OverloadInfo structure
type Overload struct {
// Total number of parameters for the overload
NumParameters int
// Total number of template types for the overload
NumTemplateTypes int
// Total number of template numbers for the overload
NumTemplateNumbers int
// Index to the first template type in IntrinsicTable.TemplateTypes
TemplateTypesOffset *int
// Index to the first template number in IntrinsicTable.TemplateNumbers
TemplateNumbersOffset *int
// Index to the first parameter in IntrinsicTable.Parameters
ParametersOffset *int
// Index into IntrinsicTable.MatcherIndices, beginning the list of matchers
// required to match the return type. The matcher indices index
// into IntrinsicTable::TMatchers and / or IntrinsicTable::NMatchers.
// These indices are consumed by the matchers themselves.
// The first index is always a TypeMatcher.
ReturnMatcherIndicesOffset *int
// StageUses describes the stages an overload can be used in
CanBeUsedInStage sem.StageUses
// True if the overload is marked as deprecated
IsDeprecated bool
// The kind of overload
Kind string
// The function name used to evaluate the overload at shader-creation time
ConstEvalFunction string
}
// Intrinsic is used to create the C++ IntrinsicInfo structure
type Intrinsic struct {
Name string
OverloadDescriptions []string
NumOverloads int
OverloadsOffset *int
}
// Helper for building the IntrinsicTable
type IntrinsicTableBuilder struct {
// The output of the builder
IntrinsicTable
// Lookup tables.
// These are packed (compressed) once all the entries have been added.
lut struct {
matcherIndices lut.LUT
templateTypes lut.LUT
templateNumbers lut.LUT
parameters lut.LUT
overloads lut.LUT
}
}
// Helper for building a single overload
type overloadBuilder struct {
*IntrinsicTableBuilder
// Maps TemplateParam to index in templateTypes
templateTypeIndex map[sem.TemplateParam]int
// Maps TemplateParam to index in templateNumbers
templateNumberIndex map[sem.TemplateParam]int
// Template types used by the overload
templateTypes []TemplateType
// Template numbers used by the overload
templateNumbers []TemplateNumber
// All parameters declared by the overload
parameters []Parameter
// Index into IntrinsicTable.MatcherIndices, beginning the list of matchers
// required to match the return type. The matcher indices index
// into IntrinsicTable::TMatchers and / or IntrinsicTable::NMatchers.
// These indices are consumed by the matchers themselves.
// The first index is always a TypeMatcher.
returnTypeMatcherIndicesOffset *int
}
// layoutMatchers assigns each of the TMatchers and NMatchers a unique index
// in the C++ Matchers::type and Matchers::number arrays, respectively.
func (b *IntrinsicTableBuilder) layoutMatchers(s *sem.Sem) {
// First MaxTemplateTypes of TMatchers are template types
b.TMatchers = make([]sem.Named, s.MaxTemplateTypes)
for _, m := range s.Types {
b.TMatcherIndex[m] = len(b.TMatchers)
b.TMatchers = append(b.TMatchers, m)
}
for _, m := range s.TypeMatchers {
b.TMatcherIndex[m] = len(b.TMatchers)
b.TMatchers = append(b.TMatchers, m)
}
// First MaxTemplateNumbers of NMatchers are template numbers
b.NMatchers = make([]sem.Named, s.MaxTemplateNumbers)
for _, m := range s.EnumMatchers {
b.NMatcherIndex[m] = len(b.NMatchers)
b.NMatchers = append(b.NMatchers, m)
}
}
// buildOverload constructs an Overload for a sem.Overload
func (b *IntrinsicTableBuilder) buildOverload(o *sem.Overload) (Overload, error) {
ob := overloadBuilder{
IntrinsicTableBuilder: b,
templateTypeIndex: map[sem.TemplateParam]int{},
templateNumberIndex: map[sem.TemplateParam]int{},
}
if err := ob.buildTemplateTypes(o); err != nil {
return Overload{}, err
}
if err := ob.buildTemplateNumbers(o); err != nil {
return Overload{}, err
}
if err := ob.buildParameters(o); err != nil {
return Overload{}, err
}
if err := ob.buildReturnType(o); err != nil {
return Overload{}, err
}
return Overload{
NumParameters: len(ob.parameters),
NumTemplateTypes: len(ob.templateTypes),
NumTemplateNumbers: len(ob.templateNumbers),
TemplateTypesOffset: b.lut.templateTypes.Add(ob.templateTypes),
TemplateNumbersOffset: b.lut.templateNumbers.Add(ob.templateNumbers),
ParametersOffset: b.lut.parameters.Add(ob.parameters),
ReturnMatcherIndicesOffset: ob.returnTypeMatcherIndicesOffset,
CanBeUsedInStage: o.CanBeUsedInStage,
IsDeprecated: o.IsDeprecated,
Kind: string(o.Decl.Kind),
ConstEvalFunction: o.ConstEvalFunction,
}, nil
}
// buildTemplateTypes constructs the TemplateTypes used by the overload, populating
// b.templateTypes
func (b *overloadBuilder) buildTemplateTypes(o *sem.Overload) error {
b.templateTypes = make([]TemplateType, len(o.TemplateTypes))
for i, t := range o.TemplateTypes {
b.templateTypeIndex[t] = i
matcherIndex := -1
if t.Type != nil {
var err error
matcherIndex, err = b.matcherIndex(t.Type)
if err != nil {
return err
}
}
b.templateTypes[i] = TemplateType{
Name: t.Name,
MatcherIndex: matcherIndex,
}
}
return nil
}
// buildTemplateNumbers constructs the TemplateNumbers used by the overload, populating
// b.templateNumbers
func (b *overloadBuilder) buildTemplateNumbers(o *sem.Overload) error {
b.templateNumbers = make([]TemplateNumber, len(o.TemplateNumbers))
for i, t := range o.TemplateNumbers {
b.templateNumberIndex[t] = i
matcherIndex := -1
if e, ok := t.(*sem.TemplateEnumParam); ok && e.Matcher != nil {
var err error
matcherIndex, err = b.matcherIndex(e.Matcher)
if err != nil {
return err
}
}
b.templateNumbers[i] = TemplateNumber{
Name: t.GetName(),
MatcherIndex: matcherIndex,
}
}
return nil
}
// buildParameters constructs the Parameters used by the overload, populating
// b.parameters
func (b *overloadBuilder) buildParameters(o *sem.Overload) error {
b.parameters = make([]Parameter, len(o.Parameters))
for i, p := range o.Parameters {
indices, err := b.collectMatcherIndices(p.Type)
if err != nil {
return err
}
b.parameters[i] = Parameter{
Usage: p.Name,
MatcherIndicesOffset: b.lut.matcherIndices.Add(indices),
}
}
return nil
}
// buildParameters calculates the matcher indices required to match the
// overload's return type (if the overload has a return value), possibly
// populating b.returnTypeMatcherIndicesOffset
func (b *overloadBuilder) buildReturnType(o *sem.Overload) error {
if o.ReturnType != nil {
indices, err := b.collectMatcherIndices(*o.ReturnType)
if err != nil {
return err
}
b.returnTypeMatcherIndicesOffset = b.lut.matcherIndices.Add(indices)
}
return nil
}
// matcherIndex returns the index of TMatcher or NMatcher in
// IntrinsicTable.TMatcher or IntrinsicTable.NMatcher, respectively.
func (b *overloadBuilder) matcherIndex(n sem.Named) (int, error) {
switch n := n.(type) {
case *sem.Type, *sem.TypeMatcher:
if i, ok := b.TMatcherIndex[n]; ok {
return i, nil
}
return 0, fmt.Errorf("matcherIndex missing entry for %v %T", n.GetName(), n)
case *sem.TemplateTypeParam:
if i, ok := b.templateTypeIndex[n]; ok {
return i, nil
}
return 0, fmt.Errorf("templateTypeIndex missing entry for %v %T", n.Name, n)
case *sem.EnumMatcher:
if i, ok := b.NMatcherIndex[n]; ok {
return i, nil
}
return 0, fmt.Errorf("matcherIndex missing entry for %v %T", n.GetName(), n)
case *sem.TemplateEnumParam:
if i, ok := b.templateNumberIndex[n]; ok {
return i, nil
}
return 0, fmt.Errorf("templateNumberIndex missing entry for %v %T", n, n)
case *sem.TemplateNumberParam:
if i, ok := b.templateNumberIndex[n]; ok {
return i, nil
}
return 0, fmt.Errorf("templateNumberIndex missing entry for %v %T", n, n)
default:
return 0, fmt.Errorf("overload.matcherIndex() does not handle %v %T", n, n)
}
}
// collectMatcherIndices returns the full list of matcher indices required to
// match the fully-qualified-name. For names that have do not have templated
// arguments, collectMatcherIndices() will return a single TMatcher index.
// For names that do have templated arguments, collectMatcherIndices() returns
// a list of type matcher indices, starting with the target of the fully
// qualified name, then followed by each of the template arguments from left to
// right. Note that template arguments may themselves have template arguments,
// and so collectMatcherIndices() may call itself.
// The order of returned matcher indices is always the order of the fully
// qualified name as read from left to right.
// For example, calling collectMatcherIndices() for the fully qualified name:
// A<B<C, D>, E<F, G<H>, I>
// Would return the matcher indices:
// A, B, C, D, E, F, G, H, I
func (b *overloadBuilder) collectMatcherIndices(fqn sem.FullyQualifiedName) ([]int, error) {
idx, err := b.matcherIndex(fqn.Target)
if err != nil {
return nil, err
}
out := []int{idx}
for _, arg := range fqn.TemplateArguments {
indices, err := b.collectMatcherIndices(arg.(sem.FullyQualifiedName))
if err != nil {
return nil, err
}
out = append(out, indices...)
}
return out, nil
}
// BuildIntrinsicTable builds the IntrinsicTable from the semantic info
func BuildIntrinsicTable(s *sem.Sem) (*IntrinsicTable, error) {
b := IntrinsicTableBuilder{
IntrinsicTable: IntrinsicTable{
Sem: s,
TMatcherIndex: map[sem.Named]int{},
NMatcherIndex: map[sem.Named]int{},
},
}
b.lut.matcherIndices = lut.New(list.Wrap(&b.MatcherIndices))
b.lut.templateTypes = lut.New(list.Wrap(&b.TemplateTypes))
b.lut.templateNumbers = lut.New(list.Wrap(&b.TemplateNumbers))
b.lut.parameters = lut.New(list.Wrap(&b.Parameters))
b.lut.overloads = lut.New(list.Wrap(&b.Overloads))
b.layoutMatchers(s)
for _, intrinsics := range []struct {
in []*sem.Intrinsic
out *[]Intrinsic
}{
{s.Builtins, &b.Builtins},
{s.UnaryOperators, &b.UnaryOperators},
{s.BinaryOperators, &b.BinaryOperators},
{s.ConstructorsAndConverters, &b.ConstructorsAndConverters},
} {
out := make([]Intrinsic, len(intrinsics.in))
for i, f := range intrinsics.in {
overloads := make([]Overload, len(f.Overloads))
overloadDescriptions := make([]string, len(f.Overloads))
for i, o := range f.Overloads {
overloadDescriptions[i] = fmt.Sprint(o.Decl)
var err error
if overloads[i], err = b.buildOverload(o); err != nil {
return nil, err
}
}
out[i] = Intrinsic{
Name: f.Name,
OverloadDescriptions: overloadDescriptions,
NumOverloads: len(overloads),
OverloadsOffset: b.lut.overloads.Add(overloads),
}
}
*intrinsics.out = out
}
b.lut.matcherIndices.Compact()
b.lut.templateTypes.Compact()
b.lut.templateNumbers.Compact()
b.lut.parameters.Compact()
b.lut.overloads.Compact()
return &b.IntrinsicTable, nil
}
// SplitDisplayName splits displayName into parts, where text wrapped in {}
// braces are not quoted and the rest is quoted. This is used to help process
// the string value of the [[display()]] decoration. For example:
// SplitDisplayName("vec{N}<{T}>")
// would return the strings:
// [`"vec"`, `N`, `"<"`, `T`, `">"`]
func SplitDisplayName(displayName string) []string {
parts := []string{}
pending := strings.Builder{}
for _, r := range displayName {
switch r {
case '{':
if pending.Len() > 0 {
parts = append(parts, fmt.Sprintf(`"%v"`, pending.String()))
pending.Reset()
}
case '}':
if pending.Len() > 0 {
parts = append(parts, pending.String())
pending.Reset()
}
default:
pending.WriteRune(r)
}
}
if pending.Len() > 0 {
parts = append(parts, fmt.Sprintf(`"%v"`, pending.String()))
}
return parts
}
// IsAbstract returns true if the FullyQualifiedName refers to an abstract
// numeric type
func IsAbstract(fqn sem.FullyQualifiedName) bool {
switch fqn.Target.GetName() {
case "ia", "fa":
return true
case "vec":
return IsAbstract(fqn.TemplateArguments[1].(sem.FullyQualifiedName))
case "mat":
return IsAbstract(fqn.TemplateArguments[2].(sem.FullyQualifiedName))
}
return false
}
// IsDeclarable returns false if the FullyQualifiedName refers to an abstract
// numeric type, or if it starts with a leading underscore.
func IsDeclarable(fqn sem.FullyQualifiedName) bool {
return !IsAbstract(fqn) && !strings.HasPrefix(fqn.Target.GetName(), "_")
}

View File

@@ -0,0 +1,383 @@
// Copyright 2021 The Tint Authors.
//
// 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 gen
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"strings"
"dawn.googlesource.com/dawn/tools/src/fileutils"
"dawn.googlesource.com/dawn/tools/src/tint/intrinsic/sem"
)
// Permuter generates permutations of intrinsic overloads
type Permuter struct {
sem *sem.Sem
allTypes []sem.FullyQualifiedName
}
// NewPermuter returns a new initialized Permuter
func NewPermuter(s *sem.Sem) (*Permuter, error) {
// allTypes are the list of FQNs that are used for unconstrained types
allTypes := []sem.FullyQualifiedName{}
for _, ty := range s.Types {
if len(ty.TemplateParams) > 0 {
// Ignore aggregate types for now.
// TODO(bclayton): Support a limited set of aggregate types
continue
}
allTypes = append(allTypes, sem.FullyQualifiedName{Target: ty})
}
return &Permuter{
sem: s,
allTypes: allTypes,
}, nil
}
// Permutation describes a single permutation of an overload
type Permutation struct {
sem.Overload // The permutated overload signature
Desc string // Description of the overload
Hash string // Hash of the overload
}
// Permute generates a set of permutations for the given intrinsic overload
func (p *Permuter) Permute(overload *sem.Overload) ([]Permutation, error) {
state := permutationState{
Permuter: p,
templateTypes: map[sem.TemplateParam]sem.FullyQualifiedName{},
templateNumbers: map[sem.TemplateParam]interface{}{},
parameters: map[int]sem.FullyQualifiedName{},
}
out := []Permutation{}
// Map of hash to permutation description. Used to detect collisions.
hashes := map[string]string{}
// permutate appends a permutation to out.
// permutate may be chained to generate N-dimensional permutations.
permutate := func() error {
o := sem.Overload{
Decl: overload.Decl,
Intrinsic: overload.Intrinsic,
CanBeUsedInStage: overload.CanBeUsedInStage,
}
for i, p := range overload.Parameters {
ty := state.parameters[i]
if !validate(ty, &o.CanBeUsedInStage) {
return nil
}
o.Parameters = append(o.Parameters, sem.Parameter{
Name: p.Name,
Type: ty,
IsConst: p.IsConst,
})
}
if overload.ReturnType != nil {
retTys, err := state.permutateFQN(*overload.ReturnType)
if err != nil {
return fmt.Errorf("while permutating return type: %w", err)
}
if len(retTys) != 1 {
return fmt.Errorf("result type not pinned")
}
o.ReturnType = &retTys[0]
}
desc := fmt.Sprint(o)
hash := sha256.Sum256([]byte(desc))
const hashLength = 6
shortHash := hex.EncodeToString(hash[:])[:hashLength]
out = append(out, Permutation{
Overload: o,
Desc: desc,
Hash: shortHash,
})
// Check for hash collisions
if existing, collision := hashes[shortHash]; collision {
return fmt.Errorf("hash '%v' collision between %v and %v\nIncrease hashLength in %v",
shortHash, existing, desc, fileutils.GoSourcePath())
}
hashes[shortHash] = desc
return nil
}
for i, param := range overload.Parameters {
i, param := i, param // Capture iterator values for anonymous function
next := permutate // Permutation chaining
permutate = func() error {
permutations, err := state.permutateFQN(param.Type)
if err != nil {
return fmt.Errorf("while processing parameter %v: %w", i, err)
}
if len(permutations) == 0 {
return fmt.Errorf("parameter %v has no permutations", i)
}
for _, fqn := range permutations {
state.parameters[i] = fqn
if err := next(); err != nil {
return err
}
}
return nil
}
}
for _, t := range overload.TemplateParams {
next := permutate // Permutation chaining
switch t := t.(type) {
case *sem.TemplateTypeParam:
types := p.allTypes
if t.Type != nil {
var err error
types, err = state.permutateFQN(sem.FullyQualifiedName{Target: t.Type})
if err != nil {
return nil, fmt.Errorf("while permutating template types: %w", err)
}
}
if len(types) == 0 {
return nil, fmt.Errorf("template type %v has no permutations", t.Name)
}
permutate = func() error {
for _, ty := range types {
state.templateTypes[t] = ty
if err := next(); err != nil {
return err
}
}
return nil
}
case *sem.TemplateEnumParam:
var permutations []sem.FullyQualifiedName
var err error
if t.Matcher != nil {
permutations, err = state.permutateFQN(sem.FullyQualifiedName{Target: t.Matcher})
} else {
permutations, err = state.permutateFQN(sem.FullyQualifiedName{Target: t.Enum})
}
if err != nil {
return nil, fmt.Errorf("while permutating template numbers: %w", err)
}
if len(permutations) == 0 {
return nil, fmt.Errorf("template type %v has no permutations", t.Name)
}
permutate = func() error {
for _, n := range permutations {
state.templateNumbers[t] = n
if err := next(); err != nil {
return err
}
}
return nil
}
case *sem.TemplateNumberParam:
// Currently all open numbers are used for vector / matrices
permutations := []int{2, 3, 4}
permutate = func() error {
for _, n := range permutations {
state.templateNumbers[t] = n
if err := next(); err != nil {
return err
}
}
return nil
}
}
}
if err := permutate(); err != nil {
return nil, fmt.Errorf("%v %v %w\nState: %v", overload.Decl.Source, overload.Decl, err, state)
}
return out, nil
}
type permutationState struct {
*Permuter
templateTypes map[sem.TemplateParam]sem.FullyQualifiedName
templateNumbers map[sem.TemplateParam]interface{}
parameters map[int]sem.FullyQualifiedName
}
func (s permutationState) String() string {
sb := &strings.Builder{}
sb.WriteString("Template types:\n")
for ct, ty := range s.templateTypes {
fmt.Fprintf(sb, " %v: %v\n", ct.GetName(), ty)
}
sb.WriteString("Template numbers:\n")
for cn, v := range s.templateNumbers {
fmt.Fprintf(sb, " %v: %v\n", cn.GetName(), v)
}
return sb.String()
}
func (s *permutationState) permutateFQN(in sem.FullyQualifiedName) ([]sem.FullyQualifiedName, error) {
args := append([]interface{}{}, in.TemplateArguments...)
out := []sem.FullyQualifiedName{}
// permutate appends a permutation to out.
// permutate may be chained to generate N-dimensional permutations.
var permutate func() error
switch target := in.Target.(type) {
case *sem.Type:
permutate = func() error {
out = append(out, sem.FullyQualifiedName{Target: in.Target, TemplateArguments: args})
args = append([]interface{}{}, in.TemplateArguments...)
return nil
}
case sem.TemplateParam:
if ty, ok := s.templateTypes[target]; ok {
permutate = func() error {
out = append(out, ty)
return nil
}
} else {
return nil, fmt.Errorf("'%v' was not found in templateTypes", target.GetName())
}
case *sem.TypeMatcher:
permutate = func() error {
for _, ty := range target.Types {
out = append(out, sem.FullyQualifiedName{Target: ty})
}
return nil
}
case *sem.EnumMatcher:
permutate = func() error {
for _, o := range target.Options {
if !o.IsInternal {
out = append(out, sem.FullyQualifiedName{Target: o})
}
}
return nil
}
case *sem.Enum:
permutate = func() error {
for _, e := range target.Entries {
if !e.IsInternal {
out = append(out, sem.FullyQualifiedName{Target: e})
}
}
return nil
}
default:
return nil, fmt.Errorf("unhandled target type: %T", in.Target)
}
for i, arg := range in.TemplateArguments {
i := i // Capture iterator value for anonymous functions
next := permutate // Permutation chaining
switch arg := arg.(type) {
case sem.FullyQualifiedName:
switch target := arg.Target.(type) {
case sem.TemplateParam:
if ty, ok := s.templateTypes[target]; ok {
args[i] = ty
} else if num, ok := s.templateNumbers[target]; ok {
args[i] = num
} else {
return nil, fmt.Errorf("'%v' was not found in templateTypes or templateNumbers", target.GetName())
}
default:
perms, err := s.permutateFQN(arg)
if err != nil {
return nil, fmt.Errorf("while processing template argument %v: %v", i, err)
}
if len(perms) == 0 {
return nil, fmt.Errorf("template argument %v has no permutations", i)
}
permutate = func() error {
for _, f := range perms {
args[i] = f
if err := next(); err != nil {
return err
}
}
return nil
}
}
default:
return nil, fmt.Errorf("permutateFQN() unhandled template argument type: %T", arg)
}
}
if err := permutate(); err != nil {
return nil, fmt.Errorf("while processing fully qualified name '%v': %w", in.Target.GetName(), err)
}
return out, nil
}
func validate(fqn sem.FullyQualifiedName, uses *sem.StageUses) bool {
if strings.HasPrefix(fqn.Target.GetName(), "_") {
return false // Builtin, untypeable return type
}
switch fqn.Target.GetName() {
case "array":
elTy := fqn.TemplateArguments[0].(sem.FullyQualifiedName)
elTyName := elTy.Target.GetName()
switch {
case elTyName == "bool" ||
strings.Contains(elTyName, "sampler"),
strings.Contains(elTyName, "texture"):
return false // Not storable
case IsAbstract(elTy):
return false // Abstract types are not typeable nor supported by arrays
}
case "ptr":
// https://gpuweb.github.io/gpuweb/wgsl/#storage-class
access := fqn.TemplateArguments[2].(sem.FullyQualifiedName).Target.(*sem.EnumEntry).Name
storageClass := fqn.TemplateArguments[0].(sem.FullyQualifiedName).Target.(*sem.EnumEntry).Name
switch storageClass {
case "function", "private":
if access != "read_write" {
return false
}
case "workgroup":
uses.Vertex = false
uses.Fragment = false
if access != "read_write" {
return false
}
case "uniform":
if access != "read" {
return false
}
case "storage":
if access != "read_write" && access != "read" {
return false
}
case "handle":
if access != "read" {
return false
}
default:
return false
}
}
for _, arg := range fqn.TemplateArguments {
if argFQN, ok := arg.(sem.FullyQualifiedName); ok {
if !validate(argFQN, uses) {
return false
}
}
}
return true
}

View File

@@ -0,0 +1,223 @@
// Copyright 2021 The Tint Authors.
//
// 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 lexer provides a basic lexer for the Tint intrinsic definition
// language
package lexer
import (
"fmt"
"unicode"
"dawn.googlesource.com/dawn/tools/src/tint/intrinsic/tok"
)
// Lex produces a list of tokens for the given source code
func Lex(src []rune, filepath string) ([]tok.Token, error) {
l := lexer{
tok.Location{Line: 1, Column: 1, Rune: 0, Filepath: filepath},
src,
[]tok.Token{},
}
if err := l.lex(); err != nil {
return nil, err
}
return l.tokens, nil
}
type lexer struct {
loc tok.Location
runes []rune
tokens []tok.Token
}
// lex() lexes the source, populating l.tokens
func (l *lexer) lex() error {
for {
switch l.peek(0) {
case 0:
return nil
case ' ', '\t':
l.next()
case '\n':
l.next()
case '@':
l.tok(1, tok.Attr)
case '(':
l.tok(1, tok.Lparen)
case ')':
l.tok(1, tok.Rparen)
case '{':
l.tok(1, tok.Lbrace)
case '}':
l.tok(1, tok.Rbrace)
case ':':
l.tok(1, tok.Colon)
case ',':
l.tok(1, tok.Comma)
case '*':
l.tok(1, tok.Star)
case '+':
l.tok(1, tok.Plus)
case '%':
l.tok(1, tok.Modulo)
case '^':
l.tok(1, tok.Xor)
case '~':
l.tok(1, tok.Complement)
case '"':
start := l.loc
l.next() // Skip opening quote
n := l.count(toFirst('\n', '"'))
if l.peek(n) != '"' {
return fmt.Errorf("%v unterminated string", start)
}
l.tok(n, tok.String)
l.next() // Skip closing quote
default:
switch {
case l.peek(0) == '/' && l.peek(1) == '/':
l.skip(l.count(toFirst('\n')))
l.next() // Consume newline
case l.match("/", tok.Divide):
case l.match("->", tok.Arrow):
case l.match("-", tok.Minus):
case l.match("fn", tok.Function):
case l.match("op", tok.Operator):
case l.match("enum", tok.Enum):
case l.match("type", tok.Type):
case l.match("ctor", tok.Constructor):
case l.match("conv", tok.Converter):
case l.match("match", tok.Match):
case unicode.IsLetter(l.peek(0)) || l.peek(0) == '_':
l.tok(l.count(alphaNumericOrUnderscore), tok.Identifier)
case unicode.IsNumber(l.peek(0)):
l.tok(l.count(unicode.IsNumber), tok.Integer)
case l.match("&&", tok.AndAnd):
case l.match("&", tok.And):
case l.match("||", tok.OrOr):
case l.match("|", tok.Or):
case l.match("!=", tok.NotEqual):
case l.match("!", tok.Not):
case l.match("==", tok.Equal):
case l.match("=", tok.Assign):
case l.match("<<", tok.Shl):
case l.match("<=", tok.Le):
case l.match("<", tok.Lt):
case l.match(">=", tok.Ge):
case l.match(">>", tok.Shr):
case l.match(">", tok.Gt):
default:
return fmt.Errorf("%v: unexpected '%v'", l.loc, string(l.runes[0]))
}
}
}
}
// next() consumes and returns the next rune in the source, or 0 if reached EOF
func (l *lexer) next() rune {
if len(l.runes) > 0 {
r := l.runes[0]
l.runes = l.runes[1:]
l.loc.Rune++
if r == '\n' {
l.loc.Line++
l.loc.Column = 1
} else {
l.loc.Column++
}
return r
}
return 0
}
// skip() consumes the next `n` runes in the source
func (l *lexer) skip(n int) {
for i := 0; i < n; i++ {
l.next()
}
}
// peek() returns the rune `i` runes ahead of the current position
func (l *lexer) peek(i int) rune {
if i >= len(l.runes) {
return 0
}
return l.runes[i]
}
// predicate is a function that can be passed to count()
type predicate func(r rune) bool
// count() returns the number of sequential runes from the current position that
// match the predicate `p`
func (l *lexer) count(p predicate) int {
for i := 0; i < len(l.runes); i++ {
if !p(l.peek(i)) {
return i
}
}
return len(l.runes)
}
// tok() appends a new token of kind `k` using the next `n` runes.
// The next `n` runes are consumed by tok().
func (l *lexer) tok(n int, k tok.Kind) {
start := l.loc
runes := l.runes[:n]
l.skip(n)
end := l.loc
src := tok.Source{S: start, E: end}
l.tokens = append(l.tokens, tok.Token{Kind: k, Source: src, Runes: runes})
}
// match() checks whether the next runes are equal to `s`. If they are, then
// these runes are used to append a new token of kind `k`, and match() returns
// true. If the next runes are not equal to `s` then false is returned, and no
// runes are consumed.
func (l *lexer) match(s string, kind tok.Kind) bool {
runes := []rune(s)
if len(l.runes) < len(runes) {
return false
}
for i, r := range runes {
if l.runes[i] != r {
return false
}
}
l.tok(len(runes), kind)
return true
}
// toFirst() returns a predicate that returns true if the rune is not in `runes`
// toFirst() is intended to be used with count(), so `count(toFirst('x'))` will
// count up to, but not including the number of consecutive runes that are not
// 'x'.
func toFirst(runes ...rune) predicate {
return func(r rune) bool {
for _, t := range runes {
if t == r {
return false
}
}
return true
}
}
// alphaNumericOrUnderscore() returns true if the rune `r` is a number, letter
// or underscore.
func alphaNumericOrUnderscore(r rune) bool {
return r == '_' || unicode.IsLetter(r) || unicode.IsNumber(r)
}

View File

@@ -0,0 +1,202 @@
// Copyright 2021 The Tint Authors.
//
// 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 lexer_test
import (
"fmt"
"testing"
"dawn.googlesource.com/dawn/tools/src/tint/intrinsic/lexer"
"dawn.googlesource.com/dawn/tools/src/tint/intrinsic/tok"
)
func TestLexTokens(t *testing.T) {
type test struct {
src string
expect tok.Token
}
filepath := "test.txt"
loc := func(l, c, r int) tok.Location {
return tok.Location{Line: l, Column: c, Rune: r, Filepath: filepath}
}
for _, test := range []test{
{"ident", tok.Token{Kind: tok.Identifier, Runes: []rune("ident"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 6, 5),
}}},
{"ident_123", tok.Token{Kind: tok.Identifier, Runes: []rune("ident_123"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 10, 9),
}}},
{"_ident_", tok.Token{Kind: tok.Identifier, Runes: []rune("_ident_"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 8, 7),
}}},
{"123456789", tok.Token{Kind: tok.Integer, Runes: []rune("123456789"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 10, 9),
}}},
{"match", tok.Token{Kind: tok.Match, Runes: []rune("match"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 6, 5),
}}},
{"fn", tok.Token{Kind: tok.Function, Runes: []rune("fn"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 3, 2),
}}},
{"op", tok.Token{Kind: tok.Operator, Runes: []rune("op"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 3, 2),
}}},
{"type", tok.Token{Kind: tok.Type, Runes: []rune("type"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 5, 4),
}}},
{"ctor", tok.Token{Kind: tok.Constructor, Runes: []rune("ctor"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 5, 4),
}}},
{"conv", tok.Token{Kind: tok.Converter, Runes: []rune("conv"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 5, 4),
}}},
{"enum", tok.Token{Kind: tok.Enum, Runes: []rune("enum"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 5, 4),
}}},
{":", tok.Token{Kind: tok.Colon, Runes: []rune(":"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 2, 1),
}}},
{",", tok.Token{Kind: tok.Comma, Runes: []rune(","), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 2, 1),
}}},
{"<", tok.Token{Kind: tok.Lt, Runes: []rune("<"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 2, 1),
}}},
{">", tok.Token{Kind: tok.Gt, Runes: []rune(">"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 2, 1),
}}},
{"{", tok.Token{Kind: tok.Lbrace, Runes: []rune("{"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 2, 1),
}}},
{"}", tok.Token{Kind: tok.Rbrace, Runes: []rune("}"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 2, 1),
}}},
{"&&", tok.Token{Kind: tok.AndAnd, Runes: []rune("&&"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 3, 2),
}}},
{"&", tok.Token{Kind: tok.And, Runes: []rune("&"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 2, 1),
}}},
{"||", tok.Token{Kind: tok.OrOr, Runes: []rune("||"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 3, 2),
}}},
{"|", tok.Token{Kind: tok.Or, Runes: []rune("|"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 2, 1),
}}},
{"!", tok.Token{Kind: tok.Not, Runes: []rune("!"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 2, 1),
}}},
{"!=", tok.Token{Kind: tok.NotEqual, Runes: []rune("!="), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 3, 2),
}}},
{"==", tok.Token{Kind: tok.Equal, Runes: []rune("=="), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 3, 2),
}}},
{"=", tok.Token{Kind: tok.Assign, Runes: []rune("="), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 2, 1),
}}},
{"<<", tok.Token{Kind: tok.Shl, Runes: []rune("<<"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 3, 2),
}}},
{"<=", tok.Token{Kind: tok.Le, Runes: []rune("<="), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 3, 2),
}}},
{"<", tok.Token{Kind: tok.Lt, Runes: []rune("<"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 2, 1),
}}},
{">=", tok.Token{Kind: tok.Ge, Runes: []rune(">="), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 3, 2),
}}},
{">>", tok.Token{Kind: tok.Shr, Runes: []rune(">>"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 3, 2),
}}},
{">", tok.Token{Kind: tok.Gt, Runes: []rune(">"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 2, 1),
}}},
{"@", tok.Token{Kind: tok.Attr, Runes: []rune("@"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 2, 1),
}}},
{"(", tok.Token{Kind: tok.Lparen, Runes: []rune("("), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 2, 1),
}}},
{")", tok.Token{Kind: tok.Rparen, Runes: []rune(")"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 2, 1),
}}},
{"|", tok.Token{Kind: tok.Or, Runes: []rune("|"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 2, 1),
}}},
{"*", tok.Token{Kind: tok.Star, Runes: []rune("*"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 2, 1),
}}},
{"->", tok.Token{Kind: tok.Arrow, Runes: []rune("->"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 3, 2),
}}},
{"x // y ", tok.Token{Kind: tok.Identifier, Runes: []rune("x"), Source: tok.Source{
S: loc(1, 1, 0), E: loc(1, 2, 1),
}}},
{`"abc"`, tok.Token{Kind: tok.String, Runes: []rune("abc"), Source: tok.Source{
S: loc(1, 2, 1), E: loc(1, 5, 4),
}}},
{`
//
ident
`, tok.Token{Kind: tok.Identifier, Runes: []rune("ident"), Source: tok.Source{
S: loc(3, 4, 10), E: loc(3, 9, 15),
}}},
} {
got, err := lexer.Lex([]rune(test.src), filepath)
name := fmt.Sprintf(`Lex("%v")`, test.src)
switch {
case err != nil:
t.Errorf("%v returned error: %v", name, err)
case len(got) != 1:
t.Errorf("%v returned %d tokens: %v", name, len(got), got)
case got[0].Kind != test.expect.Kind:
t.Errorf(`%v returned unexpected token kind: got "%+v", expected "%+v"`, name, got[0], test.expect)
case string(got[0].Runes) != string(test.expect.Runes):
t.Errorf(`%v returned unexpected token runes: got "%+v", expected "%+v"`, name, string(got[0].Runes), string(test.expect.Runes))
case got[0].Source != test.expect.Source:
t.Errorf(`%v returned unexpected token source: got %+v, expected %+v`, name, got[0].Source, test.expect.Source)
}
}
}
func TestErrors(t *testing.T) {
type test struct {
src string
expect string
}
for _, test := range []test{
{" \"abc", "test.txt:1:2 unterminated string"},
{" \"abc\n", "test.txt:1:2 unterminated string"},
{"£", "test.txt:1:1: unexpected '£'"},
} {
got, err := lexer.Lex([]rune(test.src), "test.txt")
gotErr := "<nil>"
if err != nil {
gotErr = err.Error()
}
if test.expect != gotErr {
t.Errorf(`Lex() returned error "%+v", expected error "%+v"`, gotErr, test.expect)
}
if got != nil {
t.Errorf("Lex() returned non-nil for error")
}
}
}

View File

@@ -0,0 +1,376 @@
// Copyright 2021 The Tint Authors.
//
// 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 provides a basic parser for the Tint builtin definition
// language
package parser
import (
"fmt"
"dawn.googlesource.com/dawn/tools/src/tint/intrinsic/ast"
"dawn.googlesource.com/dawn/tools/src/tint/intrinsic/lexer"
"dawn.googlesource.com/dawn/tools/src/tint/intrinsic/tok"
)
// Parse produces a list of tokens for the given source code
func Parse(source, filepath string) (*ast.AST, error) {
runes := []rune(source)
tokens, err := lexer.Lex(runes, filepath)
if err != nil {
return nil, err
}
p := parser{tokens: tokens}
return p.parse()
}
type parser struct {
tokens []tok.Token
err error
}
func (p *parser) parse() (*ast.AST, error) {
out := ast.AST{}
var attributes ast.Attributes
for p.err == nil {
t := p.peek(0)
if t == nil {
break
}
switch t.Kind {
case tok.Attr:
attributes = append(attributes, p.attributes()...)
case tok.Enum:
if len(attributes) > 0 {
p.err = fmt.Errorf("%v unexpected attribute", attributes[0].Source)
}
out.Enums = append(out.Enums, p.enumDecl())
case tok.Match:
if len(attributes) > 0 {
p.err = fmt.Errorf("%v unexpected attribute", attributes[0].Source)
}
out.Matchers = append(out.Matchers, p.matcherDecl())
case tok.Type:
out.Types = append(out.Types, p.typeDecl(attributes))
attributes = nil
case tok.Function:
out.Builtins = append(out.Builtins, p.builtinDecl(attributes))
attributes = nil
case tok.Operator:
out.Operators = append(out.Operators, p.operatorDecl(attributes))
attributes = nil
case tok.Constructor:
out.Constructors = append(out.Constructors, p.constructorDecl(attributes))
attributes = nil
case tok.Converter:
out.Converters = append(out.Converters, p.converterDecl(attributes))
attributes = nil
default:
p.err = fmt.Errorf("%v unexpected token '%v'", t.Source, t.Kind)
}
if p.err != nil {
return nil, p.err
}
}
return &out, nil
}
func (p *parser) enumDecl() ast.EnumDecl {
p.expect(tok.Enum, "enum declaration")
name := p.expect(tok.Identifier, "enum name")
e := ast.EnumDecl{Source: name.Source, Name: string(name.Runes)}
p.expect(tok.Lbrace, "enum declaration")
for p.err == nil && p.match(tok.Rbrace) == nil {
e.Entries = append(e.Entries, p.enumEntry())
}
return e
}
func (p *parser) enumEntry() ast.EnumEntry {
decos := p.attributes()
name := p.expect(tok.Identifier, "enum entry")
return ast.EnumEntry{Source: name.Source, Attributes: decos, Name: string(name.Runes)}
}
func (p *parser) matcherDecl() ast.MatcherDecl {
p.expect(tok.Match, "matcher declaration")
name := p.expect(tok.Identifier, "matcher name")
m := ast.MatcherDecl{Source: name.Source, Name: string(name.Runes)}
p.expect(tok.Colon, "matcher declaration")
for p.err == nil {
m.Options = append(m.Options, p.templatedName())
if p.match(tok.Or) == nil {
break
}
}
return m
}
func (p *parser) typeDecl(decos ast.Attributes) ast.TypeDecl {
p.expect(tok.Type, "type declaration")
name := p.expect(tok.Identifier, "type name")
m := ast.TypeDecl{
Source: name.Source,
Attributes: decos,
Name: string(name.Runes),
}
if p.peekIs(0, tok.Lt) {
m.TemplateParams = p.templateParams()
}
return m
}
func (p *parser) attributes() ast.Attributes {
var out ast.Attributes
for p.match(tok.Attr) != nil && p.err == nil {
name := p.expect(tok.Identifier, "attribute name")
values := []string{}
if p.match(tok.Lparen) != nil {
for p.err == nil {
values = append(values, string(p.next().Runes))
if p.match(tok.Comma) == nil {
break
}
}
p.expect(tok.Rparen, "attribute values")
}
out = append(out, ast.Attribute{
Source: name.Source,
Name: string(name.Runes),
Values: values,
})
}
return out
}
func (p *parser) builtinDecl(decos ast.Attributes) ast.IntrinsicDecl {
p.expect(tok.Function, "function declaration")
name := p.expect(tok.Identifier, "function name")
f := ast.IntrinsicDecl{
Source: name.Source,
Kind: ast.Builtin,
Attributes: decos,
Name: string(name.Runes),
}
if p.peekIs(0, tok.Lt) {
f.TemplateParams = p.templateParams()
}
f.Parameters = p.parameters()
if p.match(tok.Arrow) != nil {
ret := p.templatedName()
f.ReturnType = &ret
}
return f
}
func (p *parser) operatorDecl(decos ast.Attributes) ast.IntrinsicDecl {
p.expect(tok.Operator, "operator declaration")
name := p.next()
f := ast.IntrinsicDecl{
Source: name.Source,
Kind: ast.Operator,
Attributes: decos,
Name: string(name.Runes),
}
if p.peekIs(0, tok.Lt) {
f.TemplateParams = p.templateParams()
}
f.Parameters = p.parameters()
if p.match(tok.Arrow) != nil {
ret := p.templatedName()
f.ReturnType = &ret
}
return f
}
func (p *parser) constructorDecl(decos ast.Attributes) ast.IntrinsicDecl {
p.expect(tok.Constructor, "constructor declaration")
name := p.next()
f := ast.IntrinsicDecl{
Source: name.Source,
Kind: ast.Constructor,
Attributes: decos,
Name: string(name.Runes),
}
if p.peekIs(0, tok.Lt) {
f.TemplateParams = p.templateParams()
}
f.Parameters = p.parameters()
if p.match(tok.Arrow) != nil {
ret := p.templatedName()
f.ReturnType = &ret
}
return f
}
func (p *parser) converterDecl(decos ast.Attributes) ast.IntrinsicDecl {
p.expect(tok.Converter, "converter declaration")
name := p.next()
f := ast.IntrinsicDecl{
Source: name.Source,
Kind: ast.Converter,
Attributes: decos,
Name: string(name.Runes),
}
if p.peekIs(0, tok.Lt) {
f.TemplateParams = p.templateParams()
}
f.Parameters = p.parameters()
if p.match(tok.Arrow) != nil {
ret := p.templatedName()
f.ReturnType = &ret
}
return f
}
func (p *parser) parameters() ast.Parameters {
l := ast.Parameters{}
p.expect(tok.Lparen, "function parameter list")
if p.match(tok.Rparen) == nil {
for p.err == nil {
l = append(l, p.parameter())
if p.match(tok.Comma) == nil {
break
}
}
p.expect(tok.Rparen, "function parameter list")
}
return l
}
func (p *parser) parameter() ast.Parameter {
attributes := p.attributes()
if p.peekIs(1, tok.Colon) {
// name type
name := p.expect(tok.Identifier, "parameter name")
p.expect(tok.Colon, "parameter type")
return ast.Parameter{
Source: name.Source,
Name: string(name.Runes),
Attributes: attributes,
Type: p.templatedName(),
}
}
// type
ty := p.templatedName()
return ast.Parameter{
Source: ty.Source,
Attributes: attributes,
Type: ty,
}
}
func (p *parser) string() string {
s := p.expect(tok.String, "string")
return string(s.Runes)
}
func (p *parser) templatedName() ast.TemplatedName {
name := p.expect(tok.Identifier, "type name")
m := ast.TemplatedName{Source: name.Source, Name: string(name.Runes)}
if p.match(tok.Lt) != nil {
for p.err == nil {
m.TemplateArgs = append(m.TemplateArgs, p.templatedName())
if p.match(tok.Comma) == nil {
break
}
}
p.expect(tok.Gt, "template argument type list")
}
return m
}
func (p *parser) templateParams() ast.TemplateParams {
t := ast.TemplateParams{}
p.expect(tok.Lt, "template parameter list")
for p.err == nil && p.peekIs(0, tok.Identifier) {
t = append(t, p.templateParam())
}
p.expect(tok.Gt, "template parameter list")
return t
}
func (p *parser) templateParam() ast.TemplateParam {
name := p.match(tok.Identifier)
t := ast.TemplateParam{
Source: name.Source,
Name: string(name.Runes),
}
if p.match(tok.Colon) != nil {
t.Type = p.templatedName()
}
p.match(tok.Comma)
return t
}
func (p *parser) expect(kind tok.Kind, use string) tok.Token {
if p.err != nil {
return tok.Invalid
}
t := p.match(kind)
if t == nil {
if len(p.tokens) > 0 {
p.err = fmt.Errorf("%v expected '%v' for %v, got '%v'",
p.tokens[0].Source, kind, use, p.tokens[0].Kind)
} else {
p.err = fmt.Errorf("expected '%v' for %v, but reached end of file", kind, use)
}
return tok.Invalid
}
return *t
}
func (p *parser) ident(use string) string {
return string(p.expect(tok.Identifier, use).Runes)
}
func (p *parser) match(kind tok.Kind) *tok.Token {
if p.err != nil || len(p.tokens) == 0 {
return nil
}
t := p.tokens[0]
if t.Kind != kind {
return nil
}
p.tokens = p.tokens[1:]
return &t
}
func (p *parser) next() *tok.Token {
if p.err != nil {
return nil
}
if len(p.tokens) == 0 {
p.err = fmt.Errorf("reached end of file")
}
t := p.tokens[0]
p.tokens = p.tokens[1:]
return &t
}
func (p *parser) peekIs(i int, kind tok.Kind) bool {
t := p.peek(i)
if t == nil {
return false
}
return t.Kind == kind
}
func (p *parser) peek(i int) *tok.Token {
if len(p.tokens) <= i {
return nil
}
return &p.tokens[i]
}

View File

@@ -0,0 +1,705 @@
// Copyright 2021 The Tint Authors.
//
// 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_test
import (
"testing"
"dawn.googlesource.com/dawn/tools/src/tint/intrinsic/ast"
"dawn.googlesource.com/dawn/tools/src/tint/intrinsic/parser"
"dawn.googlesource.com/dawn/tools/src/utils"
"github.com/google/go-cmp/cmp"
)
var ignoreSource = cmp.FilterPath(func(p cmp.Path) bool {
return p.Last().String() == ".Source"
}, cmp.Ignore())
func TestParser(t *testing.T) {
type test struct {
location string
src string
expect ast.AST
}
for _, test := range []test{
{
utils.ThisLine(),
"enum E {}",
ast.AST{
Enums: []ast.EnumDecl{{Name: "E"}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"enum E { A @attr B C }",
ast.AST{
Enums: []ast.EnumDecl{{
Name: "E",
Entries: []ast.EnumEntry{
{Name: "A"},
{
Attributes: ast.Attributes{{
Name: "attr",
Values: []string{},
}},
Name: "B",
},
{Name: "C"},
},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"type T",
ast.AST{
Types: []ast.TypeDecl{{Name: "T"}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"type T<A, B, C>",
ast.AST{
Types: []ast.TypeDecl{{
Name: "T",
TemplateParams: ast.TemplateParams{
{Name: "A"},
{Name: "B"},
{Name: "C"},
},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"@attr type T",
ast.AST{
Types: []ast.TypeDecl{{
Attributes: ast.Attributes{
{Name: "attr", Values: []string{}},
},
Name: "T",
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"@attr_a @attr_b type T",
ast.AST{
Types: []ast.TypeDecl{{
Attributes: ast.Attributes{
{Name: "attr_a", Values: []string{}},
{Name: "attr_b", Values: []string{}},
},
Name: "T",
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
`@attr("a", "b") type T`, ast.AST{
Types: []ast.TypeDecl{{
Attributes: ast.Attributes{
{Name: "attr", Values: []string{"a", "b"}},
},
Name: "T",
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
`@attr(1, "x") type T`, ast.AST{
Types: []ast.TypeDecl{{
Attributes: ast.Attributes{
{Name: "attr", Values: []string{"1", "x"}},
},
Name: "T",
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"match M : A",
ast.AST{
Matchers: []ast.MatcherDecl{{
Name: "M",
Options: ast.MatcherOptions{
ast.TemplatedName{Name: "A"},
},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"match M : A | B",
ast.AST{
Matchers: []ast.MatcherDecl{{
Name: "M",
Options: ast.MatcherOptions{
ast.TemplatedName{Name: "A"},
ast.TemplatedName{Name: "B"},
},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"fn F()",
ast.AST{
Builtins: []ast.IntrinsicDecl{{
Kind: ast.Builtin,
Name: "F",
Parameters: ast.Parameters{},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"@attr fn F()",
ast.AST{
Builtins: []ast.IntrinsicDecl{{
Kind: ast.Builtin,
Name: "F",
Attributes: ast.Attributes{
{Name: "attr", Values: []string{}},
},
Parameters: ast.Parameters{},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"fn F(a)",
ast.AST{
Builtins: []ast.IntrinsicDecl{{
Kind: ast.Builtin,
Name: "F",
Parameters: ast.Parameters{
{Type: ast.TemplatedName{Name: "a"}},
},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"fn F(a: T)",
ast.AST{
Builtins: []ast.IntrinsicDecl{{
Kind: ast.Builtin,
Name: "F",
Parameters: ast.Parameters{
{Name: "a", Type: ast.TemplatedName{Name: "T"}},
},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"fn F(a, b)",
ast.AST{
Builtins: []ast.IntrinsicDecl{{
Kind: ast.Builtin,
Name: "F",
Parameters: ast.Parameters{
{Type: ast.TemplatedName{Name: "a"}},
{Type: ast.TemplatedName{Name: "b"}},
},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"fn F<A : B<C> >()",
ast.AST{
Builtins: []ast.IntrinsicDecl{{
Kind: ast.Builtin,
Name: "F",
TemplateParams: ast.TemplateParams{
{
Name: "A", Type: ast.TemplatedName{
Name: "B",
TemplateArgs: ast.TemplatedNames{
{Name: "C"},
},
},
},
},
Parameters: ast.Parameters{},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"fn F<T>(a: X, b: Y<T>)",
ast.AST{
Builtins: []ast.IntrinsicDecl{{
Kind: ast.Builtin,
Name: "F",
TemplateParams: ast.TemplateParams{
{Name: "T"},
},
Parameters: ast.Parameters{
{Name: "a", Type: ast.TemplatedName{Name: "X"}},
{Name: "b", Type: ast.TemplatedName{
Name: "Y",
TemplateArgs: []ast.TemplatedName{{Name: "T"}},
}},
},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"fn F() -> X",
ast.AST{
Builtins: []ast.IntrinsicDecl{{
Kind: ast.Builtin,
Name: "F",
ReturnType: &ast.TemplatedName{Name: "X"},
Parameters: ast.Parameters{},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"fn F() -> X<T>",
ast.AST{
Builtins: []ast.IntrinsicDecl{{
Kind: ast.Builtin,
Name: "F",
ReturnType: &ast.TemplatedName{
Name: "X",
TemplateArgs: []ast.TemplatedName{{Name: "T"}},
},
Parameters: ast.Parameters{},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"op F()",
ast.AST{
Operators: []ast.IntrinsicDecl{{
Kind: ast.Operator,
Name: "F",
Parameters: ast.Parameters{},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"@attr op F()",
ast.AST{
Operators: []ast.IntrinsicDecl{{
Kind: ast.Operator,
Name: "F",
Attributes: ast.Attributes{
{Name: "attr", Values: []string{}},
},
Parameters: ast.Parameters{},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"op F(a)",
ast.AST{
Operators: []ast.IntrinsicDecl{{
Kind: ast.Operator,
Name: "F",
Parameters: ast.Parameters{
{Type: ast.TemplatedName{Name: "a"}},
},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"op F(@blah a)",
ast.AST{
Operators: []ast.IntrinsicDecl{{
Kind: ast.Operator,
Name: "F",
Parameters: ast.Parameters{
{
Attributes: ast.Attributes{{Name: "blah", Values: []string{}}},
Type: ast.TemplatedName{Name: "a"}},
},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"op F(a: T)",
ast.AST{
Operators: []ast.IntrinsicDecl{{
Kind: ast.Operator,
Name: "F",
Parameters: ast.Parameters{
{Name: "a", Type: ast.TemplatedName{Name: "T"}},
},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"op F(a, b)",
ast.AST{
Operators: []ast.IntrinsicDecl{{
Kind: ast.Operator,
Name: "F",
Parameters: ast.Parameters{
{Type: ast.TemplatedName{Name: "a"}},
{Type: ast.TemplatedName{Name: "b"}},
},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"op F<A : B<C> >()",
ast.AST{
Operators: []ast.IntrinsicDecl{{
Kind: ast.Operator,
Name: "F",
TemplateParams: ast.TemplateParams{
{
Name: "A", Type: ast.TemplatedName{
Name: "B",
TemplateArgs: ast.TemplatedNames{
{Name: "C"},
},
},
},
},
Parameters: ast.Parameters{},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"op F<T>(a: X, b: Y<T>)",
ast.AST{
Operators: []ast.IntrinsicDecl{{
Kind: ast.Operator,
Name: "F",
TemplateParams: ast.TemplateParams{
{Name: "T"},
},
Parameters: ast.Parameters{
{Name: "a", Type: ast.TemplatedName{Name: "X"}},
{Name: "b", Type: ast.TemplatedName{
Name: "Y",
TemplateArgs: []ast.TemplatedName{{Name: "T"}},
}},
},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"op F() -> X",
ast.AST{
Operators: []ast.IntrinsicDecl{{
Kind: ast.Operator,
Name: "F",
ReturnType: &ast.TemplatedName{Name: "X"},
Parameters: ast.Parameters{},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"op F() -> X<T>",
ast.AST{
Operators: []ast.IntrinsicDecl{{
Kind: ast.Operator,
Name: "F",
ReturnType: &ast.TemplatedName{
Name: "X",
TemplateArgs: []ast.TemplatedName{{Name: "T"}},
},
Parameters: ast.Parameters{},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"ctor F()",
ast.AST{
Constructors: []ast.IntrinsicDecl{{
Kind: ast.Constructor,
Name: "F",
Parameters: ast.Parameters{},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"@attr ctor F()",
ast.AST{
Constructors: []ast.IntrinsicDecl{{
Kind: ast.Constructor,
Name: "F",
Attributes: ast.Attributes{
{Name: "attr", Values: []string{}},
},
Parameters: ast.Parameters{},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"ctor F(a)",
ast.AST{
Constructors: []ast.IntrinsicDecl{{
Kind: ast.Constructor,
Name: "F",
Parameters: ast.Parameters{
{Type: ast.TemplatedName{Name: "a"}},
},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"ctor F(a: T)",
ast.AST{
Constructors: []ast.IntrinsicDecl{{
Kind: ast.Constructor,
Name: "F",
Parameters: ast.Parameters{
{Name: "a", Type: ast.TemplatedName{Name: "T"}},
},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"ctor F(a, b)",
ast.AST{
Constructors: []ast.IntrinsicDecl{{
Kind: ast.Constructor,
Name: "F",
Parameters: ast.Parameters{
{Type: ast.TemplatedName{Name: "a"}},
{Type: ast.TemplatedName{Name: "b"}},
},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"ctor F<A : B<C> >()",
ast.AST{
Constructors: []ast.IntrinsicDecl{{
Kind: ast.Constructor,
Name: "F",
TemplateParams: ast.TemplateParams{
{
Name: "A", Type: ast.TemplatedName{
Name: "B",
TemplateArgs: ast.TemplatedNames{
{Name: "C"},
},
},
},
},
Parameters: ast.Parameters{},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"ctor F<T>(a: X, b: Y<T>)",
ast.AST{
Constructors: []ast.IntrinsicDecl{{
Kind: ast.Constructor,
Name: "F",
TemplateParams: ast.TemplateParams{
{Name: "T"},
},
Parameters: ast.Parameters{
{Name: "a", Type: ast.TemplatedName{Name: "X"}},
{Name: "b", Type: ast.TemplatedName{
Name: "Y",
TemplateArgs: []ast.TemplatedName{{Name: "T"}},
}},
},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"ctor F() -> X",
ast.AST{
Constructors: []ast.IntrinsicDecl{{
Kind: ast.Constructor,
Name: "F",
ReturnType: &ast.TemplatedName{Name: "X"},
Parameters: ast.Parameters{},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"ctor F() -> X<T>",
ast.AST{
Constructors: []ast.IntrinsicDecl{{
Kind: ast.Constructor,
Name: "F",
ReturnType: &ast.TemplatedName{
Name: "X",
TemplateArgs: []ast.TemplatedName{{Name: "T"}},
},
Parameters: ast.Parameters{},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"conv F()",
ast.AST{
Converters: []ast.IntrinsicDecl{{
Kind: ast.Converter,
Name: "F",
Parameters: ast.Parameters{},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"@attr conv F()",
ast.AST{
Converters: []ast.IntrinsicDecl{{
Kind: ast.Converter,
Name: "F",
Attributes: ast.Attributes{
{Name: "attr", Values: []string{}},
},
Parameters: ast.Parameters{},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"conv F(a)",
ast.AST{
Converters: []ast.IntrinsicDecl{{
Kind: ast.Converter,
Name: "F",
Parameters: ast.Parameters{
{Type: ast.TemplatedName{Name: "a"}},
},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"conv F(a: T)",
ast.AST{
Converters: []ast.IntrinsicDecl{{
Kind: ast.Converter,
Name: "F",
Parameters: ast.Parameters{
{Name: "a", Type: ast.TemplatedName{Name: "T"}},
},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"conv F(a, b)",
ast.AST{
Converters: []ast.IntrinsicDecl{{
Kind: ast.Converter,
Name: "F",
Parameters: ast.Parameters{
{Type: ast.TemplatedName{Name: "a"}},
{Type: ast.TemplatedName{Name: "b"}},
},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"conv F<A : B<C> >()",
ast.AST{
Converters: []ast.IntrinsicDecl{{
Kind: ast.Converter,
Name: "F",
TemplateParams: ast.TemplateParams{
{
Name: "A", Type: ast.TemplatedName{
Name: "B",
TemplateArgs: ast.TemplatedNames{
{Name: "C"},
},
},
},
},
Parameters: ast.Parameters{},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"conv F<T>(a: X, b: Y<T>)",
ast.AST{
Converters: []ast.IntrinsicDecl{{
Kind: ast.Converter,
Name: "F",
TemplateParams: ast.TemplateParams{
{Name: "T"},
},
Parameters: ast.Parameters{
{Name: "a", Type: ast.TemplatedName{Name: "X"}},
{Name: "b", Type: ast.TemplatedName{
Name: "Y",
TemplateArgs: []ast.TemplatedName{{Name: "T"}},
}},
},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"conv F() -> X",
ast.AST{
Converters: []ast.IntrinsicDecl{{
Kind: ast.Converter,
Name: "F",
ReturnType: &ast.TemplatedName{Name: "X"},
Parameters: ast.Parameters{},
}},
},
}, { ///////////////////////////////////////////////////////////////////
utils.ThisLine(),
"conv F() -> X<T>",
ast.AST{
Converters: []ast.IntrinsicDecl{{
Kind: ast.Converter,
Name: "F",
ReturnType: &ast.TemplatedName{
Name: "X",
TemplateArgs: []ast.TemplatedName{{Name: "T"}},
},
Parameters: ast.Parameters{},
}},
}},
} {
got, err := parser.Parse(test.src, "file.txt")
if err != nil {
t.Errorf("\n%v\nWhile parsing:\n%s\nParse() returned error: %v",
test.location, test.src, err)
continue
}
if diff := cmp.Diff(got, &test.expect, ignoreSource); diff != "" {
t.Errorf("\n%v\nWhile parsing:\n%s\n\n%s",
test.location, test.src, diff)
}
}
}
func TestErrors(t *testing.T) {
type test struct {
src string
expect string
}
for _, test := range []test{
{
"£",
"test.txt:1:1: unexpected '£'",
},
{
"123",
"test.txt:1:1 unexpected token 'integer'",
},
{
"@123",
"test.txt:1:2 expected 'ident' for attribute name, got 'integer'",
},
} {
got, err := parser.Parse(test.src, "test.txt")
gotErr := ""
if err != nil {
gotErr = err.Error()
}
if test.expect != gotErr {
t.Errorf(`Parse() returned error "%+v", expected error "%+v"`, gotErr, test.expect)
}
if got != nil {
t.Errorf("Lex() returned non-nil for error")
}
}
}

View File

@@ -0,0 +1,738 @@
// Copyright 2021 The Tint Authors.
//
// 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 resolver
import (
"fmt"
"sort"
"strconv"
"dawn.googlesource.com/dawn/tools/src/tint/intrinsic/ast"
"dawn.googlesource.com/dawn/tools/src/tint/intrinsic/sem"
"dawn.googlesource.com/dawn/tools/src/tint/intrinsic/tok"
)
type resolver struct {
a *ast.AST
s *sem.Sem
globals scope
builtins map[string]*sem.Intrinsic
unaryOperators map[string]*sem.Intrinsic
binaryOperators map[string]*sem.Intrinsic
constructorsAndConverters map[string]*sem.Intrinsic
enumEntryMatchers map[*sem.EnumEntry]*sem.EnumMatcher
}
// Resolve processes the AST
func Resolve(a *ast.AST) (*sem.Sem, error) {
r := resolver{
a: a,
s: sem.New(),
globals: newScope(nil),
builtins: map[string]*sem.Intrinsic{},
unaryOperators: map[string]*sem.Intrinsic{},
binaryOperators: map[string]*sem.Intrinsic{},
constructorsAndConverters: map[string]*sem.Intrinsic{},
enumEntryMatchers: map[*sem.EnumEntry]*sem.EnumMatcher{},
}
// Declare and resolve all the enumerators
for _, e := range a.Enums {
if err := r.enum(e); err != nil {
return nil, err
}
}
// Declare and resolve all the ty types
for _, p := range a.Types {
if err := r.ty(p); err != nil {
return nil, err
}
}
// Declare and resolve the type matchers
for _, m := range a.Matchers {
if err := r.matcher(m); err != nil {
return nil, err
}
}
// Declare and resolve the builtins
for _, f := range a.Builtins {
if err := r.intrinsic(f, r.builtins, &r.s.Builtins); err != nil {
return nil, err
}
}
// Declare and resolve the unary and binary operators
for _, o := range a.Operators {
switch len(o.Parameters) {
case 1:
if err := r.intrinsic(o, r.unaryOperators, &r.s.UnaryOperators); err != nil {
return nil, err
}
case 2:
if err := r.intrinsic(o, r.binaryOperators, &r.s.BinaryOperators); err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("%v operators must have either 1 or 2 parameters", o.Source)
}
}
// Declare and resolve type constructors and converters
for _, c := range a.Constructors {
if err := r.intrinsic(c, r.constructorsAndConverters, &r.s.ConstructorsAndConverters); err != nil {
return nil, err
}
}
for _, c := range a.Converters {
if len(c.Parameters) != 1 {
return nil, fmt.Errorf("%v conversions must have a single parameter", c.Source)
}
if err := r.intrinsic(c, r.constructorsAndConverters, &r.s.ConstructorsAndConverters); err != nil {
return nil, err
}
}
// Calculate the unique parameter names
r.s.UniqueParameterNames = r.calculateUniqueParameterNames()
return r.s, nil
}
// enum() resolves an enum declaration.
// The resulting sem.Enum is appended to Sem.Enums, and the enum and all its
// entries are registered with the global scope.
func (r *resolver) enum(e ast.EnumDecl) error {
s := &sem.Enum{
Decl: e,
Name: e.Name,
}
// Register the enum
r.s.Enums = append(r.s.Enums, s)
if err := r.globals.declare(s, e.Source); err != nil {
return err
}
// Register each of the enum entries
for _, ast := range e.Entries {
entry := &sem.EnumEntry{
Name: ast.Name,
Enum: s,
}
if internal := ast.Attributes.Take("internal"); internal != nil {
entry.IsInternal = true
if len(internal.Values) != 0 {
return fmt.Errorf("%v unexpected value for internal attribute", ast.Source)
}
}
if len(ast.Attributes) != 0 {
return fmt.Errorf("%v unknown attribute", ast.Attributes[0].Source)
}
if err := r.globals.declare(entry, e.Source); err != nil {
return err
}
s.Entries = append(s.Entries, entry)
}
return nil
}
// ty() resolves a type declaration.
// The resulting sem.Type is appended to Sem.Types, and the type is registered
// with the global scope.
func (r *resolver) ty(a ast.TypeDecl) error {
t := &sem.Type{
Decl: a,
Name: a.Name,
}
// Register the type
r.s.Types = append(r.s.Types, t)
if err := r.globals.declare(t, a.Source); err != nil {
return err
}
// Create a new scope for resolving template parameters
s := newScope(&r.globals)
// Resolve the type template parameters
templateParams, err := r.templateParams(&s, a.TemplateParams)
if err != nil {
return err
}
t.TemplateParams = templateParams
// Scan for attributes
if d := a.Attributes.Take("display"); d != nil {
if len(d.Values) != 1 {
return fmt.Errorf("%v expected a single value for 'display' attribute", d.Source)
}
t.DisplayName = d.Values[0]
}
if d := a.Attributes.Take("precedence"); d != nil {
if len(d.Values) != 1 {
return fmt.Errorf("%v expected a single integer value for 'precedence' attribute", d.Source)
}
n, err := strconv.Atoi(d.Values[0])
if err != nil {
return fmt.Errorf("%v %v", d.Source, err)
}
t.Precedence = n
}
if len(a.Attributes) != 0 {
return fmt.Errorf("%v unknown attribute", a.Attributes[0].Source)
}
return nil
}
// matcher() resolves a match declaration to either a sem.TypeMatcher or
// sem.EnumMatcher.
// The resulting matcher is appended to either Sem.TypeMatchers or
// Sem.EnumMatchers, and is registered with the global scope.
func (r *resolver) matcher(a ast.MatcherDecl) error {
// Determine whether this is a type matcher or enum matcher by resolving the
// first option
firstOption, err := r.lookupNamed(&r.globals, a.Options[0])
if err != nil {
return err
}
// Resolve to a sem.TypeMatcher or a sem.EnumMatcher
switch firstOption := firstOption.(type) {
case *sem.Type:
options := map[sem.Named]tok.Source{}
m := &sem.TypeMatcher{
Decl: a,
Name: a.Name,
}
// Register the matcher
r.s.TypeMatchers = append(r.s.TypeMatchers, m)
if err := r.globals.declare(m, a.Source); err != nil {
return err
}
// Resolve each of the types in the options list
for _, ast := range m.Decl.Options {
ty, err := r.lookupType(&r.globals, ast)
if err != nil {
return err
}
m.Types = append(m.Types, ty)
if s, dup := options[ty]; dup {
return fmt.Errorf("%v duplicate option '%v' in matcher\nFirst declared here: %v", ast.Source, ast.Name, s)
}
options[ty] = ast.Source
}
return nil
case *sem.EnumEntry:
enum := firstOption.Enum
m := &sem.EnumMatcher{
Decl: a,
Name: a.Name,
Enum: enum,
}
// Register the matcher
r.s.EnumMatchers = append(r.s.EnumMatchers, m)
if err := r.globals.declare(m, a.Source); err != nil {
return err
}
// Resolve each of the enums in the options list
for _, ast := range m.Decl.Options {
entry := enum.FindEntry(ast.Name)
if entry == nil {
return fmt.Errorf("%v enum '%v' does not contain '%v'", ast.Source, enum.Name, ast.Name)
}
m.Options = append(m.Options, entry)
}
return nil
}
return fmt.Errorf("'%v' cannot be used for matcher", a.Name)
}
// intrinsic() resolves a intrinsic overload declaration.
// The the first overload for the intrinsic creates and appends the sem.Intrinsic
// to Sem.Intrinsics. Subsequent overloads append their resolved overload to the
// sem.intrinsic.Overloads list.
func (r *resolver) intrinsic(
a ast.IntrinsicDecl,
intrinsicsByName map[string]*sem.Intrinsic,
semIntrinsics *[]*sem.Intrinsic) error {
// If this is the first overload of the intrinsic, create and register the
// semantic intrinsic.
intrinsic := intrinsicsByName[a.Name]
if intrinsic == nil {
intrinsic = &sem.Intrinsic{Name: a.Name}
intrinsicsByName[a.Name] = intrinsic
*semIntrinsics = append(*semIntrinsics, intrinsic)
}
// Create a new scope for resolving template parameters
s := newScope(&r.globals)
// Resolve the declared template parameters
templateParams, err := r.templateParams(&s, a.TemplateParams)
if err != nil {
return err
}
// Construct the semantic overload
overload := &sem.Overload{
Decl: a,
Intrinsic: intrinsic,
Parameters: make([]sem.Parameter, len(a.Parameters)),
TemplateParams: templateParams,
}
// Process overload attributes
if stageDeco := a.Attributes.Take("stage"); stageDeco != nil {
for stageDeco != nil {
for _, stage := range stageDeco.Values {
switch stage {
case "vertex":
overload.CanBeUsedInStage.Vertex = true
case "fragment":
overload.CanBeUsedInStage.Fragment = true
case "compute":
overload.CanBeUsedInStage.Compute = true
default:
return fmt.Errorf("%v unknown stage '%v'", stageDeco.Source, stage)
}
}
stageDeco = a.Attributes.Take("stage")
}
} else {
overload.CanBeUsedInStage = sem.StageUses{
Vertex: true,
Fragment: true,
Compute: true,
}
}
if constEvalFn := a.Attributes.Take("const"); constEvalFn != nil {
switch len(constEvalFn.Values) {
case 0:
switch overload.Decl.Kind {
case ast.Builtin, ast.Operator:
overload.ConstEvalFunction = overload.Decl.Name
case ast.Constructor:
overload.ConstEvalFunction = "Ctor"
case ast.Converter:
overload.ConstEvalFunction = "Conv"
}
case 1:
overload.ConstEvalFunction = constEvalFn.Values[0]
default:
return fmt.Errorf("%v too many values for @const attribute", constEvalFn.Source)
}
}
if deprecated := a.Attributes.Take("deprecated"); deprecated != nil {
overload.IsDeprecated = true
if len(deprecated.Values) != 0 {
return fmt.Errorf("%v unexpected value for deprecated attribute", deprecated.Source)
}
}
if len(a.Attributes) != 0 {
return fmt.Errorf("%v unknown attribute", a.Attributes[0].Source)
}
// Append the overload to the intrinsic
intrinsic.Overloads = append(intrinsic.Overloads, overload)
// Sort the template parameters by resolved type. Append these to
// sem.Overload.TemplateTypes or sem.Overload.TemplateNumbers based on their kind.
for _, param := range templateParams {
switch param := param.(type) {
case *sem.TemplateTypeParam:
overload.TemplateTypes = append(overload.TemplateTypes, param)
case *sem.TemplateEnumParam, *sem.TemplateNumberParam:
overload.TemplateNumbers = append(overload.TemplateNumbers, param)
}
}
// Update high-water marks of template types and numbers
if r.s.MaxTemplateTypes < len(overload.TemplateTypes) {
r.s.MaxTemplateTypes = len(overload.TemplateTypes)
}
if r.s.MaxTemplateNumbers < len(overload.TemplateNumbers) {
r.s.MaxTemplateNumbers = len(overload.TemplateNumbers)
}
// Resolve the parameters
for i, p := range a.Parameters {
usage, err := r.fullyQualifiedName(&s, p.Type)
if err != nil {
return err
}
isConst := false
if attribute := p.Attributes.Take("const"); attribute != nil {
isConst = true
}
if len(p.Attributes) != 0 {
return fmt.Errorf("%v unknown attribute", p.Attributes[0].Source)
}
overload.Parameters[i] = sem.Parameter{
Name: p.Name,
Type: usage,
IsConst: isConst,
}
}
// Resolve the return type
if a.ReturnType != nil {
usage, err := r.fullyQualifiedName(&s, *a.ReturnType)
if err != nil {
return err
}
switch usage.Target.(type) {
case *sem.Type, *sem.TemplateTypeParam:
overload.ReturnType = &usage
default:
return fmt.Errorf("%v cannot use '%v' as return type. Must be a type or template type", a.ReturnType.Source, a.ReturnType.Name)
}
}
return nil
}
// fullyQualifiedName() resolves the ast.TemplatedName to a sem.FullyQualifiedName.
func (r *resolver) fullyQualifiedName(s *scope, arg ast.TemplatedName) (sem.FullyQualifiedName, error) {
target, err := r.lookupNamed(s, arg)
if err != nil {
return sem.FullyQualifiedName{}, err
}
if entry, ok := target.(*sem.EnumEntry); ok {
// The target resolved to an enum entry.
// Automagically transform this into a synthetic matcher with a single
// option. i.e.
// This:
// enum E{ a b c }
// fn F(b)
// Becomes:
// enum E{ a b c }
// matcher b
// fn F(b)
// We don't really care right now that we have a symbol collision
// between E.b and b, as the generators return different names for
// these.
matcher, ok := r.enumEntryMatchers[entry]
if !ok {
matcher = &sem.EnumMatcher{
Name: entry.Name,
Enum: entry.Enum,
Options: []*sem.EnumEntry{entry},
}
r.enumEntryMatchers[entry] = matcher
r.s.EnumMatchers = append(r.s.EnumMatchers, matcher)
}
target = matcher
}
fqn := sem.FullyQualifiedName{
Target: target,
TemplateArguments: make([]interface{}, len(arg.TemplateArgs)),
}
for i, a := range arg.TemplateArgs {
arg, err := r.fullyQualifiedName(s, a)
if err != nil {
return sem.FullyQualifiedName{}, err
}
fqn.TemplateArguments[i] = arg
}
return fqn, nil
}
// templateParams() resolves the ast.TemplateParams into list of sem.TemplateParam.
// Each sem.TemplateParam is registered with the scope s.
func (r *resolver) templateParams(s *scope, l ast.TemplateParams) ([]sem.TemplateParam, error) {
out := []sem.TemplateParam{}
for _, ast := range l {
param, err := r.templateParam(ast)
if err != nil {
return nil, err
}
s.declare(param, ast.Source)
out = append(out, param)
}
return out, nil
}
// templateParams() resolves the ast.TemplateParam into sem.TemplateParam, which
// is either a sem.TemplateEnumParam or a sem.TemplateTypeParam.
func (r *resolver) templateParam(a ast.TemplateParam) (sem.TemplateParam, error) {
if a.Type.Name == "num" {
return &sem.TemplateNumberParam{Name: a.Name}, nil
}
if a.Type.Name != "" {
resolved, err := r.lookupNamed(&r.globals, a.Type)
if err != nil {
return nil, err
}
switch r := resolved.(type) {
case *sem.Enum:
return &sem.TemplateEnumParam{Name: a.Name, Enum: r}, nil
case *sem.EnumMatcher:
return &sem.TemplateEnumParam{Name: a.Name, Enum: r.Enum, Matcher: r}, nil
case *sem.TypeMatcher:
return &sem.TemplateTypeParam{Name: a.Name, Type: r}, nil
case *sem.Type:
return &sem.TemplateTypeParam{Name: a.Name, Type: r}, nil
default:
return nil, fmt.Errorf("%v invalid template parameter type '%v'", a.Source, a.Type.Name)
}
}
return &sem.TemplateTypeParam{Name: a.Name}, nil
}
// lookupType() searches the scope `s` and its ancestors for the sem.Type with
// the given name.
func (r *resolver) lookupType(s *scope, a ast.TemplatedName) (*sem.Type, error) {
resolved, err := r.lookupNamed(s, a)
if err != nil {
return nil, err
}
// Something with the given name was found...
if ty, ok := resolved.(*sem.Type); ok {
return ty, nil
}
// ... but that something was not a sem.Type
return nil, fmt.Errorf("%v '%v' resolves to %v but type is expected", a.Source, a.Name, describe(resolved))
}
// lookupNamed() searches `s` and its ancestors for the sem.Named object with
// the given name. If there are template arguments for the name `a`, then
// lookupNamed() performs basic validation that those arguments can be passed
// to the named object.
func (r *resolver) lookupNamed(s *scope, a ast.TemplatedName) (sem.Named, error) {
target := s.lookup(a.Name)
if target == nil {
return nil, fmt.Errorf("%v cannot resolve '%v'", a.Source, a.Name)
}
// Something with the given name was found...
var params []sem.TemplateParam
var ty sem.ResolvableType
switch target := target.object.(type) {
case *sem.Type:
ty = target
params = target.TemplateParams
case *sem.TypeMatcher:
ty = target
params = target.TemplateParams
case sem.TemplateParam:
if len(a.TemplateArgs) != 0 {
return nil, fmt.Errorf("%v '%v' template parameters do not accept template arguments", a.Source, a.Name)
}
return target.(sem.Named), nil
case sem.Named:
return target, nil
default:
panic(fmt.Errorf("Unknown resolved type %T", target))
}
// ... and that something takes template parameters
// Check the number of templated name template arguments match the number of
// templated parameters for the target.
args := a.TemplateArgs
if len(params) != len(args) {
return nil, fmt.Errorf("%v '%v' requires %d template arguments, but %d were provided", a.Source, a.Name, len(params), len(args))
}
// Check templated name template argument kinds match the parameter kinds
for i, ast := range args {
param := params[i]
arg, err := r.lookupNamed(s, args[i])
if err != nil {
return nil, err
}
if err := checkCompatible(arg, param); err != nil {
return nil, fmt.Errorf("%v %w", ast.Source, err)
}
}
return ty, nil
}
// calculateUniqueParameterNames() iterates over all the parameters of all
// builtin overloads, calculating the list of unique parameter names
func (r *resolver) calculateUniqueParameterNames() []string {
set := map[string]struct{}{"": {}}
names := []string{}
for _, intrinsics := range [][]*sem.Intrinsic{
r.s.Builtins,
r.s.UnaryOperators,
r.s.BinaryOperators,
r.s.ConstructorsAndConverters,
} {
for _, i := range intrinsics {
for _, o := range i.Overloads {
for _, p := range o.Parameters {
if _, dup := set[p.Name]; !dup {
set[p.Name] = struct{}{}
names = append(names, p.Name)
}
}
}
}
}
sort.Strings(names)
return names
}
// describe() returns a string describing a sem.Named
func describe(n sem.Named) string {
switch n := n.(type) {
case *sem.Type:
return "type '" + n.Name + "'"
case *sem.TypeMatcher:
return "type matcher '" + n.Name + "'"
case *sem.Enum:
return "enum '" + n.Name + "'"
case *sem.EnumMatcher:
return "enum matcher '" + n.Name + "'"
case *sem.TemplateTypeParam:
return "template type"
case *sem.TemplateEnumParam:
return "template enum '" + n.Enum.Name + "'"
case *sem.EnumEntry:
return "enum entry '" + n.Enum.Name + "." + n.Name + "'"
case *sem.TemplateNumberParam:
return "template number"
default:
panic(fmt.Errorf("unhandled type %T", n))
}
}
// checkCompatible() returns an error if `arg` cannot be used as an argument for
// a parameter of `param`.
func checkCompatible(arg, param sem.Named) error {
// asEnum() returns the underlying sem.Enum if n is a enum matcher,
// templated enum parameter or an enum entry, otherwise nil
asEnum := func(n sem.Named) *sem.Enum {
switch n := n.(type) {
case *sem.EnumMatcher:
return n.Enum
case *sem.TemplateEnumParam:
return n.Enum
case *sem.EnumEntry:
return n.Enum
default:
return nil
}
}
if arg := asEnum(arg); arg != nil {
param := asEnum(param)
if arg == param {
return nil
}
}
anyNumber := "any number"
// asNumber() returns anyNumber if n is a TemplateNumberParam.
// TODO(bclayton): Once we support number ranges [e.g.: fn F<N: 1..4>()], we
// should check number ranges are compatible
asNumber := func(n sem.Named) interface{} {
switch n.(type) {
case *sem.TemplateNumberParam:
return anyNumber
default:
return nil
}
}
if arg := asNumber(arg); arg != nil {
param := asNumber(param)
if arg == param {
return nil
}
}
anyType := &sem.Type{}
// asNumber() returns the sem.Type, sem.TypeMatcher if the named object
// resolves to one of these, or anyType if n is a unconstrained template
// type parameter.
asResolvableType := func(n sem.Named) sem.ResolvableType {
switch n := n.(type) {
case *sem.TemplateTypeParam:
if n.Type != nil {
return n.Type
}
return anyType
case *sem.Type:
return n
case *sem.TypeMatcher:
return n
default:
return nil
}
}
if arg := asResolvableType(arg); arg != nil {
param := asResolvableType(param)
if arg == param || param == anyType {
return nil
}
}
return fmt.Errorf("cannot use %v as %v", describe(arg), describe(param))
}
// scope is a basic hierarchical name to object table
type scope struct {
objects map[string]objectAndSource
parent *scope
}
// objectAndSource is a sem.Named object with a source
type objectAndSource struct {
object sem.Named
source tok.Source
}
// newScope returns a newly initalized scope
func newScope(parent *scope) scope {
return scope{objects: map[string]objectAndSource{}, parent: parent}
}
// lookup() searches the scope and then its parents for the symbol with the
// given name.
func (s *scope) lookup(name string) *objectAndSource {
if o, found := s.objects[name]; found {
return &o
}
if s.parent == nil {
return nil
}
return s.parent.lookup(name)
}
// declare() declares the symbol with the given name, erroring on symbol
// collision.
func (s *scope) declare(object sem.Named, source tok.Source) error {
name := object.GetName()
if existing := s.lookup(name); existing != nil {
return fmt.Errorf("%v '%v' already declared\nFirst declared here: %v", source, name, existing.source)
}
s.objects[name] = objectAndSource{object, source}
return nil
}

View File

@@ -0,0 +1,547 @@
// Copyright 2021 The Tint Authors.
//
// 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 resolver_test
import (
"fmt"
"strings"
"testing"
"dawn.googlesource.com/dawn/tools/src/tint/intrinsic/parser"
"dawn.googlesource.com/dawn/tools/src/tint/intrinsic/resolver"
)
func TestResolver(t *testing.T) {
type test struct {
src string
err string
}
success := ""
for _, test := range []test{
{
`type X`,
success,
}, {
`enum E {}`,
success,
}, {
`enum E {A B C}`,
success,
}, {
`type X`,
success,
}, {
`@display("Y") type X`,
success,
}, {
`
type x
match y: x`,
success,
}, {
`
enum e {a b c}
match y: c | a | b`,
success,
}, {
`fn f()`,
success,
}, {
`fn f<T>()`,
success,
}, {
`
type f32
fn f<N: num>()`,
success,
}, {
`
enum e { a b c }
fn f<N: e>()`,
success,
}, {
`
type f32
fn f<T>(T) -> f32`,
success,
}, {
`
type f32
type P<T>
match m: f32
fn f<T: m>(P<T>) -> T`,
success,
}, {
`
type f32
type P<T>
match m: f32
fn f(P<m>)`,
success,
}, {
`
enum e { a }
fn f(a)`,
success,
}, {
`
enum e { a b }
type T<E: e>
match m: a
fn f<E: m>(T<E>)`,
success,
}, {
`
enum e { a b }
type T<E: e>
match m: a
fn f(T<m>)`,
success,
}, {
`
enum e { a }
type T<E: e>
fn f(T<a>)`,
success,
}, {
`
type T<E: num>
fn f<E: num>(T<E>)`,
success,
}, {
`fn f<T>(T)`,
success,
}, {
`
enum e { a b }
fn f<E: e>()`,
success,
}, {
`
enum e { a b }
match m: a | b
fn f<E: m>()`,
success,
}, {
`
type f32
type T<x>
fn f(T< T<f32> >)`,
success,
}, {
`
type f32
op -(f32)`,
success,
}, {
`
type f32
type T<x>
op +(T<f32>, T<f32>)`,
success,
}, {
`
type f32
ctor f32(f32)`,
success,
}, {
`
type f32
type T<x>
ctor f32(T<f32>)`,
success,
}, {
`
type f32
type i32
conv f32(i32)`,
success,
}, {
`
type f32
type T<x>
conv f32(T<f32>)`,
success,
}, {
`enum E {A A}`,
`
file.txt:1:6 'A' already declared
First declared here: file.txt:1:6
`,
},
{
`type X type X`,
`
file.txt:1:13 'X' already declared
First declared here: file.txt:1:6`,
}, {
`@meow type X`,
`
file.txt:1:2 unknown attribute
`,
}, {
`@display("Y", "Z") type X`,
`
file.txt:1:2 expected a single value for 'display' attribute`,
}, {
`
enum e { a }
enum e { b }`,
`
file.txt:2:6 'e' already declared
First declared here: file.txt:1:6`,
}, {
`
type X
match X : X`,
`
file.txt:2:7 'X' already declared
First declared here: file.txt:1:6`,
}, {
`type T<X>
match M : T`,
`file.txt:2:11 'T' requires 1 template arguments, but 0 were provided`,
}, {
`
match x: y`,
`
file.txt:1:10 cannot resolve 'y'
`,
}, {
`
type a
match x: a | b`,
`
file.txt:2:14 cannot resolve 'b'
`,
}, {
`
type a
enum e { b }
match x: a | b`,
`
file.txt:3:14 'b' resolves to enum entry 'e.b' but type is expected
`,
}, {
`
type a
type b
match x: a | b | a`,
`
file.txt:3:18 duplicate option 'a' in matcher
First declared here: file.txt:3:10
`,
}, {
`
enum e { a c }
match x: a | b | c`,
`
file.txt:2:14 enum 'e' does not contain 'b'
`,
}, {
`
enum e { a }
match x: a
match x: a`,
`
file.txt:3:7 'x' already declared
First declared here: file.txt:2:7
`,
}, {
`
type t
match x: t
match y: x`,
`
'y' cannot be used for matcher
`,
}, {
`fn f(u)`,
`file.txt:1:6 cannot resolve 'u'`,
}, {
`fn f() -> u`,
`file.txt:1:11 cannot resolve 'u'`,
}, {
`fn f<T: u>()`,
`file.txt:1:9 cannot resolve 'u'`,
}, {
`
enum e { a }
fn f() -> e`,
`file.txt:2:11 cannot use 'e' as return type. Must be a type or template type`,
}, {
`
type T<x>
fn f(T<u>)`,
`file.txt:2:8 cannot resolve 'u'`,
}, {
`
type x
fn f<T>(T<x>)`,
`file.txt:2:9 'T' template parameters do not accept template arguments`,
}, {
`
type A<N: num>
type B
fn f(A<B>)`,
`file.txt:3:8 cannot use type 'B' as template number`,
}, {
`
type A<N>
enum E { b }
fn f(A<b>)`,
`file.txt:3:8 cannot use enum entry 'E.b' as template type`,
}, {
`
type T
type P<N: num>
match m: T
fn f(P<m>)`,
`file.txt:4:8 cannot use type matcher 'm' as template number`,
}, {
`
type P<N: num>
enum E { b }
fn f(P<E>)`,
`file.txt:3:8 cannot use enum 'E' as template number`,
}, {
`
type P<N: num>
enum E { a b }
match m: a | b
fn f(P<m>)`,
`file.txt:4:8 cannot use enum matcher 'm' as template number`,
}, {
`
type P<N: num>
enum E { a b }
match m: a | b
fn f<M: m>(P<M>)`,
`file.txt:4:14 cannot use template enum 'E' as template number`,
}, {
`
type i
enum e { a }
op << (i) -> e`,
`file.txt:3:14 cannot use 'e' as return type. Must be a type or template type`,
}, {
`
type T<x>
op << (T<u>)`,
`file.txt:2:10 cannot resolve 'u'`,
}, {
`
op << ()`,
`file.txt:1:4 operators must have either 1 or 2 parameters`,
}, {
`
type i
op << (i, i, i)`,
`file.txt:2:4 operators must have either 1 or 2 parameters`,
}, {
`
type x
op << <T>(T<x>)`,
`file.txt:2:11 'T' template parameters do not accept template arguments`,
}, {
`
type A<N: num>
type B
op << (A<B>)`,
`file.txt:3:10 cannot use type 'B' as template number`,
}, {
`
type A<N>
enum E { b }
op << (A<b>)`,
`file.txt:3:10 cannot use enum entry 'E.b' as template type`,
}, {
`
type T
type P<N: num>
match m: T
op << (P<m>)`,
`file.txt:4:10 cannot use type matcher 'm' as template number`,
}, {
`
type P<N: num>
enum E { b }
op << (P<E>)`,
`file.txt:3:10 cannot use enum 'E' as template number`,
}, {
`
type P<N: num>
enum E { a b }
match m: a | b
op << (P<m>)`,
`file.txt:4:10 cannot use enum matcher 'm' as template number`,
}, {
`
type P<N: num>
enum E { a b }
match m: a | b
op << <M: m>(P<M>)`,
`file.txt:4:16 cannot use template enum 'E' as template number`,
}, {
`
type i
enum e { a }
ctor F(i) -> e`,
`file.txt:3:14 cannot use 'e' as return type. Must be a type or template type`,
}, {
`
type T<x>
ctor F(T<u>)`,
`file.txt:2:10 cannot resolve 'u'`,
}, {
`
type x
ctor F<T>(T<x>)`,
`file.txt:2:11 'T' template parameters do not accept template arguments`,
}, {
`
type A<N: num>
type B
ctor F(A<B>)`,
`file.txt:3:10 cannot use type 'B' as template number`,
}, {
`
type A<N>
enum E { b }
ctor F(A<b>)`,
`file.txt:3:10 cannot use enum entry 'E.b' as template type`,
}, {
`
type T
type P<N: num>
match m: T
ctor F(P<m>)`,
`file.txt:4:10 cannot use type matcher 'm' as template number`,
}, {
`
type P<N: num>
enum E { b }
ctor F(P<E>)`,
`file.txt:3:10 cannot use enum 'E' as template number`,
}, {
`
type P<N: num>
enum E { a b }
match m: a | b
ctor F(P<m>)`,
`file.txt:4:10 cannot use enum matcher 'm' as template number`,
}, {
`
type P<N: num>
enum E { a b }
match m: a | b
ctor F<M: m>(P<M>)`,
`file.txt:4:16 cannot use template enum 'E' as template number`,
}, {
`
conv F()`,
`file.txt:1:6 conversions must have a single parameter`,
}, {
`
type i
conv F(i, i, i)`,
`file.txt:2:6 conversions must have a single parameter`,
}, {
`
type i
enum e { a }
conv F(i) -> e`,
`file.txt:3:14 cannot use 'e' as return type. Must be a type or template type`,
}, {
`
type T<x>
conv F(T<u>)`,
`file.txt:2:10 cannot resolve 'u'`,
}, {
`
type x
conv F<T>(T<x>)`,
`file.txt:2:11 'T' template parameters do not accept template arguments`,
}, {
`
type A<N: num>
type B
conv F(A<B>)`,
`file.txt:3:10 cannot use type 'B' as template number`,
}, {
`
type A<N>
enum E { b }
conv F(A<b>)`,
`file.txt:3:10 cannot use enum entry 'E.b' as template type`,
}, {
`
type T
type P<N: num>
match m: T
conv F(P<m>)`,
`file.txt:4:10 cannot use type matcher 'm' as template number`,
}, {
`
type P<N: num>
enum E { b }
conv F(P<E>)`,
`file.txt:3:10 cannot use enum 'E' as template number`,
}, {
`
type P<N: num>
enum E { a b }
match m: a | b
conv F(P<m>)`,
`file.txt:4:10 cannot use enum matcher 'm' as template number`,
}, {
`
type P<N: num>
enum E { a b }
match m: a | b
conv F<M: m>(P<M>)`,
`file.txt:4:16 cannot use template enum 'E' as template number`,
}, {
`
enum E { a }
type T<X: a>`,
`file.txt:2:8 invalid template parameter type 'a'`,
}, {
`
enum E { a }
fn f<M: a>()`,
`file.txt:2:6 invalid template parameter type 'a'`,
},
} {
ast, err := parser.Parse(strings.TrimSpace(string(test.src)), "file.txt")
if err != nil {
t.Errorf("Unexpected parser error: %v", err)
continue
}
expectErr := strings.TrimSpace(test.err)
_, err = resolver.Resolve(ast)
if err != nil {
gotErr := strings.TrimSpace(fmt.Sprint(err))
if gotErr != expectErr {
t.Errorf("While parsing:\n%s\nGot error:\n%s\nExpected:\n%s", test.src, gotErr, expectErr)
}
} else if expectErr != success {
t.Errorf("While parsing:\n%s\nGot no error, expected error:\n%s", test.src, expectErr)
}
}
}

View File

@@ -0,0 +1,308 @@
// Copyright 2021 The Tint Authors.
//
// 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 sem
import (
"fmt"
"sort"
"dawn.googlesource.com/dawn/tools/src/tint/intrinsic/ast"
)
// Sem is the root of the semantic tree
type Sem struct {
Enums []*Enum
Types []*Type
TypeMatchers []*TypeMatcher
EnumMatchers []*EnumMatcher
Builtins []*Intrinsic
UnaryOperators []*Intrinsic
BinaryOperators []*Intrinsic
ConstructorsAndConverters []*Intrinsic
// Maximum number of template types used across all builtins
MaxTemplateTypes int
// Maximum number of template numbers used across all builtins
MaxTemplateNumbers int
// The alphabetically sorted list of unique parameter names
UniqueParameterNames []string
}
// New returns a new Sem
func New() *Sem {
return &Sem{
Enums: []*Enum{},
Types: []*Type{},
TypeMatchers: []*TypeMatcher{},
EnumMatchers: []*EnumMatcher{},
Builtins: []*Intrinsic{},
UnaryOperators: []*Intrinsic{},
BinaryOperators: []*Intrinsic{},
}
}
// Enum describes an enumerator
type Enum struct {
Decl ast.EnumDecl
Name string
Entries []*EnumEntry
}
// FindEntry returns the enum entry with the given name
func (e *Enum) FindEntry(name string) *EnumEntry {
for _, entry := range e.Entries {
if entry.Name == name {
return entry
}
}
return nil
}
// EnumEntry is an entry in an enumerator
type EnumEntry struct {
Enum *Enum
Name string
IsInternal bool // True if this entry is not part of the WGSL grammar
}
// Format implements the fmt.Formatter interface
func (e EnumEntry) Format(w fmt.State, verb rune) {
if e.IsInternal {
fmt.Fprint(w, "[[internal]] ")
}
fmt.Fprint(w, e.Name)
}
// Type declares a type
type Type struct {
TemplateParams []TemplateParam
Decl ast.TypeDecl
Name string
DisplayName string
Precedence int
}
// TypeMatcher declares a type matcher
type TypeMatcher struct {
TemplateParams []TemplateParam
Decl ast.MatcherDecl
Name string
Types []*Type
}
func (t TypeMatcher) PrecedenceSortedTypes() []*Type {
out := make([]*Type, len(t.Types))
copy(out, t.Types)
sort.Slice(out, func(i, j int) bool { return out[i].Precedence > out[j].Precedence })
return out
}
// EnumMatcher declares a enum matcher
type EnumMatcher struct {
TemplateParams []TemplateParam
Decl ast.MatcherDecl
Name string
Enum *Enum
Options []*EnumEntry
}
// TemplateEnumParam is a template enum parameter
type TemplateEnumParam struct {
Name string
Enum *Enum
Matcher *EnumMatcher // Optional
}
// TemplateTypeParam is a template type parameter
type TemplateTypeParam struct {
Name string
Type ResolvableType
}
// TemplateNumberParam is a template type parameter
type TemplateNumberParam struct {
Name string
}
// Intrinsic describes the overloads of a builtin or operator
type Intrinsic struct {
Name string
Overloads []*Overload
}
// Overload describes a single overload of a builtin or operator
type Overload struct {
Decl ast.IntrinsicDecl
Intrinsic *Intrinsic
TemplateParams []TemplateParam
TemplateTypes []*TemplateTypeParam
TemplateNumbers []TemplateParam
ReturnType *FullyQualifiedName
Parameters []Parameter
CanBeUsedInStage StageUses
IsDeprecated bool // True if this overload is deprecated
ConstEvalFunction string // Name of the function used to evaluate the intrinsic at shader creation time
}
// StageUses describes the stages an overload can be used in
type StageUses struct {
Vertex bool
Fragment bool
Compute bool
}
// List returns the stage uses as a string list
func (u StageUses) List() []string {
out := []string{}
if u.Vertex {
out = append(out, "vertex")
}
if u.Fragment {
out = append(out, "fragment")
}
if u.Compute {
out = append(out, "compute")
}
return out
}
// Format implements the fmt.Formatter interface
func (o Overload) Format(w fmt.State, verb rune) {
switch o.Decl.Kind {
case ast.Builtin:
fmt.Fprintf(w, "fn ")
case ast.Operator:
fmt.Fprintf(w, "op ")
}
fmt.Fprintf(w, "%v", o.Intrinsic.Name)
if len(o.TemplateParams) > 0 {
fmt.Fprintf(w, "<")
for i, t := range o.TemplateParams {
if i > 0 {
fmt.Fprint(w, ", ")
}
fmt.Fprintf(w, "%v", t)
}
fmt.Fprintf(w, ">")
}
fmt.Fprint(w, "(")
for i, p := range o.Parameters {
if i > 0 {
fmt.Fprint(w, ", ")
}
fmt.Fprintf(w, "%v", p)
}
fmt.Fprint(w, ")")
if o.ReturnType != nil {
fmt.Fprintf(w, " -> %v", o.ReturnType)
}
}
// Parameter describes a single parameter of a function overload
type Parameter struct {
Name string
Type FullyQualifiedName
IsConst bool // Did this parameter have a @const attribute?
}
// Format implements the fmt.Formatter interface
func (p Parameter) Format(w fmt.State, verb rune) {
if p.IsConst {
fmt.Fprint(w, "@const ")
}
if p.Name != "" {
fmt.Fprintf(w, "%v: ", p.Name)
}
fmt.Fprintf(w, "%v", p.Type)
}
// FullyQualifiedName is the usage of a Type, TypeMatcher or TemplateTypeParam
type FullyQualifiedName struct {
Target Named
TemplateArguments []interface{}
}
// Format implements the fmt.Formatter interface
func (f FullyQualifiedName) Format(w fmt.State, verb rune) {
fmt.Fprint(w, f.Target.GetName())
if len(f.TemplateArguments) > 0 {
fmt.Fprintf(w, "<")
for i, t := range f.TemplateArguments {
if i > 0 {
fmt.Fprint(w, ", ")
}
fmt.Fprintf(w, "%v", t)
}
fmt.Fprintf(w, ">")
}
}
// TemplateParam is a TemplateEnumParam, TemplateTypeParam or TemplateNumberParam
type TemplateParam interface {
Named
isTemplateParam()
}
func (*TemplateEnumParam) isTemplateParam() {}
func (*TemplateTypeParam) isTemplateParam() {}
func (*TemplateNumberParam) isTemplateParam() {}
// ResolvableType is a Type, TypeMatcher or TemplateTypeParam
type ResolvableType interface {
Named
isResolvableType()
}
func (*Type) isResolvableType() {}
func (*TypeMatcher) isResolvableType() {}
func (*TemplateTypeParam) isResolvableType() {}
// Named is something that can be looked up by name
type Named interface {
isNamed()
GetName() string
}
func (*Enum) isNamed() {}
func (*EnumEntry) isNamed() {}
func (*Type) isNamed() {}
func (*TypeMatcher) isNamed() {}
func (*EnumMatcher) isNamed() {}
func (*TemplateTypeParam) isNamed() {}
func (*TemplateEnumParam) isNamed() {}
func (*TemplateNumberParam) isNamed() {}
// GetName returns the name of the Enum
func (e *Enum) GetName() string { return e.Name }
// GetName returns the name of the EnumEntry
func (e *EnumEntry) GetName() string { return e.Name }
// GetName returns the name of the Type
func (t *Type) GetName() string { return t.Name }
// GetName returns the name of the TypeMatcher
func (t *TypeMatcher) GetName() string { return t.Name }
// GetName returns the name of the EnumMatcher
func (e *EnumMatcher) GetName() string { return e.Name }
// GetName returns the name of the TemplateTypeParam
func (t *TemplateTypeParam) GetName() string { return t.Name }
// GetName returns the name of the TemplateEnumParam
func (t *TemplateEnumParam) GetName() string { return t.Name }
// GetName returns the name of the TemplateNumberParam
func (t *TemplateNumberParam) GetName() string { return t.Name }

View File

@@ -0,0 +1,139 @@
// Copyright 2021 The Tint Authors.
//
// 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 tok defines tokens that are produced by the Tint intrinsic definition
// lexer
package tok
import "fmt"
// Kind is an enumerator of token kinds
type Kind string
// Token enumerator types
const (
InvalidToken Kind = "<invalid>"
Identifier Kind = "ident"
Integer Kind = "integer"
String Kind = "string"
Match Kind = "match"
Function Kind = "fn"
Operator Kind = "op"
Constructor Kind = "ctor"
Converter Kind = "conv"
Type Kind = "type"
Enum Kind = "enum"
And Kind = "&"
AndAnd Kind = "&&"
Arrow Kind = "->"
Attr Kind = "@"
Assign Kind = "="
Colon Kind = ":"
Comma Kind = ","
Complement Kind = "~"
Divide Kind = "/"
Equal Kind = "=="
Ge Kind = ">="
Gt Kind = ">"
Lbrace Kind = "{"
Le Kind = "<="
Lparen Kind = "("
Lt Kind = "<"
Minus Kind = "-"
Modulo Kind = "%"
Not Kind = "!"
NotEqual Kind = "!="
Or Kind = "|"
OrOr Kind = "||"
Plus Kind = "+"
Rbrace Kind = "}"
Rparen Kind = ")"
Shl Kind = "<<"
Shr Kind = ">>"
Star Kind = "*"
Xor Kind = "^"
)
// Invalid represents an invalid token
var Invalid = Token{Kind: InvalidToken}
// Location describes a rune location in the source code
type Location struct {
// 1-based line index
Line int
// 1-based column index
Column int
// 0-based rune index
Rune int
// Optional file path
Filepath string
}
// Format implements the fmt.Formatter interface
func (l Location) Format(w fmt.State, verb rune) {
if w.Flag('+') {
if l.Filepath != "" {
fmt.Fprintf(w, "%v:%v:%v[%v]", l.Filepath, l.Line, l.Column, l.Rune)
} else {
fmt.Fprintf(w, "%v:%v[%v]", l.Line, l.Column, l.Rune)
}
} else {
if l.Filepath != "" {
fmt.Fprintf(w, "%v:%v:%v", l.Filepath, l.Line, l.Column)
} else {
fmt.Fprintf(w, "%v:%v", l.Line, l.Column)
}
}
}
// Source describes a start and end range in the source code
type Source struct {
S, E Location
}
// IsValid returns true if the source is valid
func (s Source) IsValid() bool {
return s.S.Line != 0 && s.S.Column != 0 && s.E.Line != 0 && s.E.Column != 0
}
// Format implements the fmt.Formatter interface
func (s Source) Format(w fmt.State, verb rune) {
if w.Flag('+') {
fmt.Fprint(w, "[")
s.S.Format(w, verb)
fmt.Fprint(w, " - ")
s.E.Format(w, verb)
fmt.Fprint(w, "]")
} else {
s.S.Format(w, verb)
}
}
// Token describes a parsed token
type Token struct {
Kind Kind
Runes []rune
Source Source
}
// Format implements the fmt.Formatter interface
func (t Token) Format(w fmt.State, verb rune) {
fmt.Fprint(w, "[")
t.Source.Format(w, verb)
fmt.Fprint(w, " ")
fmt.Fprint(w, t.Kind)
fmt.Fprint(w, " ")
fmt.Fprint(w, string(t.Runes))
fmt.Fprint(w, "]")
}