blob: 3aad4f0068692841f1abd6152b4770feabd14935 [file] [edit]
#!/usr/bin/env python3
# Copyright 2026 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.
"""Unit tests for parser.py and def_ast.py."""
import unittest
import def_ast as ast
import parser
class ParserTest(unittest.TestCase):
def test_parser(self):
cases: list[tuple[str, ast.AST]] = [
(
"enum E {}",
ast.AST(enums=[ast.EnumDecl(name="E")]),
),
(
"enum E { A @attr B C }",
ast.AST(enums=[
ast.EnumDecl(
name="E",
entries=[
ast.EnumEntry(name="A"),
ast.EnumEntry(
attributes=ast.Attributes(
[ast.Attribute(name="attr")]),
name="B",
),
ast.EnumEntry(name="C"),
],
)
]),
),
(
"type T",
ast.AST(types=[ast.TypeDecl(name="T")]),
),
(
"type T<A, B, C>",
ast.AST(types=[
ast.TypeDecl(
name="T",
template_params=[
ast.TemplateParam(name="A"),
ast.TemplateParam(name="B"),
ast.TemplateParam(name="C"),
],
)
]),
),
(
"@attr type T",
ast.AST(types=[
ast.TypeDecl(
attributes=ast.Attributes([ast.Attribute(
name="attr")]),
name="T",
)
]),
),
(
"@attr_a @attr_b type T",
ast.AST(types=[
ast.TypeDecl(
attributes=ast.Attributes([
ast.Attribute(name="attr_a"),
ast.Attribute(name="attr_b"),
]),
name="T",
)
]),
),
(
'@attr("a", "b") type T',
ast.AST(types=[
ast.TypeDecl(
attributes=ast.Attributes(
[ast.Attribute(name="attr", values=["a", "b"])]),
name="T",
)
]),
),
(
'@attr(1, "x", 2.0) type T',
ast.AST(types=[
ast.TypeDecl(
attributes=ast.Attributes([
ast.Attribute(name="attr", values=[1, "x", 2.0])
]),
name="T",
)
]),
),
(
"match M : A",
ast.AST(matchers=[
ast.MatcherDecl(
name="M",
options=ast.MatcherOptions(
types=[ast.TemplatedName(name="A")]),
)
]),
),
(
"match M : A | B",
ast.AST(matchers=[
ast.MatcherDecl(
name="M",
options=ast.MatcherOptions(types=[
ast.TemplatedName(name="A"),
ast.TemplatedName(name="B"),
]),
)
]),
),
(
"match M : A.B",
ast.AST(matchers=[
ast.MatcherDecl(
name="M",
options=ast.MatcherOptions(
enumerants=[ast.MemberName(owner="A", member="B")
]),
)
]),
),
(
"match M : A.B | B.C",
ast.AST(matchers=[
ast.MatcherDecl(
name="M",
options=ast.MatcherOptions(enumerants=[
ast.MemberName(owner="A", member="B"),
ast.MemberName(owner="B", member="C"),
]),
)
]),
),
(
"fn F()",
ast.AST(builtins=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Builtin,
name="F",
parameters=[],
)
]),
),
(
"@attr fn F()",
ast.AST(builtins=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Builtin,
name="F",
attributes=ast.Attributes([ast.Attribute(
name="attr")]),
parameters=[],
)
]),
),
(
"fn F(a)",
ast.AST(builtins=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Builtin,
name="F",
parameters=[
ast.Parameter(type=ast.TemplatedName(name="a"))
],
)
]),
),
(
"fn F(a: T)",
ast.AST(builtins=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Builtin,
name="F",
parameters=[
ast.Parameter(name="a",
type=ast.TemplatedName(name="T"))
],
)
]),
),
(
"fn F(a, b)",
ast.AST(builtins=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Builtin,
name="F",
parameters=[
ast.Parameter(type=ast.TemplatedName(name="a")),
ast.Parameter(type=ast.TemplatedName(name="b")),
],
)
]),
),
(
"fn F<A : B<C> >()",
ast.AST(builtins=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Builtin,
name="F",
explicit_template_params=[
ast.TemplateParam(
name="A",
type=ast.TemplatedName(
name="B",
template_args=[
ast.TemplatedName(name="C")
],
),
)
],
parameters=[],
)
]),
),
(
"fn F<T>(a: X, b: Y<T>)",
ast.AST(builtins=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Builtin,
name="F",
explicit_template_params=[ast.TemplateParam(name="T")],
parameters=[
ast.Parameter(name="a",
type=ast.TemplatedName(name="X")),
ast.Parameter(
name="b",
type=ast.TemplatedName(
name="Y",
template_args=[
ast.TemplatedName(name="T")
],
),
),
],
)
]),
),
(
"implicit(A : B<C>) fn F()",
ast.AST(builtins=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Builtin,
name="F",
implicit_template_params=[
ast.TemplateParam(
name="A",
type=ast.TemplatedName(
name="B",
template_args=[
ast.TemplatedName(name="C")
],
),
)
],
parameters=[],
)
]),
),
(
"implicit(T) fn F(a: X, b: Y<T>)",
ast.AST(builtins=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Builtin,
name="F",
implicit_template_params=[ast.TemplateParam(name="T")],
parameters=[
ast.Parameter(name="a",
type=ast.TemplatedName(name="X")),
ast.Parameter(
name="b",
type=ast.TemplatedName(
name="Y",
template_args=[
ast.TemplatedName(name="T")
],
),
),
],
)
]),
),
(
"implicit(B: C<D>) fn F<A>()",
ast.AST(builtins=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Builtin,
name="F",
explicit_template_params=[ast.TemplateParam(name="A")],
implicit_template_params=[
ast.TemplateParam(
name="B",
type=ast.TemplatedName(
name="C",
template_args=[
ast.TemplatedName(name="D")
],
),
)
],
parameters=[],
)
]),
),
(
"fn F() -> X",
ast.AST(builtins=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Builtin,
name="F",
return_type=ast.TemplatedName(name="X"),
parameters=[],
)
]),
),
(
"fn F() -> X<T>",
ast.AST(builtins=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Builtin,
name="F",
return_type=ast.TemplatedName(
name="X",
template_args=[ast.TemplatedName(name="T")],
),
parameters=[],
)
]),
),
(
"op F()",
ast.AST(operators=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Operator,
name="F",
parameters=[],
)
]),
),
(
"@attr op F()",
ast.AST(operators=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Operator,
name="F",
attributes=ast.Attributes([ast.Attribute(
name="attr")]),
parameters=[],
)
]),
),
(
"op F(a)",
ast.AST(operators=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Operator,
name="F",
parameters=[
ast.Parameter(type=ast.TemplatedName(name="a"))
],
)
]),
),
(
"op F(@blah a)",
ast.AST(operators=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Operator,
name="F",
parameters=[
ast.Parameter(
attributes=ast.Attributes(
[ast.Attribute(name="blah")]),
type=ast.TemplatedName(name="a"),
)
],
)
]),
),
(
"op F(a: T)",
ast.AST(operators=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Operator,
name="F",
parameters=[
ast.Parameter(name="a",
type=ast.TemplatedName(name="T"))
],
)
]),
),
(
"op F(a, b)",
ast.AST(operators=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Operator,
name="F",
parameters=[
ast.Parameter(type=ast.TemplatedName(name="a")),
ast.Parameter(type=ast.TemplatedName(name="b")),
],
)
]),
),
(
"op F<A : B<C> >()",
ast.AST(operators=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Operator,
name="F",
explicit_template_params=[
ast.TemplateParam(
name="A",
type=ast.TemplatedName(
name="B",
template_args=[
ast.TemplatedName(name="C")
],
),
)
],
parameters=[],
)
]),
),
(
"op F<T>(a: X, b: Y<T>)",
ast.AST(operators=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Operator,
name="F",
explicit_template_params=[ast.TemplateParam(name="T")],
parameters=[
ast.Parameter(name="a",
type=ast.TemplatedName(name="X")),
ast.Parameter(
name="b",
type=ast.TemplatedName(
name="Y",
template_args=[
ast.TemplatedName(name="T")
],
),
),
],
)
]),
),
(
"implicit(A : B<C>) op F()",
ast.AST(operators=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Operator,
name="F",
implicit_template_params=[
ast.TemplateParam(
name="A",
type=ast.TemplatedName(
name="B",
template_args=[
ast.TemplatedName(name="C")
],
),
)
],
parameters=[],
)
]),
),
(
"implicit(T) op F(a: X, b: Y<T>)",
ast.AST(operators=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Operator,
name="F",
implicit_template_params=[ast.TemplateParam(name="T")],
parameters=[
ast.Parameter(name="a",
type=ast.TemplatedName(name="X")),
ast.Parameter(
name="b",
type=ast.TemplatedName(
name="Y",
template_args=[
ast.TemplatedName(name="T")
],
),
),
],
)
]),
),
(
"implicit(D) op F<A : B<C> >()",
ast.AST(operators=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Operator,
name="F",
explicit_template_params=[
ast.TemplateParam(
name="A",
type=ast.TemplatedName(
name="B",
template_args=[
ast.TemplatedName(name="C")
],
),
)
],
implicit_template_params=[ast.TemplateParam(name="D")],
parameters=[],
)
]),
),
(
"op F() -> X",
ast.AST(operators=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Operator,
name="F",
return_type=ast.TemplatedName(name="X"),
parameters=[],
)
]),
),
(
"op F() -> X<T>",
ast.AST(operators=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Operator,
name="F",
return_type=ast.TemplatedName(
name="X",
template_args=[ast.TemplatedName(name="T")],
),
parameters=[],
)
]),
),
(
"ctor F()",
ast.AST(constructors=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Constructor,
name="F",
parameters=[],
)
]),
),
(
"@attr ctor F()",
ast.AST(constructors=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Constructor,
name="F",
attributes=ast.Attributes([ast.Attribute(
name="attr")]),
parameters=[],
)
]),
),
(
"ctor F(a)",
ast.AST(constructors=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Constructor,
name="F",
parameters=[
ast.Parameter(type=ast.TemplatedName(name="a"))
],
)
]),
),
(
"ctor F(a: T)",
ast.AST(constructors=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Constructor,
name="F",
parameters=[
ast.Parameter(name="a",
type=ast.TemplatedName(name="T"))
],
)
]),
),
(
"ctor F(a, b)",
ast.AST(constructors=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Constructor,
name="F",
parameters=[
ast.Parameter(type=ast.TemplatedName(name="a")),
ast.Parameter(type=ast.TemplatedName(name="b")),
],
)
]),
),
(
"ctor F<A : B<C> >()",
ast.AST(constructors=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Constructor,
name="F",
explicit_template_params=[
ast.TemplateParam(
name="A",
type=ast.TemplatedName(
name="B",
template_args=[
ast.TemplatedName(name="C")
],
),
)
],
parameters=[],
)
]),
),
(
"ctor F<T>(a: X, b: Y<T>)",
ast.AST(constructors=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Constructor,
name="F",
explicit_template_params=[ast.TemplateParam(name="T")],
parameters=[
ast.Parameter(name="a",
type=ast.TemplatedName(name="X")),
ast.Parameter(
name="b",
type=ast.TemplatedName(
name="Y",
template_args=[
ast.TemplatedName(name="T")
],
),
),
],
)
]),
),
(
"ctor F() -> X",
ast.AST(constructors=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Constructor,
name="F",
return_type=ast.TemplatedName(name="X"),
parameters=[],
)
]),
),
(
"ctor F() -> X<T>",
ast.AST(constructors=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Constructor,
name="F",
return_type=ast.TemplatedName(
name="X",
template_args=[ast.TemplatedName(name="T")],
),
parameters=[],
)
]),
),
(
"conv F()",
ast.AST(converters=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Converter,
name="F",
parameters=[],
)
]),
),
(
"@attr conv F()",
ast.AST(converters=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Converter,
name="F",
attributes=ast.Attributes([ast.Attribute(
name="attr")]),
parameters=[],
)
]),
),
(
"conv F(a)",
ast.AST(converters=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Converter,
name="F",
parameters=[
ast.Parameter(type=ast.TemplatedName(name="a"))
],
)
]),
),
(
"conv F(a: T)",
ast.AST(converters=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Converter,
name="F",
parameters=[
ast.Parameter(name="a",
type=ast.TemplatedName(name="T"))
],
)
]),
),
(
"conv F(a, b)",
ast.AST(converters=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Converter,
name="F",
parameters=[
ast.Parameter(type=ast.TemplatedName(name="a")),
ast.Parameter(type=ast.TemplatedName(name="b")),
],
)
]),
),
(
"conv F<A : B<C> >()",
ast.AST(converters=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Converter,
name="F",
explicit_template_params=[
ast.TemplateParam(
name="A",
type=ast.TemplatedName(
name="B",
template_args=[
ast.TemplatedName(name="C")
],
),
)
],
parameters=[],
)
]),
),
(
"conv F<T>(a: X, b: Y<T>)",
ast.AST(converters=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Converter,
name="F",
explicit_template_params=[ast.TemplateParam(name="T")],
parameters=[
ast.Parameter(name="a",
type=ast.TemplatedName(name="X")),
ast.Parameter(
name="b",
type=ast.TemplatedName(
name="Y",
template_args=[
ast.TemplatedName(name="T")
],
),
),
],
)
]),
),
(
"conv F() -> X",
ast.AST(converters=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Converter,
name="F",
return_type=ast.TemplatedName(name="X"),
parameters=[],
)
]),
),
(
"conv F() -> X<T>",
ast.AST(converters=[
ast.IntrinsicDecl(
kind=ast.IntrinsicKind.Converter,
name="F",
return_type=ast.TemplatedName(
name="X",
template_args=[ast.TemplatedName(name="T")],
),
parameters=[],
)
]),
),
]
for src, expect in cases:
with self.subTest(src=src):
got = parser.parse(src, "test.txt", {})
self.assertEqual(got, expect)
def test_parser_error(self):
cases = [
("£", "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'",
),
]
for src, expect in cases:
with self.subTest(src=src):
with self.assertRaises(parser.ParseError) as ctx:
parser.parse(src, "test.txt", {})
self.assertEqual(str(ctx.exception), expect)
def test_parser_with_imports(self):
cases: list[tuple[str, dict[str, str], ast.AST]] = [
(
'import "foo"',
{
"foo": "type T"
},
ast.AST(types=[ast.TypeDecl(name="T")]),
),
(
'import "foo"',
{
"foo": 'import "bar"',
"bar": "type T",
},
ast.AST(types=[ast.TypeDecl(name="T")]),
),
]
for src, files, expect in cases:
with self.subTest(src=src):
got = parser.parse(src, "root.txt", files)
self.assertEqual(got, expect)
def test_parser_import_errors(self):
cases = [
(
'import "missing"',
{},
"root.txt:1:1 failed to load 'missing': open missing: file does not exist",
),
(
'import "broken"',
{
"broken": "£"
},
"broken:1:1: unexpected '£'",
),
]
for src, files, expect in cases:
with self.subTest(src=src):
with self.assertRaises(parser.ParseError) as ctx:
parser.parse(src, "root.txt", files)
self.assertEqual(str(ctx.exception), expect)
def test_ast_formatting_and_attributes_take(self):
parsed = parser.parse(
"""
enum E { A @attr(1, "x") B }
@attr type T<X: Y>
match M1 : A | B<C>
match M2 : E.A | E.B
implicit(I: J) fn F<U>(@p a: T<U>) -> R
op +(a: T, b: T) -> T
ctor T(a: U) -> T
conv T(a: U) -> T
""",
"test.def",
{},
)
formatted = str(parsed)
self.assertIn("enum E {", formatted)
self.assertIn("match M1: A | B<C>", formatted)
self.assertIn("match M2: E.A | E.B", formatted)
self.assertIn("fn F<U>[I : J](@p a: T<U>) -> R", formatted)
attrs = ast.Attributes(
[ast.Attribute(name="foo"),
ast.Attribute(name="bar", values=[1])])
self.assertIsNone(attrs.take("baz"))
taken = attrs.take("foo")
self.assertIsNotNone(taken)
self.assertEqual(taken.name, "foo")
self.assertEqual(len(attrs), 1)
def test_parse_real_def_files(self):
for rel_path in (
"src/tint/lang/core/core.def",
"src/tint/lang/wgsl/wgsl.def",
"src/tint/lang/glsl/glsl.def",
"src/tint/lang/hlsl/hlsl.def",
"src/tint/lang/msl/msl.def",
"src/tint/lang/spirv/spirv.def",
):
with self.subTest(rel_path=rel_path):
full_path = parser.DAWN_ROOT / rel_path
source = full_path.read_text(encoding="utf-8")
parsed = parser.parse(source, rel_path)
self.assertGreater(len(parsed.types), 0)
self.assertGreater(len(parsed.builtins), 0)
if __name__ == "__main__":
unittest.main()