git.delta.rocks / jrsonnet / refs/commits / c678cf8e698f

difftreelog

refactor introduce ungrammar

Yaroslav Bolyukin2023-09-04parent: #5382799.patch.diff
in: master

23 files changed

added.cargo/configdiffbeforeafterboth
--- /dev/null
+++ b/.cargo/config
@@ -0,0 +1,2 @@
+[alias]
+xtask = "run --manifest-path ./xtask/Cargo.toml --"
modifiedCargo.tomldiffbeforeafterboth
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,7 @@
 [workspace]
 package.version = "0.5.0-pre95"
 package.repository = "https://github.com/CertainLach/jrsonnet"
-members = ["crates/*", "bindings/jsonnet", "cmds/*", "tests"]
+members = ["crates/*", "bindings/jsonnet", "cmds/*", "tests", "xtask"]
 default-members = ["cmds/jrsonnet"]
 resolver = "2"
 
modifiedcrates/jrsonnet-rowan-parser/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/Cargo.toml
+++ b/crates/jrsonnet-rowan-parser/Cargo.toml
@@ -18,3 +18,4 @@
 backtrace = "0.3.63"
 indoc = "1.0.3"
 insta = "1.10.0"
+anyhow = "1.0.57"
addedcrates/jrsonnet-rowan-parser/jsonnet.ungramdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/jsonnet.ungram
@@ -0,0 +1,334 @@
+SourceFile = Expr
+
+ExprBinary =
+    lhs:Expr
+    BinaryOperator
+    rhs:Expr
+ExprUnary =
+    UnaryOperator
+    rhs:Expr
+ExprSlice =
+    Expr
+    '['
+    SliceDesc
+    ']'
+ExprIndex =
+    Expr
+    '.'
+    index:Name
+ExprIndexExpr =
+    base:Expr
+    '['
+    index:Expr
+    ']'
+ExprApply =
+    Expr
+    '('
+    ArgsDesc
+    ')'
+    'tailstrict'?
+ExprObjExtend =
+    Expr
+    '{'
+    ObjBody
+    '}'
+ExprParened =
+    '('
+    Expr
+    ')'
+
+ExprLiteral =
+    Literal
+ExprIntrinsicThisFile =
+    '$intrinsicThisFile'
+ExprIntrinsicId =
+    '$intrinsicId'
+ExprIntrinsic =
+    '$intrinsic'
+    '('
+    name:Name
+    ')'
+ExprString =
+    String
+ExprNumber =
+    Number
+ExprArray =
+    '['
+    (Expr (',' Expr)* ','?)?
+    ']'
+ExprObject =
+    '{'
+    ObjBody
+    '}'
+ExprArrayComp =
+    '['
+    Expr
+    ','?
+    ForSpec
+    CompSpec*
+    ']'
+ExprImport =
+    'importstr' String
+|   'importbin' String
+|   'import' String
+
+ExprVar =
+    name:Name
+ExprLocal =
+    'local'
+    (Bind (',' Bind)* ','?)
+    ';'
+ExprIfThenElse =
+    'if'
+    cond:Expr
+    'then'
+    then:Expr
+    ('else' else_:Expr)?
+ExprFunction =
+    'function'
+    '('
+    ParamsDesc
+    ')'
+    Expr
+ExprAssert =
+    Assertion
+    ';'
+    Expr
+ExprError =
+    'error'
+    Expr
+
+Expr =
+    ExprBinary
+|   ExprUnary
+|   ExprSlice
+|   ExprIndex
+|   ExprIndexExpr
+|   ExprApply
+|   ExprObjExtend
+|   ExprParened
+|   ExprIntrinsicThisFile
+|   ExprIntrinsicId
+|   ExprIntrinsic
+|   ExprString
+|   ExprNumber
+|   ExprArray
+|   ExprObject
+|   ExprArrayComp
+|   ExprImport
+|   ExprVar
+|   ExprLocal
+|   ExprIfThenElse
+|   ExprFunction
+|   ExprAssert
+|   ExprError
+
+BinaryOperator =
+    '||' | '&&'
+|   '|' | '^' | '&'
+|   '==' | '!=' | '<' | '>' | '<=' | '>=' | 'in'
+|   '<<' | '>>'
+|   '+' | '-'
+|   '*' | '/' | '%'
+
+UnaryOperator =
+    '-' | '!' | '~'
+
+SliceDesc =
+    from:Expr?
+    ':'
+    (
+        end:Expr?
+        (
+            ':'
+            step:Expr?
+        )?
+    )?
+
+Name =
+    'ident'
+
+ArgsDesc =
+    (Arg (',' Arg)* ','?)?
+Arg =
+    (name:Name '=')? Expr
+
+ObjBodyComp =
+    pre:ObjLocalPostComma*
+    '['
+    key:Expr
+    ']'
+    '+'?
+    ':'
+    value:Expr
+    post:ObjLocalPreComma*
+    ForSpec
+    CompSpec*
+ObjBodyMemberList =
+    (Member (',' Member) ','?)?
+ObjBody =
+    ObjBodyComp
+|   ObjBodyMemberList
+
+ObjLocalPostComma =
+    ObjLocal
+    ','
+ObjLocalPreComma =
+    ','
+    ObjLocal
+
+MemberBindStmt = ObjLocal
+MemberAssertStmt = Assertion
+MemberField = Field
+Member =
+    MemberBindStmt
+|   MemberAssertStmt
+|   MemberField
+
+ObjLocal =
+    'local'
+    Bind
+
+FieldNormal =
+    FieldName
+    '+'?
+    Visibility
+    Expr
+FieldMethod =
+    FieldName
+    '('
+    ParamsDesc
+    ')'
+    Visibility
+    Expr
+Field =
+    FieldNormal
+|   FieldMethod
+
+FieldNameFixed =
+    id:Name
+|   String
+FieldNameDynamic =
+    '['
+    Expr
+    ']'
+FieldName =
+    FieldNameFixed
+|   FieldNameDynamic
+
+Visibility =
+    ':::'
+|   '::'
+|   ':'
+
+Literal =
+    'null'
+|   'true'
+|   'false'
+|   'self'
+|   '$'
+|   'super'
+
+String =
+    'string_double'
+|   'string_single'
+|   'string_double_verbatim'
+|   'string_single_verbatim'
+|   'string_block'
+
+Number =
+    'number'
+
+ForSpec =
+    'for'
+    bind:Name
+    'in'
+    Expr
+IfSpec =
+    'if'
+    Expr
+CompSpec =
+    ForSpec
+|   IfSpec
+
+BindDestruct =
+    into:Destruct
+    '='
+    value:Expr
+BindFunction =
+    name:Name
+    '('
+    params:ParamsDesc
+    ')'
+    '='
+    value:Expr
+Bind =
+    BindDestruct
+|   BindFunction
+
+ParamsDesc =
+    (Param (',' Param)* ','?)?
+Param =
+    Destruct
+    (
+        '='
+        Expr
+    )?
+
+Assertion =
+    'assert'
+    condition:Expr
+    (
+        ':'
+        Expr
+    )?
+
+DestructFull =
+    into:Name
+DestructSkip =
+    '?'
+DestructArray =
+    '['
+    start:(
+        Destruct
+        (',' Destruct)*
+        ','?
+    )?
+    DestructRest?
+    ','?
+    end:(
+        Destruct
+        (',' Destruct)*
+        ','?
+    )
+    ']'
+DestructObject =
+    '{'
+    (
+        DestructObjectField
+        (',' DestructObjectField)*
+        ','?
+    )?
+    DestructRest?
+    ','?
+    '}'
+Destruct =
+    DestructFull
+    DestructSkip
+    DestructArray
+    DestructObject
+
+DestructRest =
+    '...'
+    into:Name?
+
+DestructObjectField =
+    field:Name
+    (
+        ':'
+        Destruct
+    )?
+    (
+        '='
+        Expr
+    )?
addedcrates/jrsonnet-rowan-parser/src/ast.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/ast.rs
@@ -0,0 +1 @@
+pub trait AstToken {}
modifiedcrates/jrsonnet-rowan-parser/src/event.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/event.rs
+++ b/crates/jrsonnet-rowan-parser/src/event.rs
@@ -3,8 +3,9 @@
 use rowan::{GreenNode, GreenNodeBuilder, Language};
 
 use crate::{
-	lex::{Lang, Lexeme, SyntaxKind},
+	lex::Lexeme,
 	parser::{Parse, SyntaxError},
+	JsonnetLanguage, SyntaxKind,
 };
 
 #[derive(Clone, Debug, PartialEq, Eq)]
@@ -69,7 +70,7 @@
 					}
 
 					for kind in kinds.into_iter().rev() {
-						self.builder.start_node(Lang::kind_to_raw(kind));
+						self.builder.start_node(JsonnetLanguage::kind_to_raw(kind));
 					}
 				}
 				Event::Token => self.token(),
