|  | // Copyright 2021 The Dawn & Tint Authors | 
|  | // | 
|  | // Redistribution and use in source and binary forms, with or without | 
|  | // modification, are permitted provided that the following conditions are met: | 
|  | // | 
|  | // 1. Redistributions of source code must retain the above copyright notice, this | 
|  | //    list of conditions and the following disclaimer. | 
|  | // | 
|  | // 2. Redistributions in binary form must reproduce the above copyright notice, | 
|  | //    this list of conditions and the following disclaimer in the documentation | 
|  | //    and/or other materials provided with the distribution. | 
|  | // | 
|  | // 3. Neither the name of the copyright holder nor the names of its | 
|  | //    contributors may be used to endorse or promote products derived from | 
|  | //    this software without specific prior written permission. | 
|  | // | 
|  | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | 
|  | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
|  | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | 
|  | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | 
|  | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | 
|  | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | 
|  | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | 
|  | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | 
|  | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
|  | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
|  |  | 
|  | // Package parser provides a basic parser for the Tint builtin definition | 
|  | // language | 
|  | package parser | 
|  |  | 
|  | import ( | 
|  | "fmt" | 
|  | "os" | 
|  | "strconv" | 
|  |  | 
|  | "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) { | 
|  | out := &ast.AST{} | 
|  | if err := parse(source, filepath, out); err != nil { | 
|  | return nil, err | 
|  | } | 
|  | return out, nil | 
|  | } | 
|  |  | 
|  | func parse(source, filepath string, out *ast.AST) error { | 
|  | runes := []rune(source) | 
|  | tokens, err := lexer.Lex(runes, filepath) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  | p := parser{tokens: tokens} | 
|  | return p.parse(out) | 
|  | } | 
|  |  | 
|  | type parser struct { | 
|  | tokens []tok.Token | 
|  | err    error | 
|  | } | 
|  |  | 
|  | func (p *parser) parse(out *ast.AST) error { | 
|  | var attributes ast.Attributes | 
|  | var implicitParams []ast.TemplateParam | 
|  | 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) | 
|  | } | 
|  | if len(implicitParams) > 0 { | 
|  | p.err = fmt.Errorf("%v unexpected implicitParams", implicitParams[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) | 
|  | } | 
|  | if len(implicitParams) > 0 { | 
|  | p.err = fmt.Errorf("%v unexpected implicitParams", implicitParams[0].Source) | 
|  | } | 
|  | out.Matchers = append(out.Matchers, p.matcherDecl()) | 
|  | case tok.Import: | 
|  | if len(attributes) > 0 { | 
|  | p.err = fmt.Errorf("%v unexpected attribute", attributes[0].Source) | 
|  | } | 
|  | if len(implicitParams) > 0 { | 
|  | p.err = fmt.Errorf("%v unexpected implicitParams", implicitParams[0].Source) | 
|  | } | 
|  | p.importDecl(out) | 
|  | case tok.Type: | 
|  | if len(implicitParams) > 0 { | 
|  | p.err = fmt.Errorf("%v unexpected implicitParams", implicitParams[0].Source) | 
|  | } | 
|  | out.Types = append(out.Types, p.typeDecl(attributes)) | 
|  | attributes = nil | 
|  | case tok.Function: | 
|  | out.Builtins = append(out.Builtins, p.builtinDecl(attributes, implicitParams)) | 
|  | attributes = nil | 
|  | implicitParams = nil | 
|  | case tok.Operator: | 
|  | out.Operators = append(out.Operators, p.operatorDecl(attributes, implicitParams)) | 
|  | attributes = nil | 
|  | implicitParams = nil | 
|  | case tok.Constructor: | 
|  | out.Constructors = append(out.Constructors, p.constructorDecl(attributes, implicitParams)) | 
|  | attributes = nil | 
|  | implicitParams = nil | 
|  | case tok.Converter: | 
|  | out.Converters = append(out.Converters, p.converterDecl(attributes, implicitParams)) | 
|  | attributes = nil | 
|  | implicitParams = nil | 
|  | case tok.Implicit: | 
|  | p.expect(tok.Implicit, "implicit") | 
|  | p.expect(tok.Lparen, "implicit template parameter list") | 
|  | for p.err == nil && p.peekIs(0, tok.Identifier) { | 
|  | implicitParams = append(implicitParams, p.templateParam()) | 
|  | } | 
|  | p.expect(tok.Rparen, "implicit template parameter list") | 
|  |  | 
|  | default: | 
|  | p.err = fmt.Errorf("%v unexpected token '%v'", t.Source, t.Kind) | 
|  | } | 
|  | if p.err != nil { | 
|  | return p.err | 
|  | } | 
|  | } | 
|  | return 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") | 
|  | if p.peekIs(1, tok.Dot) { // enum list | 
|  | for p.err == nil { | 
|  | m.Options.Enums = append(m.Options.Enums, p.memberName()) | 
|  | if p.match(tok.Or) == nil { | 
|  | break | 
|  | } | 
|  | } | 
|  | } else { // type list | 
|  | for p.err == nil { | 
|  | m.Options.Types = append(m.Options.Types, p.templatedName()) | 
|  | if p.match(tok.Or) == nil { | 
|  | break | 
|  | } | 
|  | } | 
|  | } | 
|  | return m | 
|  | } | 
|  |  | 
|  | func (p *parser) importDecl(out *ast.AST) { | 
|  | p.expect(tok.Import, "import declaration") | 
|  | path := p.string() | 
|  |  | 
|  | content, err := os.ReadFile(path) | 
|  | if err != nil { | 
|  | p.err = fmt.Errorf("%v failed to load '%v': %w", | 
|  | p.tokens[0].Source, path, err) | 
|  | return | 
|  | } | 
|  |  | 
|  | p.err = parse(string(content), path, out) | 
|  | } | 
|  |  | 
|  | 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), | 
|  | } | 
|  | m.TemplateParams = p.typeTemplateParams() | 
|  |  | 
|  | 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") | 
|  | var values []any | 
|  | if p.match(tok.Lparen) != nil { | 
|  | loop: | 
|  | for p.err == nil { | 
|  | t := p.next() | 
|  | switch t.Kind { | 
|  | case tok.Rparen: | 
|  | break loop | 
|  | case tok.String: | 
|  | values = append(values, string(t.Runes)) | 
|  | case tok.Integer: | 
|  | i, _ := strconv.ParseInt(string(t.Runes), 10, 64) | 
|  | values = append(values, int(i)) | 
|  | case tok.Float: | 
|  | f, _ := strconv.ParseFloat(string(t.Runes), 64) | 
|  | values = append(values, f) | 
|  | default: | 
|  | p.err = fmt.Errorf("%v invalid attribute value kind: %v", t.Source, t.Kind) | 
|  | return nil | 
|  | } | 
|  | 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, implicitParams []ast.TemplateParam) 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), | 
|  | } | 
|  | f.ExplicitTemplateParams = p.intrinsicTemplateParams() | 
|  | f.ImplicitTemplateParams = implicitParams | 
|  | 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, implicitParams []ast.TemplateParam) 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), | 
|  | } | 
|  | f.ExplicitTemplateParams = p.intrinsicTemplateParams() | 
|  | f.ImplicitTemplateParams = implicitParams | 
|  | 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, implicitParams []ast.TemplateParam) 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), | 
|  | } | 
|  | f.ExplicitTemplateParams = p.intrinsicTemplateParams() | 
|  | f.ImplicitTemplateParams = implicitParams | 
|  | 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, implicitParams []ast.TemplateParam) 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), | 
|  | } | 
|  | f.ExplicitTemplateParams = p.intrinsicTemplateParams() | 
|  | f.ImplicitTemplateParams = implicitParams | 
|  | 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) memberName() ast.MemberName { | 
|  | owner := p.expect(tok.Identifier, "member name") | 
|  | p.expect(tok.Dot, "member name") | 
|  | member := p.expect(tok.Identifier, "member name") | 
|  | return ast.MemberName{ | 
|  | Source: member.Source, | 
|  | Owner:  string(owner.Runes), | 
|  | Member: string(member.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) typeTemplateParams() []ast.TemplateParam { | 
|  | if p.match(tok.Lt) == nil { | 
|  | return nil | 
|  | } | 
|  | t := []ast.TemplateParam{} | 
|  | for p.err == nil && p.peekIs(0, tok.Identifier) { | 
|  | t = append(t, p.templateParam()) | 
|  | } | 
|  | p.expect(tok.Gt, "type template parameter list") | 
|  | return t | 
|  | } | 
|  |  | 
|  | func (p *parser) intrinsicTemplateParams() (explicit []ast.TemplateParam) { | 
|  | if p.match(tok.Lt) != nil { // <...> | 
|  | for p.err == nil && p.peekIs(0, tok.Identifier) { | 
|  | explicit = append(explicit, p.templateParam()) | 
|  | } | 
|  | p.expect(tok.Gt, "explicit template parameter list") | 
|  | } | 
|  | return explicit | 
|  | } | 
|  |  | 
|  | 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] | 
|  | } |