blob: cacbb13a63fcb74b04f2ad63b8e160de7bbae3bf [file] [log] [blame]
// 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
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.Import:
if len(attributes) > 0 {
p.err = fmt.Errorf("%v unexpected attribute", attributes[0].Source)
}
p.importDecl(out)
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 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),
}
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")
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) 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) 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) 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]
}