From 16e86d3225ac44c8e3b75887b9dbd11198e12163 Mon Sep 17 00:00:00 2001 From: Ben Clayton Date: Mon, 31 May 2021 19:45:20 +0000 Subject: [PATCH] Add cmd/intrinsic-gen resolver and sem Part of the new intrinsic definition parser. Bug: tint:832 Change-Id: I701072def1a4ca723d10d08b44c1e271b9458212 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/52540 Kokoro: Kokoro Reviewed-by: David Neto Commit-Queue: Ben Clayton --- .../src/cmd/intrinsic-gen/resolver/resolve.go | 584 ++++++++++++++++++ .../intrinsic-gen/resolver/resolver_test.go | 330 ++++++++++ tools/src/cmd/intrinsic-gen/sem/sem.go | 196 ++++++ 3 files changed, 1110 insertions(+) create mode 100644 tools/src/cmd/intrinsic-gen/resolver/resolve.go create mode 100644 tools/src/cmd/intrinsic-gen/resolver/resolver_test.go create mode 100644 tools/src/cmd/intrinsic-gen/sem/sem.go diff --git a/tools/src/cmd/intrinsic-gen/resolver/resolve.go b/tools/src/cmd/intrinsic-gen/resolver/resolve.go new file mode 100644 index 0000000000..792a33a3c1 --- /dev/null +++ b/tools/src/cmd/intrinsic-gen/resolver/resolve.go @@ -0,0 +1,584 @@ +// 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" + + "dawn.googlesource.com/tint/tools/src/cmd/intrinsic-gen/ast" + "dawn.googlesource.com/tint/tools/src/cmd/intrinsic-gen/sem" + "dawn.googlesource.com/tint/tools/src/cmd/intrinsic-gen/tok" +) + +type resolver struct { + a *ast.AST + s *sem.Sem + + globals scope + functions map[string]*sem.Function + 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), + functions: map[string]*sem.Function{}, + 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 functions + for _, f := range a.Functions { + if err := r.function(f); err != nil { + return nil, err + } + } + + 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, + Enum: s, + } + 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 decorations + if d := a.Decorations.Take("display"); d != nil { + if len(d.Values) != 1 { + return fmt.Errorf("%v expected a single value for 'display' decoration", d.Source) + } + t.DisplayName = d.Values[0] + } + if len(a.Decorations) != 0 { + return fmt.Errorf("%v unknown decoration", a.Decorations[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) +} + +// function() resolves a function overload declaration. +// The the first overload for the function creates and appends the sem.Function +// to Sem.Functions. Subsequent overloads append their resolved overload to the +// sem.Function.Overloads list. +func (r *resolver) function(a ast.FunctionDecl) error { + // If this is the first overload of the function, create and register the + // semantic function. + f := r.functions[a.Name] + if f == nil { + f = &sem.Function{Name: a.Name} + r.functions[a.Name] = f + r.s.Functions = append(r.s.Functions, f) + } + + // 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 and append it to the function + overload := &sem.Overload{ + Decl: a, + Parameters: make([]sem.Parameter, len(a.Parameters)), + TemplateParams: templateParams, + } + f.Overloads = append(f.Overloads, overload) + + // Sort the template parameters by resolved type. Append these to + // sem.Overload.OpenTypes or sem.Overload.OpenNumbers based on their kind. + for _, param := range templateParams { + switch param := param.(type) { + case *sem.TemplateTypeParam: + overload.OpenTypes = append(overload.OpenTypes, param) + case *sem.TemplateEnumParam, *sem.TemplateNumberParam: + overload.OpenNumbers = append(overload.OpenNumbers, param) + } + } + + // Update high-water marks of open types / numbers + if r.s.MaxOpenTypes < len(overload.OpenTypes) { + r.s.MaxOpenTypes = len(overload.OpenTypes) + } + if r.s.MaxOpenNumbers < len(overload.OpenNumbers) { + r.s.MaxOpenNumbers = len(overload.OpenNumbers) + } + + // Resolve the parameters + for i, p := range a.Parameters { + usage, err := r.fullyQualifiedName(&s, p.Type) + if err != nil { + return err + } + overload.Parameters[i] = sem.Parameter{ + Name: p.Name, + Type: usage, + } + } + + // 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([]sem.FullyQualifiedName, 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 + 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 +} + +// 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()], 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 +} diff --git a/tools/src/cmd/intrinsic-gen/resolver/resolver_test.go b/tools/src/cmd/intrinsic-gen/resolver/resolver_test.go new file mode 100644 index 0000000000..944ddb44e7 --- /dev/null +++ b/tools/src/cmd/intrinsic-gen/resolver/resolver_test.go @@ -0,0 +1,330 @@ +// 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/tint/tools/src/cmd/intrinsic-gen/parser" + "dawn.googlesource.com/tint/tools/src/cmd/intrinsic-gen/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()`, + success, + }, { + ` +type f32 +fn f()`, + success, + }, { + ` +enum e { a b c } +fn f()`, + success, + }, { + ` +type f32 +fn f(T) -> f32`, + success, + }, { + ` +type f32 +type P +match m: f32 +fn f(P) -> T`, + success, + }, { + ` +type f32 +type P +match m: f32 +fn f(P)`, + success, + }, { + ` +enum e { a } +fn f(a)`, + success, + }, { + ` +enum e { a b } +type T +match m: a +fn f(T)`, + success, + }, { + ` +enum e { a b } +type T +match m: a +fn f(T)`, + success, + }, { + ` +enum e { a } +type T +fn f(T)`, + success, + }, { + ` +type T +fn f(T)`, + success, + }, { + `fn f(T)`, + success, + }, { + ` +enum e { a b } +fn f()`, + success, + }, { + ` +enum e { a b } +match m: a | b +fn f()`, + success, + }, { + ` +type f32 +type T +fn f(T>)`, + 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:3 unknown decoration +`, + }, { + `[[display("Y", "Z")]] type X`, + ` +file.txt:1:3 expected a single value for 'display' decoration`, + }, { + ` +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 +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()`, + `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 +fn f(T)`, + `file.txt:2:8 cannot resolve 'u'`, + }, { + ` +type x +fn f(T)`, + `file.txt:2:9 'T' template parameters do not accept template arguments`, + }, { + ` +type A +type B +fn f(A)`, + `file.txt:3:8 cannot use type 'B' as template number`, + }, { + ` +type A +enum E { b } +fn f(A)`, + `file.txt:3:8 cannot use enum entry 'E.b' as template type`, + }, { + ` +type T +type P +match m: T +fn f(P)`, + `file.txt:4:8 cannot use type matcher 'm' as template number`, + }, { + ` +type P +enum E { b } +fn f(P)`, + `file.txt:3:8 cannot use enum 'E' as template number`, + }, { + ` +type P +enum E { a b } +match m: a | b +fn f(P)`, + `file.txt:4:8 cannot use enum matcher 'm' as template number`, + }, { + ` +type P +enum E { a b } +match m: a | b +fn f(P)`, + `file.txt:4:14 cannot use template enum 'E' as template number`, + }, { + ` +enum E { a } +type T`, + `file.txt:2:8 invalid template parameter type 'a'`, + }, { + ` +enum E { a } +fn f()`, + `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) + } + } +} diff --git a/tools/src/cmd/intrinsic-gen/sem/sem.go b/tools/src/cmd/intrinsic-gen/sem/sem.go new file mode 100644 index 0000000000..9cf135b7e5 --- /dev/null +++ b/tools/src/cmd/intrinsic-gen/sem/sem.go @@ -0,0 +1,196 @@ +// 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 ( + "dawn.googlesource.com/tint/tools/src/cmd/intrinsic-gen/ast" +) + +// Sem is the root of the semantic tree +type Sem struct { + Enums []*Enum + Types []*Type + TypeMatchers []*TypeMatcher + EnumMatchers []*EnumMatcher + Functions []*Function + // Maximum number of open-types used across all intrinsics + MaxOpenTypes int + // Maximum number of open-numbers used across all intrinsics + MaxOpenNumbers int +} + +// New returns a new Sem +func New() *Sem { + return &Sem{ + Enums: []*Enum{}, + Types: []*Type{}, + TypeMatchers: []*TypeMatcher{}, + EnumMatchers: []*EnumMatcher{}, + Functions: []*Function{}, + } +} + +// 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 +} + +// Type declares a type +type Type struct { + TemplateParams []TemplateParam + Decl ast.TypeDecl + Name string + DisplayName string +} + +// TypeMatcher declares a type matcher +type TypeMatcher struct { + TemplateParams []TemplateParam + Decl ast.MatcherDecl + Name string + Types []*Type +} + +// 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 +} + +// Function describes the overloads of an intrinsic function +type Function struct { + Name string + Overloads []*Overload +} + +// Overload describes a single overload of a function +type Overload struct { + Decl ast.FunctionDecl + TemplateParams []TemplateParam + OpenTypes []*TemplateTypeParam + OpenNumbers []TemplateParam + ReturnType *FullyQualifiedName + Parameters []Parameter +} + +// Parameter describes a single parameter of a function overload +type Parameter struct { + Name string + Type FullyQualifiedName +} + +// FullyQualifiedName is the usage of a Type, TypeMatcher or TemplateTypeParam +type FullyQualifiedName struct { + Target Named + TemplateArguments []FullyQualifiedName +} + +// 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 }