@@ -92,7 +93,7 @@
 	fn token(&mut self) {
 		let lexeme = self.lexemes[self.offset];
 		self.builder
-			.token(Lang::kind_to_raw(lexeme.kind), lexeme.text);
+			.token(JsonnetLanguage::kind_to_raw(lexeme.kind), lexeme.text);
 		self.offset += 1;
 	}
 	fn skip_whitespace(&mut self) {
addedcrates/jrsonnet-rowan-parser/src/generated/mod.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/generated/mod.rs
@@ -0,0 +1,3 @@
+// mod tokens;
+// mod nodes;
+pub mod syntax_kinds;
addedcrates/jrsonnet-rowan-parser/src/generated/nodes.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/generated/nodes.rs
@@ -0,0 +1,2072 @@
+//! This is a generated file, please do not edit manually. Changes can be
+//! made in codegeneration that lives in `xtask` top-level dir.
+
+#![allow(non_snake_case)]
+use crate::{
+	ast::{self, support, AstChildren, AstNode},
+	SyntaxKind::{self, *},
+	SyntaxNode, SyntaxToken, T,
+};
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct SourceFile {
+	pub(crate) syntax: SyntaxNode,
+}
+impl SourceFile {
+	pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprBinary {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprBinary {
+	pub fn lhs(&self) -> Option<Expr> { support::child(&self.syntax) }
+	pub fn binary_operator(&self) -> Option<BinaryOperator> { support::child(&self.syntax) }
+	pub fn rhs(&self) -> Option<Expr> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct BinaryOperator {
+	pub(crate) syntax: SyntaxNode,
+}
+impl BinaryOperator {
+	pub fn or_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![||]) }
+	pub fn and_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![&&]) }
+	pub fn bit_or_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![|]) }
+	pub fn bit_xor_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![^]) }
+	pub fn bit_and_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![&]) }
+	pub fn eq_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![==]) }
+	pub fn ne_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![!=]) }
+	pub fn lt_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![<]) }
+	pub fn gt_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![>]) }
+	pub fn le_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![<=]) }
+	pub fn ge_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![>=]) }
+	pub fn in_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![in]) }
+	pub fn lhs_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![<<]) }
+	pub fn rhs_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![>>]) }
+	pub fn plus_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![+]) }
+	pub fn minus_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![-]) }
+	pub fn mul_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![*]) }
+	pub fn div_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![/]) }
+	pub fn modulo_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![%]) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprUnary {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprUnary {
+	pub fn unary_operator(&self) -> Option<UnaryOperator> { support::child(&self.syntax) }
+	pub fn rhs(&self) -> Option<Expr> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct UnaryOperator {
+	pub(crate) syntax: SyntaxNode,
+}
+impl UnaryOperator {
+	pub fn minus_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![-]) }
+	pub fn not_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![!]) }
+	pub fn bit_not_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![~]) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprSlice {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprSlice {
+	pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+	pub fn l_brack_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['[']) }
+	pub fn slice_desc(&self) -> Option<SliceDesc> { support::child(&self.syntax) }
+	pub fn r_brack_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![']']) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct SliceDesc {
+	pub(crate) syntax: SyntaxNode,
+}
+impl SliceDesc {
+	pub fn from(&self) -> Option<Expr> { support::child(&self.syntax) }
+	pub fn colon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![:]) }
+	pub fn end(&self) -> Option<Expr> { support::child(&self.syntax) }
+	pub fn step(&self) -> Option<Expr> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprIndex {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprIndex {
+	pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+	pub fn dot_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![.]) }
+	pub fn index(&self) -> Option<Name> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Name {
+	pub(crate) syntax: SyntaxNode,
+}
+impl Name {
+	pub fn ident_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![ident]) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprIndexExpr {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprIndexExpr {
+	pub fn base(&self) -> Option<Expr> { support::child(&self.syntax) }
+	pub fn l_brack_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['[']) }
+	pub fn index(&self) -> Option<Expr> { support::child(&self.syntax) }
+	pub fn r_brack_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![']']) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprApply {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprApply {
+	pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+	pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) }
+	pub fn args_desc(&self) -> Option<ArgsDesc> { support::child(&self.syntax) }
+	pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) }
+	pub fn tailstrict_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T![tailstrict])
+	}
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ArgsDesc {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ArgsDesc {
+	pub fn args(&self) -> AstChildren<Arg> { support::children(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprObjExtend {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprObjExtend {
+	pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+	pub fn l_brace_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['{']) }
+	pub fn obj_body(&self) -> Option<ObjBody> { support::child(&self.syntax) }
+	pub fn r_brace_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['}']) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprParened {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprParened {
+	pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) }
+	pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+	pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprLiteral {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprLiteral {
+	pub fn literal(&self) -> Option<Literal> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Literal {
+	pub(crate) syntax: SyntaxNode,
+}
+impl Literal {
+	pub fn null_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![null]) }
+	pub fn true_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![true]) }
+	pub fn false_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![false]) }
+	pub fn self_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![self]) }
+	pub fn dollar_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['$']) }
+	pub fn super_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![super]) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprIntrinsicThisFile {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprIntrinsicThisFile {
+	pub fn intrinsic_this_file_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T!["$intrinsicThisFile"])
+	}
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprIntrinsicId {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprIntrinsicId {
+	pub fn intrinsic_id_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T!["$intrinsicId"])
+	}
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprIntrinsic {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprIntrinsic {
+	pub fn intrinsic_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T!["$intrinsic"])
+	}
+	pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) }
+	pub fn name(&self) -> Option<Name> { support::child(&self.syntax) }
+	pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprString {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprString {
+	pub fn string(&self) -> Option<String> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct String {
+	pub(crate) syntax: SyntaxNode,
+}
+impl String {
+	pub fn string_double_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T![string_double])
+	}
+	pub fn string_single_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T![string_single])
+	}
+	pub fn string_double_verbatim_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T![string_double_verbatim])
+	}
+	pub fn string_single_verbatim_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T![string_single_verbatim])
+	}
+	pub fn string_block_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T![string_block])
+	}
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprNumber {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprNumber {
+	pub fn number(&self) -> Option<Number> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Number {
+	pub(crate) syntax: SyntaxNode,
+}
+impl Number {
+	pub fn number_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![number]) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprArray {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprArray {
+	pub fn l_brack_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['[']) }
+	pub fn exprs(&self) -> AstChildren<Expr> { support::children(&self.syntax) }
+	pub fn r_brack_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![']']) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprObject {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprObject {
+	pub fn l_brace_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['{']) }
+	pub fn obj_body(&self) -> Option<ObjBody> { support::child(&self.syntax) }
+	pub fn r_brace_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['}']) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprArrayComp {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprArrayComp {
+	pub fn l_brack_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['[']) }
+	pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+	pub fn comma_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![,]) }
+	pub fn for_spec(&self) -> Option<ForSpec> { support::child(&self.syntax) }
+	pub fn comp_specs(&self) -> AstChildren<CompSpec> { support::children(&self.syntax) }
+	pub fn r_brack_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![']']) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ForSpec {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ForSpec {
+	pub fn for_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![for]) }
+	pub fn bind(&self) -> Option<Name> { support::child(&self.syntax) }
+	pub fn in_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![in]) }
+	pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprImport {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprImport {
+	pub fn importstr_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T![importstr])
+	}
+	pub fn string(&self) -> Option<String> { support::child(&self.syntax) }
+	pub fn importbin_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T![importbin])
+	}
+	pub fn import_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![import]) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprVar {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprVar {
+	pub fn name(&self) -> Option<Name> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprLocal {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprLocal {
+	pub fn local_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![local]) }
+	pub fn binds(&self) -> AstChildren<Bind> { support::children(&self.syntax) }
+	pub fn semi_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![;]) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprIfThenElse {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprIfThenElse {
+	pub fn if_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![if]) }
+	pub fn cond(&self) -> Option<Expr> { support::child(&self.syntax) }
+	pub fn then_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![then]) }
+	pub fn then(&self) -> Option<Expr> { support::child(&self.syntax) }
+	pub fn else_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![else]) }
+	pub fn else_(&self) -> Option<Expr> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprFunction {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprFunction {
+	pub fn function_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T![function])
+	}
+	pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) }
+	pub fn params_desc(&self) -> Option<ParamsDesc> { support::child(&self.syntax) }
+	pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) }
+	pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ParamsDesc {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ParamsDesc {
+	pub fn params(&self) -> AstChildren<Param> { support::children(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprAssert {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprAssert {
+	pub fn assertion(&self) -> Option<Assertion> { support::child(&self.syntax) }
+	pub fn semi_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![;]) }
+	pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Assertion {
+	pub(crate) syntax: SyntaxNode,
+}
+impl Assertion {
+	pub fn assert_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![assert]) }
+	pub fn condition(&self) -> Option<Expr> { support::child(&self.syntax) }
+	pub fn colon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![:]) }
+	pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprError {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprError {
+	pub fn error_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![error]) }
+	pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Arg {
+	pub(crate) syntax: SyntaxNode,
+}
+impl Arg {
+	pub fn name(&self) -> Option<Name> { support::child(&self.syntax) }
+	pub fn assign_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![=]) }
+	pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ObjBodyComp {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ObjBodyComp {
+	pub fn pre(&self) -> AstChildren<ObjLocalPostComma> { support::children(&self.syntax) }
+	pub fn l_brack_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['[']) }
+	pub fn key(&self) -> Option<Expr> { support::child(&self.syntax) }
+	pub fn r_brack_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![']']) }
+	pub fn plus_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![+]) }
+	pub fn colon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![:]) }
+	pub fn value(&self) -> Option<Expr> { support::child(&self.syntax) }
+	pub fn post(&self) -> AstChildren<ObjLocalPreComma> { support::children(&self.syntax) }
+	pub fn for_spec(&self) -> Option<ForSpec> { support::child(&self.syntax) }
+	pub fn comp_specs(&self) -> AstChildren<CompSpec> { support::children(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ObjLocalPostComma {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ObjLocalPostComma {
+	pub fn obj_local(&self) -> Option<ObjLocal> { support::child(&self.syntax) }
+	pub fn comma_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![,]) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ObjLocalPreComma {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ObjLocalPreComma {
+	pub fn comma_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![,]) }
+	pub fn obj_local(&self) -> Option<ObjLocal> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ObjBodyMemberList {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ObjBodyMemberList {
+	pub fn member(&self) -> Option<Member> { support::child(&self.syntax) }
+	pub fn comma_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![,]) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ObjLocal {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ObjLocal {
+	pub fn local_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![local]) }
+	pub fn bind(&self) -> Option<Bind> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct MemberBindStmt {
+	pub(crate) syntax: SyntaxNode,
+}
+impl MemberBindStmt {
+	pub fn obj_local(&self) -> Option<ObjLocal> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct MemberAssertStmt {
+	pub(crate) syntax: SyntaxNode,
+}
+impl MemberAssertStmt {
+	pub fn assertion(&self) -> Option<Assertion> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct MemberField {
+	pub(crate) syntax: SyntaxNode,
+}
+impl MemberField {
+	pub fn field(&self) -> Option<Field> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct FieldNormal {
+	pub(crate) syntax: SyntaxNode,
+}
+impl FieldNormal {
+	pub fn field_name(&self) -> Option<FieldName> { support::child(&self.syntax) }
+	pub fn plus_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![+]) }
+	pub fn visibility(&self) -> Option<Visibility> { support::child(&self.syntax) }
+	pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Visibility {
+	pub(crate) syntax: SyntaxNode,
+}
+impl Visibility {
+	pub fn coloncoloncolon_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T![:::])
+	}
+	pub fn coloncolon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![::]) }
+	pub fn colon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![:]) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct FieldMethod {
+	pub(crate) syntax: SyntaxNode,
+}
+impl FieldMethod {
+	pub fn field_name(&self) -> Option<FieldName> { support::child(&self.syntax) }
+	pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) }
+	pub fn params_desc(&self) -> Option<ParamsDesc> { support::child(&self.syntax) }
+	pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) }
+	pub fn visibility(&self) -> Option<Visibility> { support::child(&self.syntax) }
+	pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct FieldNameFixed {
+	pub(crate) syntax: SyntaxNode,
+}
+impl FieldNameFixed {
+	pub fn id(&self) -> Option<Name> { support::child(&self.syntax) }
+	pub fn string(&self) -> Option<String> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct FieldNameDynamic {
+	pub(crate) syntax: SyntaxNode,
+}
+impl FieldNameDynamic {
+	pub fn l_brack_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['[']) }
+	pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+	pub fn r_brack_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![']']) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct IfSpec {
+	pub(crate) syntax: SyntaxNode,
+}
+impl IfSpec {
+	pub fn if_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![if]) }
+	pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct BindDestruct {
+	pub(crate) syntax: SyntaxNode,
+}
+impl BindDestruct {
+	pub fn into(&self) -> Option<Destruct> { support::child(&self.syntax) }
+	pub fn assign_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![=]) }
+	pub fn value(&self) -> Option<Expr> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Destruct {
+	pub(crate) syntax: SyntaxNode,
+}
+impl Destruct {
+	pub fn destruct_full(&self) -> Option<DestructFull> { support::child(&self.syntax) }
+	pub fn destruct_skip(&self) -> Option<DestructSkip> { support::child(&self.syntax) }
+	pub fn destruct_array(&self) -> Option<DestructArray> { support::child(&self.syntax) }
+	pub fn destruct_object(&self) -> Option<DestructObject> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct BindFunction {
+	pub(crate) syntax: SyntaxNode,
+}
+impl BindFunction {
+	pub fn name(&self) -> Option<Name> { support::child(&self.syntax) }
+	pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) }
+	pub fn params(&self) -> Option<ParamsDesc> { support::child(&self.syntax) }
+	pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) }
+	pub fn assign_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![=]) }
+	pub fn value(&self) -> Option<Expr> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Param {
+	pub(crate) syntax: SyntaxNode,
+}
+impl Param {
+	pub fn destruct(&self) -> Option<Destruct> { support::child(&self.syntax) }
+	pub fn assign_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![=]) }
+	pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DestructFull {
+	pub(crate) syntax: SyntaxNode,
+}
+impl DestructFull {
+	pub fn into(&self) -> Option<Name> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DestructSkip {
+	pub(crate) syntax: SyntaxNode,
+}
+impl DestructSkip {
+	pub fn question_mark_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![?]) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DestructArray {
+	pub(crate) syntax: SyntaxNode,
+}
+impl DestructArray {
+	pub fn l_brack_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['[']) }
+	pub fn start(&self) -> AstChildren<Destruct> { support::children(&self.syntax) }
+	pub fn destruct_rest(&self) -> Option<DestructRest> { support::child(&self.syntax) }
+	pub fn comma_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![,]) }
+	pub fn end(&self) -> AstChildren<Destruct> { support::children(&self.syntax) }
+	pub fn r_brack_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![']']) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DestructRest {
+	pub(crate) syntax: SyntaxNode,
+}
+impl DestructRest {
+	pub fn dotdotdot_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![...]) }
+	pub fn into(&self) -> Option<Name> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DestructObject {
+	pub(crate) syntax: SyntaxNode,
+}
+impl DestructObject {
+	pub fn l_brace_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['{']) }
+	pub fn destruct_object_fields(&self) -> AstChildren<DestructObjectField> {
+		support::children(&self.syntax)
+	}
+	pub fn destruct_rest(&self) -> Option<DestructRest> { support::child(&self.syntax) }
+	pub fn comma_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![,]) }
+	pub fn r_brace_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['}']) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DestructObjectField {
+	pub(crate) syntax: SyntaxNode,
+}
+impl DestructObjectField {
+	pub fn field(&self) -> Option<Name> { support::child(&self.syntax) }
+	pub fn colon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![:]) }
+	pub fn destruct(&self) -> Option<Destruct> { support::child(&self.syntax) }
+	pub fn assign_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![=]) }
+	pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum Expr {
+	ExprBinary(ExprBinary),
+	ExprUnary(ExprUnary),
+	ExprSlice(ExprSlice),
+	ExprIndex(ExprIndex),
+	ExprIndexExpr(ExprIndexExpr),
+	ExprApply(ExprApply),
+	ExprObjExtend(ExprObjExtend),
+	ExprParened(ExprParened),
+	ExprIntrinsicThisFile(ExprIntrinsicThisFile),
+	ExprIntrinsicId(ExprIntrinsicId),
+	ExprIntrinsic(ExprIntrinsic),
+	ExprString(ExprString),
+	ExprNumber(ExprNumber),
+	ExprArray(ExprArray),
+	ExprObject(ExprObject),
+	ExprArrayComp(ExprArrayComp),
+	ExprImport(ExprImport),
+	ExprVar(ExprVar),
+	ExprLocal(ExprLocal),
+	ExprIfThenElse(ExprIfThenElse),
+	ExprFunction(ExprFunction),
+	ExprAssert(ExprAssert),
+	ExprError(ExprError),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum ObjBody {
+	ObjBodyComp(ObjBodyComp),
+	ObjBodyMemberList(ObjBodyMemberList),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum CompSpec {
+	ForSpec(ForSpec),
+	IfSpec(IfSpec),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum Bind {
+	BindDestruct(BindDestruct),
+	BindFunction(BindFunction),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum Member {
+	MemberBindStmt(MemberBindStmt),
+	MemberAssertStmt(MemberAssertStmt),
+	MemberField(MemberField),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum Field {
+	FieldNormal(FieldNormal),
+	FieldMethod(FieldMethod),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum FieldName {
+	FieldNameFixed(FieldNameFixed),
+	FieldNameDynamic(FieldNameDynamic),
+}
+impl AstNode for SourceFile {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == SOURCE_FILE }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ExprBinary {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == EXPR_BINARY }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for BinaryOperator {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == BINARY_OPERATOR }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ExprUnary {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == EXPR_UNARY }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for UnaryOperator {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == UNARY_OPERATOR }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ExprSlice {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == EXPR_SLICE }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for SliceDesc {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == SLICE_DESC }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ExprIndex {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == EXPR_INDEX }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for Name {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == NAME }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ExprIndexExpr {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == EXPR_INDEX_EXPR }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ExprApply {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == EXPR_APPLY }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ArgsDesc {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == ARGS_DESC }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ExprObjExtend {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == EXPR_OBJ_EXTEND }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ExprParened {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == EXPR_PARENED }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ExprLiteral {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == EXPR_LITERAL }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for Literal {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == LITERAL }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ExprIntrinsicThisFile {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == EXPR_INTRINSIC_THIS_FILE }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ExprIntrinsicId {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == EXPR_INTRINSIC_ID }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ExprIntrinsic {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == EXPR_INTRINSIC }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ExprString {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == EXPR_STRING }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for String {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == STRING }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ExprNumber {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == EXPR_NUMBER }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for Number {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == NUMBER }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ExprArray {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == EXPR_ARRAY }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ExprObject {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == EXPR_OBJECT }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ExprArrayComp {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == EXPR_ARRAY_COMP }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ForSpec {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == FOR_SPEC }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ExprImport {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == EXPR_IMPORT }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ExprVar {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == EXPR_VAR }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ExprLocal {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == EXPR_LOCAL }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ExprIfThenElse {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == EXPR_IF_THEN_ELSE }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ExprFunction {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == EXPR_FUNCTION }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ParamsDesc {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == PARAMS_DESC }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ExprAssert {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == EXPR_ASSERT }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for Assertion {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == ASSERTION }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ExprError {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == EXPR_ERROR }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for Arg {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == ARG }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ObjBodyComp {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == OBJ_BODY_COMP }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ObjLocalPostComma {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == OBJ_LOCAL_POST_COMMA }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ObjLocalPreComma {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == OBJ_LOCAL_PRE_COMMA }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ObjBodyMemberList {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == OBJ_BODY_MEMBER_LIST }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for ObjLocal {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == OBJ_LOCAL }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for MemberBindStmt {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == MEMBER_BIND_STMT }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for MemberAssertStmt {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == MEMBER_ASSERT_STMT }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for MemberField {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == MEMBER_FIELD }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for FieldNormal {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == FIELD_NORMAL }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for Visibility {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == VISIBILITY }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for FieldMethod {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == FIELD_METHOD }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for FieldNameFixed {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == FIELD_NAME_FIXED }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for FieldNameDynamic {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == FIELD_NAME_DYNAMIC }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for IfSpec {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == IF_SPEC }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for BindDestruct {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == BIND_DESTRUCT }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for Destruct {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == DESTRUCT }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for BindFunction {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == BIND_FUNCTION }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for Param {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == PARAM }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for DestructFull {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == DESTRUCT_FULL }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for DestructSkip {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == DESTRUCT_SKIP }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for DestructArray {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == DESTRUCT_ARRAY }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for DestructRest {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == DESTRUCT_REST }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for DestructObject {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == DESTRUCT_OBJECT }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for DestructObjectField {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == DESTRUCT_OBJECT_FIELD }
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl From<ExprBinary> for Expr {
+	fn from(node: ExprBinary) -> Expr { Expr::ExprBinary(node) }
+}
+impl From<ExprUnary> for Expr {
+	fn from(node: ExprUnary) -> Expr { Expr::ExprUnary(node) }
+}
+impl From<ExprSlice> for Expr {
+	fn from(node: ExprSlice) -> Expr { Expr::ExprSlice(node) }
+}
+impl From<ExprIndex> for Expr {
+	fn from(node: ExprIndex) -> Expr { Expr::ExprIndex(node) }
+}
+impl From<ExprIndexExpr> for Expr {
+	fn from(node: ExprIndexExpr) -> Expr { Expr::ExprIndexExpr(node) }
+}
+impl From<ExprApply> for Expr {
+	fn from(node: ExprApply) -> Expr { Expr::ExprApply(node) }
+}
+impl From<ExprObjExtend> for Expr {
+	fn from(node: ExprObjExtend) -> Expr { Expr::ExprObjExtend(node) }
+}
+impl From<ExprParened> for Expr {
+	fn from(node: ExprParened) -> Expr { Expr::ExprParened(node) }
+}
+impl From<ExprIntrinsicThisFile> for Expr {
+	fn from(node: ExprIntrinsicThisFile) -> Expr { Expr::ExprIntrinsicThisFile(node) }
+}
+impl From<ExprIntrinsicId> for Expr {
+	fn from(node: ExprIntrinsicId) -> Expr { Expr::ExprIntrinsicId(node) }
+}
+impl From<ExprIntrinsic> for Expr {
+	fn from(node: ExprIntrinsic) -> Expr { Expr::ExprIntrinsic(node) }
+}
+impl From<ExprString> for Expr {
+	fn from(node: ExprString) -> Expr { Expr::ExprString(node) }
+}
+impl From<ExprNumber> for Expr {
+	fn from(node: ExprNumber) -> Expr { Expr::ExprNumber(node) }
+}
+impl From<ExprArray> for Expr {
+	fn from(node: ExprArray) -> Expr { Expr::ExprArray(node) }
+}
+impl From<ExprObject> for Expr {
+	fn from(node: ExprObject) -> Expr { Expr::ExprObject(node) }
+}
+impl From<ExprArrayComp> for Expr {
+	fn from(node: ExprArrayComp) -> Expr { Expr::ExprArrayComp(node) }
+}
+impl From<ExprImport> for Expr {
+	fn from(node: ExprImport) -> Expr { Expr::ExprImport(node) }
+}
+impl From<ExprVar> for Expr {
+	fn from(node: ExprVar) -> Expr { Expr::ExprVar(node) }
+}
+impl From<ExprLocal> for Expr {
+	fn from(node: ExprLocal) -> Expr { Expr::ExprLocal(node) }
+}
+impl From<ExprIfThenElse> for Expr {
+	fn from(node: ExprIfThenElse) -> Expr { Expr::ExprIfThenElse(node) }
+}
+impl From<ExprFunction> for Expr {
+	fn from(node: ExprFunction) -> Expr { Expr::ExprFunction(node) }
+}
+impl From<ExprAssert> for Expr {
+	fn from(node: ExprAssert) -> Expr { Expr::ExprAssert(node) }
+}
+impl From<ExprError> for Expr {
+	fn from(node: ExprError) -> Expr { Expr::ExprError(node) }
+}
+impl AstNode for Expr {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		match kind {
+			EXPR_BINARY
+			| EXPR_UNARY
+			| EXPR_SLICE
+			| EXPR_INDEX
+			| EXPR_INDEX_EXPR
+			| EXPR_APPLY
+			| EXPR_OBJ_EXTEND
+			| EXPR_PARENED
+			| EXPR_INTRINSIC_THIS_FILE
+			| EXPR_INTRINSIC_ID
+			| EXPR_INTRINSIC
+			| EXPR_STRING
+			| EXPR_NUMBER
+			| EXPR_ARRAY
+			| EXPR_OBJECT
+			| EXPR_ARRAY_COMP
+			| EXPR_IMPORT
+			| EXPR_VAR
+			| EXPR_LOCAL
+			| EXPR_IF_THEN_ELSE
+			| EXPR_FUNCTION
+			| EXPR_ASSERT
+			| EXPR_ERROR => true,
+			_ => false,
+		}
+	}
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		let res = match syntax.kind() {
+			EXPR_BINARY => Expr::ExprBinary(ExprBinary { syntax }),
+			EXPR_UNARY => Expr::ExprUnary(ExprUnary { syntax }),
+			EXPR_SLICE => Expr::ExprSlice(ExprSlice { syntax }),
+			EXPR_INDEX => Expr::ExprIndex(ExprIndex { syntax }),
+			EXPR_INDEX_EXPR => Expr::ExprIndexExpr(ExprIndexExpr { syntax }),
+			EXPR_APPLY => Expr::ExprApply(ExprApply { syntax }),
+			EXPR_OBJ_EXTEND => Expr::ExprObjExtend(ExprObjExtend { syntax }),
+			EXPR_PARENED => Expr::ExprParened(ExprParened { syntax }),
+			EXPR_INTRINSIC_THIS_FILE => {
+				Expr::ExprIntrinsicThisFile(ExprIntrinsicThisFile { syntax })
+			}
+			EXPR_INTRINSIC_ID => Expr::ExprIntrinsicId(ExprIntrinsicId { syntax }),
+			EXPR_INTRINSIC => Expr::ExprIntrinsic(ExprIntrinsic { syntax }),
+			EXPR_STRING => Expr::ExprString(ExprString { syntax }),
+			EXPR_NUMBER => Expr::ExprNumber(ExprNumber { syntax }),
+			EXPR_ARRAY => Expr::ExprArray(ExprArray { syntax }),
+			EXPR_OBJECT => Expr::ExprObject(ExprObject { syntax }),
+			EXPR_ARRAY_COMP => Expr::ExprArrayComp(ExprArrayComp { syntax }),
+			EXPR_IMPORT => Expr::ExprImport(ExprImport { syntax }),
+			EXPR_VAR => Expr::ExprVar(ExprVar { syntax }),
+			EXPR_LOCAL => Expr::ExprLocal(ExprLocal { syntax }),
+			EXPR_IF_THEN_ELSE => Expr::ExprIfThenElse(ExprIfThenElse { syntax }),
+			EXPR_FUNCTION => Expr::ExprFunction(ExprFunction { syntax }),
+			EXPR_ASSERT => Expr::ExprAssert(ExprAssert { syntax }),
+			EXPR_ERROR => Expr::ExprError(ExprError { syntax }),
+			_ => return None,
+		};
+		Some(res)
+	}
+	fn syntax(&self) -> &SyntaxNode {
+		match self {
+			Expr::ExprBinary(it) => &it.syntax,
+			Expr::ExprUnary(it) => &it.syntax,
+			Expr::ExprSlice(it) => &it.syntax,
+			Expr::ExprIndex(it) => &it.syntax,
+			Expr::ExprIndexExpr(it) => &it.syntax,
+			Expr::ExprApply(it) => &it.syntax,
+			Expr::ExprObjExtend(it) => &it.syntax,
+			Expr::ExprParened(it) => &it.syntax,
+			Expr::ExprIntrinsicThisFile(it) => &it.syntax,
+			Expr::ExprIntrinsicId(it) => &it.syntax,
+			Expr::ExprIntrinsic(it) => &it.syntax,
+			Expr::ExprString(it) => &it.syntax,
+			Expr::ExprNumber(it) => &it.syntax,
+			Expr::ExprArray(it) => &it.syntax,
+			Expr::ExprObject(it) => &it.syntax,
+			Expr::ExprArrayComp(it) => &it.syntax,
+			Expr::ExprImport(it) => &it.syntax,
+			Expr::ExprVar(it) => &it.syntax,
+			Expr::ExprLocal(it) => &it.syntax,
+			Expr::ExprIfThenElse(it) => &it.syntax,
+			Expr::ExprFunction(it) => &it.syntax,
+			Expr::ExprAssert(it) => &it.syntax,
+			Expr::ExprError(it) => &it.syntax,
+		}
+	}
+}
+impl From<ObjBodyComp> for ObjBody {
+	fn from(node: ObjBodyComp) -> ObjBody { ObjBody::ObjBodyComp(node) }
+}
+impl From<ObjBodyMemberList> for ObjBody {
+	fn from(node: ObjBodyMemberList) -> ObjBody { ObjBody::ObjBodyMemberList(node) }
+}
+impl AstNode for ObjBody {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		match kind {
+			OBJ_BODY_COMP | OBJ_BODY_MEMBER_LIST => true,
+			_ => false,
+		}
+	}
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		let res = match syntax.kind() {
+			OBJ_BODY_COMP => ObjBody::ObjBodyComp(ObjBodyComp { syntax }),
+			OBJ_BODY_MEMBER_LIST => ObjBody::ObjBodyMemberList(ObjBodyMemberList { syntax }),
+			_ => return None,
+		};
+		Some(res)
+	}
+	fn syntax(&self) -> &SyntaxNode {
+		match self {
+			ObjBody::ObjBodyComp(it) => &it.syntax,
+			ObjBody::ObjBodyMemberList(it) => &it.syntax,
+		}
+	}
+}
+impl From<ForSpec> for CompSpec {
+	fn from(node: ForSpec) -> CompSpec { CompSpec::ForSpec(node) }
+}
+impl From<IfSpec> for CompSpec {
+	fn from(node: IfSpec) -> CompSpec { CompSpec::IfSpec(node) }
+}
+impl AstNode for CompSpec {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		match kind {
+			FOR_SPEC | IF_SPEC => true,
+			_ => false,
+		}
+	}
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		let res = match syntax.kind() {
+			FOR_SPEC => CompSpec::ForSpec(ForSpec { syntax }),
+			IF_SPEC => CompSpec::IfSpec(IfSpec { syntax }),
+			_ => return None,
+		};
+		Some(res)
+	}
+	fn syntax(&self) -> &SyntaxNode {
+		match self {
+			CompSpec::ForSpec(it) => &it.syntax,
+			CompSpec::IfSpec(it) => &it.syntax,
+		}
+	}
+}
+impl From<BindDestruct> for Bind {
+	fn from(node: BindDestruct) -> Bind { Bind::BindDestruct(node) }
+}
+impl From<BindFunction> for Bind {
+	fn from(node: BindFunction) -> Bind { Bind::BindFunction(node) }
+}
+impl AstNode for Bind {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		match kind {
+			BIND_DESTRUCT | BIND_FUNCTION => true,
+			_ => false,
+		}
+	}
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		let res = match syntax.kind() {
+			BIND_DESTRUCT => Bind::BindDestruct(BindDestruct { syntax }),
+			BIND_FUNCTION => Bind::BindFunction(BindFunction { syntax }),
+			_ => return None,
+		};
+		Some(res)
+	}
+	fn syntax(&self) -> &SyntaxNode {
+		match self {
+			Bind::BindDestruct(it) => &it.syntax,
+			Bind::BindFunction(it) => &it.syntax,
+		}
+	}
+}
+impl From<MemberBindStmt> for Member {
+	fn from(node: MemberBindStmt) -> Member { Member::MemberBindStmt(node) }
+}
+impl From<MemberAssertStmt> for Member {
+	fn from(node: MemberAssertStmt) -> Member { Member::MemberAssertStmt(node) }
+}
+impl From<MemberField> for Member {
+	fn from(node: MemberField) -> Member { Member::MemberField(node) }
+}
+impl AstNode for Member {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		match kind {
+			MEMBER_BIND_STMT | MEMBER_ASSERT_STMT | MEMBER_FIELD => true,
+			_ => false,
+		}
+	}
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		let res = match syntax.kind() {
+			MEMBER_BIND_STMT => Member::MemberBindStmt(MemberBindStmt { syntax }),
+			MEMBER_ASSERT_STMT => Member::MemberAssertStmt(MemberAssertStmt { syntax }),
+			MEMBER_FIELD => Member::MemberField(MemberField { syntax }),
+			_ => return None,
+		};
+		Some(res)
+	}
+	fn syntax(&self) -> &SyntaxNode {
+		match self {
+			Member::MemberBindStmt(it) => &it.syntax,
+			Member::MemberAssertStmt(it) => &it.syntax,
+			Member::MemberField(it) => &it.syntax,
+		}
+	}
+}
+impl From<FieldNormal> for Field {
+	fn from(node: FieldNormal) -> Field { Field::FieldNormal(node) }
+}
+impl From<FieldMethod> for Field {
+	fn from(node: FieldMethod) -> Field { Field::FieldMethod(node) }
+}
+impl AstNode for Field {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		match kind {
+			FIELD_NORMAL | FIELD_METHOD => true,
+			_ => false,
+		}
+	}
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		let res = match syntax.kind() {
+			FIELD_NORMAL => Field::FieldNormal(FieldNormal { syntax }),
+			FIELD_METHOD => Field::FieldMethod(FieldMethod { syntax }),
+			_ => return None,
+		};
+		Some(res)
+	}
+	fn syntax(&self) -> &SyntaxNode {
+		match self {
+			Field::FieldNormal(it) => &it.syntax,
+			Field::FieldMethod(it) => &it.syntax,
+		}
+	}
+}
+impl From<FieldNameFixed> for FieldName {
+	fn from(node: FieldNameFixed) -> FieldName { FieldName::FieldNameFixed(node) }
+}
+impl From<FieldNameDynamic> for FieldName {
+	fn from(node: FieldNameDynamic) -> FieldName { FieldName::FieldNameDynamic(node) }
+}
+impl AstNode for FieldName {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		match kind {
+			FIELD_NAME_FIXED | FIELD_NAME_DYNAMIC => true,
+			_ => false,
+		}
+	}
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		let res = match syntax.kind() {
+			FIELD_NAME_FIXED => FieldName::FieldNameFixed(FieldNameFixed { syntax }),
+			FIELD_NAME_DYNAMIC => FieldName::FieldNameDynamic(FieldNameDynamic { syntax }),
+			_ => return None,
+		};
+		Some(res)
+	}
+	fn syntax(&self) -> &SyntaxNode {
+		match self {
+			FieldName::FieldNameFixed(it) => &it.syntax,
+			FieldName::FieldNameDynamic(it) => &it.syntax,
+		}
+	}
+}
+impl std::fmt::Display for Expr {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ObjBody {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for CompSpec {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for Bind {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for Member {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for Field {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for FieldName {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for SourceFile {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ExprBinary {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for BinaryOperator {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ExprUnary {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for UnaryOperator {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ExprSlice {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for SliceDesc {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ExprIndex {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for Name {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ExprIndexExpr {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ExprApply {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ArgsDesc {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ExprObjExtend {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ExprParened {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ExprLiteral {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for Literal {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ExprIntrinsicThisFile {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ExprIntrinsicId {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ExprIntrinsic {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ExprString {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for String {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ExprNumber {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for Number {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ExprArray {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ExprObject {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ExprArrayComp {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ForSpec {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ExprImport {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ExprVar {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ExprLocal {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ExprIfThenElse {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ExprFunction {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ParamsDesc {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ExprAssert {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for Assertion {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ExprError {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for Arg {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ObjBodyComp {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ObjLocalPostComma {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ObjLocalPreComma {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ObjBodyMemberList {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for ObjLocal {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for MemberBindStmt {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for MemberAssertStmt {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for MemberField {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for FieldNormal {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for Visibility {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for FieldMethod {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for FieldNameFixed {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for FieldNameDynamic {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for IfSpec {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for BindDestruct {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for Destruct {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for BindFunction {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for Param {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for DestructFull {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for DestructSkip {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for DestructArray {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for DestructRest {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for DestructObject {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for DestructObjectField {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
addedcrates/jrsonnet-rowan-parser/src/generated/syntax_kinds.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/generated/syntax_kinds.rs
@@ -0,0 +1,307 @@
+//! This is a generated file, please do not edit manually. Changes can be
+//! made in codegeneration that lives in `xtask` top-level dir.
+
+#![allow(bad_style, missing_docs, unreachable_pub)]
+use logos::Logos;
+#[doc = r" The kind of syntax node, e.g. `IDENT`, `USE_KW`, or `STRUCT`."]
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Logos)]
+#[repr(u16)]
+pub enum SyntaxKind {
+	#[doc(hidden)]
+	TOMBSTONE,
+	#[doc(hidden)]
+	EOF,
+	#[token("||")]
+	OR,
+	#[token("&&")]
+	AND,
+	#[token("|")]
+	BIT_OR,
+	#[token("^")]
+	BIT_XOR,
+	#[token("&")]
+	BIT_AND,
+	#[token("==")]
+	EQ,
+	#[token("!=")]
+	NE,
+	#[token("<")]
+	LT,
+	#[token(">")]
+	GT,
+	#[token("<=")]
+	LE,
+	#[token(">=")]
+	GE,
+	#[token("<<")]
+	LHS,
+	#[token(">>")]
+	RHS,
+	#[token("+")]
+	PLUS,
+	#[token("-")]
+	MINUS,
+	#[token("*")]
+	MUL,
+	#[token("/")]
+	DIV,
+	#[token("%")]
+	MODULO,
+	#[token("!")]
+	NOT,
+	#[token("~")]
+	BIT_NOT,
+	#[token("[")]
+	L_BRACK,
+	#[token("]")]
+	R_BRACK,
+	#[token("(")]
+	L_PAREN,
+	#[token(")")]
+	R_PAREN,
+	#[token("{")]
+	L_BRACE,
+	#[token("}")]
+	R_BRACE,
+	#[token(":")]
+	COLON,
+	#[token("::")]
+	COLONCOLON,
+	#[token(":::")]
+	COLONCOLONCOLON,
+	#[token(";")]
+	SEMI,
+	#[token(".")]
+	DOT,
+	#[token("...")]
+	DOTDOTDOT,
+	#[token(",")]
+	COMMA,
+	#[token("$")]
+	DOLLAR,
+	#[token("=")]
+	ASSIGN,
+	#[token("?")]
+	QUESTION_MARK,
+	#[token("$intrinsicThisFile")]
+	INTRINSIC_THIS_FILE,
+	#[token("$intrinsicId")]
+	INTRINSIC_ID,
+	#[token("$intrinsic")]
+	INTRINSIC,
+	#[token("tailstrict")]
+	TAILSTRICT_KW,
+	#[token("importstr")]
+	IMPORTSTR_KW,
+	#[token("importbin")]
+	IMPORTBIN_KW,
+	#[token("import")]
+	IMPORT_KW,
+	#[token("local")]
+	LOCAL_KW,
+	#[token("if")]
+	IF_KW,
+	#[token("then")]
+	THEN_KW,
+	#[token("else")]
+	ELSE_KW,
+	#[token("function")]
+	FUNCTION_KW,
+	#[token("error")]
+	ERROR_KW,
+	#[token("in")]
+	IN_KW,
+	#[token("null")]
+	NULL_KW,
+	#[token("true")]
+	TRUE_KW,
+	#[token("false")]
+	FALSE_KW,
+	#[token("self")]
+	SELF_KW,
+	#[token("super")]
+	SUPER_KW,
+	#[token("for")]
+	FOR_KW,
+	#[token("assert")]
+	ASSERT_KW,
+	#[regex("(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?")]
+	NUMBER,
+	#[regex("\"(?s:[^\"\\\\]|\\\\.)*\"")]
+	STRING_DOUBLE,
+	#[regex("'(?s:[^'\\\\]|\\\\.)*'")]
+	STRING_SINGLE,
+	#[regex("@\"(?:[^\"]|\"\")*\"")]
+	STRING_DOUBLE_VERBATIM,
+	#[regex("@'(?:[^']|'')*'")]
+	STRING_SINGLE_VERBATIM,
+	#[regex("\\|\\|\\|")]
+	STRING_BLOCK,
+	#[regex("[_a-zA-Z][_a-zA-Z0-9]*")]
+	IDENT,
+	#[regex("[ \\t\\n\\r]+")]
+	WHITESPACE,
+	#[regex("//[^\\r\\n]*(\\r\\n|\\n)?")]
+	SINGLE_LINE_SLASH_COMMENT,
+	#[regex("#[^\\r\\n]*(\\r\\n|\\n)?")]
+	SINGLE_LINE_HASH_COMMENT,
+	#[regex("/\\*([^*]|\\*[^/])*\\*/")]
+	MULTI_LINE_COMMENT,
+	#[error]
+	ERROR,
+	SOURCE_FILE,
+	EXPR_BINARY,
+	BINARY_OPERATOR,
+	EXPR_UNARY,
+	UNARY_OPERATOR,
+	EXPR_SLICE,
+	SLICE_DESC,
+	EXPR_INDEX,
+	NAME,
+	EXPR_INDEX_EXPR,
+	EXPR_APPLY,
+	ARGS_DESC,
+	EXPR_OBJ_EXTEND,
+	EXPR_PARENED,
+	EXPR_LITERAL,
+	LITERAL,
+	EXPR_INTRINSIC_THIS_FILE,
+	EXPR_INTRINSIC_ID,
+	EXPR_INTRINSIC,
+	EXPR_STRING,
+	STRING,
+	EXPR_NUMBER,
+	EXPR_ARRAY,
+	EXPR_OBJECT,
+	EXPR_ARRAY_COMP,
+	FOR_SPEC,
+	EXPR_IMPORT,
+	EXPR_VAR,
+	EXPR_LOCAL,
+	EXPR_IF_THEN_ELSE,
+	EXPR_FUNCTION,
+	PARAMS_DESC,
+	EXPR_ASSERT,
+	ASSERTION,
+	EXPR_ERROR,
+	ARG,
+	OBJ_BODY_COMP,
+	OBJ_LOCAL_POST_COMMA,
+	OBJ_LOCAL_PRE_COMMA,
+	OBJ_BODY_MEMBER_LIST,
+	OBJ_LOCAL,
+	MEMBER_BIND_STMT,
+	MEMBER_ASSERT_STMT,
+	MEMBER_FIELD,
+	FIELD_NORMAL,
+	VISIBILITY,
+	FIELD_METHOD,
+	FIELD_NAME_FIXED,
+	FIELD_NAME_DYNAMIC,
+	IF_SPEC,
+	BIND_DESTRUCT,
+	DESTRUCT,
+	BIND_FUNCTION,
+	PARAM,
+	DESTRUCT_FULL,
+	DESTRUCT_SKIP,
+	DESTRUCT_ARRAY,
+	DESTRUCT_REST,
+	DESTRUCT_OBJECT,
+	DESTRUCT_OBJECT_FIELD,
+	EXPR,
+	OBJ_BODY,
+	COMP_SPEC,
+	BIND,
+	MEMBER,
+	FIELD,
+	FIELD_NAME,
+	#[doc(hidden)]
+	__LAST,
+}
+use self::SyntaxKind::*;
+impl SyntaxKind {
+	pub fn is_keyword(self) -> bool {
+		match self {
+			TAILSTRICT_KW | IMPORTSTR_KW | IMPORTBIN_KW | IMPORT_KW | LOCAL_KW | IF_KW
+			| THEN_KW | ELSE_KW | FUNCTION_KW | ERROR_KW | IN_KW | NULL_KW | TRUE_KW | FALSE_KW
+			| SELF_KW | SUPER_KW | FOR_KW | ASSERT_KW => true,
+			_ => false,
+		}
+	}
+	pub fn is_punct(self) -> bool {
+		match self {
+			OR | AND | BIT_OR | BIT_XOR | BIT_AND | EQ | NE | LT | GT | LE | GE | LHS | RHS
+			| PLUS | MINUS | MUL | DIV | MODULO | NOT | BIT_NOT | L_BRACK | R_BRACK | L_PAREN
+			| R_PAREN | L_BRACE | R_BRACE | COLON | COLONCOLON | COLONCOLONCOLON | SEMI | DOT
+			| DOTDOTDOT | COMMA | DOLLAR | ASSIGN | QUESTION_MARK | INTRINSIC_THIS_FILE
+			| INTRINSIC_ID | INTRINSIC => true,
+			_ => false,
+		}
+	}
+	pub fn from_keyword(ident: &str) -> Option<SyntaxKind> {
+		let kw = match ident {
+			"tailstrict" => TAILSTRICT_KW,
+			"importstr" => IMPORTSTR_KW,
+			"importbin" => IMPORTBIN_KW,
+			"import" => IMPORT_KW,
+			"local" => LOCAL_KW,
+			"if" => IF_KW,
+			"then" => THEN_KW,
+			"else" => ELSE_KW,
+			"function" => FUNCTION_KW,
+			"error" => ERROR_KW,
+			"in" => IN_KW,
+			"null" => NULL_KW,
+			"true" => TRUE_KW,
+			"false" => FALSE_KW,
+			"self" => SELF_KW,
+			"super" => SUPER_KW,
+			"for" => FOR_KW,
+			"assert" => ASSERT_KW,
+			_ => return None,
+		};
+		Some(kw)
+	}
+	pub fn from_char(c: char) -> Option<SyntaxKind> {
+		let tok = match c {
+			'|' => BIT_OR,
+			'^' => BIT_XOR,
+			'&' => BIT_AND,
+			'<' => LT,
+			'>' => GT,
+			'+' => PLUS,
+			'-' => MINUS,
+			'*' => MUL,
+			'/' => DIV,
+			'%' => MODULO,
+			'!' => NOT,
+			'~' => BIT_NOT,
+			'[' => L_BRACK,
+			']' => R_BRACK,
+			'(' => L_PAREN,
+			')' => R_PAREN,
+			'{' => L_BRACE,
+			'}' => R_BRACE,
+			':' => COLON,
+			';' => SEMI,
+			'.' => DOT,
+			',' => COMMA,
+			'$' => DOLLAR,
+			'=' => ASSIGN,
+			'?' => QUESTION_MARK,
+			_ => return None,
+		};
+		Some(tok)
+	}
+	pub fn from_raw(r: u16) -> Self {
+		assert!(r < Self::__LAST as u16);
+		unsafe { std::mem::transmute(r) }
+	}
+	pub fn into_raw(self) -> u16 {
+		self as u16
+	}
+}
+#[macro_export]
+macro_rules ! T { [||] => { $ crate :: SyntaxKind :: OR } ; [&&] => { $ crate :: SyntaxKind :: AND } ; [|] => { $ crate :: SyntaxKind :: BIT_OR } ; [^] => { $ crate :: SyntaxKind :: BIT_XOR } ; [&] => { $ crate :: SyntaxKind :: BIT_AND } ; [==] => { $ crate :: SyntaxKind :: EQ } ; [!=] => { $ crate :: SyntaxKind :: NE } ; [<] => { $ crate :: SyntaxKind :: LT } ; [>] => { $ crate :: SyntaxKind :: GT } ; [<=] => { $ crate :: SyntaxKind :: LE } ; [>=] => { $ crate :: SyntaxKind :: GE } ; [<<] => { $ crate :: SyntaxKind :: LHS } ; [>>] => { $ crate :: SyntaxKind :: RHS } ; [+] => { $ crate :: SyntaxKind :: PLUS } ; [-] => { $ crate :: SyntaxKind :: MINUS } ; [*] => { $ crate :: SyntaxKind :: MUL } ; [/] => { $ crate :: SyntaxKind :: DIV } ; [%] => { $ crate :: SyntaxKind :: MODULO } ; [!] => { $ crate :: SyntaxKind :: NOT } ; [~] => { $ crate :: SyntaxKind :: BIT_NOT } ; ['['] => { $ crate :: SyntaxKind :: L_BRACK } ; [']'] => { $ crate :: SyntaxKind :: R_BRACK } ; ['('] => { $ crate :: SyntaxKind :: L_PAREN } ; [')'] => { $ crate :: SyntaxKind :: R_PAREN } ; ['{'] => { $ crate :: SyntaxKind :: L_BRACE } ; ['}'] => { $ crate :: SyntaxKind :: R_BRACE } ; [:] => { $ crate :: SyntaxKind :: COLON } ; [::] => { $ crate :: SyntaxKind :: COLONCOLON } ; [:::] => { $ crate :: SyntaxKind :: COLONCOLONCOLON } ; [;] => { $ crate :: SyntaxKind :: SEMI } ; [.] => { $ crate :: SyntaxKind :: DOT } ; [...] => { $ crate :: SyntaxKind :: DOTDOTDOT } ; [,] => { $ crate :: SyntaxKind :: COMMA } ; ['$'] => { $ crate :: SyntaxKind :: DOLLAR } ; [=] => { $ crate :: SyntaxKind :: ASSIGN } ; [?] => { $ crate :: SyntaxKind :: QUESTION_MARK } ; ["$intrinsicThisFile"] => { $ crate :: SyntaxKind :: INTRINSIC_THIS_FILE } ; ["$intrinsicId"] => { $ crate :: SyntaxKind :: INTRINSIC_ID } ; ["$intrinsic"] => { $ crate :: SyntaxKind :: INTRINSIC } ; [tailstrict] => { $ crate :: SyntaxKind :: TAILSTRICT_KW } ; [importstr] => { $ crate :: SyntaxKind :: IMPORTSTR_KW } ; [importbin] => { $ crate :: SyntaxKind :: IMPORTBIN_KW } ; [import] => { $ crate :: SyntaxKind :: IMPORT_KW } ; [local] => { $ crate :: SyntaxKind :: LOCAL_KW } ; [if] => { $ crate :: SyntaxKind :: IF_KW } ; [then] => { $ crate :: SyntaxKind :: THEN_KW } ; [else] => { $ crate :: SyntaxKind :: ELSE_KW } ; [function] => { $ crate :: SyntaxKind :: FUNCTION_KW } ; [error] => { $ crate :: SyntaxKind :: ERROR_KW } ; [in] => { $ crate :: SyntaxKind :: IN_KW } ; [null] => { $ crate :: SyntaxKind :: NULL_KW } ; [true] => { $ crate :: SyntaxKind :: TRUE_KW } ; [false] => { $ crate :: SyntaxKind :: FALSE_KW } ; [self] => { $ crate :: SyntaxKind :: SELF_KW } ; [super] => { $ crate :: SyntaxKind :: SUPER_KW } ; [for] => { $ crate :: SyntaxKind :: FOR_KW } ; [assert] => { $ crate :: SyntaxKind :: ASSERT_KW } ; [lifetime_ident] => { $ crate :: SyntaxKind :: LIFETIME_IDENT } ; [ident] => { $ crate :: SyntaxKind :: IDENT } ; [shebang] => { $ crate :: SyntaxKind :: SHEBANG } ; }
+pub use T;
addedcrates/jrsonnet-rowan-parser/src/generated/tokens.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/generated/tokens.rs
@@ -0,0 +1,155 @@
+//! This is a generated file, please do not edit manually. Changes can be
+//! made in codegeneration that lives in `xtask` top-level dir.
+
+use crate::{
+	ast::AstToken,
+	SyntaxKind::{self, *},
+	SyntaxToken,
+};
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Whitespace {
+	pub(crate) syntax: SyntaxToken,
+}
+impl std::fmt::Display for Whitespace {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(&self.syntax, f)
+	}
+}
+impl AstToken for Whitespace {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == WHITESPACE }
+	fn cast(syntax: SyntaxToken) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxToken { &self.syntax }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Comment {
+	pub(crate) syntax: SyntaxToken,
+}
+impl std::fmt::Display for Comment {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(&self.syntax, f)
+	}
+}
+impl AstToken for Comment {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == COMMENT }
+	fn cast(syntax: SyntaxToken) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxToken { &self.syntax }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct String {
+	pub(crate) syntax: SyntaxToken,
+}
+impl std::fmt::Display for String {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(&self.syntax, f)
+	}
+}
+impl AstToken for String {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == STRING }
+	fn cast(syntax: SyntaxToken) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxToken { &self.syntax }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct StringVerbantim {
+	pub(crate) syntax: SyntaxToken,
+}
+impl std::fmt::Display for StringVerbantim {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(&self.syntax, f)
+	}
+}
+impl AstToken for StringVerbantim {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == STRING_VERBANTIM }
+	fn cast(syntax: SyntaxToken) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxToken { &self.syntax }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct StringBlock {
+	pub(crate) syntax: SyntaxToken,
+}
+impl std::fmt::Display for StringBlock {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(&self.syntax, f)
+	}
+}
+impl AstToken for StringBlock {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == STRING_BLOCK }
+	fn cast(syntax: SyntaxToken) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxToken { &self.syntax }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Number {
+	pub(crate) syntax: SyntaxToken,
+}
+impl std::fmt::Display for Number {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(&self.syntax, f)
+	}
+}
+impl AstToken for Number {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == NUMBER }
+	fn cast(syntax: SyntaxToken) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxToken { &self.syntax }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Ident {
+	pub(crate) syntax: SyntaxToken,
+}
+impl std::fmt::Display for Ident {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(&self.syntax, f)
+	}
+}
+impl AstToken for Ident {
+	fn can_cast(kind: SyntaxKind) -> bool { kind == IDENT }
+	fn cast(syntax: SyntaxToken) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxToken { &self.syntax }
+}
addedcrates/jrsonnet-rowan-parser/src/language.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/language.rs
@@ -0,0 +1,24 @@
+use rowan::Language;
+
+use crate::SyntaxKind;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum JsonnetLanguage {}
+impl Language for JsonnetLanguage {
+	type Kind = SyntaxKind;
+
+	fn kind_from_raw(raw: rowan::SyntaxKind) -> SyntaxKind {
+		SyntaxKind::from_raw(raw.0)
+	}
+
+	fn kind_to_raw(kind: SyntaxKind) -> rowan::SyntaxKind {
+		rowan::SyntaxKind(kind.into_raw())
+	}
+}
+
+pub type SyntaxNode = rowan::SyntaxNode<JsonnetLanguage>;
+pub type SyntaxToken = rowan::SyntaxToken<JsonnetLanguage>;
+pub type SyntaxElement = rowan::SyntaxElement<JsonnetLanguage>;
+pub type SyntaxNodeChildren = rowan::SyntaxNodeChildren<JsonnetLanguage>;
+pub type SyntaxElementChildren = rowan::SyntaxElementChildren<JsonnetLanguage>;
+pub type PreorderWithTokens = rowan::api::PreorderWithTokens<JsonnetLanguage>;
modifiedcrates/jrsonnet-rowan-parser/src/lex.rsdiffbeforeafterboth
1use crate::string_block::lex_str_block_test;1use core::ops::Range;
2use core::ops::Range;2use std::convert::TryFrom;
3
3use logos::Logos;4use logos::Logos;
4use rowan::{Checkpoint, TextRange, TextSize};5use rowan::{TextRange, TextSize};
6
5use std::{convert::TryFrom, iter::Peekable};7use crate::SyntaxKind;
6
7#[derive(Logos, Debug, PartialEq, Hash, Eq, PartialOrd, Ord, Clone, Copy)]
8#[repr(u16)]
9pub enum SyntaxKind {
10 #[token("assert")]
11 KeywordAssert = 0,
12
13 #[token("else")]
14 KeywordElse,
15
16 #[token("error")]
17 KeywordError,
18
19 #[token("false")]
20 KeywordFalse,
21
22 #[token("for")]
23 KeywordFor,
24
25 #[token("function")]
26 KeywordFunction,
27
28 #[token("if")]
29 KeywordIf,
30
31 #[token("import")]
32 KeywordImport,
33
34 #[token("importstr")]
35 KeywordImportStr,
36
37 #[token("local")]
38 KeywordLocal,
39
40 #[token("null")]
41 KeywordNull,
42
43 #[token("tailstrict")]
44 KeywordTailStrict,
45
46 #[token("then")]
47 KeywordThen,
48
49 #[token("self")]
50 KeywordSelf,
51
52 #[token("super")]
53 KeywordSuper,
54
55 #[token("true")]
56 KeywordTrue,
57
58 #[regex(r"[_a-zA-Z][_a-zA-Z0-9]*")]
59 Ident,
60
61 #[regex(r"(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?")]
62 Number,
63
64 #[regex(r"(?:0|[1-9][0-9]*)\.[^0-9]")]
65 ErrorNumJunkAfterDecimalPoint,
66
67 #[regex(r"(?:0|[1-9][0-9]*)(?:\.[0-9]+)?[eE][^+\-0-9]")]
68 ErrorNumJunkAfterExponent,
69
70 #[regex(r"(?:0|[1-9][0-9]*)(?:\.[0-9]+)?[eE][+-][^0-9]")]
71 ErrorNumJunkAfterExponentSign,
72
73 #[token("{")]
74 SymbolLeftBrace,
75
76 #[token("}")]
77 SymbolRightBrace,
78
79 #[token("[")]
80 SymbolLeftBracket,
81
82 #[token("]")]
83 SymbolRightBracket,
84
85 #[token(",")]
86 SymbolComma,
87
88 #[token(".")]
89 SymbolDot,
90
91 #[token("(")]
92 LParen,
93
94 #[token(")")]
95 RParen,
96
97 #[token(";")]
98 SymbolSemi,
99 #[token(":")]
100 SymbolColon,
101
102 #[token("$")]
103 SymbolDollar,
104
105 #[token("*")]
106 OpMul,
107 #[token("/")]
108 OpDiv,
109 #[token("%")]
110 OpMod,
111 #[token("+")]
112 OpPlus,
113 #[token("-")]
114 OpMinus,
115 #[token("<<")]
116 OpShiftLeft,
117 #[token(">>")]
118 OpShiftRight,
119 #[token("<")]
120 OpLessThan,
121 #[token(">")]
122 OpGreaterThan,
123 #[token("<=")]
124 OpLessThanOrEqual,
125 #[token(">=")]
126 OpGreaterThanOrEqual,
127 #[token("==")]
128 OpEqual,
129 #[token("!=")]
130 OpNotEqual,
131 #[token("&")]
132 OpBitAnd,
133 #[token("^")]
134 OpBitXor,
135 #[token("|")]
136 OpBitOr,
137 #[token("&&")]
138 OpAnd,
139 #[token("||")]
140 OpOr,
141 #[token("in")]
142 OpIn,
143 #[token("!")]
144 OpNot,
145 #[token("~")]
146 OpBitNegate,
147 #[token("=")]
148 SymbolAssign,
149
150 #[regex("\"(?s:[^\"\\\\]|\\\\.)*\"")]
151 StringDoubleQuoted,
152
153 #[regex("'(?s:[^'\\\\]|\\\\.)*'")]
154 StringSingleQuoted,
155
156 #[regex("@\"(?:[^\"]|\"\")*\"")]
157 StringDoubleVerbatim,
158
159 #[regex("@'(?:[^']|'')*'")]
160 StringSingleVerbatim,
161
162 #[regex(r"\|\|\|", lex_str_block_test)]
163 StringBlock, //(StringBlockToken),
164
165 #[regex("\"(?s:[^\"\\\\]|\\\\.)*")]
166 ErrorStringDoubleQuotedUnterminated,
167
168 #[regex("'(?s:[^'\\\\]|\\\\.)*")]
169 ErrorStringSingleQuotedUnterminated,
170
171 #[regex("@\"(?:[^\"]|\"\")*")]
172 ErrorStringDoubleVerbatimUnterminated,
173
174 #[regex("@'(?:[^']|'')*")]
175 ErrorStringSingleVerbatimUnterminated,
176
177 #[regex("@[^\"'\\s]\\S+")]
178 ErrorStringMissingQuotes,
179
180 #[token("/*/")]
181 ErrorCommentTooShort,
182
183 #[regex(r"/\*([^*]|\*[^/])+")]
184 ErrorCommentUnterminated,
185
186 #[regex(r"[ \t\n\r]+")]
187 Whitespace,
188
189 #[regex(r"//[^\r\n]*(\r\n|\n)?")]
190 SingelLineSlashComment,
191
192 #[regex(r"#[^\r\n]*(\r\n|\n)?")]
193 SingleLineHashComment,
194
195 #[regex(r"/\*([^*]|\*[^/])*\*/")]
196 MultiLineComment,
197
198 #[error]
199 Error,
200
201 ErrorPositionalAfterNamed,
202
203 Literal,
204 Expr,
205 Array,
206 ArrayElem,
207 Object,
208 Field,
209
210 CompspecFor,
211 CompspecIf,
212
213 Slice,
214 FieldAccess,
215 ObjectApply,
216 FunctionCall,
217 FunctionDef,
218 BodyDef,
219
220 BinOp,
221 UnaryOp,
222 Local,
223 ExprError,
224 ExprAssert,
225 ExprImport,
226
227 DefParam,
228 DefParams,
229
230 DefArgs,
231 DefNamedArg,
232 DefPositionalArg,
233
234 Parened,
235
236 Root,
237}
2388
239impl SyntaxKind {9impl SyntaxKind {
240 pub fn is_trivia(self) -> bool {10 pub fn is_trivia(self) -> bool {
241 matches!(11 matches!(
242 self,12 self,
243 Self::Whitespace13 Self::WHITESPACE
244 | Self::MultiLineComment14 | Self::MULTI_LINE_COMMENT
245 | Self::SingelLineSlashComment15 | Self::SINGLE_LINE_HASH_COMMENT
246 | Self::SingleLineHashComment16 | Self::SINGLE_LINE_SLASH_COMMENT
247 )17 )
248 }18 }
19 pub fn is_string(self) -> bool {
20 matches!(
21 self,
22 Self::STRING_SINGLE
23 | Self::STRING_DOUBLE
24 | Self::STRING_SINGLE_VERBATIM
25 | Self::STRING_DOUBLE_VERBATIM
26 | Self::STRING_BLOCK
27 )
28 }
29 pub fn is_number(self) -> bool {
30 matches!(self, Self::NUMBER)
31 }
32 pub fn is_literal(self) -> bool {
33 matches!(
34 self,
35 Self::NULL_KW
36 | Self::TRUE_KW | Self::FALSE_KW
37 | Self::SELF_KW | Self::DOLLAR
38 | Self::SUPER_KW
39 )
40 }
249}41}
25042
251pub struct Lexer<'a> {43pub struct Lexer<'a> {
293 Lexer::new(input).collect()85 Lexer::new(input).collect()
294}86}
295
296impl From<SyntaxKind> for rowan::SyntaxKind {
297 fn from(kind: SyntaxKind) -> Self {
298 Self(kind as u16)
299 }
300}
301
302use SyntaxKind::*;
303
304#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
305pub enum Lang {}
306impl rowan::Language for Lang {
307 type Kind = SyntaxKind;
308 fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
309 assert!(raw.0 <= Root as u16);
310 unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
311 }
312 fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
313 kind.into()
314 }
315}
31687
modifiedcrates/jrsonnet-rowan-parser/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/lib.rs
+++ b/crates/jrsonnet-rowan-parser/src/lib.rs
@@ -1,139 +1,20 @@
 #![deny(unused_must_use)]
 
+mod ast;
 mod binary;
 mod event;
+mod generated;
+mod language;
 mod lex;
 mod marker;
 mod parser;
 mod string_block;
+mod tests;
 mod token_set;
 mod unary;
-
-#[cfg(test)]
-mod tests {
-	use miette::{Diagnostic, GraphicalReportHandler, LabeledSpan};
-	use thiserror::Error;
 
-	use crate::parser::parse;
-
-	#[derive(Debug, Error)]
-	#[error("syntax error")]
-	struct MyDiagnostic {
-		code: String,
-		spans: Vec<LabeledSpan>,
-	}
-	impl Diagnostic for MyDiagnostic {
-		fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
-			None
-		}
-
-		fn severity(&self) -> Option<miette::Severity> {
-			None
-		}
-
-		fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
-			None
-		}
-
-		fn url<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
-			None
-		}
-
-		fn source_code(&self) -> Option<&dyn miette::SourceCode> {
-			Some(&self.code)
-		}
-
-		fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
-			Some(Box::new(self.spans.clone().into_iter()))
-		}
-
-		fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
-			None
-		}
-	}
-
-	fn process(text: &str) -> String {
-		use std::fmt::Write;
-		let mut out = String::new();
-		let node = parse(text);
-		write!(out, "{:#?}", node.syntax()).unwrap();
-		if !node.errors.is_empty() && !text.is_empty() {
-			writeln!(out, "===").unwrap();
-			for err in &node.errors {
-				writeln!(out, "{:?}", err).unwrap();
-			}
-			let diag = MyDiagnostic {
-				code: text.to_string(),
-				spans: node.errors.into_iter().map(|e| e.into()).collect(),
-			};
-
-			let handler = GraphicalReportHandler::new();
-
-			write!(out, "===").unwrap();
-			handler.render_report(&mut out, &diag).unwrap();
-		}
-		out
-	}
-	macro_rules! mk_test {
-		($($name:ident => $test:expr)+) => {$(
-			#[test]
-			fn $name() {
-				let src = indoc::indoc!($test);
-				let result = process(&src);
-				insta::assert_snapshot!(stringify!($name), result, src);
-
-			}
-		)+};
-	}
-	mk_test!(
-		empty => r#" "#
-		function => r#"
-			function(a, b = 1) a + b
-		"#
-		function_error_no_value => r#"
-			function(a, b = ) a + b
-		"#
-		function_error_rparen => r#"
-			function(a, b
-		"#
-		function_error_body => r#"
-			function(a, b)
-		"#
-		local_novalue => r#"
-			local a =
-		"#
-		local_no_value_recovery => r#"
-			local a =
-			local b = 3;
-			1
-		"#
-
-		array_comp => r#"
-			[a for a in [1, 2, 3]]
-		"#
-		array_comp_incompatible_with_multiple_elems => r#"
-			[a for a in [1, 2, 3], b]
-		"#
-
-		no_rhs => r#"
-			a +
-		"#
-		no_lhs => r#"
-			+ 2
-		"#
-		no_operator => "
-			2 2
-		"
-
-		named_before_positional => "
-			a(1, 2, b=4, 3, 5, k = 12, 6)
-		"
-
-		wrong_field_end => "
-			{
-				a: 1;
-				b: 2;
-			}
-		"
-	);
-}
+pub use generated::syntax_kinds::SyntaxKind;
+pub use language::{
+	JsonnetLanguage, PreorderWithTokens, SyntaxElement, SyntaxElementChildren, SyntaxNode,
+	SyntaxNodeChildren, SyntaxToken,
+};
modifiedcrates/jrsonnet-rowan-parser/src/marker.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/marker.rs
+++ b/crates/jrsonnet-rowan-parser/src/marker.rs
@@ -1,7 +1,7 @@
 use drop_bomb::DropBomb;
 use rowan::TextRange;
 
-use crate::{event::Event, lex::SyntaxKind, parser::Parser};
+use crate::{event::Event, parser::Parser, SyntaxKind};
 
 pub struct Ranger {
 	pub pos: usize,
modifiedcrates/jrsonnet-rowan-parser/src/parser.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/parser.rs
+++ b/crates/jrsonnet-rowan-parser/src/parser.rs
@@ -1,32 +1,19 @@
-use std::cell::Cell;
-use std::fmt::Display;
-use std::rc::Rc;
+use std::{cell::Cell, fmt::Display, rc::Rc};
 
-use miette::Diagnostic;
-use miette::LabeledSpan;
-use miette::SourceOffset;
-use miette::SourceSpan;
-use rowan::GreenNode;
+use miette::{LabeledSpan, SourceOffset, SourceSpan};
+use rowan::{GreenNode, TextRange, TextSize};
 
-use rowan::TextRange;
-use rowan::TextSize;
-use thiserror::Error;
-
-use crate::binary::BinaryOperator;
-use crate::event::Event;
-use crate::event::Sink;
-use crate::lex::lex;
-use crate::lex::Lang;
-use crate::lex::Lexeme;
-use crate::lex::SyntaxKind;
-use crate::lex::SyntaxKind::*;
-use crate::marker::AsRange;
-use crate::marker::CompletedMarker;
-use crate::marker::FinishedRanger;
-use crate::marker::Marker;
-use crate::marker::Ranger;
-use crate::token_set::TokenSet;
-use crate::unary::UnaryOperator;
+use crate::{
+	binary::BinaryOperator,
+	event::{Event, Sink},
+	lex::{lex, Lexeme},
+	marker::{AsRange, CompletedMarker, Marker, Ranger},
+	token_set::SyntaxKindSet,
+	unary::UnaryOperator,
+	SyntaxKind,
+	SyntaxKind::*,
+	SyntaxNode, T, TS,
+};
 
 pub struct Parse {
 	pub green_node: GreenNode,
@@ -58,13 +45,7 @@
 	expected_syntax_tracking_state: Rc<Cell<ExpectedSyntaxTrackingState>>,
 }
 
-const DEFAULT_RECOVERY_SET: TokenSet = TokenSet::new(&[
-	SymbolSemi,
-	RParen,
-	SymbolRightBracket,
-	SymbolRightBrace,
-	KeywordLocal,
-]);
+const DEFAULT_RECOVERY_SET: SyntaxKindSet = TS![; ')' ']' '}' local];
 
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub enum SyntaxError {
@@ -172,16 +153,20 @@
 			let end = ranger.finish(&self);
 			self.custom_error(end, "unexpected input after expression");
 		}
-		m.complete(&mut self, Root);
+		m.complete(&mut self, SOURCE_FILE);
 
 		self.events
 	}
 
 	pub(crate) fn expect(&mut self, kind: SyntaxKind) {
-		self.expect_with_recovery_set(kind, TokenSet::default())
+		self.expect_with_recovery_set(kind, TS![])
 	}
 
-	pub(crate) fn expect_with_recovery_set(&mut self, kind: SyntaxKind, recovery_set: TokenSet) {
+	pub(crate) fn expect_with_recovery_set(
+		&mut self,
+		kind: SyntaxKind,
+		recovery_set: SyntaxKindSet,
+	) {
 		if self.at(kind) {
 			self.bump();
 		} else {
@@ -239,17 +224,17 @@
 	}
 	pub(crate) fn error_with_recovery_set(
 		&mut self,
-		recovery_set: TokenSet,
+		recovery_set: SyntaxKindSet,
 	) -> Option<CompletedMarker> {
 		self.error_with_recovery_set_no_default(recovery_set.union(DEFAULT_RECOVERY_SET))
 	}
 	pub fn error_with_no_skip(&mut self) -> Option<CompletedMarker> {
-		self.error_with_recovery_set_no_default(TokenSet::ALL)
+		self.error_with_recovery_set_no_default(SyntaxKindSet::ALL)
 	}
 
 	pub fn error_with_recovery_set_no_default(
 		&mut self,
-		recovery_set: TokenSet,
+		recovery_set: SyntaxKindSet,
 	) -> Option<CompletedMarker> {
 		let expected_syntax = self.expected_syntax.take().unwrap();
 		self.expected_syntax_tracking_state
@@ -280,7 +265,7 @@
 
 		let m = self.start();
 		self.bump();
-		Some(m.complete(self, SyntaxKind::Error))
+		Some(m.complete(self, SyntaxKind::ERROR))
 	}
 
 	fn bump(&mut self) {
@@ -323,7 +308,7 @@
 		}
 		self.peek() == Some(kind)
 	}
-	pub fn at_set(&mut self, set: TokenSet) -> bool {
+	pub fn at_set(&mut self, set: SyntaxKindSet) -> bool {
 		self.peek().map_or(false, |k| set.contains(k))
 	}
 	pub fn at_end(&mut self) -> bool {
@@ -356,7 +341,7 @@
 }
 macro_rules! at_match {
 	($p:ident {
-		$($r:ident => $e:expr,)*
+		$($r:expr => $e:expr,)*
 		_ => $else:expr $(,)?
 	}) => {{
 		$(
@@ -375,26 +360,26 @@
 
 	loop {
 		let op = at_match!(p {
-			OpMul => BinaryOperator::Mul,
-			OpDiv => BinaryOperator::Div,
-			OpMod => BinaryOperator::Mod,
-			OpPlus => BinaryOperator::Plus,
-			OpMinus => BinaryOperator::Minus,
-			OpShiftLeft => BinaryOperator::ShiftLeft,
-			OpShiftRight => BinaryOperator::ShiftRight,
-			OpLessThan => BinaryOperator::LessThan,
-			OpGreaterThan => BinaryOperator::GreaterThan,
-			OpLessThanOrEqual => BinaryOperator::LessThanOrEqual,
-			OpGreaterThanOrEqual => BinaryOperator::GreaterThanOrEqual,
-			OpEqual => BinaryOperator::Equal,
-			OpNotEqual => BinaryOperator::NotEqual,
-			OpBitAnd => BinaryOperator::BitAnd,
-			OpBitXor => BinaryOperator::BitXor,
-			OpBitOr => BinaryOperator::BitOr,
-			OpAnd => BinaryOperator::And,
-			OpOr => BinaryOperator::Or,
-			OpIn => BinaryOperator::In,
-			SymbolLeftBrace => BinaryOperator::ObjectApply,
+			T![*] => BinaryOperator::Mul,
+			T![/] => BinaryOperator::Div,
+			T![%] => BinaryOperator::Mod,
+			T![+] => BinaryOperator::Plus,
+			T![-] => BinaryOperator::Minus,
+			T![<<] => BinaryOperator::ShiftLeft,
+			T![>>] => BinaryOperator::ShiftRight,
+			T![<] => BinaryOperator::LessThan,
+			T![>] => BinaryOperator::GreaterThan,
+			T![<=] => BinaryOperator::LessThanOrEqual,
+			T![>=] => BinaryOperator::GreaterThanOrEqual,
+			T![==] => BinaryOperator::Equal,
+			T![!=] => BinaryOperator::NotEqual,
+			T![&] => BinaryOperator::BitAnd,
+			T![^] => BinaryOperator::BitXor,
+			T![|] => BinaryOperator::BitOr,
+			T![&&] => BinaryOperator::And,
+			T![||] => BinaryOperator::Or,
+			T![in] => BinaryOperator::In,
+			T!['{'] => BinaryOperator::ObjectApply,
 			_ => break,
 		});
 		let (left_binding_power, right_binding_power) = op.binding_power();
@@ -412,9 +397,9 @@
 		lhs = m.complete(
 			p,
 			if op == BinaryOperator::ObjectApply {
-				ObjectApply
+				EXPR_OBJ_EXTEND
 			} else {
-				BinOp
+				EXPR_BINARY
 			},
 		);
 
@@ -425,37 +410,37 @@
 	Some(lhs)
 }
 fn compspec(p: &mut Parser) {
-	assert!(p.at(KeywordFor) || p.at(KeywordIf));
-	if p.at(KeywordFor) {
+	assert!(p.at(T![for]) || p.at(T![if]));
+	if p.at(T![for]) {
 		let m = p.start();
 		p.bump();
-		p.expect(Ident);
-		p.expect(OpIn);
+		p.expect(IDENT);
+		p.expect(T![in]);
 		expr(p);
-		m.complete(p, CompspecFor);
-	} else if p.at(KeywordIf) {
+		m.complete(p, FOR_SPEC);
+	} else if p.at(T![in]) {
 		let m = p.start();
 		p.bump();
 		expr(p);
-		m.complete(p, CompspecIf);
+		m.complete(p, IF_SPEC);
 	} else {
 		unreachable!()
 	}
 }
 fn comma(p: &mut Parser) -> bool {
-	if p.at(SymbolComma) {
+	if p.at(T![,]) {
 		p.bump();
 		true
 	} else {
 		false
 	}
 }
-fn comma_with_alternatives(p: &mut Parser, set: TokenSet) -> bool {
-	if p.at(SymbolComma) {
+fn comma_with_alternatives(p: &mut Parser, set: SyntaxKindSet) -> bool {
+	if p.at(T![,]) {
 		p.bump();
 		true
 	} else if p.at_set(set) {
-		p.expect_with_no_skip(SymbolComma);
+		p.expect_with_no_skip(T![,]);
 		p.bump();
 		true
 	} else {
@@ -464,94 +449,94 @@
 }
 fn field_name(p: &mut Parser) {
 	let _e = p.expected_syntax_name("field name");
-	if p.at(SymbolLeftBracket) {
+	if p.at(T!['[']) {
 		p.bump();
 		expr(p);
-		p.expect(SymbolRightBracket);
-	} else if p.at(Ident) {
+		p.expect(T![']']);
+	} else if p.at(IDENT) {
 		p.bump()
 	} else {
-		p.error_with_recovery_set(TokenSet::new(&[SymbolSemi]));
+		p.error_with_recovery_set(TS![;]);
 	}
 }
 fn object(p: &mut Parser) -> CompletedMarker {
-	assert!(p.at(SymbolLeftBrace));
+	assert!(p.at(T!['{']));
 	let m = p.start();
 	p.bump();
 
 	loop {
-		if p.at(SymbolRightBrace) {
+		if p.at(T!['}']) {
 			p.bump();
 			break;
 		}
 		let m = p.start();
 		field_name(p);
-		p.expect(SymbolColon);
+		p.expect(T![,]);
 		expr(p);
-		while p.at(KeywordFor) || p.at(KeywordIf) {
+		while p.at(T![for]) || p.at(T![if]) {
 			compspec(p)
 		}
-		m.complete(p, Field);
-		if comma_with_alternatives(p, TokenSet::new(&[SymbolAssign])) {
+		m.complete(p, MEMBER);
+		if comma_with_alternatives(p, SyntaxKindSet::new(&[T![=]])) {
 			continue;
 		}
-		p.expect(SymbolRightBrace);
+		p.expect(R_BRACE);
 		break;
 	}
 
-	m.complete(p, Object)
+	m.complete(p, OBJ_BODY)
 }
 
 fn params(p: &mut Parser) -> CompletedMarker {
-	assert!(p.at(LParen));
+	assert!(p.at(T!['(']));
 	let m = p.start();
 	p.bump();
 
 	loop {
-		if p.at(RParen) {
+		if p.at(T![')']) {
 			p.bump();
 			break;
 		}
 		let m = p.start();
-		p.expect(Ident);
-		if p.at(SymbolAssign) {
+		p.expect(IDENT);
+		if p.at(T![=]) {
 			p.bump();
 			expr(p);
 		}
-		m.complete(p, DefParam);
+		m.complete(p, PARAM);
 		if comma(p) {
 			continue;
 		}
-		p.expect(RParen);
+		p.expect(T![')']);
 		break;
 	}
 
-	m.complete(p, DefParams)
+	m.complete(p, PARAMS_DESC)
 }
 fn args(p: &mut Parser) {
-	assert!(p.at(LParen));
+	assert!(p.at(T!['(']));
 	p.bump();
 
 	let mut error_positional_start = None::<Marker>;
 	let mut started_named = Cell::new(false);
 	let mut on_positional = |p: &mut Parser, m: Marker| {
-		let c = m.complete(p, DefPositionalArg);
+		let c = m.complete(p, ARG);
 		if started_named.get() && error_positional_start.is_none() {
 			error_positional_start = Some(c.precede(p));
 		}
 	};
 	loop {
-		if p.at(RParen) {
+		if p.at(T![')']) {
 			break;
 		}
 
 		let m = p.start();
-		if p.at(Ident) {
+		if p.at(IDENT) {
 			p.bump();
-			if p.at(SymbolAssign) {
+			if p.at(T![=]) {
 				p.bump();
 				expr(p);
-				m.complete(p, DefNamedArg);
+				m.complete(p, ARG);
 				started_named.set(true);
 			} else {
 				on_positional(p, m);
@@ -566,14 +551,14 @@
 		break;
 	}
 	if let Some(error_positional_start) = error_positional_start {
-		let c = error_positional_start.complete(p, ErrorPositionalAfterNamed);
+		let c = error_positional_start.complete(p, ERROR);
 		p.custom_error(c, "positional arguments can't be placed after named")
 	}
-	p.expect(RParen);
+	p.expect(T![')']);
 }
 
 fn array(p: &mut Parser) -> CompletedMarker {
-	assert!(p.at(SymbolLeftBracket));
+	assert!(p.at(T!['[']));
 	// Start the list node
 	let m = p.start();
 	p.bump(); // '['
@@ -583,31 +568,25 @@
 	let mut elems = 0;
 
 	loop {
-		if p.at(SymbolRightBracket) {
+		if p.at(T![']']) {
 			p.bump();
 			break;
 		}
 		elems += 1;
-		let m = p.start();
-		{
-			let m = p.start();
-			expr(p);
-			m.complete(p, BodyDef);
-		}
+		expr(p);
 		let c = p.start_ranger();
 		let mut had_spec = false;
-		while p.at(KeywordFor) || p.at(KeywordIf) {
+		while p.at(T![for]) || p.at(T![if]) {
 			had_spec = true;
 			compspec(p)
 		}
 		if had_spec {
 			compspecs.push(c.finish(p));
 		}
-		m.complete(p, ArrayElem);
 		if comma(p) {
 			continue;
 		}
-		p.expect(SymbolRightBracket);
+		p.expect(T![']']);
 		break;
 	}
 
@@ -618,47 +597,51 @@
 				"compspec may only be used if there is only one array element",
 			)
 		}
-	}
 
-	m.complete(p, Array)
+		m.complete(p, EXPR_ARRAY)
+	} else if !compspecs.is_empty() {
+		m.complete(p, EXPR_ARRAY_COMP)
+	} else {
+		m.complete(p, EXPR_ARRAY)
+	}
 }
 
 fn lhs(p: &mut Parser) -> Option<CompletedMarker> {
 	let mut lhs = lhs_basic(p)?;
 
 	loop {
-		if p.at(SymbolDot) {
+		if p.at(T![.]) {
 			let m = lhs.precede(p);
 			p.bump();
-			p.expect(Ident);
-			lhs = m.complete(p, FieldAccess);
-		} else if p.at(SymbolLeftBracket) {
+			p.expect(IDENT);
+			lhs = m.complete(p, EXPR_INDEX);
+		} else if p.at(T!['[']) {
 			let m = lhs.precede(p);
 			p.bump();
 			// Start
-			if !p.at(SymbolColon) {
+			if !p.at(T![:]) {
 				expr(p);
 			}
-			if p.at(SymbolColon) {
+			if p.at(T![:]) {
 				p.bump();
 				// End
-				if !p.at(SymbolRightBracket) && !p.at(SymbolColon) {
+				if !p.at(T![']']) && !p.at(T![:]) {
 					expr(p);
 				}
-				if p.at(SymbolColon) {
+				if p.at(T![:]) {
 					p.bump();
 					// Step
-					if !p.at(SymbolRightBracket) {
+					if !p.at(T![']']) {
 						expr(p);
 					}
 				}
 			}
-			p.expect(SymbolRightBracket);
-			lhs = m.complete(p, Slice);
-		} else if p.at(LParen) {
+			p.expect(T![']']);
+			lhs = m.complete(p, EXPR_SLICE);
+		} else if p.at(T!['(']) {
 			let m = lhs.precede(p);
 			args(p);
-			lhs = m.complete(p, FunctionCall);
+			lhs = m.complete(p, EXPR_APPLY);
 		} else {
 			break;
 		}
@@ -669,128 +652,104 @@
 
 fn lhs_basic(p: &mut Parser) -> Option<CompletedMarker> {
 	let _e = p.expected_syntax_name("value");
-	Some(
-		if p.at(Number)
-			|| p.at(StringSingleQuoted)
-			|| p.at(StringDoubleQuoted)
-			|| p.at(StringSingleVerbatim)
-			|| p.at(StringDoubleVerbatim)
-			|| p.at(StringBlock)
-			|| p.at(KeywordNull)
-			|| p.at(SymbolDollar)
-			|| p.at(KeywordSuper)
-			|| p.at(KeywordSelf)
-		{
-			let m = p.start();
-			p.bump();
-			m.complete(p, Literal)
-		} else if p.at(Ident) {
-			let m = p.start();
-			p.bump();
-			m.complete(p, Ident)
-		} else if p.at(SymbolLeftBracket) {
-			array(p)
-		} else if p.at(SymbolLeftBrace) {
-			object(p)
-		} else if p.at(KeywordLocal) {
-			let m = p.start();
-			p.bump();
-			let mut sus_local = None;
-			loop {
-				p.expect_with_recovery_set(
-					Ident,
-					TokenSet::new(&[SymbolAssign, SymbolSemi, KeywordLocal]),
-				);
-				if p.at(LParen) {
-					params(p);
-				}
+	Some(if p.peek().map(|l| l.is_literal()).unwrap_or(false) {
+		let m = p.start();
+		p.bump();
+		m.complete(p, EXPR_LITERAL)
+	} else if p.peek().map(|l| l.is_string()).unwrap_or(false) {
+		let m = p.start();
+		p.bump();
+		m.complete(p, EXPR_STRING)
+	} else if p.peek().map(|l| l.is_number()).unwrap_or(false) {
+		let m = p.start();
+		p.bump();
+		m.complete(p, EXPR_NUMBER)
+	} else if p.at(IDENT) {
+		let m = p.start();
+		p.bump();
+		m.complete(p, EXPR_VAR)
+	} else if p.at(T!['[']) {
+		array(p)
+	} else if p.at(T!['{']) {
+		object(p)
+	} else if p.at(T![local]) {
+		let m = p.start();
+		p.bump();
+		let mut sus_local = None;
+		loop {
+			p.expect_with_recovery_set(IDENT, TS![= ; local]);
+			if p.at(T!['(']) {
+				params(p);
+			}
 
-				let sus_local_candidate = p.start_ranger();
-				p.expect_with_recovery_set(
-					SymbolAssign,
-					TokenSet::new(&[SymbolSemi, KeywordLocal]),
-				);
+			let sus_local_candidate = p.start_ranger();
+			p.expect_with_recovery_set(T![=], TS![; local]);
 
-				sus_local = p.at(KeywordLocal).then(|| sus_local_candidate.finish(p));
-				expr(p);
+			sus_local = p.at(T![local]).then(|| sus_local_candidate.finish(p));
+			expr(p);
 
-				if !comma(p) {
-					break;
-				}
-			}
-			p.expect(SymbolSemi);
-			if let Some(sus_local) = sus_local {
-				if sus_local.had_error_since(p) {
-					p.custom_error(sus_local, "unusal local placement, missing ';' ?")
-				}
-			}
-			{
-				let m = p.start();
-				expr(p);
-				m.complete(p, BodyDef);
-			}
-			m.complete(p, Local)
-		} else if p.at(KeywordFunction) {
-			let m = p.start();
-			p.bump();
-			args(p);
-			{
-				let m = p.start();
-				expr(p);
-				m.complete(p, BodyDef);
+			if !comma(p) {
+				break;
 			}
-			m.complete(p, FunctionDef)
-		} else if p.at(KeywordError) {
-			let m = p.start();
-			p.bump();
-			expr(p);
-			m.complete(p, ExprError)
-		} else if p.at(KeywordAssert) {
-			let m = p.start();
-			p.bump();
-			expr(p);
-			if p.at(SymbolColon) {
-				p.bump();
-				expr(p);
+		}
+		p.expect(T![;]);
+		if let Some(sus_local) = sus_local {
+			if sus_local.had_error_since(p) {
+				p.custom_error(sus_local, "unusal local placement, missing ';' ?")
 			}
-			m.complete(p, ExprAssert)
-		} else if p.at(KeywordImport) || p.at(KeywordImportStr) {
-			let m = p.start();
+		}
+		expr(p);
+		m.complete(p, T![local])
+	} else if p.at(T![function]) {
+		let m = p.start();
+		p.bump();
+		args(p);
+		expr(p);
+		m.complete(p, EXPR_FUNCTION)
+	} else if p.at(T![error]) {
+		let m = p.start();
+		p.bump();
+		expr(p);
+		m.complete(p, EXPR_ERROR)
+	} else if p.at(T![assert]) {
+		let m = p.start();
+		p.bump();
+		expr(p);
+		if p.at(T![:]) {
 			p.bump();
 			expr(p);
-			m.complete(p, ExprImport)
-		} else if p.at(OpMinus) || p.at(OpNot) || p.at(OpBitNegate) {
-			let op = match p.peek().unwrap() {
-				OpMinus => UnaryOperator::Minus,
-				OpNot => UnaryOperator::Not,
-				OpBitNegate => UnaryOperator::BitNegate,
-				_ => unreachable!(),
-			};
-			let ((), right_binding_power) = op.binding_power();
+		}
+		m.complete(p, EXPR_ASSERT)
+	} else if p.at(T![import]) || p.at(T![importstr]) || p.at(T![importbin]) {
+		let m = p.start();
+		p.bump();
+		expr(p);
+		m.complete(p, EXPR_IMPORT)
+	} else if p.at(T![-]) || p.at(T![!]) || p.at(T![~]) {
+		let op = match p.peek().unwrap() {
+			T![-] => UnaryOperator::Minus,
+			T![!] => UnaryOperator::Not,
+			T![~] => UnaryOperator::BitNegate,
+			_ => unreachable!(),
+		};
+		let ((), right_binding_power) = op.binding_power();
 
-			let m = p.start();
-			p.bump();
-			expr_binding_power(p, right_binding_power);
-			m.complete(p, UnaryOp)
-		} else if p.at(LParen) {
-			let m = p.start();
-			p.bump();
-			expr(p);
-			assert!(p.at(RParen));
-			p.bump();
-			m.complete(p, Parened)
-		} else {
-			p.error_with_no_skip();
-			return None;
-		},
-	)
+		let m = p.start();
+		p.bump();
+		expr_binding_power(p, right_binding_power);
+		m.complete(p, EXPR_UNARY)
+	} else if p.at(T!['(']) {
+		let m = p.start();
+		p.bump();
+		expr(p);
+		assert!(p.at(T![')']));
+		p.bump();
+		m.complete(p, EXPR_PARENED)
+	} else {
+		p.error_with_no_skip();
+		return None;
+	})
 }
-
-type SyntaxNode = rowan::SyntaxNode<Lang>;
-#[allow(unused)]
-type SyntaxToken = rowan::SyntaxToken<Lang>;
-#[allow(unused)]
-type SyntaxElement = rowan::NodeOrToken<SyntaxNode, SyntaxToken>;
 
 impl Parse {
 	pub fn syntax(&self) -> SyntaxNode {
modifiedcrates/jrsonnet-rowan-parser/src/string_block.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/string_block.rs
+++ b/crates/jrsonnet-rowan-parser/src/string_block.rs
@@ -11,7 +11,7 @@
 
 use StringBlockToken::*;
 
-use crate::lex::SyntaxKind;
+use crate::SyntaxKind;
 
 pub fn lex_str_block_test<'a>(lex: &mut logos::Lexer<'a, SyntaxKind>) {
 	lex_str_block(lex);
addedcrates/jrsonnet-rowan-parser/src/tests.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/tests.rs
@@ -0,0 +1,127 @@
+#![cfg(test)]
+
+use miette::{Diagnostic, GraphicalReportHandler, LabeledSpan};
+use thiserror::Error;
+
+use crate::parser::parse;
+
+#[derive(Debug, Error)]
+#[error("syntax error")]
+struct MyDiagnostic {
+	code: String,
+	spans: Vec<LabeledSpan>,
+}
+impl Diagnostic for MyDiagnostic {
+	fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
+		None
+	}
+
+	fn severity(&self) -> Option<miette::Severity> {
+		None
+	}
+
+	fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
+		None
+	}
+
+	fn url<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
+		None
+	}
+
+	fn source_code(&self) -> Option<&dyn miette::SourceCode> {
+		Some(&self.code)
+	}
+
+	fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
+		Some(Box::new(self.spans.clone().into_iter()))
+	}
+
+	fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
+		None
+	}
+}
+
+fn process(text: &str) -> String {
+	use std::fmt::Write;
+	let mut out = String::new();
+	let node = parse(text);
+	write!(out, "{:#?}", node.syntax()).unwrap();
+	if !node.errors.is_empty() && !text.is_empty() {
+		writeln!(out, "===").unwrap();
+		for err in &node.errors {
+			writeln!(out, "{:?}", err).unwrap();
+		}
+		let diag = MyDiagnostic {
+			code: text.to_string(),
+			spans: node.errors.into_iter().map(|e| e.into()).collect(),
+		};
+
+		let handler = GraphicalReportHandler::new();
+
+		write!(out, "===").unwrap();
+		handler.render_report(&mut out, &diag).unwrap();
+	}
+	out
+}
+macro_rules! mk_test {
+		($($name:ident => $test:expr)+) => {$(
+			#[test]
+			fn $name() {
+				let src = indoc::indoc!($test);
+				let result = process(&src);
+				insta::assert_snapshot!(stringify!($name), result, src);
+
+			}
+		)+};
+	}
+mk_test!(
+	empty => r#" "#
+	function => r#"
+			function(a, b = 1) a + b
+		"#
+	function_error_no_value => r#"
+			function(a, b = ) a + b
+		"#
+	function_error_rparen => r#"
+			function(a, b
+		"#
+	function_error_body => r#"
+			function(a, b)
+		"#
+	local_novalue => r#"
+			local a =
+		"#
+	local_no_value_recovery => r#"
+			local a =
+			local b = 3;
+			1
+		"#
+
+	array_comp => r#"
+			[a for a in [1, 2, 3]]
+		"#
+	array_comp_incompatible_with_multiple_elems => r#"
+			[a for a in [1, 2, 3], b]
+		"#
+
+	no_rhs => r#"
+			a +
+		"#
+	no_lhs => r#"
+			+ 2
+		"#
+	no_operator => "
+			2 2
+		"
+
+	named_before_positional => "
+			a(1, 2, b=4, 3, 5, k = 12, 6)
+		"
+
+	wrong_field_end => "
+			{
+				a: 1;
+				b: 2;
+			}
+		"
+);
modifiedcrates/jrsonnet-rowan-parser/src/token_set.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/token_set.rs
+++ b/crates/jrsonnet-rowan-parser/src/token_set.rs
@@ -1,24 +1,24 @@
-use crate::lex::SyntaxKind;
+use crate::SyntaxKind;
 
 #[derive(Clone, Copy, Default)]
-pub struct TokenSet(u64);
+pub struct SyntaxKindSet(u64);
 
-impl TokenSet {
+impl SyntaxKindSet {
 	pub const EMPTY: Self = Self(0);
 	pub const ALL: Self = Self(u64::MAX);
 
-	pub const fn new(kinds: &[SyntaxKind]) -> TokenSet {
+	pub const fn new(kinds: &[SyntaxKind]) -> SyntaxKindSet {
 		let mut res = 0u64;
 		let mut i = 0;
 		while i < kinds.len() {
 			res |= mask(kinds[i]);
 			i += 1
 		}
-		TokenSet(res)
+		SyntaxKindSet(res)
 	}
 
-	pub const fn union(self, other: TokenSet) -> TokenSet {
-		TokenSet(self.0 | other.0)
+	pub const fn union(self, other: SyntaxKindSet) -> SyntaxKindSet {
+		SyntaxKindSet(self.0 | other.0)
 	}
 
 	pub const fn contains(&self, kind: SyntaxKind) -> bool {
@@ -29,3 +29,14 @@
 const fn mask(kind: SyntaxKind) -> u64 {
 	1u64 << (kind as usize)
 }
+
+#[macro_export]
+macro_rules! TS {
+	($($tt:tt)*) => {
+		SyntaxKindSet::new(&[
+			$(
+				T![$tt]
+			),*
+		])
+	};
+}
addedxtask/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/xtask/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "xtask"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+anyhow = "1.0.57"
+itertools = "0.10.3"
+proc-macro2 = "1.0.39"
+quote = "1.0.18"
+ungrammar = "1.16.1"
+xshell = "0.2.2"
addedxtask/src/main.rsdiffbeforeafterboth
--- /dev/null
+++ b/xtask/src/main.rs
@@ -0,0 +1,7 @@
+use anyhow::Result;
+
+mod sourcegen;
+
+fn main() -> Result<()> {
+	sourcegen::generate_ungrammar()
+}
addedxtask/src/sourcegen/ast.rsdiffbeforeafterboth
--- /dev/null
+++ b/xtask/src/sourcegen/ast.rs
@@ -0,0 +1,93 @@
+use proc_macro2::TokenStream;
+use quote::{format_ident, quote};
+
+use super::{escape_token_macro, KindsSrc};
+
+impl AstNodeSrc {
+	pub fn remove_field(&mut self, to_remove: Vec<usize>) {
+		to_remove.into_iter().rev().for_each(|idx| {
+			self.fields.remove(idx);
+		});
+	}
+}
+
+#[allow(dead_code)]
+#[derive(Default, Debug)]
+pub struct AstSrc {
+	pub tokens: Vec<String>,
+	pub nodes: Vec<AstNodeSrc>,
+	pub enums: Vec<AstEnumSrc>,
+}
+#[derive(Debug)]
+pub struct AstNodeSrc {
+	pub doc: Vec<String>,
+	pub name: String,
+	pub traits: Vec<String>,
+	pub fields: Vec<Field>,
+}
+
+#[derive(Debug, Eq, PartialEq)]
+pub enum Field {
+	Token(String),
+	Node {
+		name: String,
+		ty: String,
+		cardinality: Cardinality,
+	},
+}
+
+#[derive(Debug, Eq, PartialEq)]
+pub enum Cardinality {
+	Optional,
+	Many,
+}
+
+#[derive(Debug, Clone)]
+pub struct AstEnumSrc {
+	pub doc: Vec<String>,
+	pub name: String,
+	pub traits: Vec<String>,
+	pub variants: Vec<String>,
+}
+
+impl Field {
+	pub fn is_many(&self) -> bool {
+		matches!(
+			self,
+			Field::Node {
+				cardinality: Cardinality::Many,
+				..
+			}
+		)
+	}
+	pub fn token_kind(&self) -> Option<TokenStream> {
+		match self {
+			Field::Token(token) => {
+				let token: TokenStream = escape_token_macro(token);
+				Some(quote! { T![#token] })
+			}
+			_ => None,
+		}
+	}
+
+	pub fn method_name(&self, kinds: &KindsSrc) -> proc_macro2::Ident {
+		match self {
+			Field::Token(name) => {
+				if let Some(punct_name) = kinds.get_punct_name(name) {
+					format_ident!("{}_token", punct_name.to_lowercase())
+				} else {
+					format_ident!("{}_token", name.to_lowercase())
+				}
+			}
+			Field::Node { name, .. } => {
+				format_ident!("{}", name)
+			}
+		}
+	}
+	pub fn ty(&self) -> proc_macro2::Ident {
+		match self {
+			Field::Token(_) => format_ident!("SyntaxToken"),
+			Field::Node { ty, .. } => format_ident!("{}", ty),
+		}
+	}
+}
addedxtask/src/sourcegen/mod.rsdiffbeforeafterboth
--- /dev/null
+++ b/xtask/src/sourcegen/mod.rs
@@ -0,0 +1,922 @@
+use std::{
+	collections::{BTreeSet, HashSet},
+	path::PathBuf,
+};
+
+use anyhow::Result;
+use ast::{AstEnumSrc, AstNodeSrc, AstSrc, Cardinality, Field};
+use itertools::Itertools;
+use proc_macro2::{Punct, Spacing, TokenStream};
+use quote::{format_ident, quote};
+use ungrammar::{Grammar, Rule};
+use util::{
+	ensure_file_contents, pluralize, reformat, to_lower_snake_case, to_pascal_case,
+	to_upper_snake_case,
+};
+
+mod ast;
+mod util;
+
+pub fn generate_ungrammar() -> Result<()> {
+	let grammar: Grammar = include_str!(concat!(
+		env!("CARGO_MANIFEST_DIR"),
+		"/../crates/jrsonnet-rowan-parser/jsonnet.ungram"
+	))
+	.parse()?;
+
+	let mut kinds: KindsSrc = KindsSrc {
+		punct: puncts![
+			"||" => "OR";
+			"&&" => "AND";
+			"|" => "BIT_OR";
+			"^" => "BIT_XOR";
+			"&" => "BIT_AND";
+			"==" => "EQ";
+			"!=" => "NE";
+			"<" => "LT";
+			">" => "GT";
+			"<=" => "LE";
+			">=" => "GE";
+			"<<" => "LHS";
+			">>" => "RHS";
+			"+" => "PLUS";
+			"-" => "MINUS";
+			"*" => "MUL";
+			"/" => "DIV";
+			"%" => "MODULO";
+			"!" => "NOT";
+			"~" => "BIT_NOT";
+			"[" => "L_BRACK";
+			"]" => "R_BRACK";
+			"(" => "L_PAREN";
+			")" => "R_PAREN";
+			"{" => "L_BRACE";
+			"}" => "R_BRACE";
+			":" => "COLON";
+			"::" => "COLONCOLON";
+			":::" => "COLONCOLONCOLON";
+			";" => "SEMI";
+			"." => "DOT";
+			"..." => "DOTDOTDOT";
+			"," => "COMMA";
+			"$" => "DOLLAR";
+			"=" => "ASSIGN";
+			"?" => "QUESTION_MARK";
+			"$intrinsicThisFile" => "INTRINSIC_THIS_FILE";
+			"$intrinsicId" => "INTRINSIC_ID";
+			"$intrinsic" => "INTRINSIC";
+		],
+		keywords: vec![],
+		literals: literals![
+			"NUMBER" => r"(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?";
+			"STRING_DOUBLE" => "\"(?s:[^\"\\\\]|\\\\.)*\"";
+			"STRING_SINGLE" => "'(?s:[^'\\\\]|\\\\.)*'";
+			"STRING_DOUBLE_VERBATIM" => "@\"(?:[^\"]|\"\")*\"";
+			"STRING_SINGLE_VERBATIM" => "@'(?:[^']|'')*'";
+			"STRING_BLOCK" => r"\|\|\|";
+
+			"IDENT" => r"[_a-zA-Z][_a-zA-Z0-9]*";
+			"WHITESPACE" => r"[ \t\n\r]+";
+			"SINGLE_LINE_SLASH_COMMENT" => r"//[^\r\n]*(\r\n|\n)?";
+			"SINGLE_LINE_HASH_COMMENT" => r"#[^\r\n]*(\r\n|\n)?";
+			"MULTI_LINE_COMMENT" => r"/\*([^*]|\*[^/])*\*/";
+		],
+		nodes: vec![],
+	};
+
+	let ast = lower(&kinds, &grammar);
+
+	for node in &ast.nodes {
+		let name = to_upper_snake_case(&node.name);
+		if !kinds.is_literal(&name) {
+			kinds.nodes.push(name);
+		}
+	}
+	for enum_ in &ast.enums {
+		let name = to_upper_snake_case(&enum_.name);
+		if !kinds.is_literal(&name) {
+			kinds.nodes.push(name);
+		}
+	}
+	for token in grammar.tokens() {
+		let token = &grammar[token];
+		let token = &token.name.clone();
+		let name = to_upper_snake_case(token);
+		if !kinds.is_punct(token) && !kinds.is_literal(&name) {
+			kinds.keywords.push(token.to_owned());
+		}
+	}
+
+	let syntax_kinds = generate_syntax_kinds(&kinds)?;
+
+	let tokens = generate_tokens(&ast)?;
+
+	let nodes = generate_nodes(&kinds, &ast)?;
+	ensure_file_contents(
+		&PathBuf::from(concat!(
+			env!("CARGO_MANIFEST_DIR"),
+			"/../crates/jrsonnet-rowan-parser/src/generated/syntax_kinds.rs",
+		)),
+		&syntax_kinds,
+	)?;
+	ensure_file_contents(
+		&PathBuf::from(concat!(
+			env!("CARGO_MANIFEST_DIR"),
+			"/../crates/jrsonnet-rowan-parser/src/generated/tokens.rs",
+		)),
+		&tokens,
+	)?;
+	ensure_file_contents(
+		&PathBuf::from(concat!(
+			env!("CARGO_MANIFEST_DIR"),
+			"/../crates/jrsonnet-rowan-parser/src/generated/nodes.rs",
+		)),
+		&nodes,
+	)?;
+	Ok(())
+}
+
+fn generate_tokens(grammar: &AstSrc) -> Result<String> {
+	let tokens = grammar.tokens.iter().map(|token| {
+		let name = format_ident!("{}", token);
+		let kind = format_ident!("{}", to_upper_snake_case(token));
+		quote! {
+			#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+			pub struct #name {
+				pub(crate) syntax: SyntaxToken,
+			}
+			impl std::fmt::Display for #name {
+				fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+					std::fmt::Display::fmt(&self.syntax, f)
+				}
+			}
+			impl AstToken for #name {
+				fn can_cast(kind: SyntaxKind) -> bool { kind == #kind }
+				fn cast(syntax: SyntaxToken) -> Option<Self> {
+					if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None }
+				}
+				fn syntax(&self) -> &SyntaxToken { &self.syntax }
+			}
+		}
+	});
+
+	Ok(reformat(
+		&quote! {
+			use crate::{SyntaxKind::{self, *}, SyntaxToken, ast::AstToken};
+			#(#tokens)*
+		}
+		.to_string(),
+	)?
+	.replace("#[derive", "\n#[derive"))
+}
+
+fn generate_syntax_kinds(grammar: &KindsSrc) -> Result<String> {
+	let (single_byte_tokens_values, single_byte_tokens): (Vec<_>, Vec<_>) = grammar
+		.punct
+		.iter()
+		.filter(|(token, _name)| token.len() == 1)
+		.map(|(token, name)| (token.chars().next().unwrap(), format_ident!("{}", name)))
+		.unzip();
+
+	let punctuation_values = grammar
+		.punct
+		.iter()
+		.map(|(token, _name)| escape_token_macro(token));
+	let punctuation = grammar
+		.punct
+		.iter()
+		.map(|(_token, name)| format_ident!("{}", name))
+		.collect::<Vec<_>>();
+	let punctuation_enum = grammar
+		.punct
+		.iter()
+		.map(|(token, name)| {
+			let id = format_ident!("{}", name);
+			quote! {
+				#[token(#token)]
+				#id
+			}
+		})
+		.collect::<Vec<_>>();
+
+	let x = |name: &str| format_ident!("{}_KW", to_upper_snake_case(name));
+	let full_keywords_values = &grammar.keywords;
+	let full_keywords = full_keywords_values.iter().map(|s| x(s.as_str()));
+
+	let all_keywords_values = grammar.keywords.to_vec();
+	let all_keywords_idents = all_keywords_values.iter().map(|kw| format_ident!("{}", kw));
+	let all_keywords = all_keywords_values
+		.iter()
+		.map(|s| x(&**s))
+		.collect::<Vec<_>>();
+	let all_keywords_enum = all_keywords_values
+		.iter()
+		.map(|s| {
+			let id = x(&**s);
+			quote! {
+				#[token(#s)]
+				#id
+			}
+		})
+		.collect::<Vec<_>>();
+
+	let tokens_enum = grammar
+		.literals
+		.iter()
+		.map(|l| {
+			let regex = &l.regex;
+			let id = format_ident!("{}", l.name);
+			let lexer = l
+				.lexer
+				.as_ref()
+				.map(|l| {
+					let id: TokenStream = l.parse().expect("path");
+					quote! {
+						, #id
+					}
+				})
+				.unwrap_or_else(|| quote! {});
+			quote! {
+				#[regex(#regex #lexer)]
+				#id
+			}
+		})
+		.collect::<Vec<_>>();
+
+	let nodes = grammar
+		.nodes
+		.iter()
+		.map(|name| format_ident!("{}", name))
+		.collect::<Vec<_>>();
+
+	let ast = quote! {
+		#![allow(bad_style, missing_docs, unreachable_pub)]
+		use logos::Logos;
+
+		/// The kind of syntax node, e.g. `IDENT`, `USE_KW`, or `STRUCT`.
+		#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Logos)]
+		#[repr(u16)]
+		pub enum SyntaxKind {
+			#[doc(hidden)]
+			TOMBSTONE,
+			#[doc(hidden)]
+			EOF,
+			#(#punctuation_enum,)*
+			#(#all_keywords_enum,)*
+			#(#tokens_enum,)*
+			#[error]
+			ERROR,
+			#(#nodes,)*
+			#[doc(hidden)]
+			__LAST,
+		}
+		use self::SyntaxKind::*;
+
+		impl SyntaxKind {
+			pub fn is_keyword(self) -> bool {
+				match self {
+					#(#all_keywords)|* => true,
+					_ => false,
+				}
+			}
+
+			pub fn is_punct(self) -> bool {
+				match self {
+					#(#punctuation)|* => true,
+					_ => false,
+				}
+			}
+
+			pub fn from_keyword(ident: &str) -> Option<SyntaxKind> {
+				let kw = match ident {
+					#(#full_keywords_values => #full_keywords,)*
+					_ => return None,
+				};
+				Some(kw)
+			}
+
+			pub fn from_char(c: char) -> Option<SyntaxKind> {
+				let tok = match c {
+					#(#single_byte_tokens_values => #single_byte_tokens,)*
+					_ => return None,
+				};
+				Some(tok)
+			}
+
+			pub fn from_raw(r: u16) -> Self {
+				assert!(r < Self::__LAST as u16);
+				unsafe { std::mem::transmute(r) }
+			}
+			pub fn into_raw(self) -> u16 {
+				self as u16
+			}
+		}
+
+		#[macro_export]
+		macro_rules! T {
+			#([#punctuation_values] => { $crate::SyntaxKind::#punctuation };)*
+			#([#all_keywords_idents] => { $crate::SyntaxKind::#all_keywords };)*
+			[lifetime_ident] => { $crate::SyntaxKind::LIFETIME_IDENT };
+			[ident] => { $crate::SyntaxKind::IDENT };
+			[shebang] => { $crate::SyntaxKind::SHEBANG };
+		}
+		pub use T;
+	};
+
+	reformat(&ast.to_string())
+}
+
+pub struct KindsSrc {
+	pub punct: Vec<(String, String)>,
+	pub keywords: Vec<String>,
+	pub literals: Vec<LiteralKind>,
+	pub nodes: Vec<String>,
+}
+
+pub struct LiteralKind {
+	name: String,
+	regex: String,
+	lexer: Option<String>,
+}
+
+#[macro_export]
+macro_rules! literals {
+	($($name:expr => $regex:expr $(, $lexer:expr)?);* $(;)?) => {
+		vec![
+			$(LiteralKind {
+				name: $name.to_owned(),
+				regex: $regex.to_owned(),
+				lexer: None $(.or_else(|| Some($lexer.to_string())))?,
+			}),*
+		]
+	};
+}
+
+#[macro_export]
+macro_rules! puncts {
+	($($tok:expr => $name:expr);* $(;)?) => {
+		vec![
+			$(($tok.to_owned(), $name.to_owned())),*
+		]
+	};
+}
+use crate::{literals, puncts};
+
+impl KindsSrc {
+	pub fn is_punct(&self, tok: &str) -> bool {
+		self.punct.iter().any(|(t, _)| *t == tok)
+	}
+	pub fn is_literal(&self, tok: &str) -> bool {
+		self.literals.iter().any(|l| l.name == tok)
+	}
+
+	fn get_punct_name(&self, tok: &str) -> Option<&str> {
+		self.punct
+			.iter()
+			.find(|(t, _)| *t == tok)
+			.map(|(_, n)| n.as_str())
+	}
+}
+
+fn generate_nodes(kinds: &KindsSrc, grammar: &AstSrc) -> Result<String> {
+	let (node_defs, node_boilerplate_impls): (Vec<_>, Vec<_>) = grammar
+		.nodes
+		.iter()
+		.map(|node| {
+			let name = format_ident!("{}", node.name);
+			let kind = format_ident!("{}", to_upper_snake_case(&node.name));
+			let traits = node.traits.iter().map(|trait_name| {
+				let trait_name = format_ident!("{}", trait_name);
+				quote!(impl ast::#trait_name for #name {})
+			});
+
+			let methods = node.fields.iter().map(|field| {
+				let method_name = field.method_name(kinds);
+				let ty = field.ty();
+
+				if field.is_many() {
+					quote! {
+						pub fn #method_name(&self) -> AstChildren<#ty> {
+							support::children(&self.syntax)
+						}
+					}
+				} else if let Some(token_kind) = field.token_kind() {
+					quote! {
+						pub fn #method_name(&self) -> Option<#ty> {
+							support::token(&self.syntax, #token_kind)
+						}
+					}
+				} else {
+					quote! {
+						pub fn #method_name(&self) -> Option<#ty> {
+							support::child(&self.syntax)
+						}
+					}
+				}
+			});
+			(
+				quote! {
+					#[pretty_doc_comment_placeholder_workaround]
+					#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+					pub struct #name {
+						pub(crate) syntax: SyntaxNode,
+					}
+
+					#(#traits)*
+
+					impl #name {
+						#(#methods)*
+					}
+				},
+				quote! {
+					impl AstNode for #name {
+						fn can_cast(kind: SyntaxKind) -> bool {
+							kind == #kind
+						}
+						fn cast(syntax: SyntaxNode) -> Option<Self> {
+							if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None }
+						}
+						fn syntax(&self) -> &SyntaxNode { &self.syntax }
+					}
+				},
+			)
+		})
+		.unzip();
+
+	let (enum_defs, enum_boilerplate_impls): (Vec<_>, Vec<_>) = grammar
+		.enums
+		.iter()
+		.map(|en| {
+			let variants: Vec<_> = en
+				.variants
+				.iter()
+				.map(|var| format_ident!("{}", var))
+				.collect();
+			let name = format_ident!("{}", en.name);
+			let kinds: Vec<_> = variants
+				.iter()
+				.map(|name| format_ident!("{}", to_upper_snake_case(&name.to_string())))
+				.collect();
+			let traits = en.traits.iter().map(|trait_name| {
+				let trait_name = format_ident!("{}", trait_name);
+				quote!(impl ast::#trait_name for #name {})
+			});
+
+			let ast_node = quote! {
+				impl AstNode for #name {
+					fn can_cast(kind: SyntaxKind) -> bool {
+						match kind {
+							#(#kinds)|* => true,
+							_ => false,
+						}
+					}
+					fn cast(syntax: SyntaxNode) -> Option<Self> {
+						let res = match syntax.kind() {
+							#(
+							#kinds => #name::#variants(#variants { syntax }),
+							)*
+							_ => return None,
+						};
+						Some(res)
+					}
+					fn syntax(&self) -> &SyntaxNode {
+						match self {
+							#(
+							#name::#variants(it) => &it.syntax,
+							)*
+						}
+					}
+				}
+			};
+
+			(
+				quote! {
+					#[pretty_doc_comment_placeholder_workaround]
+					#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+					pub enum #name {
+						#(#variants(#variants),)*
+					}
+
+					#(#traits)*
+				},
+				quote! {
+					#(
+						impl From<#variants> for #name {
+							fn from(node: #variants) -> #name {
+								#name::#variants(node)
+							}
+						}
+					)*
+					#ast_node
+				},
+			)
+		})
+		.unzip();
+
+	let (any_node_defs, any_node_boilerplate_impls): (Vec<_>, Vec<_>) = grammar
+		.nodes
+		.iter()
+		.flat_map(|node| node.traits.iter().map(move |t| (t, node)))
+		.into_group_map()
+		.into_iter()
+		.sorted_by_key(|(k, _)| *k)
+		.map(|(trait_name, nodes)| {
+			let name = format_ident!("Any{}", trait_name);
+			let trait_name = format_ident!("{}", trait_name);
+			let kinds: Vec<_> = nodes
+				.iter()
+				.map(|name| format_ident!("{}", to_upper_snake_case(&name.name.to_string())))
+				.collect();
+
+			(
+				quote! {
+					#[pretty_doc_comment_placeholder_workaround]
+					#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+					pub struct #name {
+						pub(crate) syntax: SyntaxNode,
+					}
+					impl ast::#trait_name for #name {}
+				},
+				quote! {
+					impl #name {
+						#[inline]
+						pub fn new<T: ast::#trait_name>(node: T) -> #name {
+							#name {
+								syntax: node.syntax().clone()
+							}
+						}
+					}
+					impl AstNode for #name {
+						fn can_cast(kind: SyntaxKind) -> bool {
+							match kind {
+								#(#kinds)|* => true,
+								_ => false,
+							}
+						}
+						fn cast(syntax: SyntaxNode) -> Option<Self> {
+							Self::can_cast(syntax.kind()).then(|| #name { syntax })
+						}
+						fn syntax(&self) -> &SyntaxNode {
+							&self.syntax
+						}
+					}
+				},
+			)
+		})
+		.unzip();
+
+	let enum_names = grammar.enums.iter().map(|it| &it.name);
+	let node_names = grammar.nodes.iter().map(|it| &it.name);
+
+	let display_impls = enum_names
+		.chain(node_names.clone())
+		.map(|it| format_ident!("{}", it))
+		.map(|name| {
+			quote! {
+				impl std::fmt::Display for #name {
+					fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+						std::fmt::Display::fmt(self.syntax(), f)
+					}
+				}
+			}
+		});
+
+	let defined_nodes: HashSet<_> = node_names.collect();
+
+	for node in kinds
+		.nodes
+		.iter()
+		.map(|kind| to_pascal_case(kind))
+		.filter(|name| !defined_nodes.iter().any(|&it| it == name))
+	{
+		drop(node)
+		// FIXME: restore this
+		// eprintln!("Warning: node {} not defined in ast source", node);
+	}
+
+	let ast = quote! {
+		#![allow(non_snake_case)]
+		use crate::{
+			SyntaxNode, SyntaxToken, SyntaxKind::{self, *},
+			ast::{self, AstNode, AstChildren, support},
+			T,
+		};
+
+		#(#node_defs)*
+		#(#enum_defs)*
+		#(#any_node_defs)*
+		#(#node_boilerplate_impls)*
+		#(#enum_boilerplate_impls)*
+		#(#any_node_boilerplate_impls)*
+		#(#display_impls)*
+	};
+
+	let ast = ast.to_string().replace("T ! [", "T![");
+
+	let mut res = String::with_capacity(ast.len() * 2);
+
+	let mut docs = grammar
+		.nodes
+		.iter()
+		.map(|it| &it.doc)
+		.chain(grammar.enums.iter().map(|it| &it.doc));
+
+	for chunk in ast.split("# [pretty_doc_comment_placeholder_workaround] ") {
+		res.push_str(chunk);
+		if let Some(doc) = docs.next() {
+			write_doc_comment(doc, &mut res);
+		}
+	}
+
+	let res = reformat(&res)?;
+	Ok(res.replace("#[derive", "\n#[derive"))
+}
+
+fn write_doc_comment(contents: &[String], dest: &mut String) {
+	use std::fmt::Write;
+	for line in contents {
+		writeln!(dest, "///{}", line).unwrap();
+	}
+}
+
+fn lower(kinds: &KindsSrc, grammar: &Grammar) -> AstSrc {
+	let tokens = "Whitespace Comment String StringVerbantim StringBlock Number Ident"
+		.split_ascii_whitespace()
+		.map(|it| it.to_string())
+		.collect::<Vec<_>>();
+
+	let mut res = AstSrc {
+		tokens,
+		..Default::default()
+	};
+
+	let nodes = grammar.iter().collect::<Vec<_>>();
+
+	for &node in &nodes {
+		let name = grammar[node].name.clone();
+		let rule = &grammar[node].rule;
+		match lower_enum(grammar, rule) {
+			Some(variants) => {
+				let enum_src = AstEnumSrc {
+					doc: Vec::new(),
+					name,
+					traits: Vec::new(),
+					variants,
+				};
+				res.enums.push(enum_src);
+			}
+			None => {
+				let mut fields = Vec::new();
+				lower_rule(&mut fields, grammar, None, rule);
+				res.nodes.push(AstNodeSrc {
+					doc: Vec::new(),
+					name,
+					traits: Vec::new(),
+					fields,
+				});
+			}
+		}
+	}
+
+	deduplicate_fields(&mut res);
+	extract_enums(&mut res);
+	extract_struct_traits(kinds, &mut res);
+	extract_enum_traits(&mut res);
+	res
+}
+
+fn lower_enum(grammar: &Grammar, rule: &Rule) -> Option<Vec<String>> {
+	let alternatives = match rule {
+		Rule::Alt(it) => it,
+		_ => return None,
+	};
+	let mut variants = Vec::new();
+	for alternative in alternatives {
+		match alternative {
+			Rule::Node(it) => variants.push(grammar[*it].name.clone()),
+			Rule::Token(it) if grammar[*it].name == ";" => (),
+			_ => return None,
+		}
+	}
+	Some(variants)
+}
+
+fn lower_rule(acc: &mut Vec<Field>, grammar: &Grammar, label: Option<&String>, rule: &Rule) {
+	if lower_comma_list(acc, grammar, label, rule) {
+		return;
+	}
+
+	match rule {
+		Rule::Node(node) => {
+			let ty = grammar[*node].name.clone();
+			let name = label.cloned().unwrap_or_else(|| to_lower_snake_case(&ty));
+			let field = Field::Node {
+				name,
+				ty,
+				cardinality: Cardinality::Optional,
+			};
+			acc.push(field);
+		}
+		Rule::Token(token) => {
+			assert!(label.is_none(), "uexpected label: {:?}", label);
+			let name = grammar[*token].name.clone();
+			let field = Field::Token(name);
+			acc.push(field);
+		}
+		Rule::Rep(inner) => {
+			if let Rule::Node(node) = &**inner {
+				let ty = grammar[*node].name.clone();
+				let name = label
+					.cloned()
+					.unwrap_or_else(|| pluralize(&to_lower_snake_case(&ty)));
+				let field = Field::Node {
+					name,
+					ty,
+					cardinality: Cardinality::Many,
+				};
+				acc.push(field);
+				return;
+			}
+			todo!("unsupported repitition: {:?}", rule)
+		}
+		Rule::Labeled { label: l, rule } => {
+			assert!(label.is_none());
+			lower_rule(acc, grammar, Some(l), rule);
+		}
+		Rule::Seq(rules) | Rule::Alt(rules) => {
+			for rule in rules {
+				lower_rule(acc, grammar, label, rule)
+			}
+		}
+		Rule::Opt(rule) => lower_rule(acc, grammar, label, rule),
+	}
+}
+
+// (T (',' T)* ','?)
+fn lower_comma_list(
+	acc: &mut Vec<Field>,
+	grammar: &Grammar,
+	label: Option<&String>,
+	rule: &Rule,
+) -> bool {
+	let rule = match rule {
+		Rule::Seq(it) => it,
+		_ => return false,
+	};
+	let (node, repeat, trailing_comma) = match rule.as_slice() {
+		[Rule::Node(node), Rule::Rep(repeat), Rule::Opt(trailing_comma)] => {
+			(node, repeat, trailing_comma)
+		}
+		_ => return false,
+	};
+	let repeat = match &**repeat {
+		Rule::Seq(it) => it,
+		_ => return false,
+	};
+	match repeat.as_slice() {
+		[comma, Rule::Node(n)] if comma == &**trailing_comma && n == node => (),
+		_ => return false,
+	}
+	let ty = grammar[*node].name.clone();
+	let name = label
+		.cloned()
+		.unwrap_or_else(|| pluralize(&to_lower_snake_case(&ty)));
+	let field = Field::Node {
+		name,
+		ty,
+		cardinality: Cardinality::Many,
+	};
+	acc.push(field);
+	true
+}
+
+fn deduplicate_fields(ast: &mut AstSrc) {
+	for node in &mut ast.nodes {
+		let mut i = 0;
+		'outer: while i < node.fields.len() {
+			for j in 0..i {
+				let f1 = &node.fields[i];
+				let f2 = &node.fields[j];
+				if f1 == f2 {
+					node.fields.remove(i);
+					continue 'outer;
+				}
+			}
+			i += 1;
+		}
+	}
+}
+
+fn extract_enums(ast: &mut AstSrc) {
+	for node in &mut ast.nodes {
+		for enm in &ast.enums {
+			let mut to_remove = Vec::new();
+			for (i, field) in node.fields.iter().enumerate() {
+				let ty = field.ty().to_string();
+				if enm.variants.iter().any(|it| it == &ty) {
+					to_remove.push(i);
+				}
+			}
+			if to_remove.len() == enm.variants.len() {
+				node.remove_field(to_remove);
+				let ty = enm.name.clone();
+				let name = to_lower_snake_case(&ty);
+				node.fields.push(Field::Node {
+					name,
+					ty,
+					cardinality: Cardinality::Optional,
+				});
+			}
+		}
+	}
+}
+
+fn extract_struct_traits(kinds: &KindsSrc, ast: &mut AstSrc) {
+	// TODO: add common accessor traits here.
+	let traits: &[(&str, &[&str])] = &[];
+
+	for node in &mut ast.nodes {
+		for (name, methods) in traits {
+			extract_struct_trait(kinds, node, name, methods);
+		}
+	}
+}
+
+fn extract_struct_trait(
+	kinds: &KindsSrc,
+	node: &mut AstNodeSrc,
+	trait_name: &str,
+	methods: &[&str],
+) {
+	let mut to_remove = Vec::new();
+	for (i, field) in node.fields.iter().enumerate() {
+		let method_name = field.method_name(kinds).to_string();
+		if methods.iter().any(|&it| it == method_name) {
+			to_remove.push(i);
+		}
+	}
+	if to_remove.len() == methods.len() {
+		node.traits.push(trait_name.to_string());
+		node.remove_field(to_remove);
+	}
+}
+
+fn extract_enum_traits(ast: &mut AstSrc) {
+	let enums = ast.enums.clone();
+	for enm in &mut ast.enums {
+		if enm.name == "Stmt" {
+			continue;
+		}
+		let nodes = &ast.nodes;
+
+		let mut variant_traits = enm.variants.iter().map(|var| {
+			nodes
+				.iter()
+				.find_map(|node| {
+					if &node.name != var {
+						return None;
+					}
+					Some(node.traits.iter().cloned().collect::<BTreeSet<_>>())
+				})
+				.unwrap_or_else(|| {
+					enums
+						.iter()
+						.find_map(|node| {
+							if &node.name != var {
+								return None;
+							}
+							Some(node.traits.iter().cloned().collect::<BTreeSet<_>>())
+						})
+						.unwrap_or_else(|| {
+							panic!("{}", {
+								&format!(
+									"Could not find a struct `{}` for enum `{}::{}`",
+									var, enm.name, var
+								)
+							})
+						})
+				})
+		});
+
+		let mut enum_traits = match variant_traits.next() {
+			Some(it) => it,
+			None => continue,
+		};
+		for traits in variant_traits {
+			enum_traits = enum_traits.intersection(&traits).cloned().collect();
+		}
+		enm.traits = enum_traits.into_iter().collect();
+	}
+}
+
+pub fn escape_token_macro(token: &str) -> TokenStream {
+	if "{}[]()$".contains(token) {
+		let c = token.chars().next().unwrap();
+		quote! { #c }
+	} else if token.contains('$') {
+		quote! { #token }
+	} else {
+		let cs = token.chars().map(|c| Punct::new(c, Spacing::Joint));
+		quote! { #(#cs)* }
+	}
+}
addedxtask/src/sourcegen/util.rsdiffbeforeafterboth
--- /dev/null
+++ b/xtask/src/sourcegen/util.rs
@@ -0,0 +1,92 @@
+use std::{fs, path::Path};
+
+use anyhow::{bail, Result};
+use xshell::{cmd, Shell};
+
+/// Checks that the `file` has the specified `contents`. If that is not the
+/// case, updates the file and then fails the test.
+pub fn ensure_file_contents(file: &Path, contents: &str) -> Result<()> {
+	if let Ok(old_contents) = fs::read_to_string(file) {
+		if normalize_newlines(&old_contents) == normalize_newlines(contents) {
+			// File is already up to date.
+			return Ok(());
+		}
+	}
+
+	eprintln!(" {} was not up-to-date, updating\n", file.display());
+	if std::env::var("CI").is_ok() {
+		eprintln!("NOTE: run `cargo test` locally and commit the updated files\n");
+	}
+	if let Some(parent) = file.parent() {
+		let _ = fs::create_dir_all(parent);
+	}
+	fs::write(file, contents).unwrap();
+	bail!("some file was not up to date and has been updated, simply re-run the tests");
+}
+
+// Eww, someone configured git to use crlf?
+fn normalize_newlines(s: &str) -> String {
+	s.replace("\r\n", "\n")
+}
+
+pub(crate) fn pluralize(s: &str) -> String {
+	format!("{}s", s)
+}
+
+pub fn to_upper_snake_case(s: &str) -> String {
+	let mut buf = String::with_capacity(s.len());
+	let mut prev = false;
+	for c in s.chars() {
+		if c.is_ascii_uppercase() && prev {
+			buf.push('_')
+		}
+		prev = true;
+
+		buf.push(c.to_ascii_uppercase());
+	}
+	buf
+}
+pub fn to_lower_snake_case(s: &str) -> String {
+	let mut buf = String::with_capacity(s.len());
+	let mut prev = false;
+	for c in s.chars() {
+		if c.is_ascii_uppercase() && prev {
+			buf.push('_')
+		}
+		prev = true;
+
+		buf.push(c.to_ascii_lowercase());
+	}
+	buf
+}
+
+pub fn to_pascal_case(s: &str) -> String {
+	let mut buf = String::with_capacity(s.len());
+	let mut prev_is_underscore = true;
+	for c in s.chars() {
+		if c == '_' {
+			prev_is_underscore = true;
+		} else if prev_is_underscore {
+			buf.push(c.to_ascii_uppercase());
+			prev_is_underscore = false;
+		} else {
+			buf.push(c.to_ascii_lowercase());
+		}
+	}
+	buf
+}
+
+pub fn reformat(text: &str) -> Result<String> {
+	// let _e = pushenv("RUSTUP_TOOLCHAIN", "stable");
+	// rustfmt()?;
+	let sh = Shell::new()?;
+	let stdout = cmd!(sh, "rustfmt --config fn_single_line=true")
+		.stdin(text)
+		.read()?;
+	Ok(format!(
+		"{}\n\n{}\n",
+		"//! This is a generated file, please do not edit manually. Changes can be
+//! made in codegeneration that lives in `xtask` top-level dir.",
+		stdout
+	))
+}