git.delta.rocks / jrsonnet / refs/commits / 4ebc2d766e69

difftreelog

Merge pull request #132 from CertainLach/lsp

Yaroslav Bolyukin2023-09-22parents: #609e5ef #d1754bf.patch.diff
in: master
Introduce experimental rowan parser and pretty-printer

74 files changed

added.cargo/config.tomldiffbeforeafterboth
--- /dev/null
+++ b/.cargo/config.toml
@@ -0,0 +1,2 @@
+[alias]
+xtask = "run --package xtask --bin xtask --"
modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,6 +3,21 @@
 version = 3
 
 [[package]]
+name = "addr2line"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
 name = "ahash"
 version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -25,24 +40,23 @@
 
 [[package]]
 name = "anstream"
-version = "0.3.2"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
+checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c"
 dependencies = [
  "anstyle",
  "anstyle-parse",
  "anstyle-query",
  "anstyle-wincon",
  "colorchoice",
- "is-terminal",
  "utf8parse",
 ]
 
 [[package]]
 name = "anstyle"
-version = "1.0.1"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd"
+checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea"
 
 [[package]]
 name = "anstyle-parse"
@@ -59,34 +73,57 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
 dependencies = [
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
 name = "anstyle-wincon"
-version = "1.0.1"
+version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
+checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
 dependencies = [
  "anstyle",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
 name = "anyhow"
-version = "1.0.72"
+version = "1.0.75"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
+checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
+
+[[package]]
+name = "ass-stroke"
+version = "0.1.0"
+dependencies = [
+ "num-traits",
+ "rand 0.8.5",
+ "random_color",
+ "range-map",
+ "smallvec",
+]
 
 [[package]]
+name = "ass-stroke"
+version = "0.1.0"
+source = "git+https://github.com/CertainLach/ass-stroke#c98c0213b9c5f775c0bddaa7b233a38c60859008"
+dependencies = [
+ "num-traits",
+ "rand 0.8.5",
+ "random_color",
+ "range-map",
+ "smallvec",
+]
+
+[[package]]
 name = "async-trait"
-version = "0.1.71"
+version = "0.1.73"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf"
+checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.28",
+ "syn 2.0.31",
 ]
 
 [[package]]
@@ -96,10 +133,40 @@
 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
 [[package]]
+name = "backtrace"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "backtrace-ext"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50"
+dependencies = [
+ "backtrace",
+]
+
+[[package]]
 name = "base64"
-version = "0.21.2"
+version = "0.21.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
+checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53"
+
+[[package]]
+name = "beef"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
 
 [[package]]
 name = "bincode"
@@ -118,9 +185,9 @@
 
 [[package]]
 name = "bitflags"
-version = "2.3.3"
+version = "2.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
+checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
 
 [[package]]
 name = "block-buffer"
@@ -132,10 +199,19 @@
 ]
 
 [[package]]
+name = "bumpalo"
+version = "3.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
+
+[[package]]
 name = "cc"
-version = "1.0.79"
+version = "1.0.83"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "libc",
+]
 
 [[package]]
 name = "cfg-if"
@@ -145,20 +221,19 @@
 
 [[package]]
 name = "clap"
-version = "4.3.12"
+version = "4.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3eab9e8ceb9afdade1ab3f0fd8dbce5b1b2f468ad653baf10e771781b2b67b73"
+checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6"
 dependencies = [
  "clap_builder",
  "clap_derive",
- "once_cell",
 ]
 
 [[package]]
 name = "clap_builder"
-version = "4.3.12"
+version = "4.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f2763db829349bf00cfc06251268865ed4363b93a943174f638daf3ecdba2cd"
+checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08"
 dependencies = [
  "anstream",
  "anstyle",
@@ -168,30 +243,30 @@
 
 [[package]]
 name = "clap_complete"
-version = "4.3.2"
+version = "4.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fc443334c81a804575546c5a8a79b4913b50e28d69232903604cada1de817ce"
+checksum = "586a385f7ef2f8b4d86bddaa0c094794e7ccbfe5ffef1f434fe928143fc783a5"
 dependencies = [
  "clap",
 ]
 
 [[package]]
 name = "clap_derive"
-version = "4.3.12"
+version = "4.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050"
+checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
 dependencies = [
  "heck",
  "proc-macro2",
  "quote",
- "syn 2.0.28",
+ "syn 2.0.31",
 ]
 
 [[package]]
 name = "clap_lex"
-version = "0.5.0"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
+checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
 
 [[package]]
 name = "colorchoice"
@@ -200,6 +275,24 @@
 checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
 
 [[package]]
+name = "console"
+version = "0.15.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8"
+dependencies = [
+ "encode_unicode",
+ "lazy_static",
+ "libc",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "countme"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
+
+[[package]]
 name = "cpufeatures"
 version = "0.2.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -209,6 +302,25 @@
 ]
 
 [[package]]
+name = "crossbeam-channel"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
 name = "crypto-common"
 version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -240,14 +352,46 @@
 ]
 
 [[package]]
+name = "dprint-core"
+version = "0.63.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77fb4fc41e8a0217e1c0031c26640126e3ff3aba40a98db8b1db7b4e13bfce29"
+dependencies = [
+ "anyhow",
+ "bumpalo",
+ "indexmap",
+ "rustc-hash",
+ "serde",
+ "unicode-width",
+]
+
+[[package]]
+name = "drop_bomb"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1"
+
+[[package]]
+name = "either"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
+
+[[package]]
+name = "encode_unicode"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
+
+[[package]]
 name = "errno"
-version = "0.3.1"
+version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
+checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd"
 dependencies = [
  "errno-dragonfly",
  "libc",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -261,6 +405,27 @@
 ]
 
 [[package]]
+name = "fastrand"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
 name = "generic-array"
 version = "0.14.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -271,6 +436,34 @@
 ]
 
 [[package]]
+name = "getrandom"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "gimli"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
+
+[[package]]
 name = "hashbrown"
 version = "0.12.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -298,6 +491,16 @@
 checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
 
 [[package]]
+name = "idna"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
 name = "indexmap"
 version = "1.9.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -305,9 +508,29 @@
 dependencies = [
  "autocfg",
  "hashbrown 0.12.3",
+ "serde",
 ]
 
 [[package]]
+name = "indoc"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306"
+
+[[package]]
+name = "insta"
+version = "1.31.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0770b0a3d4c70567f0d58331f3088b0e4c4f56c9b8d764efe654b4a5d46de3a"
+dependencies = [
+ "console",
+ "lazy_static",
+ "linked-hash-map",
+ "similar",
+ "yaml-rust",
+]
+
+[[package]]
 name = "is-terminal"
 version = "0.4.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -315,7 +538,22 @@
 dependencies = [
  "hermit-abi",
  "rustix",
- "windows-sys",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "is_ci"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb"
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
 ]
 
 [[package]]
@@ -328,6 +566,7 @@
 name = "jrsonnet"
 version = "0.5.0-pre95"
 dependencies = [
+ "ass-stroke 0.1.0 (git+https://github.com/CertainLach/ass-stroke)",
  "clap",
  "clap_complete",
  "jrsonnet-cli",
@@ -376,6 +615,20 @@
 ]
 
 [[package]]
+name = "jrsonnet-fmt"
+version = "0.5.0-pre95"
+dependencies = [
+ "ass-stroke 0.1.0",
+ "clap",
+ "dprint-core",
+ "indoc",
+ "insta",
+ "jrsonnet-rowan-parser",
+ "tempfile",
+ "thiserror",
+]
+
+[[package]]
 name = "jrsonnet-gcmodule"
 version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -408,6 +661,19 @@
 ]
 
 [[package]]
+name = "jrsonnet-lsp"
+version = "0.5.0-pre95"
+dependencies = [
+ "anyhow",
+ "jrsonnet-evaluator",
+ "jrsonnet-rowan-parser",
+ "lsp-server",
+ "lsp-types",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
 name = "jrsonnet-macros"
 version = "0.5.0-pre95"
 dependencies = [
@@ -429,6 +695,22 @@
 ]
 
 [[package]]
+name = "jrsonnet-rowan-parser"
+version = "0.5.0-pre95"
+dependencies = [
+ "anyhow",
+ "backtrace",
+ "drop_bomb",
+ "indoc",
+ "insta",
+ "logos",
+ "miette",
+ "rowan",
+ "text-size",
+ "thiserror",
+]
+
+[[package]]
 name = "jrsonnet-stdlib"
 version = "0.5.0-pre95"
 dependencies = [
@@ -467,6 +749,12 @@
 ]
 
 [[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
 name = "libc"
 version = "0.2.147"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -490,9 +778,9 @@
 
 [[package]]
 name = "linux-raw-sys"
-version = "0.4.3"
+version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0"
+checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
 
 [[package]]
 name = "lock_api"
@@ -505,12 +793,113 @@
 ]
 
 [[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "logos"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf8b031682c67a8e3d5446840f9573eb7fe26efe7ec8d195c9ac4c0647c502f1"
+dependencies = [
+ "logos-derive",
+]
+
+[[package]]
+name = "logos-derive"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1d849148dbaf9661a6151d1ca82b13bb4c4c128146a88d05253b38d4e2f496c"
+dependencies = [
+ "beef",
+ "fnv",
+ "proc-macro2",
+ "quote",
+ "regex-syntax",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "lsp-server"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f70570c1c29cf6654029b8fe201a5507c153f0d85be6f234d471d756bc36775a"
+dependencies = [
+ "crossbeam-channel",
+ "log",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "lsp-types"
+version = "0.93.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9be6e9c7e2d18f651974370d7aff703f9513e0df6e464fd795660edc77e6ca51"
+dependencies = [
+ "bitflags 1.3.2",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "url",
+]
+
+[[package]]
 name = "md5"
 version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
 
 [[package]]
+name = "memchr"
+version = "2.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
+
+[[package]]
+name = "memoffset"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "miette"
+version = "5.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e"
+dependencies = [
+ "backtrace",
+ "backtrace-ext",
+ "is-terminal",
+ "miette-derive",
+ "once_cell",
+ "owo-colors",
+ "supports-color",
+ "supports-hyperlinks",
+ "supports-unicode",
+ "terminal_size",
+ "textwrap",
+ "thiserror",
+ "unicode-width",
+]
+
+[[package]]
+name = "miette-derive"
+version = "5.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.31",
+]
+
+[[package]]
 name = "mimalloc-sys"
 version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -530,10 +919,19 @@
 ]
 
 [[package]]
+name = "miniz_oxide"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+dependencies = [
+ "adler",
+]
+
+[[package]]
 name = "num-bigint"
-version = "0.4.3"
+version = "0.4.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
+checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
 dependencies = [
  "autocfg",
  "num-integer",
@@ -553,20 +951,35 @@
 
 [[package]]
 name = "num-traits"
-version = "0.2.15"
+version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
 dependencies = [
  "autocfg",
 ]
 
 [[package]]
+name = "object"
+version = "0.32.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
 name = "once_cell"
 version = "1.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
 
 [[package]]
+name = "owo-colors"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
+
+[[package]]
 name = "parking_lot"
 version = "0.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -586,7 +999,7 @@
  "libc",
  "redox_syscall",
  "smallvec",
- "windows-targets",
+ "windows-targets 0.48.5",
 ]
 
 [[package]]
@@ -623,24 +1036,135 @@
 checksum = "9fa00462b37ead6d11a82c9d568b26682d78e0477dc02d1966c013af80969739"
 
 [[package]]
+name = "percent-encoding"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
 name = "proc-macro2"
-version = "1.0.65"
+version = "1.0.66"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92de25114670a878b1261c79c9f8f729fb97e95bac93f6312f583c60dd6a1dfe"
+checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.30"
+version = "1.0.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5907a1b7c277254a8b15170f6e7c97cfa60ee7872a3217663bb81151e48184bb"
+checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
 dependencies = [
  "proc-macro2",
 ]
 
 [[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom 0.1.16",
+ "libc",
+ "rand_chacha 0.2.2",
+ "rand_core 0.5.1",
+ "rand_hc",
+ "rand_pcg",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom 0.1.16",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.10",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_pcg"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "random_color"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5f34bd6526786b2ce5141fd37a4084b5da1ebae74595b5b0d05482a7cef7181"
+dependencies = [
+ "rand 0.7.3",
+]
+
+[[package]]
+name = "range-map"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12a5a2d6c7039059af621472a4389be1215a816df61aa4d531cfe85264aee95f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
 name = "redox_syscall"
 version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -650,6 +1174,31 @@
 ]
 
 [[package]]
+name = "regex-syntax"
+version = "0.6.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
+
+[[package]]
+name = "rowan"
+version = "0.15.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64449cfef9483a475ed56ae30e2da5ee96448789fb2aa240a04beb6a055078bf"
+dependencies = [
+ "countme",
+ "hashbrown 0.12.3",
+ "memoffset",
+ "rustc-hash",
+ "text-size",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+
+[[package]]
 name = "rustc-hash"
 version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -657,15 +1206,15 @@
 
 [[package]]
 name = "rustix"
-version = "0.38.4"
+version = "0.38.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5"
+checksum = "c0c3dde1fc030af041adc40e79c0e7fbcf431dd24870053d187d7c66e4b87453"
 dependencies = [
- "bitflags 2.3.3",
+ "bitflags 2.4.0",
  "errno",
  "libc",
  "linux-raw-sys",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -676,9 +1225,9 @@
 
 [[package]]
 name = "scopeguard"
-version = "1.1.0"
+version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
 [[package]]
 name = "serde"
@@ -697,14 +1246,14 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.28",
+ "syn 2.0.31",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.104"
+version = "1.0.105"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c"
+checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
 dependencies = [
  "itoa",
  "ryu",
@@ -712,6 +1261,17 @@
 ]
 
 [[package]]
+name = "serde_repr"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.31",
+]
+
+[[package]]
 name = "serde_yaml_with_quirks"
 version = "0.8.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -756,12 +1316,24 @@
 ]
 
 [[package]]
+name = "similar"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf"
+
+[[package]]
 name = "smallvec"
 version = "1.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
 
 [[package]]
+name = "smawk"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
+
+[[package]]
 name = "static_assertions"
 version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -796,6 +1368,34 @@
 ]
 
 [[package]]
+name = "supports-color"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4950e7174bffabe99455511c39707310e7e9b440364a2fcb1cc21521be57b354"
+dependencies = [
+ "is-terminal",
+ "is_ci",
+]
+
+[[package]]
+name = "supports-hyperlinks"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f84231692eb0d4d41e4cdd0cabfdd2e6cd9e255e65f80c9aa7c98dd502b4233d"
+dependencies = [
+ "is-terminal",
+]
+
+[[package]]
+name = "supports-unicode"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b6c2cb240ab5dd21ed4906895ee23fe5a48acdbd15a3ce388e7b62a9b66baf7"
+dependencies = [
+ "is-terminal",
+]
+
+[[package]]
 name = "syn"
 version = "1.0.109"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -808,9 +1408,9 @@
 
 [[package]]
 name = "syn"
-version = "2.0.28"
+version = "2.0.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567"
+checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -818,6 +1418,29 @@
 ]
 
 [[package]]
+name = "tempfile"
+version = "3.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "redox_syscall",
+ "rustix",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "terminal_size"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
 name = "tests"
 version = "0.1.0"
 dependencies = [
@@ -828,44 +1451,115 @@
 ]
 
 [[package]]
+name = "text-size"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233"
+
+[[package]]
+name = "textwrap"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d"
+dependencies = [
+ "smawk",
+ "unicode-linebreak",
+ "unicode-width",
+]
+
+[[package]]
 name = "thiserror"
-version = "1.0.43"
+version = "1.0.48"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42"
+checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.43"
+version = "1.0.48"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f"
+checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.28",
+ "syn 2.0.31",
 ]
 
 [[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
 name = "typenum"
 version = "1.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
 
 [[package]]
+name = "ungrammar"
+version = "1.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e5df347f0bf3ec1d670aad6ca5c6a1859cd9ea61d2113125794654ccced68f"
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
+
+[[package]]
 name = "unicode-ident"
 version = "1.0.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
 
 [[package]]
+name = "unicode-linebreak"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
 name = "unicode-width"
 version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
 
 [[package]]
+name = "url"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
 name = "utf8parse"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -878,6 +1572,18 @@
 checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
 
 [[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
 name = "winapi"
 version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -901,69 +1607,163 @@
 
 [[package]]
 name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
 version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
 dependencies = [
- "windows-targets",
+ "windows-targets 0.48.5",
 ]
 
 [[package]]
 name = "windows-targets"
-version = "0.48.1"
+version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
 dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
 ]
 
 [[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
 name = "windows_aarch64_gnullvm"
-version = "0.48.0"
+version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
 
 [[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
 name = "windows_aarch64_msvc"
-version = "0.48.0"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
 
 [[package]]
 name = "windows_i686_gnu"
-version = "0.48.0"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 
 [[package]]
 name = "windows_i686_msvc"
-version = "0.48.0"
+version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
 
 [[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
 name = "windows_x86_64_gnu"
-version = "0.48.0"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
-version = "0.48.0"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 
 [[package]]
 name = "windows_x86_64_msvc"
-version = "0.48.0"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "xshell"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce2107fe03e558353b4c71ad7626d58ed82efaf56c54134228608893c77023ad"
+dependencies = [
+ "xshell-macros",
+]
+
+[[package]]
+name = "xshell-macros"
+version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
+checksum = "7e2c411759b501fb9501aac2b1b2d287a6e93e5bdcf13c25306b23e1b716dd0e"
+
+[[package]]
+name = "xtask"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "indexmap",
+ "itertools",
+ "proc-macro2",
+ "quote",
+ "ungrammar",
+ "xshell",
+]
 
 [[package]]
 name = "yaml-rust"
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/jrsonnet", "tests"]
+members = ["crates/*", "bindings/jsonnet", "cmds/*", "tests", "xtask"]
 default-members = ["cmds/jrsonnet"]
 resolver = "2"
 
@@ -9,6 +9,7 @@
 jrsonnet-evaluator = { path = "./crates/jrsonnet-evaluator", version = "0.5.0-pre95" }
 jrsonnet-macros = { path = "./crates/jrsonnet-macros", version = "0.5.0-pre95" }
 jrsonnet-parser = { path = "./crates/jrsonnet-parser", version = "0.5.0-pre95" }
+jrsonnet-rowan-parser = { path = "./crates/jrsonnet-rowan-parser", version = "0.5.0-pre95" }
 jrsonnet-interner = { path = "./crates/jrsonnet-interner", version = "0.5.0-pre95" }
 jrsonnet-stdlib = { path = "./crates/jrsonnet-stdlib", version = "0.5.0-pre95" }
 jrsonnet-cli = { path = "./crates/jrsonnet-cli", version = "0.5.0-pre95" }
addedcmds/jrsonnet-fmt/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/cmds/jrsonnet-fmt/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "jrsonnet-fmt"
+version.workspace = true
+edition = "2021"
+
+[dependencies]
+dprint-core = "0.63.2"
+jrsonnet-rowan-parser.workspace = true
+insta = "1.15"
+indoc = "1.0"
+ass-stroke = { git = "https://github.com/CertainLach/ass-stroke.git", version = "0.1.0" }
+clap = { version = "4.4.2", features = ["derive"] }
+tempfile = "3.8.0"
+thiserror = "1.0.48"
addedcmds/jrsonnet-fmt/src/children.rsdiffbeforeafterboth
--- /dev/null
+++ b/cmds/jrsonnet-fmt/src/children.rs
@@ -0,0 +1,290 @@
+// TODO: Return errors as trivia
+
+use std::{fmt::Debug, mem};
+
+use jrsonnet_rowan_parser::{
+	nodes::{CustomError, Trivia, TriviaKind},
+	AstNode, AstToken, SyntaxElement, SyntaxNode, TS,
+};
+
+pub type ChildTrivia = Vec<Result<Trivia, String>>;
+
+/// Node should have no non-trivia tokens before element
+pub fn trivia_before(node: SyntaxNode, end: Option<&SyntaxElement>) -> ChildTrivia {
+	let mut out = Vec::new();
+	for item in node.children_with_tokens() {
+		if Some(&item) == end {
+			break;
+		}
+
+		if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {
+			out.push(Ok(trivia));
+		} else if CustomError::can_cast(item.kind()) {
+			out.push(Err(item.to_string()));
+		} else if end.is_none() {
+			break;
+		} else {
+			assert!(
+				TS![, ;].contains(item.kind()),
+				"silently eaten token: {:?}",
+				item.kind()
+			)
+		}
+	}
+	out
+}
+/// Node should have no non-trivia tokens after element
+pub fn trivia_after(node: SyntaxNode, start: Option<&SyntaxElement>) -> ChildTrivia {
+	if start.is_none() {
+		return Vec::new();
+	}
+	let mut iter = node.children_with_tokens().peekable();
+	while iter.peek() != start {
+		iter.next();
+	}
+	iter.next();
+	let mut out = Vec::new();
+	for item in iter {
+		if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {
+			out.push(Ok(trivia));
+		} else if CustomError::can_cast(item.kind()) {
+			out.push(Err(item.to_string()))
+		} else {
+			assert!(
+				TS![, ;].contains(item.kind()),
+				"silently eaten token: {:?}",
+				item.kind()
+			)
+		}
+	}
+	out
+}
+
+pub fn trivia_between(
+	node: SyntaxNode,
+	start: Option<&SyntaxElement>,
+	end: Option<&SyntaxElement>,
+) -> EndingComments {
+	let mut iter = node.children_with_tokens().peekable();
+	while iter.peek() != start {
+		iter.next();
+	}
+	iter.next();
+
+	let loose = start.is_none() || end.is_none();
+
+	let mut out = Vec::new();
+	for item in iter.take_while(|i| Some(i) != end) {
+		if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {
+			out.push(Ok(trivia));
+		} else if CustomError::can_cast(item.kind()) {
+			out.push(Err(item.to_string()))
+		} else if loose {
+			break;
+		} else {
+			assert!(
+				TS![, ;].contains(item.kind()),
+				"silently eaten token: {:?}",
+				item.kind()
+			)
+		}
+	}
+	EndingComments {
+		should_start_with_newline: should_start_with_newline(None, &out),
+		trivia: out,
+	}
+}
+
+pub fn children_between<T: AstNode + Debug>(
+	node: SyntaxNode,
+	start: Option<&SyntaxElement>,
+	end: Option<&SyntaxElement>,
+	trailing: Option<ChildTrivia>,
+) -> (Vec<Child<T>>, EndingComments) {
+	let mut iter = node.children_with_tokens().peekable();
+	if start.is_some() {
+		while iter.peek() != start {
+			iter.next();
+		}
+		iter.next();
+	}
+	children(
+		iter.take_while(|i| Some(i) != end),
+		start.is_none() && end.is_none(),
+		trailing,
+	)
+}
+
+pub fn should_start_with_newline(prev_inline: Option<&ChildTrivia>, tt: &ChildTrivia) -> bool {
+	count_newlines_before(tt)
+		+ prev_inline
+			.map(count_newlines_after)
+			.unwrap_or_default()
+
+		// First for previous item end, second for current item
+		>= 2
+}
+
+fn count_newlines_before(tt: &ChildTrivia) -> usize {
+	let mut nl_count = 0;
+	for t in tt {
+		match t {
+			Ok(t) => match t.kind() {
+				TriviaKind::Whitespace => {
+					nl_count += t.text().bytes().filter(|b| *b == b'\n').count();
+				}
+				_ => break,
+			},
+			Err(_) => {
+				nl_count += 1;
+			}
+		}
+	}
+	nl_count
+}
+fn count_newlines_after(tt: &ChildTrivia) -> usize {
+	let mut nl_count = 0;
+	for t in tt.iter().rev() {
+		match t {
+			Ok(t) => match t.kind() {
+				TriviaKind::Whitespace => {
+					nl_count += t.text().bytes().filter(|b| *b == b'\n').count();
+				}
+				TriviaKind::SingleLineHashComment => {
+					nl_count += 1;
+					break;
+				}
+				TriviaKind::SingleLineSlashComment => {
+					nl_count += 1;
+					break;
+				}
+				_ => {}
+			},
+			Err(_) => nl_count += 1,
+		}
+	}
+	nl_count
+}
+
+pub fn children<T: AstNode + Debug>(
+	items: impl Iterator<Item = SyntaxElement>,
+	loose: bool,
+	mut trailing: Option<ChildTrivia>,
+) -> (Vec<Child<T>>, EndingComments) {
+	let mut out = Vec::new();
+	let mut current_child = None::<Child<T>>;
+	let mut next = ChildTrivia::new();
+	// Previous element ended, do not add more inline comments
+	let mut started_next = false;
+	let mut had_some = false;
+
+	for item in items {
+		if let Some(value) = item.as_node().cloned().and_then(T::cast) {
+			let before_trivia = if let Some(trailing) = trailing.take() {
+				assert!(next.is_empty());
+				trailing
+			} else {
+				mem::take(&mut next)
+			};
+			let last_child = current_child.replace(Child {
+				// First item should not start with newline
+				should_start_with_newline: had_some
+					&& should_start_with_newline(
+						current_child.as_ref().map(|c| &c.inline_trivia),
+						&before_trivia,
+					),
+				before_trivia,
+				value,
+				inline_trivia: Vec::new(),
+			});
+			if let Some(last_child) = last_child {
+				out.push(last_child)
+			}
+			had_some = true;
+			started_next = false;
+		} else if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {
+			let is_single_line_comment = trivia.kind() == TriviaKind::SingleLineHashComment
+				|| trivia.kind() == TriviaKind::SingleLineSlashComment;
+			if trailing.is_some() {
+				// Someone have already parsed trivia for us
+				continue;
+			} else if started_next
+				|| current_child.is_none()
+				|| trivia.text().contains('\n') && !is_single_line_comment
+			{
+				next.push(Ok(trivia.clone()));
+				started_next = true;
+			} else {
+				let cur = current_child.as_mut().expect("checked not none");
+				cur.inline_trivia.push(Ok(trivia));
+				if is_single_line_comment {
+					started_next = true;
+				}
+			}
+			had_some = true;
+		} else if CustomError::can_cast(item.kind()) {
+			next.push(Err(item.to_string()))
+		} else if loose {
+			if had_some {
+				break;
+			}
+			started_next = true;
+		} else {
+			assert!(
+				TS![, ;].contains(item.kind()),
+				"silently eaten token: {:?}",
+				item.kind()
+			)
+		}
+	}
+
+	let ending_comments = EndingComments {
+		should_start_with_newline: should_start_with_newline(
+			current_child.as_ref().map(|c| &c.inline_trivia),
+			&next,
+		),
+		trivia: next,
+	};
+
+	if let Some(current_child) = current_child {
+		out.push(current_child);
+	}
+
+	(out, ending_comments)
+}
+
+#[derive(Debug)]
+pub struct Child<T> {
+	/// If this child has two newlines above in source code, so it needs to have it in the output
+	pub should_start_with_newline: bool,
+	/// Comment before item, i.e
+	///
+	/// ```ignore
+	/// // Comment
+	/// item
+	/// ```
+	pub before_trivia: ChildTrivia,
+	pub value: T,
+	/// Comment after line, but located at same line
+	///
+	/// ```ignore
+	/// item1, // Inline comment
+	/// // Not inline comment
+	/// item2,
+	/// ```
+	pub inline_trivia: ChildTrivia,
+}
+
+pub struct EndingComments {
+	/// If this child has two newlines above in source code, so it needs to have it in the output
+	pub should_start_with_newline: bool,
+	pub trivia: ChildTrivia,
+}
+impl EndingComments {
+	pub fn is_empty(&self) -> bool {
+		!self.should_start_with_newline && self.trivia.is_empty()
+	}
+	pub fn extract_trailing(&mut self) -> ChildTrivia {
+		mem::take(&mut self.trivia)
+	}
+}
addedcmds/jrsonnet-fmt/src/comments.rsdiffbeforeafterboth
--- /dev/null
+++ b/cmds/jrsonnet-fmt/src/comments.rs
@@ -0,0 +1,183 @@
+use dprint_core::formatting::PrintItems;
+use jrsonnet_rowan_parser::{nodes::TriviaKind, AstToken};
+
+use crate::{children::ChildTrivia, p, pi};
+
+pub enum CommentLocation {
+	/// Above local, field, other things
+	AboveItem,
+	/// After item
+	ItemInline,
+	/// After all items in object
+	EndOfItems,
+}
+
+#[must_use]
+pub fn format_comments(comments: &ChildTrivia, loc: CommentLocation) -> PrintItems {
+	let mut pi = p!(new:);
+
+	for c in comments {
+		let Ok(c) = c else {
+			let mut text = c.as_ref().unwrap_err() as &str;
+			while !text.is_empty() {
+				let pos = text.find(|c| c == '\n' || c == '\t').unwrap_or(text.len());
+				let sliced = &text[..pos];
+				p!(pi: string(sliced.to_string()));
+				text = &text[pos..];
+				if !text.is_empty() {
+					match text.as_bytes()[0] {
+						b'\n' => p!(pi: nl),
+						b'\t' => p!(pi: tab),
+						_ => unreachable!(),
+					}
+					text = &text[1..];
+				}
+			}
+			continue;
+		};
+		match c.kind() {
+			TriviaKind::Whitespace => {}
+			TriviaKind::MultiLineComment => {
+				let mut text = c
+					.text()
+					.strip_prefix("/*")
+					.expect("ml comment starts with /*")
+					.strip_suffix("*/")
+					.expect("ml comment ends with */");
+				// doc-style comment, /**
+				let doc = if text.starts_with('*') {
+					text = &text[1..];
+					true
+				} else {
+					false
+				};
+				// Is comment starts with text immediatly, i.e /*text
+				let mut immediate_start = true;
+				let mut lines = text
+					.split('\n')
+					.map(|l| l.trim_end().to_string())
+					.skip_while(|l| {
+						if l.is_empty() {
+							immediate_start = false;
+							true
+						} else {
+							false
+						}
+					})
+					.collect::<Vec<_>>();
+				while lines.last().map(|l| l.is_empty()).unwrap_or(false) {
+					lines.pop();
+				}
+				if lines.len() == 1 && !doc {
+					if matches!(loc, CommentLocation::ItemInline) {
+						p!(pi: str(" "));
+					}
+					p!(pi: str("/* ") string(lines[0].trim().to_string()) str(" */"))
+				} else if !lines.is_empty() {
+					fn common_ws_prefix<'a>(a: &'a str, b: &str) -> &'a str {
+						let offset = a
+							.bytes()
+							.zip(b.bytes())
+							.take_while(|(a, b)| a == b && (a.is_ascii_whitespace() || *a == b'*'))
+							.count();
+						&a[..offset]
+					}
+					// First line is not empty, extract ws prefix of it
+					let mut common_ws_padding = (if immediate_start && lines.len() > 1 {
+						common_ws_prefix(&lines[1], &lines[1])
+					} else {
+						common_ws_prefix(&lines[0], &lines[0])
+					})
+					.to_string();
+					for line in lines
+						.iter()
+						.skip(if immediate_start { 2 } else { 1 })
+						.filter(|l| !l.is_empty())
+					{
+						common_ws_padding = common_ws_prefix(&common_ws_padding, line).to_string();
+					}
+					for line in lines
+						.iter_mut()
+						.skip(if immediate_start { 1 } else { 0 })
+						.filter(|l| !l.is_empty())
+					{
+						*line = line
+							.strip_prefix(&common_ws_padding)
+							.expect("all non-empty lines start with this padding")
+							.to_string();
+					}
+
+					p!(pi: str("/*"));
+					if doc {
+						p!(pi: str("*"));
+					}
+					p!(pi: nl);
+					for mut line in lines {
+						if doc {
+							p!(pi: str(" *"));
+						}
+						if line.is_empty() {
+							p!(pi: nl);
+						} else {
+							if doc {
+								p!(pi: str(" "));
+							}
+							while let Some(new_line) = line.strip_prefix('\t') {
+								if doc {
+									p!(pi: str("    "));
+								} else {
+									p!(pi: tab);
+								}
+								line = new_line.to_string();
+							}
+							p!(pi: string(line.to_string()) nl)
+						}
+					}
+					if doc {
+						p!(pi: str(" "));
+					}
+					p!(pi: str("*/") nl)
+				}
+			}
+			// TODO: Keep common padding for multiple continous lines of single-line comments
+			// I.e
+			// ```
+			// #  Line1
+			// #    Line2
+			// ```
+			// Should be reformatted as
+			// ```
+			// # Line1
+			// #   Line2
+			// ```
+			// But currently comment formatter is not aware of continous comment lines, and reformats it as
+			// ```
+			// # Line1
+			// # Line2
+			// ```
+			TriviaKind::SingleLineHashComment => {
+				if matches!(loc, CommentLocation::ItemInline) {
+					p!(pi: str(" "))
+				}
+				p!(pi: str("# ") string(c.text().strip_prefix('#').expect("hash comment starts with #").trim().to_string()));
+				if !matches!(loc, CommentLocation::ItemInline) {
+					p!(pi: nl)
+				}
+			}
+			TriviaKind::SingleLineSlashComment => {
+				if matches!(loc, CommentLocation::ItemInline) {
+					p!(pi: str(" "))
+				}
+				p!(pi: str("// ") string(c.text().strip_prefix("//").expect("comment starts with //").trim().to_string()));
+				if !matches!(loc, CommentLocation::ItemInline) {
+					p!(pi: nl)
+				}
+			}
+			// Garbage in - garbage out
+			TriviaKind::ErrorCommentTooShort => p!(pi: str("/*/")),
+			TriviaKind::ErrorCommentUnterminated => p!(pi: string(c.text().to_string())),
+		}
+	}
+
+	pi
+}
addedcmds/jrsonnet-fmt/src/main.rsdiffbeforeafterboth
--- /dev/null
+++ b/cmds/jrsonnet-fmt/src/main.rs
@@ -0,0 +1,804 @@
+use std::{
+	any::type_name,
+	fs,
+	io::{self, Write},
+	path::PathBuf,
+	process,
+};
+
+use children::{children_between, trivia_before};
+use clap::Parser;
+use dprint_core::formatting::{PrintItems, PrintOptions};
+use jrsonnet_rowan_parser::{
+	nodes::{
+		ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,
+		DestructRest, Expr, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal, Member,
+		Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Stmt, Suffix, Text,
+		UnaryOperator, Visibility,
+	},
+	AstNode, AstToken, SyntaxToken,
+};
+
+use crate::{
+	children::{trivia_after, trivia_between},
+	comments::{format_comments, CommentLocation},
+};
+
+mod children;
+mod comments;
+#[cfg(test)]
+mod tests;
+
+pub trait Printable {
+	fn print(&self) -> PrintItems;
+}
+
+macro_rules! pi {
+	(@i; $($t:tt)*) => {{
+		#[allow(unused_mut)]
+		let mut o = dprint_core::formatting::PrintItems::new();
+		pi!(@s; o: $($t)*);
+		o
+	}};
+	(@s; $o:ident: str($e:expr $(,)?) $($t:tt)*) => {{
+		$o.push_str($e);
+		pi!(@s; $o: $($t)*);
+	}};
+	(@s; $o:ident: string($e:expr $(,)?) $($t:tt)*) => {{
+		$o.push_string($e);
+		pi!(@s; $o: $($t)*);
+	}};
+	(@s; $o:ident: nl $($t:tt)*) => {{
+		$o.push_signal(dprint_core::formatting::Signal::NewLine);
+		pi!(@s; $o: $($t)*);
+	}};
+	(@s; $o:ident: tab $($t:tt)*) => {{
+		$o.push_signal(dprint_core::formatting::Signal::Tab);
+		pi!(@s; $o: $($t)*);
+	}};
+	(@s; $o:ident: >i $($t:tt)*) => {{
+		$o.push_signal(dprint_core::formatting::Signal::StartIndent);
+		pi!(@s; $o: $($t)*);
+	}};
+	(@s; $o:ident: <i $($t:tt)*) => {{
+		$o.push_signal(dprint_core::formatting::Signal::FinishIndent);
+		pi!(@s; $o: $($t)*);
+	}};
+	(@s; $o:ident: {$expr:expr} $($t:tt)*) => {{
+		$o.extend($expr.print());
+		pi!(@s; $o: $($t)*);
+	}};
+	(@s; $o:ident: items($expr:expr) $($t:tt)*) => {{
+		$o.extend($expr);
+		pi!(@s; $o: $($t)*);
+	}};
+	(@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{
+		if $e {
+			pi!(@s; $o: $($then)*);
+		}
+		pi!(@s; $o: $($t)*);
+	}};
+	(@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{
+		if $e {
+			pi!(@s; $o: $($then)*);
+		} else {
+			pi!(@s; $o: $($else)*);
+		}
+		pi!(@s; $o: $($t)*);
+	}};
+	(@s; $i:ident:) => {}
+}
+macro_rules! p {
+	(new: $($t:tt)*) => {
+		pi!(@i; $($t)*)
+	};
+	($o:ident: $($t:tt)*) => {
+		pi!(@s; $o: $($t)*)
+	};
+}
+pub(crate) use p;
+pub(crate) use pi;
+
+impl<P> Printable for Option<P>
+where
+	P: Printable,
+{
+	fn print(&self) -> PrintItems {
+		if let Some(v) = self {
+			v.print()
+		} else {
+			p!(new: string(
+				format!(
+					"/*missing {}*/",
+					type_name::<P>().replace("jrsonnet_rowan_parser::generated::nodes::", "")
+				),
+			))
+		}
+	}
+}
+
+impl Printable for SyntaxToken {
+	fn print(&self) -> PrintItems {
+		p!(new: string(self.to_string()))
+	}
+}
+
+impl Printable for Text {
+	fn print(&self) -> PrintItems {
+		p!(new: string(format!("{}", self)))
+	}
+}
+impl Printable for Number {
+	fn print(&self) -> PrintItems {
+		p!(new: string(format!("{}", self)))
+	}
+}
+
+impl Printable for Name {
+	fn print(&self) -> PrintItems {
+		p!(new: {self.ident_lit()})
+	}
+}
+
+impl Printable for DestructRest {
+	fn print(&self) -> PrintItems {
+		let mut pi = p!(new: str("..."));
+		if let Some(name) = self.into() {
+			p!(pi: {name});
+		}
+		pi
+	}
+}
+
+impl Printable for Destruct {
+	fn print(&self) -> PrintItems {
+		let mut pi = p!(new:);
+		match self {
+			Destruct::DestructFull(f) => {
+				p!(pi: {f.name()})
+			}
+			Destruct::DestructSkip(_) => p!(pi: str("?")),
+			Destruct::DestructArray(a) => {
+				p!(pi: str("[") >i nl);
+				for el in a.destruct_array_parts() {
+					match el {
+						DestructArrayPart::DestructArrayElement(e) => {
+							p!(pi: {e.destruct()} str(",") nl)
+						}
+						DestructArrayPart::DestructRest(d) => {
+							p!(pi: {d} str(",") nl)
+						}
+					}
+				}
+				p!(pi: <i str("]"));
+			}
+			Destruct::DestructObject(o) => {
+				p!(pi: str("{") >i nl);
+				for item in o.destruct_object_fields() {
+					p!(pi: {item.field()});
+					if let Some(des) = item.destruct() {
+						p!(pi: str(": ") {des})
+					}
+					if let Some(def) = item.expr() {
+						p!(pi: str(" = ") {def});
+					}
+					p!(pi: str(",") nl);
+				}
+				if let Some(rest) = o.destruct_rest() {
+					p!(pi: {rest} nl)
+				}
+				p!(pi: <i str("}"));
+			}
+		}
+		pi
+	}
+}
+
+impl Printable for FieldName {
+	fn print(&self) -> PrintItems {
+		match self {
+			FieldName::FieldNameFixed(f) => {
+				if let Some(id) = f.id() {
+					p!(new: {id})
+				} else if let Some(str) = f.text() {
+					p!(new: {str})
+				} else {
+					p!(new: str("/*missing FieldName*/"))
+				}
+			}
+			FieldName::FieldNameDynamic(d) => {
+				p!(new: str("[") {d.expr()} str("]"))
+			}
+		}
+	}
+}
+
+impl Printable for Visibility {
+	fn print(&self) -> PrintItems {
+		p!(new: string(self.to_string()))
+	}
+}
+
+impl Printable for ObjLocal {
+	fn print(&self) -> PrintItems {
+		p!(new: str("local ") {self.bind()})
+	}
+}
+
+impl Printable for Assertion {
+	fn print(&self) -> PrintItems {
+		let mut pi = p!(new: str("assert ") {self.condition()});
+		if self.colon_token().is_some() || self.message().is_some() {
+			p!(pi: str(": ") {self.message()})
+		}
+		pi
+	}
+}
+
+impl Printable for ParamsDesc {
+	fn print(&self) -> PrintItems {
+		let mut pi = p!(new: str("(") >i nl);
+		for param in self.params() {
+			p!(pi: {param.destruct()});
+			if param.assign_token().is_some() || param.expr().is_some() {
+				p!(pi: str(" = ") {param.expr()})
+			}
+			p!(pi: str(",") nl)
+		}
+		p!(pi: <i str(")"));
+		pi
+	}
+}
+impl Printable for ArgsDesc {
+	fn print(&self) -> PrintItems {
+		let mut pi = p!(new: str("(") >i nl);
+		for arg in self.args() {
+			if arg.name().is_some() || arg.assign_token().is_some() {
+				p!(pi: {arg.name()} str(" = "));
+			}
+			p!(pi: {arg.expr()} str(",") nl)
+		}
+		p!(pi: <i str(")"));
+		pi
+	}
+}
+impl Printable for SliceDesc {
+	fn print(&self) -> PrintItems {
+		let mut pi = p!(new: str("["));
+		if self.from().is_some() {
+			p!(pi: {self.from()});
+		}
+		p!(pi: str(":"));
+		if self.end().is_some() {
+			p!(pi: {self.end().map(|e|e.expr())})
+		}
+		// Keep only one : in case if we don't need step
+		if self.step().is_some() {
+			p!(pi: str(":") {self.step().map(|e|e.expr())});
+		}
+		p!(pi: str("]"));
+		pi
+	}
+}
+
+impl Printable for Member {
+	fn print(&self) -> PrintItems {
+		match self {
+			Member::MemberBindStmt(b) => {
+				p!(new: {b.obj_local()})
+			}
+			Member::MemberAssertStmt(ass) => {
+				p!(new: {ass.assertion()})
+			}
+			Member::MemberFieldNormal(n) => {
+				p!(new: {n.field_name()} if(n.plus_token().is_some())({n.plus_token()}) {n.visibility()} str(" ") {n.expr()})
+			}
+			Member::MemberFieldMethod(m) => {
+				p!(new: {m.field_name()} {m.params_desc()} {m.visibility()} str(" ") {m.expr()})
+			}
+		}
+	}
+}
+
+impl Printable for ObjBody {
+	fn print(&self) -> PrintItems {
+		match self {
+			ObjBody::ObjBodyComp(l) => {
+				let (children, mut end_comments) = children_between::<Member>(
+					l.syntax().clone(),
+					l.l_brace_token().map(Into::into).as_ref(),
+					Some(
+						&(l.comp_specs()
+							.next()
+							.expect("at least one spec is defined")
+							.syntax()
+							.clone())
+						.into(),
+					),
+					None,
+				);
+				let trailing_for_comp = end_comments.extract_trailing();
+				let mut pi = p!(new: str("{") >i nl);
+				for mem in children.into_iter() {
+					if mem.should_start_with_newline {
+						p!(pi: nl);
+					}
+					p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));
+					p!(pi: {mem.value} str(","));
+					p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));
+					p!(pi: nl)
+				}
+
+				if end_comments.should_start_with_newline {
+					p!(pi: nl);
+				}
+				p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));
+
+				let (compspecs, end_comments) = children_between::<CompSpec>(
+					l.syntax().clone(),
+					l.member_comps()
+						.last()
+						.map(|m| m.syntax().clone())
+						.map(Into::into)
+						.or_else(|| l.l_brace_token().map(Into::into))
+						.as_ref(),
+					l.r_brace_token().map(Into::into).as_ref(),
+					Some(trailing_for_comp),
+				);
+				for mem in compspecs.into_iter() {
+					if mem.should_start_with_newline {
+						p!(pi: nl);
+					}
+					p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));
+					p!(pi: {mem.value});
+					p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));
+				}
+				if end_comments.should_start_with_newline {
+					p!(pi: nl);
+				}
+				p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));
+
+				p!(pi: nl <i str("}"));
+				pi
+			}
+			ObjBody::ObjBodyMemberList(l) => {
+				let (children, end_comments) = children_between::<Member>(
+					l.syntax().clone(),
+					l.l_brace_token().map(Into::into).as_ref(),
+					l.r_brace_token().map(Into::into).as_ref(),
+					None,
+				);
+				if children.is_empty() && end_comments.is_empty() {
+					return p!(new: str("{ }"));
+				}
+				let mut pi = p!(new: str("{") >i nl);
+				for mem in children.into_iter() {
+					if mem.should_start_with_newline {
+						p!(pi: nl);
+					}
+					p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));
+					p!(pi: {mem.value} str(","));
+					p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));
+					p!(pi: nl)
+				}
+
+				if end_comments.should_start_with_newline {
+					p!(pi: nl);
+				}
+				p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));
+				p!(pi: <i str("}"));
+				pi
+			}
+		}
+	}
+}
+impl Printable for UnaryOperator {
+	fn print(&self) -> PrintItems {
+		p!(new: string(self.text().to_string()))
+	}
+}
+impl Printable for BinaryOperator {
+	fn print(&self) -> PrintItems {
+		p!(new: string(self.text().to_string()))
+	}
+}
+impl Printable for Bind {
+	fn print(&self) -> PrintItems {
+		match self {
+			Bind::BindDestruct(d) => {
+				p!(new: {d.into()} str(" = ") {d.value()})
+			}
+			Bind::BindFunction(f) => {
+				p!(new: {f.name()} {f.params()} str(" = ") {f.value()})
+			}
+		}
+	}
+}
+impl Printable for Literal {
+	fn print(&self) -> PrintItems {
+		p!(new: string(self.syntax().to_string()))
+	}
+}
+impl Printable for ImportKind {
+	fn print(&self) -> PrintItems {
+		p!(new: string(self.syntax().to_string()))
+	}
+}
+impl Printable for ForSpec {
+	fn print(&self) -> PrintItems {
+		p!(new: str("for ") {self.bind()} str(" in ") {self.expr()})
+	}
+}
+impl Printable for IfSpec {
+	fn print(&self) -> PrintItems {
+		p!(new: str("if ") {self.expr()})
+	}
+}
+impl Printable for CompSpec {
+	fn print(&self) -> PrintItems {
+		match self {
+			CompSpec::ForSpec(f) => f.print(),
+			CompSpec::IfSpec(i) => i.print(),
+		}
+	}
+}
+impl Printable for Expr {
+	fn print(&self) -> PrintItems {
+		let mut o = p!(new:);
+		let (stmts, ending) = children_between::<Stmt>(
+			self.syntax().clone(),
+			None,
+			self.expr_base()
+				.as_ref()
+				.map(ExprBase::syntax)
+				.cloned()
+				.map(Into::into)
+				.as_ref(),
+			None,
+		);
+		for stmt in stmts {
+			p!(o: {stmt.value});
+		}
+		p!(o: {self.expr_base()});
+		let (suffixes, ending) = children_between::<Suffix>(
+			self.syntax().clone(),
+			self.expr_base()
+				.as_ref()
+				.map(ExprBase::syntax)
+				.cloned()
+				.map(Into::into)
+				.as_ref(),
+			None,
+			None,
+		);
+		for suffix in suffixes {
+			p!(o: {suffix.value});
+		}
+		o
+	}
+}
+impl Printable for Suffix {
+	fn print(&self) -> PrintItems {
+		let mut o = p!(new:);
+		match self {
+			Suffix::SuffixIndex(i) => {
+				if i.question_mark_token().is_some() {
+					p!(o: str("?"));
+				}
+				p!(o: str(".") {i.index()});
+			}
+			Suffix::SuffixIndexExpr(e) => {
+				if e.question_mark_token().is_some() {
+					p!(o: str(".?"));
+				}
+				p!(o: str("[") {e.index()} str("]"))
+			}
+			Suffix::SuffixSlice(d) => {
+				p!(o: {d.slice_desc()})
+			}
+			Suffix::SuffixApply(a) => {
+				p!(o: {a.args_desc()})
+			}
+		}
+		o
+	}
+}
+impl Printable for Stmt {
+	fn print(&self) -> PrintItems {
+		match self {
+			Stmt::StmtLocal(l) => {
+				let mut pi = p!(new:);
+				let (binds, end_comments) = children_between::<Bind>(
+					l.syntax().clone(),
+					l.local_kw_token().map(Into::into).as_ref(),
+					l.semi_token().map(Into::into).as_ref(),
+					None,
+				);
+				if binds.len() == 1 {
+					let bind = &binds[0];
+					p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));
+					p!(pi: str("local ") {bind.value});
+				// TODO: keep end_comments, child.inline_trivia somehow, force multiple locals formatting in case of presence?
+				} else {
+					p!(pi: str("local") >i nl);
+					for bind in binds {
+						if bind.should_start_with_newline {
+							p!(pi: nl);
+						}
+						p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));
+						p!(pi: {bind.value} str(","));
+						p!(pi: items(format_comments(&bind.inline_trivia, CommentLocation::ItemInline)) nl);
+					}
+					if end_comments.should_start_with_newline {
+						p!(pi: nl)
+					}
+					p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));
+					p!(pi: <i);
+				}
+				p!(pi: str(";") nl);
+				pi
+			}
+			Stmt::StmtAssert(a) => {
+				p!(new: {a.assertion()} str(";") nl)
+			}
+		}
+	}
+}
+impl Printable for ExprBase {
+	fn print(&self) -> PrintItems {
+		match self {
+			Self::ExprBinary(b) => {
+				p!(new: {b.lhs_work()} str(" ") {b.binary_operator()} str(" ") {b.rhs_work()})
+			}
+			Self::ExprUnary(u) => p!(new: {u.unary_operator()} {u.rhs()}),
+			// Self::ExprSlice(s) => {
+			// 	p!(new: {s.expr()} {s.slice_desc()})
+			// }
+			// Self::ExprIndex(i) => {
+			// 	p!(new: {i.expr()} str(".") {i.index()})
+			// }
+			// Self::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),
+			// Self::ExprApply(a) => {
+			// 	let mut pi = p!(new: {a.expr()} {a.args_desc()});
+			// 	if a.tailstrict_kw_token().is_some() {
+			// 		p!(pi: str(" tailstrict"));
+			// 	}
+			// 	pi
+			// }
+			Self::ExprObjExtend(ex) => {
+				p!(new: {ex.lhs_work()} str(" ") {ex.rhs_work()})
+			}
+			Self::ExprParened(p) => {
+				p!(new: str("(") {p.expr()} str(")"))
+			}
+			Self::ExprString(s) => p!(new: {s.text()}),
+			Self::ExprNumber(n) => p!(new: {n.number()}),
+			Self::ExprArray(a) => {
+				let mut pi = p!(new: str("[") >i nl);
+				for el in a.exprs() {
+					p!(pi: {el} str(",") nl);
+				}
+				p!(pi: <i str("]"));
+				pi
+			}
+			Self::ExprObject(obj) => {
+				p!(new: {obj.obj_body()})
+			}
+			Self::ExprArrayComp(arr) => {
+				let mut pi = p!(new: str("[") {arr.expr()});
+				for spec in arr.comp_specs() {
+					p!(pi: str(" ") {spec});
+				}
+				p!(pi: str("]"));
+				pi
+			}
+			Self::ExprImport(v) => {
+				p!(new: {v.import_kind()} str(" ") {v.text()})
+			}
+			Self::ExprVar(n) => p!(new: {n.name()}),
+			// Self::ExprLocal(l) => {
+			// }
+			Self::ExprIfThenElse(ite) => {
+				let mut pi =
+					p!(new: str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});
+				if ite.else_kw_token().is_some() || ite.else_().is_some() {
+					p!(pi: str(" else ") {ite.else_().map(|t| t.expr())})
+				}
+				pi
+			}
+			Self::ExprFunction(f) => p!(new: str("function") {f.params_desc()} nl {f.expr()}),
+			// Self::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),
+			Self::ExprError(e) => p!(new: str("error ") {e.expr()}),
+			Self::ExprLiteral(l) => {
+				p!(new: {l.literal()})
+			}
+		}
+	}
+}
+
+impl Printable for SourceFile {
+	fn print(&self) -> PrintItems {
+		let mut pi = p!(new:);
+		let before = trivia_before(
+			self.syntax().clone(),
+			self.expr()
+				.map(|e| e.syntax().clone())
+				.map(Into::into)
+				.as_ref(),
+		);
+		let after = trivia_after(
+			self.syntax().clone(),
+			self.expr()
+				.map(|e| e.syntax().clone())
+				.map(Into::into)
+				.as_ref(),
+		);
+		p!(pi: items(format_comments(&before, CommentLocation::AboveItem)));
+		p!(pi: {self.expr()} nl);
+		p!(pi: items(format_comments(&after, CommentLocation::EndOfItems)));
+		pi
+	}
+}
+
+struct FormatOptions {
+	// 0 for hard tabs
+	indent: u8,
+}
+fn format(input: &str, opts: &FormatOptions) -> Option<String> {
+	let (parsed, errors) = jrsonnet_rowan_parser::parse(input);
+	if !errors.is_empty() {
+		let mut builder = ass_stroke::SnippetBuilder::new(input);
+		for error in errors {
+			builder
+				.error(ass_stroke::Text::single(
+					format!("{:?}", error.error).chars(),
+					Default::default(),
+				))
+				.range(
+					error.range.start().into()
+						..=(usize::from(error.range.end()) - 1).max(error.range.start().into()),
+				)
+				.build();
+		}
+		let snippet = builder.build();
+		let ansi = ass_stroke::source_to_ansi(&snippet);
+		eprintln!("{ansi}");
+		// It is possible to recover from this failure, but the output may be broken, as formatter is free to skip
+		// ERROR rowan nodes.
+		// Recovery needs to be enabled for LSP, though.
+		//
+		// TODO: Verify how formatter interacts in cases of missing positional values, i.e `if cond then /*missing Expr*/ else residual`.
+		return None;
+	}
+	Some(dprint_core::formatting::format(
+		|| parsed.print(),
+		PrintOptions {
+			indent_width: if opts.indent == 0 {
+				// Reasonable max length for both 2 and 4 space sized tabs.
+				3
+			} else {
+				opts.indent
+			},
+			max_width: 100,
+			use_tabs: opts.indent == 0,
+			new_line_text: "\n",
+		},
+	))
+}
+
+#[derive(Parser)]
+struct Opts {
+	/// Treat input as code, reformat it instead of reading file.
+	#[clap(long, short = 'e')]
+	exec: bool,
+	/// Path to be reformatted if `--exec` if unset, otherwise code itself.
+	input: String,
+	/// Replace code with formatted in-place, instead of printing it to stdout.
+	/// Only applicable if `--exec` is unset.
+	#[clap(long, short = 'i')]
+	in_place: bool,
+
+	/// Exit with error if formatted does not match input
+	#[arg(long)]
+	test: bool,
+	/// Number of spaces to indent with
+	///
+	/// 0 for guess from input (default), and use hard tabs if unable to guess.
+	#[arg(long, default_value = "0")]
+	indent: u8,
+	/// Force hard tab for indentation
+	#[arg(long)]
+	hard_tabs: bool,
+
+	/// Debug option: how many times to call reformatting in case of unstable dprint output resolution.
+	///
+	/// 0 for not retrying to reformat.
+	#[arg(long, default_value = "0")]
+	conv_limit: usize,
+}
+
+#[derive(thiserror::Error, Debug)]
+enum Error {
+	#[error("--in-place is incompatible with --exec")]
+	InPlaceExec,
+	#[error("io: {0}")]
+	Io(#[from] io::Error),
+	#[error("persist: {0}")]
+	Persist(#[from] tempfile::PersistError),
+	#[error("parsing failed, refusing to reformat corrupted input")]
+	ParseError,
+}
+
+fn main_result() -> Result<(), Error> {
+	eprintln!("jrsonnet-fmt is a prototype of a jsonnet code formatter, do not expect it to produce meaningful results right now.");
+	eprintln!("It is not expected for its output to match other implementations, it will be completly separate implementation with maybe different name.");
+	let mut opts = Opts::parse();
+	let input = if opts.exec {
+		if opts.in_place {
+			return Err(Error::InPlaceExec);
+		}
+		opts.input.clone()
+	} else {
+		fs::read_to_string(&opts.input)?
+	};
+
+	if opts.indent == 0 {
+		// Sane default.
+		// TODO: Implement actual guessing.
+		opts.hard_tabs = true;
+	}
+
+	let mut iteration = 0;
+	let mut formatted = input.clone();
+	let mut tmp;
+	// https://github.com/dprint/dprint/pull/423
+	loop {
+		let Some(reformatted) = format(
+			&formatted,
+			&FormatOptions {
+				indent: if opts.indent == 0 || opts.hard_tabs {
+					0
+				} else {
+					opts.indent
+				},
+			},
+		) else {
+			return Err(Error::ParseError);
+		};
+		tmp = reformatted.trim().to_owned();
+		if formatted == tmp {
+			break;
+		}
+		formatted = tmp;
+		if opts.conv_limit == 0 {
+			break;
+		}
+		iteration += 1;
+		if iteration > opts.conv_limit {
+			panic!("formatting not converged");
+		}
+	}
+	formatted.push('\n');
+	if opts.test && formatted != input {
+		process::exit(1);
+	}
+	if opts.in_place {
+		let path = PathBuf::from(opts.input);
+		let mut temp = tempfile::NamedTempFile::new_in(path.parent().expect(
+			"not failed during read, this path is not a directory, and there is a parent",
+		))?;
+		temp.write_all(formatted.as_bytes())?;
+		temp.flush()?;
+		temp.persist(&path)?;
+	} else {
+		print!("{formatted}")
+	}
+	Ok(())
+}
+
+fn main() {
+	if let Err(e) = main_result() {
+		eprintln!("{e}");
+		process::exit(1);
+	}
+}
addedcmds/jrsonnet-fmt/src/snapshots/jrsonnet_fmt__tests__complex_comments_snapshot.snapdiffbeforeafterboth
--- /dev/null
+++ b/cmds/jrsonnet-fmt/src/snapshots/jrsonnet_fmt__tests__complex_comments_snapshot.snap
@@ -0,0 +1,53 @@
+---
+source: cmds/jrsonnet-fmt/src/tests.rs
+expression: "reformat(indoc!(\"{\n\t\t  comments: {\n\t\t\t_: '',\n\t\t\t//     Plain comment\n\t\t\ta: '',\n\n\t\t\t#    Plain comment with empty line before\n\t\t\tb: '',\n\t\t\t/*Single-line multiline comment\n\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/**Single-line multiline doc comment\n\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/**Multiline doc\n\t\t\tComment\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/*\n\n\tMulti-line\n\n\tcomment\n\t\t\t*/\n\t\t\td: '',\n\n\t\t\te: '', // Inline comment\n\n\t\t\tk: '',\n\n\t\t\t// Text after everything\n\t\t  },\n\t\t  comments2: {\n\t\t\tk: '',\n\t\t\t// Text after everything, but no newline above\n\t\t  },\n          spacing: {\n            a: '',\n\n            b: '',\n          },\n          noSpacing: {\n            a: '',\n            b: '',\n          },\n        }\"))"
+---
+{
+  comments: {
+    _: '',
+    // Plain comment
+    a: '',
+
+    # Plain comment with empty line before
+    b: '',
+    /* Single-line multiline comment */
+    c: '',
+
+    /**
+     * Single-line multiline doc comment
+     */
+    c: '',
+
+    /**
+     * Multiline doc
+     * Comment
+     */
+    c: '',
+
+    /*
+    Multi-line
+
+    comment
+    */
+    d: '',
+
+    e: '', // Inline comment
+
+    k: '',
+
+    // Text after everything
+  },
+  comments2: {
+    k: '',
+    // Text after everything, but no newline above
+  },
+  spacing: {
+    a: '',
+
+    b: '',
+  },
+  noSpacing: {
+    a: '',
+    b: '',
+  },
+}
addedcmds/jrsonnet-fmt/src/tests.rsdiffbeforeafterboth
--- /dev/null
+++ b/cmds/jrsonnet-fmt/src/tests.rs
@@ -0,0 +1,124 @@
+use dprint_core::formatting::PrintOptions;
+use indoc::indoc;
+
+use crate::Printable;
+
+fn reformat(input: &str) -> String {
+	let (source, _) = jrsonnet_rowan_parser::parse(input);
+
+	dprint_core::formatting::format(
+		|| source.print(),
+		PrintOptions {
+			indent_width: 2,
+			max_width: 100,
+			use_tabs: false,
+			new_line_text: "\n",
+		},
+	)
+}
+
+macro_rules! assert_formatted {
+	($input:literal, $output:literal) => {
+		let formatted = reformat(indoc!($input));
+		let mut expected = indoc!($output).to_owned();
+		expected.push('\n');
+		if formatted != expected {
+			panic!(
+				"bad formatting, expected\n```\n{formatted}\n```\nto be equal to\n```\n{expected}\n```",
+			)
+		}
+	};
+}
+
+#[test]
+fn padding_stripped_for_multiline_comment() {
+	assert_formatted!(
+		"{
+            /*
+                Hello
+                    World
+            */
+            _: null,
+        }",
+		"{
+          /*
+          Hello
+              World
+          */
+          _: null,
+        }"
+	);
+}
+
+#[test]
+fn last_comment_respects_spacing_with_inline_comment_above() {
+	assert_formatted!(
+		"{
+			a: '', // Inline
+
+			// Comment
+        }",
+		"{
+		  a: '', // Inline
+
+		  // Comment
+		}"
+	);
+}
+
+#[test]
+fn complex_comments_snapshot() {
+	insta::assert_display_snapshot!(reformat(indoc!(
+		"{
+		  comments: {
+			_: '',
+			//     Plain comment
+			a: '',
+
+			#    Plain comment with empty line before
+			b: '',
+			/*Single-line multiline comment
+
+			*/
+			c: '',
+
+			/**Single-line multiline doc comment
+
+			*/
+			c: '',
+
+			/**Multiline doc
+			Comment
+			*/
+			c: '',
+
+			/*
+
+	Multi-line
+
+	comment
+			*/
+			d: '',
+
+			e: '', // Inline comment
+
+			k: '',
+
+			// Text after everything
+		  },
+		  comments2: {
+			k: '',
+			// Text after everything, but no newline above
+		  },
+          spacing: {
+            a: '',
+
+            b: '',
+          },
+          noSpacing: {
+            a: '',
+            b: '',
+          },
+        }"
+	)))
+}
addedcmds/jrsonnet-lsp/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/cmds/jrsonnet-lsp/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "jrsonnet-lsp"
+version.workspace = true
+edition = "2021"
+
+[dependencies]
+anyhow = "1.0.48"
+jrsonnet-evaluator.workspace = true
+jrsonnet-rowan-parser.workspace = true
+lsp-server = "0.6.0"
+lsp-types = "0.93.0"
+serde = "1.0.130"
+serde_json = "1.0.71"
addedcmds/jrsonnet-lsp/src/main.rsdiffbeforeafterboth
--- /dev/null
+++ b/cmds/jrsonnet-lsp/src/main.rs
@@ -0,0 +1,188 @@
+use std::{fs::File, io::Write, path::PathBuf, str::FromStr};
+
+use lsp_server::{Connection, ErrorCode, Message, Request, RequestId, Response};
+use lsp_types::{
+	notification::{DidChangeTextDocument, DidOpenTextDocument, Notification},
+	request::{DocumentLinkRequest, HoverRequest},
+	CompletionOptions, DidChangeTextDocumentParams, DidOpenTextDocumentParams, DocumentLink,
+	DocumentLinkOptions, ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind,
+	TextDocumentSyncOptions, Url, WorkDoneProgressOptions,
+};
+
+fn main() {
+	let mut log = File::create("test").unwrap();
+	writeln!(log, "start").unwrap();
+	let (connection, io_threads) = Connection::stdio();
+	let capabilities = serde_json::to_value(&ServerCapabilities {
+		completion_provider: Some(CompletionOptions::default()),
+		definition_provider: Some(lsp_types::OneOf::Left(true)),
+		document_link_provider: Some(DocumentLinkOptions {
+			resolve_provider: Some(false),
+			work_done_progress_options: WorkDoneProgressOptions::default(),
+		}),
+		hover_provider: Some(lsp_types::HoverProviderCapability::Simple(true)),
+		text_document_sync: Some(TextDocumentSyncCapability::Options(
+			TextDocumentSyncOptions {
+				change: Some(TextDocumentSyncKind::FULL),
+				open_close: Some(true),
+				..TextDocumentSyncOptions::default()
+			},
+		)),
+		..ServerCapabilities::default()
+	})
+	.expect("failed to convert capabilities to json");
+
+	connection
+		.initialize(capabilities)
+		.expect("failed to initialize connection");
+
+	writeln!(log, "initialized").unwrap();
+
+	main_loop(&mut log, &connection).expect("main loop failed");
+
+	io_threads.join().expect("failed to join io_threads");
+}
+fn main_loop(log: &mut File, connection: &Connection) -> anyhow::Result<()> {
+	// let mut es = EvaluationState::default();
+	// es.set_import_resolver(Box::new(FileImportResolver::default()));
+
+	let reply = |response: Response| {
+		connection
+			.sender
+			.send(Message::Response(response))
+			.expect("failed to respond");
+	};
+
+	for msg in &connection.receiver {
+		match msg {
+			Message::Response(_) => (),
+			Message::Request(req) => {
+				if connection.handle_shutdown(&req)? {
+					return Ok(());
+				}
+				if let Some((id, params)) = cast::<DocumentLinkRequest>(&req) {
+					reply(Response::new_ok(id, <Vec<DocumentLink>>::new()));
+				} else if let Some((id, params)) = cast::<HoverRequest>(&req) {
+					let pos = params
+						.text_document_position_params
+						.text_document
+						.uri
+						.path();
+					let buf = PathBuf::from_str(pos).unwrap();
+				// let pos = es
+				// 	.map_from_source_location(
+				// 		&buf,
+				// 		params.text_document_position_params.position.line as usize + 1,
+				// 		params.text_document_position_params.position.character as usize + 1,
+				// 	)
+				// 	.unwrap();
+				// let el = ExprLocation(buf.clone().into(), pos as usize, pos as usize);
+				// let es2 = es.clone();
+				// reply(Response::new_ok(
+				// 	id,
+				// 	Some(Hover {
+				// 		range: None,
+				// 		contents: HoverContents::Markup(MarkupContent {
+				// 			kind: MarkupKind::Markdown,
+				// 			value: es
+				// 				.run_in_state_with_breakpoint(el, move || {
+				// 					es2.reset_evaluation_state(&buf);
+				// 					es2.import_file(&PathBuf::new(), &buf)?
+				// 						.to_string()
+				// 						.map(|_| ())
+				// 				})
+				// 				.unwrap()
+				// 				.unwrap_or_else(|| Val::Null)
+				// 				.value_type()
+				// 				.to_string(),
+				// 		}),
+				// 	}),
+				// ));
+				} else {
+					reply(Response::new_err(
+						req.id,
+						ErrorCode::MethodNotFound as i32,
+						format!("unrecognized request {}", req.method),
+					))
+				}
+				/*
+				if let Some((id, params)) = cast::<DocumentLinkRequest>(&req) {
+					 let links = handle_links(&files, params).unwrap_or_default();
+					 reply(Response::new_ok(id, links));
+				} else if let Some((id, params)) = cast::<GotoDefinition>(&req) {
+					 if let Some(loc) = handle_goto(&files, params) {
+						  reply(Response::new_ok(id, loc))
+					 } else {
+						  reply(Response::new_ok(id, ()))
+					 }
+				} else if let Some((id, params)) = cast::<HoverRequest>(&req) {
+					 match handle_hover(&files, params) {
+						  Some((range, markdown)) => {
+								reply(Response::new_ok(
+									 id,
+									 Hover {
+										  contents: HoverContents::Markup(MarkupContent {
+												kind: MarkupKind::Markdown,
+												value: markdown,
+										  }),
+										  range,
+									 },
+								));
+						  }
+						  None => {
+								reply(Response::new_ok(id, ()));
+						  }
+					 }
+				} else if let Some((id, params)) = cast::<Completion>(&req) {
+					 let completions = handle_completion(&files, params.text_document_position)
+						  .unwrap_or_default();
+					 reply(Response::new_ok(id, completions));
+				} else
+				*/
+			}
+			Message::Notification(req) => {
+				let mut handle = |text: String, uri: Url| {
+					writeln!(log, "updated file: {:?}", uri).unwrap();
+					let path = match PathBuf::from_str(uri.path()) {
+						Ok(x) => x,
+						Err(_) => return,
+					};
+					let (ast, errors) = jrsonnet_rowan_parser::parse(&text);
+					// es.add_parsed_file(path.into(), text.into(), parsed)
+					// 	.unwrap();
+					writeln!(log, "parsed: {:?}", uri).unwrap();
+				};
+
+				match &*req.method {
+					DidOpenTextDocument::METHOD => {
+						let params: DidOpenTextDocumentParams =
+							match serde_json::from_value(req.params) {
+								Ok(x) => x,
+								Err(_) => continue,
+							};
+						handle(params.text_document.text, params.text_document.uri);
+					}
+					DidChangeTextDocument::METHOD => {
+						let params: DidChangeTextDocumentParams =
+							match serde_json::from_value(req.params) {
+								Ok(x) => x,
+								Err(_) => continue,
+							};
+						for change in params.content_changes.into_iter() {
+							handle(change.text, params.text_document.uri.clone());
+						}
+					}
+					_ => continue,
+				}
+			}
+		}
+	}
+	Ok(())
+}
+fn cast<R>(req: &Request) -> Option<(RequestId, R::Params)>
+where
+	R: lsp_types::request::Request,
+	R::Params: serde::de::DeserializeOwned,
+{
+	req.clone().extract(R::METHOD).ok()
+}
modifiedcmds/jrsonnet/Cargo.tomldiffbeforeafterboth
--- a/cmds/jrsonnet/Cargo.toml
+++ b/cmds/jrsonnet/Cargo.toml
@@ -12,10 +12,7 @@
 # Use mimalloc as allocator
 mimalloc = ["mimallocator"]
 # Experimental feature, which allows to preserve order of object fields
-exp-preserve-order = [
-    "jrsonnet-evaluator/exp-preserve-order",
-    "jrsonnet-cli/exp-preserve-order",
-]
+exp-preserve-order = ["jrsonnet-evaluator/exp-preserve-order", "jrsonnet-cli/exp-preserve-order"]
 # Destructuring of locals
 exp-destruct = ["jrsonnet-evaluator/exp-destruct"]
 # Iteration over objects yields [key, value] elements
@@ -44,3 +41,4 @@
 clap_complete = { version = "4.1" }
 serde_json = "1.0.104"
 serde = { workspace = true, features = ["derive"] }
+ass-stroke = { git = "https://github.com/CertainLach/ass-stroke", version = "0.1.0" }
modifiedcmds/jrsonnet/src/main.rsdiffbeforeafterboth
--- a/cmds/jrsonnet/src/main.rs
+++ b/cmds/jrsonnet/src/main.rs
@@ -37,15 +37,15 @@
 #[derive(Parser)]
 #[clap(next_help_heading = "INPUT")]
 struct InputOpts {
-	/// Treat input as code, evaluate them instead of reading file
+	/// Treat input as code, evaluate it instead of reading file.
 	#[clap(long, short = 'e')]
 	pub exec: bool,
 
-	/// Path to the file to be compiled if `--evaluate` is unset, otherwise code itself
+	/// Path to the file to be compiled if `--exec` is unset, otherwise code itself.
 	pub input: Option<String>,
 
 	/// After executing input, apply specified code.
-	/// Output of the initial input will be accessible using `$`
+	/// Output of the initial input will be accessible using `_`.
 	#[cfg(feature = "exp-apply")]
 	#[clap(long)]
 	pub exp_apply: Vec<String>,
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -41,6 +41,10 @@
 	clippy::missing_const_for_fn,
 	// too many false-positives with .expect() calls
 	clippy::missing_panics_doc,
+    // false positive for IStr type. There is an configuration option for
+    // such cases, but it doesn't work:
+    // https://github.com/rust-lang/rust-clippy/issues/9801
+    clippy::mutable_key_type,
 )]
 
 // For jrsonnet-macros
addedcrates/jrsonnet-rowan-parser/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "jrsonnet-rowan-parser"
+version.workspace = true
+edition = "2021"
+
+[dependencies]
+anyhow = "1.0"
+backtrace = "0.3.63"
+drop_bomb = "0.1.5"
+indoc = "1.0"
+logos = "0.12"
+miette = { version = "5.5.0", features = ["fancy"] }
+rowan = "0.15"
+text-size = "1.1"
+thiserror = "1.0"
+
+[dev-dependencies]
+backtrace = "0.3.63"
+indoc = "1.0"
+insta = "1.15"
+anyhow = "1.0"
addedcrates/jrsonnet-rowan-parser/jsonnet.ungramdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/jsonnet.ungram
@@ -0,0 +1,355 @@
+// This file describes structure of jsonnet source code
+// It is also used to generate files in src/generated
+
+// Token names ending with `!` are considered meta, and handled specifically
+
+SourceFile = Expr
+
+SuffixIndex =
+    '?'?
+    '.'
+    index:Name
+SuffixIndexExpr =
+    ('?' '.')?
+    '['
+    index:Expr
+    ']'
+SuffixSlice =
+    SliceDesc
+SuffixApply =
+    ArgsDesc
+    'tailstrict'?
+Suffix =
+    SuffixIndex
+|   SuffixIndexExpr
+|   SuffixSlice
+|   SuffixApply
+
+StmtLocal =
+    'local'
+    (Bind (',' Bind)* ','?)
+    ';'
+StmtAssert =
+    Assertion
+    ';'
+Stmt =
+    StmtLocal
+|   StmtAssert
+
+ExprBinary =
+    lhs:Expr
+    BinaryOperator
+    rhs:Expr
+ExprUnary =
+    UnaryOperator
+    rhs:Expr
+ExprObjExtend =
+    Expr
+    Expr
+ExprParened =
+    '('
+    Expr
+    ')'
+
+ExprLiteral =
+    Literal
+ExprString =
+    Text
+ExprNumber =
+    Number
+ExprArray =
+    '['
+    (Expr (',' Expr)* ','?)?
+    ']'
+ExprObject =
+    ObjBody
+ExprArrayComp =
+    '['
+    Expr
+    ','?
+    CompSpec*
+    ']'
+
+ExprImport =
+    ImportKind Text
+
+ImportKind =
+    'importstr'
+|   'importbin'
+|   'import'
+
+ExprVar =
+    name:Name
+
+ExprIfThenElse =
+    'if'
+    cond:Expr
+    'then'
+    then:TrueExpr
+    ('else' else_:FalseExpr)?
+
+ExprFunction =
+    'function'
+    '('
+    ParamsDesc
+    ')'
+    Expr
+ExprError =
+    'error'
+    Expr
+
+Expr =
+    Stmt*
+    ExprBase
+    Suffix*
+
+ExprBase =
+    ExprBinary
+|   ExprUnary
+|   ExprObjExtend
+|   ExprParened
+|   ExprString
+|   ExprNumber
+|   ExprLiteral
+|   ExprArray
+|   ExprObject
+|   ExprArrayComp
+|   ExprImport
+|   ExprVar
+|   ExprIfThenElse
+|   ExprFunction
+|   ExprError
+
+BinaryOperator =
+    '||' | '??' | '&&'
+|   '|' | '^' | '&'
+|   '==' | '!=' | '<' | '>' | '<=' | '>=' | 'in'
+|   '<<' | '>>'
+|   '+' | '-'
+|   '*' | '/' | '%'
+|   'META_OBJECT_APPLY!'
+|   'ERROR_NO_OPERATOR!'
+
+UnaryOperator =
+    '-' | '!' | '~'
+
+SliceDescEnd=Expr
+SliceDescStep=Expr
+SliceDesc =
+    '['
+    from:Expr?
+    ':'
+    (
+        end:SliceDescEnd?
+        (
+            ':'
+            step:SliceDescStep?
+        )?
+    )?
+    ']'
+
+Name =
+    'LIT_IDENT!'
+
+ArgsDesc =
+    '('
+    (Arg (',' Arg)* ','?)?
+    ')'
+Arg =
+    (name:Name '=')? Expr
+
+ObjBodyComp =
+    '{'
+    (MemberComp (',' MemberComp)* ','?)?
+    CompSpec*
+    '}'
+ObjBodyMemberList =
+    '{'
+    (Member (',' Member)* ','?)?
+    '}'
+ObjBody =
+    ObjBodyComp
+|   ObjBodyMemberList
+
+MemberBindStmt = ObjLocal
+MemberAssertStmt = Assertion
+MemberFieldNormal =
+    FieldName
+    '+'?
+    Visibility
+    Expr
+MemberFieldMethod =
+    FieldName
+    ParamsDesc
+    Visibility
+    Expr
+MemberComp =
+    MemberBindStmt
+|    MemberFieldNormal
+|   MemberFieldMethod
+Member =
+    MemberBindStmt
+|   MemberAssertStmt
+|   MemberFieldNormal
+|   MemberFieldMethod
+
+ObjLocal =
+    'local'
+    Bind
+
+FieldNameFixed =
+    id:Name
+|   Text
+FieldNameDynamic =
+    '['
+    Expr
+    ']'
+FieldName =
+    FieldNameFixed
+|   FieldNameDynamic
+
+Visibility =
+    ':::'
+|   '::'
+|   ':'
+
+Literal =
+    'null'
+|   'true'
+|   'false'
+|   'self'
+|   '$'
+|   'super'
+
+Text =
+    'LIT_STRING_DOUBLE!'
+|   'ERROR_STRING_DOUBLE_UNTERMINATED!'
+|   'LIT_STRING_SINGLE!'
+|   'ERROR_STRING_SINGLE_UNTERMINATED!'
+|   'LIT_STRING_DOUBLE_VERBATIM!'
+|   'ERROR_STRING_DOUBLE_VERBATIM_UNTERMINATED!'
+|   'LIT_STRING_SINGLE_VERBATIM!'
+|   'ERROR_STRING_SINGLE_VERBATIM_UNTERMINATED!'
+|   'ERROR_STRING_VERBATIM_MISSING_QUOTES!'
+|   'LIT_STRING_BLOCK!'
+|   'ERROR_STRING_BLOCK_UNEXPECTED_END!'
+|   'ERROR_STRING_BLOCK_MISSING_NEW_LINE!'
+|   'ERROR_STRING_BLOCK_MISSING_TERMINATION!'
+|   'ERROR_STRING_BLOCK_MISSING_INDENT!'
+
+Number =
+    'LIT_FLOAT!'
+|   'ERROR_FLOAT_JUNK_AFTER_POINT!'
+|   'ERROR_FLOAT_JUNK_AFTER_EXPONENT!'
+|   'ERROR_FLOAT_JUNK_AFTER_EXPONENT_SIGN!'
+
+ForSpec =
+    'for'
+    bind:Destruct
+    '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
+    (
+        ':'
+        message:Expr
+    )?
+
+DestructFull =
+    Name
+DestructSkip =
+    '?'
+DestructArray =
+    '['
+    (
+        DestructArrayPart
+        (',' DestructArrayPart)*
+        ','?
+    )?
+    ']'
+DestructObject =
+    '{'
+    (
+        DestructObjectField
+        (',' DestructObjectField)*
+        ','?
+    )?
+    DestructRest?
+    ','?
+    '}'
+Destruct =
+    DestructFull
+|   DestructSkip
+|   DestructArray
+|   DestructObject
+
+DestructArrayElement =
+    Destruct
+DestructArrayPart =
+    DestructArrayElement
+|   DestructRest
+
+DestructRest =
+    '...'
+    into:Name?
+
+DestructObjectField =
+    field:Name
+    (
+        ':'
+        Destruct
+    )?
+    (
+        '='
+        Expr
+    )?
+
+// Aliases used to resolve node type conflicts
+TrueExpr=Expr
+FalseExpr=Expr
+
+// Trivia - tokens which will be implicitly skipped for parser
+Trivia =
+    'LIT_WHITESPACE!'
+|   'LIT_MULTI_LINE_COMMENT!'
+|   'ERROR_COMMENT_TOO_SHORT!'
+|   'ERROR_COMMENT_UNTERMINATED!'
+|   'LIT_SINGLE_LINE_HASH_COMMENT!'
+|   'LIT_SINGLE_LINE_SLASH_COMMENT!'
+
+CustomError =
+    'ERROR_MISSING_TOKEN!'
+|   'ERROR_UNEXPECTED_TOKEN!'
+|   'ERROR_CUSTOM!'
addedcrates/jrsonnet-rowan-parser/src/ast.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/ast.rs
@@ -0,0 +1,95 @@
+use std::marker::PhantomData;
+
+use crate::{SyntaxKind, SyntaxNode, SyntaxNodeChildren, SyntaxToken};
+
+/// The main trait to go from untyped `SyntaxNode`  to a typed ast. The
+/// conversion itself has zero runtime cost: ast and syntax nodes have exactly
+/// the same representation: a pointer to the tree root and a pointer to the
+/// node itself.
+pub trait AstNode {
+	fn can_cast(kind: SyntaxKind) -> bool
+	where
+		Self: Sized;
+
+	fn cast(syntax: SyntaxNode) -> Option<Self>
+	where
+		Self: Sized;
+
+	fn syntax(&self) -> &SyntaxNode;
+	fn clone_for_update(&self) -> Self
+	where
+		Self: Sized,
+	{
+		Self::cast(self.syntax().clone_for_update()).unwrap()
+	}
+	fn clone_subtree(&self) -> Self
+	where
+		Self: Sized,
+	{
+		Self::cast(self.syntax().clone_subtree()).unwrap()
+	}
+}
+
+/// Like `AstNode`, but wraps tokens rather than interior nodes.
+pub trait AstToken {
+	fn can_cast(token: SyntaxKind) -> bool
+	where
+		Self: Sized;
+
+	fn cast(syntax: SyntaxToken) -> Option<Self>
+	where
+		Self: Sized;
+
+	fn syntax(&self) -> &SyntaxToken;
+
+	fn text(&self) -> &str {
+		self.syntax().text()
+	}
+}
+
+#[derive(Debug, Clone)]
+pub struct AstChildren<N> {
+	inner: SyntaxNodeChildren,
+	ph: PhantomData<N>,
+}
+
+impl<N> AstChildren<N> {
+	fn new(parent: &SyntaxNode) -> Self {
+		AstChildren {
+			inner: parent.children(),
+			ph: PhantomData,
+		}
+	}
+}
+
+impl<N: AstNode> Iterator for AstChildren<N> {
+	type Item = N;
+	fn next(&mut self) -> Option<N> {
+		self.inner.find_map(N::cast)
+	}
+}
+
+pub mod support {
+	use super::{AstChildren, AstNode, AstToken, SyntaxKind, SyntaxNode, SyntaxToken};
+
+	pub fn child<N: AstNode>(parent: &SyntaxNode) -> Option<N> {
+		parent.children().find_map(N::cast)
+	}
+	pub fn token_child<N: AstToken>(parent: &SyntaxNode) -> Option<N> {
+		parent.children_with_tokens().find_map(|n| match n {
+			rowan::NodeOrToken::Node(_) => None,
+			rowan::NodeOrToken::Token(t) => N::cast(t),
+		})
+	}
+
+	pub fn children<N: AstNode>(parent: &SyntaxNode) -> AstChildren<N> {
+		AstChildren::new(parent)
+	}
+
+	pub fn token(parent: &SyntaxNode, kind: SyntaxKind) -> Option<SyntaxToken> {
+		parent
+			.children_with_tokens()
+			.filter_map(|it| it.into_token())
+			.find(|it| it.kind() == kind)
+	}
+}
addedcrates/jrsonnet-rowan-parser/src/event.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/event.rs
@@ -0,0 +1,204 @@
+use std::{mem, num::NonZeroUsize};
+
+use rowan::{GreenNodeBuilder, Language, TextRange, TextSize};
+
+use crate::{
+	lex::Lexeme,
+	nodes::Trivia,
+	parser::{LocatedSyntaxError, Parse, SyntaxError},
+	AstToken, JsonnetLanguage, SyntaxKind,
+};
+
+#[derive(Clone, Debug)]
+pub enum Event {
+	/// Used for unfinished markers
+	Pending,
+	/// After marker is completed, Pending event is replaced with Start
+	Start {
+		kind: SyntaxKind,
+		/// If marker is preceded or wrapped - instead of reordering events, we
+		/// insert start event in the end of events Vec instead, and store relative offset to this event here
+		forward_parent: Option<NonZeroUsize>,
+	},
+	/// Eat token
+	Token { kind: SyntaxKind },
+	/// Push token, but do not eat anything,
+	VirtualToken { kind: SyntaxKind },
+	/// Position of finished node
+	Finish {
+		/// Same as forward_parent of Start, but for wrapping
+		wrapper: Option<NonZeroUsize>,
+		error: Option<Box<SyntaxError>>,
+	},
+	/// Used for dropped markers and other things
+	Noop,
+}
+
+pub(super) struct Sink<'i> {
+	pub builder: GreenNodeBuilder<'static>,
+	lexemes: &'i [Lexeme<'i>],
+	offset: usize,
+	events: Vec<Event>,
+	pub errors: Vec<LocatedSyntaxError>,
+}
+
+impl<'i> Sink<'i> {
+	pub(super) fn new(events: Vec<Event>, lexemes: &'i [Lexeme<'i>]) -> Self {
+		Self {
+			builder: GreenNodeBuilder::new(),
+			lexemes,
+			offset: 0,
+			events,
+			errors: vec![],
+		}
+	}
+
+	fn text_offset(&self) -> TextSize {
+		if self.offset == 0 {
+			return 0.into();
+		};
+		if let Some(lex) = self.lexemes.get(self.offset) {
+			lex.range.start()
+		} else if let Some(lex) = self.lexemes.get(self.offset - 1) {
+			lex.range.end()
+		} else {
+			panic!("hard oob")
+		}
+	}
+
+	pub(super) fn finish(mut self) -> Parse {
+		let mut eat_start_whitespace = false;
+		let mut depth = 0;
+		let mut error_starts_at = Vec::new();
+		for idx in 0..self.events.len() {
+			match mem::replace(&mut self.events[idx], Event::Noop) {
+				Event::Start {
+					kind,
+					forward_parent,
+				} => {
+					if depth != 0 {
+						self.skip_whitespace();
+					}
+					let mut kinds = vec![kind];
+
+					let mut idx = idx;
+					let mut forward_parent = forward_parent;
+
+					// Walk through the forward parent of the forward parent, and the forward parent
+					// of that, and of that, etc. until we reach a StartNode event without a forward
+					// parent.
+					while let Some(fp) = forward_parent {
+						idx += fp.get();
+
+						forward_parent = if let Event::Start {
+							kind,
+							forward_parent,
+						} = mem::replace(&mut self.events[idx], Event::Noop)
+						{
+							kinds.push(kind);
+							forward_parent
+						} else {
+							unreachable!()
+						};
+					}
+
+					for kind in kinds.into_iter().rev() {
+						self.builder.start_node(JsonnetLanguage::kind_to_raw(kind));
+						depth += 1;
+						if depth == 1 {
+							self.skip_whitespace();
+						}
+						error_starts_at.push(self.text_offset());
+					}
+
+					eat_start_whitespace = false;
+				}
+				Event::Token { kind } => {
+					if eat_start_whitespace {
+						self.skip_whitespace();
+					}
+					self.token(kind);
+					eat_start_whitespace = true;
+				}
+				Event::VirtualToken { kind } => {
+					if eat_start_whitespace {
+						self.skip_whitespace();
+					}
+					self.virtual_token(kind);
+					eat_start_whitespace = false;
+				}
+				Event::Finish { wrapper, error } => {
+					if depth == 1 {
+						self.skip_whitespace();
+					}
+					let range = (
+						error_starts_at.pop().expect("starts == finishes"),
+						self.text_offset(),
+					);
+					if let Some(error) = error {
+						self.errors.push(LocatedSyntaxError {
+							error: *error,
+							range: TextRange::new(range.0, range.1),
+						})
+					}
+					self.builder.finish_node();
+					depth -= 1;
+					let mut idx = idx;
+					let mut wrapper = wrapper;
+					while let Some(w) = wrapper {
+						idx += w.get();
+						wrapper = if let Event::Finish { wrapper, error } =
+							mem::replace(&mut self.events[idx], Event::Noop)
+						{
+							let range = (
+								error_starts_at.pop().expect("starts == finishes"),
+								self.text_offset(),
+							);
+							if let Some(error) = error {
+								self.errors.push(LocatedSyntaxError {
+									error: *error,
+									range: TextRange::new(range.0, range.1),
+								})
+							}
+
+							if depth == 1 {
+								self.skip_whitespace();
+							}
+							self.builder.finish_node();
+							depth -= 1;
+							wrapper
+						} else {
+							unreachable!()
+						}
+					}
+					eat_start_whitespace = true;
+				}
+				Event::Pending => panic!("pending event should not appear in finished events"),
+				Event::Noop => {}
+			}
+		}
+
+		Parse {
+			green_node: self.builder.finish(),
+			errors: self.errors,
+		}
+	}
+	fn virtual_token(&mut self, kind: SyntaxKind) {
+		self.builder.token(JsonnetLanguage::kind_to_raw(kind), "")
+	}
+	fn token(&mut self, kind: SyntaxKind) {
+		let lexeme = self.lexemes[self.offset];
+		self.builder
+			.token(JsonnetLanguage::kind_to_raw(kind), lexeme.text);
+		self.offset += 1;
+	}
+	fn skip_whitespace(&mut self) {
+		while let Some(lexeme) = self.lexemes.get(self.offset) {
+			if !Trivia::can_cast(lexeme.kind) {
+				break;
+			}
+
+			self.token(lexeme.kind);
+		}
+	}
+}
addedcrates/jrsonnet-rowan-parser/src/generated/mod.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/generated/mod.rs
@@ -0,0 +1,2 @@
+pub 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,3092 @@
+//! 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, clippy::match_like_matches_macro)]
+use crate::{
+	ast::{support, AstChildren, AstNode, AstToken},
+	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 Expr {
+	pub(crate) syntax: SyntaxNode,
+}
+impl Expr {
+	pub fn stmts(&self) -> AstChildren<Stmt> {
+		support::children(&self.syntax)
+	}
+	pub fn expr_base(&self) -> Option<ExprBase> {
+		support::child(&self.syntax)
+	}
+	pub fn suffixs(&self) -> AstChildren<Suffix> {
+		support::children(&self.syntax)
+	}
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct SuffixIndex {
+	pub(crate) syntax: SyntaxNode,
+}
+impl SuffixIndex {
+	pub fn question_mark_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T![?])
+	}
+	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_lit(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, IDENT)
+	}
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct SuffixIndexExpr {
+	pub(crate) syntax: SyntaxNode,
+}
+impl SuffixIndexExpr {
+	pub fn question_mark_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T![?])
+	}
+	pub fn dot_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T![.])
+	}
+	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 SuffixSlice {
+	pub(crate) syntax: SyntaxNode,
+}
+impl SuffixSlice {
+	pub fn slice_desc(&self) -> Option<SliceDesc> {
+		support::child(&self.syntax)
+	}
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct SliceDesc {
+	pub(crate) syntax: SyntaxNode,
+}
+impl SliceDesc {
+	pub fn l_brack_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T!['['])
+	}
+	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<SliceDescEnd> {
+		support::child(&self.syntax)
+	}
+	pub fn step(&self) -> Option<SliceDescStep> {
+		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 SuffixApply {
+	pub(crate) syntax: SyntaxNode,
+}
+impl SuffixApply {
+	pub fn args_desc(&self) -> Option<ArgsDesc> {
+		support::child(&self.syntax)
+	}
+	pub fn tailstrict_kw_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 l_paren_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T!['('])
+	}
+	pub fn args(&self) -> AstChildren<Arg> {
+		support::children(&self.syntax)
+	}
+	pub fn r_paren_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T![')'])
+	}
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct StmtLocal {
+	pub(crate) syntax: SyntaxNode,
+}
+impl StmtLocal {
+	pub fn local_kw_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 StmtAssert {
+	pub(crate) syntax: SyntaxNode,
+}
+impl StmtAssert {
+	pub fn assertion(&self) -> Option<Assertion> {
+		support::child(&self.syntax)
+	}
+	pub fn semi_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T![;])
+	}
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Assertion {
+	pub(crate) syntax: SyntaxNode,
+}
+impl Assertion {
+	pub fn assert_kw_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 message(&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::token_child(&self.syntax)
+	}
+	pub fn rhs(&self) -> Option<Expr> {
+		support::child(&self.syntax)
+	}
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprUnary {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprUnary {
+	pub fn unary_operator(&self) -> Option<UnaryOperator> {
+		support::token_child(&self.syntax)
+	}
+	pub fn rhs(&self) -> Option<Expr> {
+		support::child(&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)
+	}
+}
+
+#[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::token_child(&self.syntax)
+	}
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprString {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprString {
+	pub fn text(&self) -> Option<Text> {
+		support::token_child(&self.syntax)
+	}
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprNumber {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprNumber {
+	pub fn number(&self) -> Option<Number> {
+		support::token_child(&self.syntax)
+	}
+}
+
+#[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 obj_body(&self) -> Option<ObjBody> {
+		support::child(&self.syntax)
+	}
+}
+
+#[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 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 ExprImport {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprImport {
+	pub fn import_kind(&self) -> Option<ImportKind> {
+		support::token_child(&self.syntax)
+	}
+	pub fn text(&self) -> Option<Text> {
+		support::token_child(&self.syntax)
+	}
+}
+
+#[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 ExprIfThenElse {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprIfThenElse {
+	pub fn if_kw_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T![if])
+	}
+	pub fn cond(&self) -> Option<Expr> {
+		support::child(&self.syntax)
+	}
+	pub fn then_kw_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T![then])
+	}
+	pub fn then(&self) -> Option<TrueExpr> {
+		support::child(&self.syntax)
+	}
+	pub fn else_kw_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T![else])
+	}
+	pub fn else_(&self) -> Option<FalseExpr> {
+		support::child(&self.syntax)
+	}
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct TrueExpr {
+	pub(crate) syntax: SyntaxNode,
+}
+impl TrueExpr {
+	pub fn expr(&self) -> Option<Expr> {
+		support::child(&self.syntax)
+	}
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct FalseExpr {
+	pub(crate) syntax: SyntaxNode,
+}
+impl FalseExpr {
+	pub fn expr(&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_kw_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 l_paren_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T!['('])
+	}
+	pub fn params(&self) -> AstChildren<Param> {
+		support::children(&self.syntax)
+	}
+	pub fn r_paren_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T![')'])
+	}
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ExprError {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ExprError {
+	pub fn error_kw_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 SliceDescEnd {
+	pub(crate) syntax: SyntaxNode,
+}
+impl SliceDescEnd {
+	pub fn expr(&self) -> Option<Expr> {
+		support::child(&self.syntax)
+	}
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct SliceDescStep {
+	pub(crate) syntax: SyntaxNode,
+}
+impl SliceDescStep {
+	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 l_brace_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T!['{'])
+	}
+	pub fn member_comps(&self) -> AstChildren<MemberComp> {
+		support::children(&self.syntax)
+	}
+	pub fn comp_specs(&self) -> AstChildren<CompSpec> {
+		support::children(&self.syntax)
+	}
+	pub fn r_brace_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T!['}'])
+	}
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ObjBodyMemberList {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ObjBodyMemberList {
+	pub fn l_brace_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T!['{'])
+	}
+	pub fn members(&self) -> AstChildren<Member> {
+		support::children(&self.syntax)
+	}
+	pub fn r_brace_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T!['}'])
+	}
+}
+
+#[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 ObjLocal {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ObjLocal {
+	pub fn local_kw_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 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 MemberFieldNormal {
+	pub(crate) syntax: SyntaxNode,
+}
+impl MemberFieldNormal {
+	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::token_child(&self.syntax)
+	}
+	pub fn expr(&self) -> Option<Expr> {
+		support::child(&self.syntax)
+	}
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct MemberFieldMethod {
+	pub(crate) syntax: SyntaxNode,
+}
+impl MemberFieldMethod {
+	pub fn field_name(&self) -> Option<FieldName> {
+		support::child(&self.syntax)
+	}
+	pub fn params_desc(&self) -> Option<ParamsDesc> {
+		support::child(&self.syntax)
+	}
+	pub fn visibility(&self) -> Option<Visibility> {
+		support::token_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 text(&self) -> Option<Text> {
+		support::token_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 ForSpec {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ForSpec {
+	pub fn for_kw_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T![for])
+	}
+	pub fn bind(&self) -> Option<Destruct> {
+		support::child(&self.syntax)
+	}
+	pub fn in_kw_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 IfSpec {
+	pub(crate) syntax: SyntaxNode,
+}
+impl IfSpec {
+	pub fn if_kw_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 BindFunction {
+	pub(crate) syntax: SyntaxNode,
+}
+impl BindFunction {
+	pub fn name(&self) -> Option<Name> {
+		support::child(&self.syntax)
+	}
+	pub fn params(&self) -> Option<ParamsDesc> {
+		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 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 name(&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 destruct_array_parts(&self) -> AstChildren<DestructArrayPart> {
+		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 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 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 DestructArrayElement {
+	pub(crate) syntax: SyntaxNode,
+}
+impl DestructArrayElement {
+	pub fn destruct(&self) -> Option<Destruct> {
+		support::child(&self.syntax)
+	}
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum Suffix {
+	SuffixIndex(SuffixIndex),
+	SuffixIndexExpr(SuffixIndexExpr),
+	SuffixSlice(SuffixSlice),
+	SuffixApply(SuffixApply),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum Bind {
+	BindDestruct(BindDestruct),
+	BindFunction(BindFunction),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum Stmt {
+	StmtLocal(StmtLocal),
+	StmtAssert(StmtAssert),
+}
+
+#[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 ExprBase {
+	ExprBinary(ExprBinary),
+	ExprUnary(ExprUnary),
+	ExprObjExtend(ExprObjExtend),
+	ExprParened(ExprParened),
+	ExprString(ExprString),
+	ExprNumber(ExprNumber),
+	ExprLiteral(ExprLiteral),
+	ExprArray(ExprArray),
+	ExprObject(ExprObject),
+	ExprArrayComp(ExprArrayComp),
+	ExprImport(ExprImport),
+	ExprVar(ExprVar),
+	ExprIfThenElse(ExprIfThenElse),
+	ExprFunction(ExprFunction),
+	ExprError(ExprError),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum MemberComp {
+	MemberBindStmt(MemberBindStmt),
+	MemberFieldNormal(MemberFieldNormal),
+	MemberFieldMethod(MemberFieldMethod),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum Member {
+	MemberBindStmt(MemberBindStmt),
+	MemberAssertStmt(MemberAssertStmt),
+	MemberFieldNormal(MemberFieldNormal),
+	MemberFieldMethod(MemberFieldMethod),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum FieldName {
+	FieldNameFixed(FieldNameFixed),
+	FieldNameDynamic(FieldNameDynamic),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum Destruct {
+	DestructFull(DestructFull),
+	DestructSkip(DestructSkip),
+	DestructArray(DestructArray),
+	DestructObject(DestructObject),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum DestructArrayPart {
+	DestructArrayElement(DestructArrayElement),
+	DestructRest(DestructRest),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct BinaryOperator {
+	syntax: SyntaxToken,
+	kind: BinaryOperatorKind,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum BinaryOperatorKind {
+	Or,
+	NullCoaelse,
+	And,
+	BitOr,
+	BitXor,
+	BitAnd,
+	Eq,
+	Ne,
+	Lt,
+	Gt,
+	Le,
+	Ge,
+	InKw,
+	Lhs,
+	Rhs,
+	Plus,
+	Minus,
+	Mul,
+	Div,
+	Modulo,
+	MetaObjectApply,
+	ErrorNoOperator,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct UnaryOperator {
+	syntax: SyntaxToken,
+	kind: UnaryOperatorKind,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum UnaryOperatorKind {
+	Minus,
+	Not,
+	BitNot,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Literal {
+	syntax: SyntaxToken,
+	kind: LiteralKind,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum LiteralKind {
+	NullKw,
+	TrueKw,
+	FalseKw,
+	SelfKw,
+	Dollar,
+	SuperKw,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Text {
+	syntax: SyntaxToken,
+	kind: TextKind,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum TextKind {
+	StringDouble,
+	ErrorStringDoubleUnterminated,
+	StringSingle,
+	ErrorStringSingleUnterminated,
+	StringDoubleVerbatim,
+	ErrorStringDoubleVerbatimUnterminated,
+	StringSingleVerbatim,
+	ErrorStringSingleVerbatimUnterminated,
+	ErrorStringVerbatimMissingQuotes,
+	StringBlock,
+	ErrorStringBlockUnexpectedEnd,
+	ErrorStringBlockMissingNewLine,
+	ErrorStringBlockMissingTermination,
+	ErrorStringBlockMissingIndent,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Number {
+	syntax: SyntaxToken,
+	kind: NumberKind,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum NumberKind {
+	Float,
+	ErrorFloatJunkAfterPoint,
+	ErrorFloatJunkAfterExponent,
+	ErrorFloatJunkAfterExponentSign,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ImportKind {
+	syntax: SyntaxToken,
+	kind: ImportKindKind,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum ImportKindKind {
+	ImportstrKw,
+	ImportbinKw,
+	ImportKw,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Visibility {
+	syntax: SyntaxToken,
+	kind: VisibilityKind,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum VisibilityKind {
+	Coloncoloncolon,
+	Coloncolon,
+	Colon,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Trivia {
+	syntax: SyntaxToken,
+	kind: TriviaKind,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum TriviaKind {
+	Whitespace,
+	MultiLineComment,
+	ErrorCommentTooShort,
+	ErrorCommentUnterminated,
+	SingleLineHashComment,
+	SingleLineSlashComment,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct CustomError {
+	syntax: SyntaxToken,
+	kind: CustomErrorKind,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum CustomErrorKind {
+	ErrorMissingToken,
+	ErrorUnexpectedToken,
+	ErrorCustom,
+}
+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 Expr {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		kind == 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 SuffixIndex {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		kind == SUFFIX_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 SuffixIndexExpr {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		kind == SUFFIX_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 SuffixSlice {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		kind == SUFFIX_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 SuffixApply {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		kind == SUFFIX_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 StmtLocal {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		kind == STMT_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 StmtAssert {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		kind == STMT_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 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 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 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 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 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 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 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 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 TrueExpr {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		kind == TRUE_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 FalseExpr {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		kind == FALSE_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 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 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 SliceDescEnd {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		kind == SLICE_DESC_END
+	}
+	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 SliceDescStep {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		kind == SLICE_DESC_STEP
+	}
+	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 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 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 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 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 MemberFieldNormal {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		kind == MEMBER_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 MemberFieldMethod {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		kind == MEMBER_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 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 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 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 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 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 DestructArrayElement {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		kind == DESTRUCT_ARRAY_ELEMENT
+	}
+	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<SuffixIndex> for Suffix {
+	fn from(node: SuffixIndex) -> Suffix {
+		Suffix::SuffixIndex(node)
+	}
+}
+impl From<SuffixIndexExpr> for Suffix {
+	fn from(node: SuffixIndexExpr) -> Suffix {
+		Suffix::SuffixIndexExpr(node)
+	}
+}
+impl From<SuffixSlice> for Suffix {
+	fn from(node: SuffixSlice) -> Suffix {
+		Suffix::SuffixSlice(node)
+	}
+}
+impl From<SuffixApply> for Suffix {
+	fn from(node: SuffixApply) -> Suffix {
+		Suffix::SuffixApply(node)
+	}
+}
+impl AstNode for Suffix {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		match kind {
+			SUFFIX_INDEX | SUFFIX_INDEX_EXPR | SUFFIX_SLICE | SUFFIX_APPLY => true,
+			_ => false,
+		}
+	}
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		let res = match syntax.kind() {
+			SUFFIX_INDEX => Suffix::SuffixIndex(SuffixIndex { syntax }),
+			SUFFIX_INDEX_EXPR => Suffix::SuffixIndexExpr(SuffixIndexExpr { syntax }),
+			SUFFIX_SLICE => Suffix::SuffixSlice(SuffixSlice { syntax }),
+			SUFFIX_APPLY => Suffix::SuffixApply(SuffixApply { syntax }),
+			_ => return None,
+		};
+		Some(res)
+	}
+	fn syntax(&self) -> &SyntaxNode {
+		match self {
+			Suffix::SuffixIndex(it) => &it.syntax,
+			Suffix::SuffixIndexExpr(it) => &it.syntax,
+			Suffix::SuffixSlice(it) => &it.syntax,
+			Suffix::SuffixApply(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<StmtLocal> for Stmt {
+	fn from(node: StmtLocal) -> Stmt {
+		Stmt::StmtLocal(node)
+	}
+}
+impl From<StmtAssert> for Stmt {
+	fn from(node: StmtAssert) -> Stmt {
+		Stmt::StmtAssert(node)
+	}
+}
+impl AstNode for Stmt {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		match kind {
+			STMT_LOCAL | STMT_ASSERT => true,
+			_ => false,
+		}
+	}
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		let res = match syntax.kind() {
+			STMT_LOCAL => Stmt::StmtLocal(StmtLocal { syntax }),
+			STMT_ASSERT => Stmt::StmtAssert(StmtAssert { syntax }),
+			_ => return None,
+		};
+		Some(res)
+	}
+	fn syntax(&self) -> &SyntaxNode {
+		match self {
+			Stmt::StmtLocal(it) => &it.syntax,
+			Stmt::StmtAssert(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<ExprBinary> for ExprBase {
+	fn from(node: ExprBinary) -> ExprBase {
+		ExprBase::ExprBinary(node)
+	}
+}
+impl From<ExprUnary> for ExprBase {
+	fn from(node: ExprUnary) -> ExprBase {
+		ExprBase::ExprUnary(node)
+	}
+}
+impl From<ExprObjExtend> for ExprBase {
+	fn from(node: ExprObjExtend) -> ExprBase {
+		ExprBase::ExprObjExtend(node)
+	}
+}
+impl From<ExprParened> for ExprBase {
+	fn from(node: ExprParened) -> ExprBase {
+		ExprBase::ExprParened(node)
+	}
+}
+impl From<ExprString> for ExprBase {
+	fn from(node: ExprString) -> ExprBase {
+		ExprBase::ExprString(node)
+	}
+}
+impl From<ExprNumber> for ExprBase {
+	fn from(node: ExprNumber) -> ExprBase {
+		ExprBase::ExprNumber(node)
+	}
+}
+impl From<ExprLiteral> for ExprBase {
+	fn from(node: ExprLiteral) -> ExprBase {
+		ExprBase::ExprLiteral(node)
+	}
+}
+impl From<ExprArray> for ExprBase {
+	fn from(node: ExprArray) -> ExprBase {
+		ExprBase::ExprArray(node)
+	}
+}
+impl From<ExprObject> for ExprBase {
+	fn from(node: ExprObject) -> ExprBase {
+		ExprBase::ExprObject(node)
+	}
+}
+impl From<ExprArrayComp> for ExprBase {
+	fn from(node: ExprArrayComp) -> ExprBase {
+		ExprBase::ExprArrayComp(node)
+	}
+}
+impl From<ExprImport> for ExprBase {
+	fn from(node: ExprImport) -> ExprBase {
+		ExprBase::ExprImport(node)
+	}
+}
+impl From<ExprVar> for ExprBase {
+	fn from(node: ExprVar) -> ExprBase {
+		ExprBase::ExprVar(node)
+	}
+}
+impl From<ExprIfThenElse> for ExprBase {
+	fn from(node: ExprIfThenElse) -> ExprBase {
+		ExprBase::ExprIfThenElse(node)
+	}
+}
+impl From<ExprFunction> for ExprBase {
+	fn from(node: ExprFunction) -> ExprBase {
+		ExprBase::ExprFunction(node)
+	}
+}
+impl From<ExprError> for ExprBase {
+	fn from(node: ExprError) -> ExprBase {
+		ExprBase::ExprError(node)
+	}
+}
+impl AstNode for ExprBase {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		match kind {
+			EXPR_BINARY | EXPR_UNARY | EXPR_OBJ_EXTEND | EXPR_PARENED | EXPR_STRING
+			| EXPR_NUMBER | EXPR_LITERAL | EXPR_ARRAY | EXPR_OBJECT | EXPR_ARRAY_COMP
+			| EXPR_IMPORT | EXPR_VAR | EXPR_IF_THEN_ELSE | EXPR_FUNCTION | EXPR_ERROR => true,
+			_ => false,
+		}
+	}
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		let res = match syntax.kind() {
+			EXPR_BINARY => ExprBase::ExprBinary(ExprBinary { syntax }),
+			EXPR_UNARY => ExprBase::ExprUnary(ExprUnary { syntax }),
+			EXPR_OBJ_EXTEND => ExprBase::ExprObjExtend(ExprObjExtend { syntax }),
+			EXPR_PARENED => ExprBase::ExprParened(ExprParened { syntax }),
+			EXPR_STRING => ExprBase::ExprString(ExprString { syntax }),
+			EXPR_NUMBER => ExprBase::ExprNumber(ExprNumber { syntax }),
+			EXPR_LITERAL => ExprBase::ExprLiteral(ExprLiteral { syntax }),
+			EXPR_ARRAY => ExprBase::ExprArray(ExprArray { syntax }),
+			EXPR_OBJECT => ExprBase::ExprObject(ExprObject { syntax }),
+			EXPR_ARRAY_COMP => ExprBase::ExprArrayComp(ExprArrayComp { syntax }),
+			EXPR_IMPORT => ExprBase::ExprImport(ExprImport { syntax }),
+			EXPR_VAR => ExprBase::ExprVar(ExprVar { syntax }),
+			EXPR_IF_THEN_ELSE => ExprBase::ExprIfThenElse(ExprIfThenElse { syntax }),
+			EXPR_FUNCTION => ExprBase::ExprFunction(ExprFunction { syntax }),
+			EXPR_ERROR => ExprBase::ExprError(ExprError { syntax }),
+			_ => return None,
+		};
+		Some(res)
+	}
+	fn syntax(&self) -> &SyntaxNode {
+		match self {
+			ExprBase::ExprBinary(it) => &it.syntax,
+			ExprBase::ExprUnary(it) => &it.syntax,
+			ExprBase::ExprObjExtend(it) => &it.syntax,
+			ExprBase::ExprParened(it) => &it.syntax,
+			ExprBase::ExprString(it) => &it.syntax,
+			ExprBase::ExprNumber(it) => &it.syntax,
+			ExprBase::ExprLiteral(it) => &it.syntax,
+			ExprBase::ExprArray(it) => &it.syntax,
+			ExprBase::ExprObject(it) => &it.syntax,
+			ExprBase::ExprArrayComp(it) => &it.syntax,
+			ExprBase::ExprImport(it) => &it.syntax,
+			ExprBase::ExprVar(it) => &it.syntax,
+			ExprBase::ExprIfThenElse(it) => &it.syntax,
+			ExprBase::ExprFunction(it) => &it.syntax,
+			ExprBase::ExprError(it) => &it.syntax,
+		}
+	}
+}
+impl From<MemberBindStmt> for MemberComp {
+	fn from(node: MemberBindStmt) -> MemberComp {
+		MemberComp::MemberBindStmt(node)
+	}
+}
+impl From<MemberFieldNormal> for MemberComp {
+	fn from(node: MemberFieldNormal) -> MemberComp {
+		MemberComp::MemberFieldNormal(node)
+	}
+}
+impl From<MemberFieldMethod> for MemberComp {
+	fn from(node: MemberFieldMethod) -> MemberComp {
+		MemberComp::MemberFieldMethod(node)
+	}
+}
+impl AstNode for MemberComp {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		match kind {
+			MEMBER_BIND_STMT | MEMBER_FIELD_NORMAL | MEMBER_FIELD_METHOD => true,
+			_ => false,
+		}
+	}
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		let res = match syntax.kind() {
+			MEMBER_BIND_STMT => MemberComp::MemberBindStmt(MemberBindStmt { syntax }),
+			MEMBER_FIELD_NORMAL => MemberComp::MemberFieldNormal(MemberFieldNormal { syntax }),
+			MEMBER_FIELD_METHOD => MemberComp::MemberFieldMethod(MemberFieldMethod { syntax }),
+			_ => return None,
+		};
+		Some(res)
+	}
+	fn syntax(&self) -> &SyntaxNode {
+		match self {
+			MemberComp::MemberBindStmt(it) => &it.syntax,
+			MemberComp::MemberFieldNormal(it) => &it.syntax,
+			MemberComp::MemberFieldMethod(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<MemberFieldNormal> for Member {
+	fn from(node: MemberFieldNormal) -> Member {
+		Member::MemberFieldNormal(node)
+	}
+}
+impl From<MemberFieldMethod> for Member {
+	fn from(node: MemberFieldMethod) -> Member {
+		Member::MemberFieldMethod(node)
+	}
+}
+impl AstNode for Member {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		match kind {
+			MEMBER_BIND_STMT | MEMBER_ASSERT_STMT | MEMBER_FIELD_NORMAL | MEMBER_FIELD_METHOD => {
+				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_NORMAL => Member::MemberFieldNormal(MemberFieldNormal { syntax }),
+			MEMBER_FIELD_METHOD => Member::MemberFieldMethod(MemberFieldMethod { syntax }),
+			_ => return None,
+		};
+		Some(res)
+	}
+	fn syntax(&self) -> &SyntaxNode {
+		match self {
+			Member::MemberBindStmt(it) => &it.syntax,
+			Member::MemberAssertStmt(it) => &it.syntax,
+			Member::MemberFieldNormal(it) => &it.syntax,
+			Member::MemberFieldMethod(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 From<DestructFull> for Destruct {
+	fn from(node: DestructFull) -> Destruct {
+		Destruct::DestructFull(node)
+	}
+}
+impl From<DestructSkip> for Destruct {
+	fn from(node: DestructSkip) -> Destruct {
+		Destruct::DestructSkip(node)
+	}
+}
+impl From<DestructArray> for Destruct {
+	fn from(node: DestructArray) -> Destruct {
+		Destruct::DestructArray(node)
+	}
+}
+impl From<DestructObject> for Destruct {
+	fn from(node: DestructObject) -> Destruct {
+		Destruct::DestructObject(node)
+	}
+}
+impl AstNode for Destruct {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		match kind {
+			DESTRUCT_FULL | DESTRUCT_SKIP | DESTRUCT_ARRAY | DESTRUCT_OBJECT => true,
+			_ => false,
+		}
+	}
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		let res = match syntax.kind() {
+			DESTRUCT_FULL => Destruct::DestructFull(DestructFull { syntax }),
+			DESTRUCT_SKIP => Destruct::DestructSkip(DestructSkip { syntax }),
+			DESTRUCT_ARRAY => Destruct::DestructArray(DestructArray { syntax }),
+			DESTRUCT_OBJECT => Destruct::DestructObject(DestructObject { syntax }),
+			_ => return None,
+		};
+		Some(res)
+	}
+	fn syntax(&self) -> &SyntaxNode {
+		match self {
+			Destruct::DestructFull(it) => &it.syntax,
+			Destruct::DestructSkip(it) => &it.syntax,
+			Destruct::DestructArray(it) => &it.syntax,
+			Destruct::DestructObject(it) => &it.syntax,
+		}
+	}
+}
+impl From<DestructArrayElement> for DestructArrayPart {
+	fn from(node: DestructArrayElement) -> DestructArrayPart {
+		DestructArrayPart::DestructArrayElement(node)
+	}
+}
+impl From<DestructRest> for DestructArrayPart {
+	fn from(node: DestructRest) -> DestructArrayPart {
+		DestructArrayPart::DestructRest(node)
+	}
+}
+impl AstNode for DestructArrayPart {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		match kind {
+			DESTRUCT_ARRAY_ELEMENT | DESTRUCT_REST => true,
+			_ => false,
+		}
+	}
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		let res = match syntax.kind() {
+			DESTRUCT_ARRAY_ELEMENT => {
+				DestructArrayPart::DestructArrayElement(DestructArrayElement { syntax })
+			}
+			DESTRUCT_REST => DestructArrayPart::DestructRest(DestructRest { syntax }),
+			_ => return None,
+		};
+		Some(res)
+	}
+	fn syntax(&self) -> &SyntaxNode {
+		match self {
+			DestructArrayPart::DestructArrayElement(it) => &it.syntax,
+			DestructArrayPart::DestructRest(it) => &it.syntax,
+		}
+	}
+}
+impl AstToken for BinaryOperator {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		BinaryOperatorKind::can_cast(kind)
+	}
+	fn cast(syntax: SyntaxToken) -> Option<Self> {
+		let kind = BinaryOperatorKind::cast(syntax.kind())?;
+		Some(BinaryOperator { syntax, kind })
+	}
+	fn syntax(&self) -> &SyntaxToken {
+		&self.syntax
+	}
+}
+impl BinaryOperatorKind {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		match kind {
+			OR | NULL_COAELSE | AND | BIT_OR | BIT_XOR | BIT_AND | EQ | NE | LT | GT | LE | GE
+			| IN_KW | LHS | RHS | PLUS | MINUS | MUL | DIV | MODULO | META_OBJECT_APPLY
+			| ERROR_NO_OPERATOR => true,
+			_ => false,
+		}
+	}
+	pub fn cast(kind: SyntaxKind) -> Option<Self> {
+		let res = match kind {
+			OR => Self::Or,
+			NULL_COAELSE => Self::NullCoaelse,
+			AND => Self::And,
+			BIT_OR => Self::BitOr,
+			BIT_XOR => Self::BitXor,
+			BIT_AND => Self::BitAnd,
+			EQ => Self::Eq,
+			NE => Self::Ne,
+			LT => Self::Lt,
+			GT => Self::Gt,
+			LE => Self::Le,
+			GE => Self::Ge,
+			IN_KW => Self::InKw,
+			LHS => Self::Lhs,
+			RHS => Self::Rhs,
+			PLUS => Self::Plus,
+			MINUS => Self::Minus,
+			MUL => Self::Mul,
+			DIV => Self::Div,
+			MODULO => Self::Modulo,
+			META_OBJECT_APPLY => Self::MetaObjectApply,
+			ERROR_NO_OPERATOR => Self::ErrorNoOperator,
+			_ => return None,
+		};
+		Some(res)
+	}
+}
+impl BinaryOperator {
+	pub fn kind(&self) -> BinaryOperatorKind {
+		self.kind
+	}
+}
+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 AstToken for UnaryOperator {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		UnaryOperatorKind::can_cast(kind)
+	}
+	fn cast(syntax: SyntaxToken) -> Option<Self> {
+		let kind = UnaryOperatorKind::cast(syntax.kind())?;
+		Some(UnaryOperator { syntax, kind })
+	}
+	fn syntax(&self) -> &SyntaxToken {
+		&self.syntax
+	}
+}
+impl UnaryOperatorKind {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		match kind {
+			MINUS | NOT | BIT_NOT => true,
+			_ => false,
+		}
+	}
+	pub fn cast(kind: SyntaxKind) -> Option<Self> {
+		let res = match kind {
+			MINUS => Self::Minus,
+			NOT => Self::Not,
+			BIT_NOT => Self::BitNot,
+			_ => return None,
+		};
+		Some(res)
+	}
+}
+impl UnaryOperator {
+	pub fn kind(&self) -> UnaryOperatorKind {
+		self.kind
+	}
+}
+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 AstToken for Literal {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		LiteralKind::can_cast(kind)
+	}
+	fn cast(syntax: SyntaxToken) -> Option<Self> {
+		let kind = LiteralKind::cast(syntax.kind())?;
+		Some(Literal { syntax, kind })
+	}
+	fn syntax(&self) -> &SyntaxToken {
+		&self.syntax
+	}
+}
+impl LiteralKind {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		match kind {
+			NULL_KW | TRUE_KW | FALSE_KW | SELF_KW | DOLLAR | SUPER_KW => true,
+			_ => false,
+		}
+	}
+	pub fn cast(kind: SyntaxKind) -> Option<Self> {
+		let res = match kind {
+			NULL_KW => Self::NullKw,
+			TRUE_KW => Self::TrueKw,
+			FALSE_KW => Self::FalseKw,
+			SELF_KW => Self::SelfKw,
+			DOLLAR => Self::Dollar,
+			SUPER_KW => Self::SuperKw,
+			_ => return None,
+		};
+		Some(res)
+	}
+}
+impl Literal {
+	pub fn kind(&self) -> LiteralKind {
+		self.kind
+	}
+}
+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 AstToken for Text {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		TextKind::can_cast(kind)
+	}
+	fn cast(syntax: SyntaxToken) -> Option<Self> {
+		let kind = TextKind::cast(syntax.kind())?;
+		Some(Text { syntax, kind })
+	}
+	fn syntax(&self) -> &SyntaxToken {
+		&self.syntax
+	}
+}
+impl TextKind {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		match kind {
+			STRING_DOUBLE
+			| ERROR_STRING_DOUBLE_UNTERMINATED
+			| STRING_SINGLE
+			| ERROR_STRING_SINGLE_UNTERMINATED
+			| STRING_DOUBLE_VERBATIM
+			| ERROR_STRING_DOUBLE_VERBATIM_UNTERMINATED
+			| STRING_SINGLE_VERBATIM
+			| ERROR_STRING_SINGLE_VERBATIM_UNTERMINATED
+			| ERROR_STRING_VERBATIM_MISSING_QUOTES
+			| STRING_BLOCK
+			| ERROR_STRING_BLOCK_UNEXPECTED_END
+			| ERROR_STRING_BLOCK_MISSING_NEW_LINE
+			| ERROR_STRING_BLOCK_MISSING_TERMINATION
+			| ERROR_STRING_BLOCK_MISSING_INDENT => true,
+			_ => false,
+		}
+	}
+	pub fn cast(kind: SyntaxKind) -> Option<Self> {
+		let res = match kind {
+			STRING_DOUBLE => Self::StringDouble,
+			ERROR_STRING_DOUBLE_UNTERMINATED => Self::ErrorStringDoubleUnterminated,
+			STRING_SINGLE => Self::StringSingle,
+			ERROR_STRING_SINGLE_UNTERMINATED => Self::ErrorStringSingleUnterminated,
+			STRING_DOUBLE_VERBATIM => Self::StringDoubleVerbatim,
+			ERROR_STRING_DOUBLE_VERBATIM_UNTERMINATED => {
+				Self::ErrorStringDoubleVerbatimUnterminated
+			}
+			STRING_SINGLE_VERBATIM => Self::StringSingleVerbatim,
+			ERROR_STRING_SINGLE_VERBATIM_UNTERMINATED => {
+				Self::ErrorStringSingleVerbatimUnterminated
+			}
+			ERROR_STRING_VERBATIM_MISSING_QUOTES => Self::ErrorStringVerbatimMissingQuotes,
+			STRING_BLOCK => Self::StringBlock,
+			ERROR_STRING_BLOCK_UNEXPECTED_END => Self::ErrorStringBlockUnexpectedEnd,
+			ERROR_STRING_BLOCK_MISSING_NEW_LINE => Self::ErrorStringBlockMissingNewLine,
+			ERROR_STRING_BLOCK_MISSING_TERMINATION => Self::ErrorStringBlockMissingTermination,
+			ERROR_STRING_BLOCK_MISSING_INDENT => Self::ErrorStringBlockMissingIndent,
+			_ => return None,
+		};
+		Some(res)
+	}
+}
+impl Text {
+	pub fn kind(&self) -> TextKind {
+		self.kind
+	}
+}
+impl std::fmt::Display for Text {
+	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 {
+		NumberKind::can_cast(kind)
+	}
+	fn cast(syntax: SyntaxToken) -> Option<Self> {
+		let kind = NumberKind::cast(syntax.kind())?;
+		Some(Number { syntax, kind })
+	}
+	fn syntax(&self) -> &SyntaxToken {
+		&self.syntax
+	}
+}
+impl NumberKind {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		match kind {
+			FLOAT
+			| ERROR_FLOAT_JUNK_AFTER_POINT
+			| ERROR_FLOAT_JUNK_AFTER_EXPONENT
+			| ERROR_FLOAT_JUNK_AFTER_EXPONENT_SIGN => true,
+			_ => false,
+		}
+	}
+	pub fn cast(kind: SyntaxKind) -> Option<Self> {
+		let res = match kind {
+			FLOAT => Self::Float,
+			ERROR_FLOAT_JUNK_AFTER_POINT => Self::ErrorFloatJunkAfterPoint,
+			ERROR_FLOAT_JUNK_AFTER_EXPONENT => Self::ErrorFloatJunkAfterExponent,
+			ERROR_FLOAT_JUNK_AFTER_EXPONENT_SIGN => Self::ErrorFloatJunkAfterExponentSign,
+			_ => return None,
+		};
+		Some(res)
+	}
+}
+impl Number {
+	pub fn kind(&self) -> NumberKind {
+		self.kind
+	}
+}
+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 ImportKind {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		ImportKindKind::can_cast(kind)
+	}
+	fn cast(syntax: SyntaxToken) -> Option<Self> {
+		let kind = ImportKindKind::cast(syntax.kind())?;
+		Some(ImportKind { syntax, kind })
+	}
+	fn syntax(&self) -> &SyntaxToken {
+		&self.syntax
+	}
+}
+impl ImportKindKind {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		match kind {
+			IMPORTSTR_KW | IMPORTBIN_KW | IMPORT_KW => true,
+			_ => false,
+		}
+	}
+	pub fn cast(kind: SyntaxKind) -> Option<Self> {
+		let res = match kind {
+			IMPORTSTR_KW => Self::ImportstrKw,
+			IMPORTBIN_KW => Self::ImportbinKw,
+			IMPORT_KW => Self::ImportKw,
+			_ => return None,
+		};
+		Some(res)
+	}
+}
+impl ImportKind {
+	pub fn kind(&self) -> ImportKindKind {
+		self.kind
+	}
+}
+impl std::fmt::Display for ImportKind {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl AstToken for Visibility {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		VisibilityKind::can_cast(kind)
+	}
+	fn cast(syntax: SyntaxToken) -> Option<Self> {
+		let kind = VisibilityKind::cast(syntax.kind())?;
+		Some(Visibility { syntax, kind })
+	}
+	fn syntax(&self) -> &SyntaxToken {
+		&self.syntax
+	}
+}
+impl VisibilityKind {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		match kind {
+			COLONCOLONCOLON | COLONCOLON | COLON => true,
+			_ => false,
+		}
+	}
+	pub fn cast(kind: SyntaxKind) -> Option<Self> {
+		let res = match kind {
+			COLONCOLONCOLON => Self::Coloncoloncolon,
+			COLONCOLON => Self::Coloncolon,
+			COLON => Self::Colon,
+			_ => return None,
+		};
+		Some(res)
+	}
+}
+impl Visibility {
+	pub fn kind(&self) -> VisibilityKind {
+		self.kind
+	}
+}
+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 AstToken for Trivia {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		TriviaKind::can_cast(kind)
+	}
+	fn cast(syntax: SyntaxToken) -> Option<Self> {
+		let kind = TriviaKind::cast(syntax.kind())?;
+		Some(Trivia { syntax, kind })
+	}
+	fn syntax(&self) -> &SyntaxToken {
+		&self.syntax
+	}
+}
+impl TriviaKind {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		match kind {
+			WHITESPACE
+			| MULTI_LINE_COMMENT
+			| ERROR_COMMENT_TOO_SHORT
+			| ERROR_COMMENT_UNTERMINATED
+			| SINGLE_LINE_HASH_COMMENT
+			| SINGLE_LINE_SLASH_COMMENT => true,
+			_ => false,
+		}
+	}
+	pub fn cast(kind: SyntaxKind) -> Option<Self> {
+		let res = match kind {
+			WHITESPACE => Self::Whitespace,
+			MULTI_LINE_COMMENT => Self::MultiLineComment,
+			ERROR_COMMENT_TOO_SHORT => Self::ErrorCommentTooShort,
+			ERROR_COMMENT_UNTERMINATED => Self::ErrorCommentUnterminated,
+			SINGLE_LINE_HASH_COMMENT => Self::SingleLineHashComment,
+			SINGLE_LINE_SLASH_COMMENT => Self::SingleLineSlashComment,
+			_ => return None,
+		};
+		Some(res)
+	}
+}
+impl Trivia {
+	pub fn kind(&self) -> TriviaKind {
+		self.kind
+	}
+}
+impl std::fmt::Display for Trivia {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl AstToken for CustomError {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		CustomErrorKind::can_cast(kind)
+	}
+	fn cast(syntax: SyntaxToken) -> Option<Self> {
+		let kind = CustomErrorKind::cast(syntax.kind())?;
+		Some(CustomError { syntax, kind })
+	}
+	fn syntax(&self) -> &SyntaxToken {
+		&self.syntax
+	}
+}
+impl CustomErrorKind {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		match kind {
+			ERROR_MISSING_TOKEN | ERROR_UNEXPECTED_TOKEN | ERROR_CUSTOM => true,
+			_ => false,
+		}
+	}
+	pub fn cast(kind: SyntaxKind) -> Option<Self> {
+		let res = match kind {
+			ERROR_MISSING_TOKEN => Self::ErrorMissingToken,
+			ERROR_UNEXPECTED_TOKEN => Self::ErrorUnexpectedToken,
+			ERROR_CUSTOM => Self::ErrorCustom,
+			_ => return None,
+		};
+		Some(res)
+	}
+}
+impl CustomError {
+	pub fn kind(&self) -> CustomErrorKind {
+		self.kind
+	}
+}
+impl std::fmt::Display for CustomError {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for Suffix {
+	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 Stmt {
+	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 ExprBase {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for MemberComp {
+	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 FieldName {
+	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 DestructArrayPart {
+	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 Expr {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for SuffixIndex {
+	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 SuffixIndexExpr {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for SuffixSlice {
+	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 SuffixApply {
+	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 StmtLocal {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for StmtAssert {
+	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 ExprBinary {
+	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 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 ExprString {
+	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 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 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 ExprIfThenElse {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for TrueExpr {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for FalseExpr {
+	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 ExprError {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for SliceDescEnd {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for SliceDescStep {
+	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 ObjBodyMemberList {
+	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 ObjLocal {
+	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 MemberFieldNormal {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
+impl std::fmt::Display for MemberFieldMethod {
+	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 ForSpec {
+	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 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 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)
+	}
+}
+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 DestructArrayElement {
+	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,293 @@
+//! 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,
+	clippy::manual_non_exhaustive,
+	clippy::match_like_matches_macro
+)]
+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("??")]
+	NULL_COAELSE,
+	#[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,
+	#[regex("(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?")]
+	FLOAT,
+	#[regex("(?:0|[1-9][0-9]*)\\.[^0-9]")]
+	ERROR_FLOAT_JUNK_AFTER_POINT,
+	#[regex("(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?[eE][^+\\-0-9]")]
+	ERROR_FLOAT_JUNK_AFTER_EXPONENT,
+	#[regex("(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?[eE][+-][^0-9]")]
+	ERROR_FLOAT_JUNK_AFTER_EXPONENT_SIGN,
+	#[regex("\"(?s:[^\"\\\\]|\\\\.)*\"")]
+	STRING_DOUBLE,
+	#[regex("\"(?s:[^\"\\\\]|\\\\.)*")]
+	ERROR_STRING_DOUBLE_UNTERMINATED,
+	#[regex("'(?s:[^'\\\\]|\\\\.)*'")]
+	STRING_SINGLE,
+	#[regex("'(?s:[^'\\\\]|\\\\.)*")]
+	ERROR_STRING_SINGLE_UNTERMINATED,
+	#[regex("@\"(?:[^\"]|\"\")*\"")]
+	STRING_DOUBLE_VERBATIM,
+	#[regex("@\"(?:[^\"]|\"\")*")]
+	ERROR_STRING_DOUBLE_VERBATIM_UNTERMINATED,
+	#[regex("@'(?:[^']|'')*'")]
+	STRING_SINGLE_VERBATIM,
+	#[regex("@'(?:[^']|'')*")]
+	ERROR_STRING_SINGLE_VERBATIM_UNTERMINATED,
+	#[regex("@[^\"'\\s]\\S+")]
+	ERROR_STRING_VERBATIM_MISSING_QUOTES,
+	#[regex("\\|\\|\\|", crate::string_block::lex_str_block_test)]
+	STRING_BLOCK,
+	ERROR_STRING_BLOCK_UNEXPECTED_END,
+	ERROR_STRING_BLOCK_MISSING_NEW_LINE,
+	ERROR_STRING_BLOCK_MISSING_TERMINATION,
+	ERROR_STRING_BLOCK_MISSING_INDENT,
+	#[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,
+	#[regex("/\\*/")]
+	ERROR_COMMENT_TOO_SHORT,
+	#[regex("/\\*([^*]|\\*[^/])+")]
+	ERROR_COMMENT_UNTERMINATED,
+	#[token("tailstrict")]
+	TAILSTRICT_KW,
+	#[token("local")]
+	LOCAL_KW,
+	#[token("importstr")]
+	IMPORTSTR_KW,
+	#[token("importbin")]
+	IMPORTBIN_KW,
+	#[token("import")]
+	IMPORT_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,
+	META_OBJECT_APPLY,
+	ERROR_NO_OPERATOR,
+	#[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,
+	ERROR_MISSING_TOKEN,
+	ERROR_UNEXPECTED_TOKEN,
+	ERROR_CUSTOM,
+	#[doc = r" Also acts as __LAST_TOKEN"]
+	#[error]
+	LEXING_ERROR,
+	SOURCE_FILE,
+	EXPR,
+	SUFFIX_INDEX,
+	NAME,
+	SUFFIX_INDEX_EXPR,
+	SUFFIX_SLICE,
+	SLICE_DESC,
+	SUFFIX_APPLY,
+	ARGS_DESC,
+	STMT_LOCAL,
+	STMT_ASSERT,
+	ASSERTION,
+	EXPR_BINARY,
+	EXPR_UNARY,
+	EXPR_OBJ_EXTEND,
+	EXPR_PARENED,
+	EXPR_LITERAL,
+	EXPR_STRING,
+	EXPR_NUMBER,
+	EXPR_ARRAY,
+	EXPR_OBJECT,
+	EXPR_ARRAY_COMP,
+	EXPR_IMPORT,
+	EXPR_VAR,
+	EXPR_IF_THEN_ELSE,
+	TRUE_EXPR,
+	FALSE_EXPR,
+	EXPR_FUNCTION,
+	PARAMS_DESC,
+	EXPR_ERROR,
+	SLICE_DESC_END,
+	SLICE_DESC_STEP,
+	ARG,
+	OBJ_BODY_COMP,
+	OBJ_BODY_MEMBER_LIST,
+	MEMBER_BIND_STMT,
+	OBJ_LOCAL,
+	MEMBER_ASSERT_STMT,
+	MEMBER_FIELD_NORMAL,
+	MEMBER_FIELD_METHOD,
+	FIELD_NAME_FIXED,
+	FIELD_NAME_DYNAMIC,
+	FOR_SPEC,
+	IF_SPEC,
+	BIND_DESTRUCT,
+	BIND_FUNCTION,
+	PARAM,
+	DESTRUCT_FULL,
+	DESTRUCT_SKIP,
+	DESTRUCT_ARRAY,
+	DESTRUCT_OBJECT,
+	DESTRUCT_OBJECT_FIELD,
+	DESTRUCT_REST,
+	DESTRUCT_ARRAY_ELEMENT,
+	SUFFIX,
+	BIND,
+	STMT,
+	OBJ_BODY,
+	COMP_SPEC,
+	EXPR_BASE,
+	MEMBER_COMP,
+	MEMBER,
+	FIELD_NAME,
+	DESTRUCT,
+	DESTRUCT_ARRAY_PART,
+	BINARY_OPERATOR,
+	UNARY_OPERATOR,
+	LITERAL,
+	TEXT,
+	NUMBER,
+	IMPORT_KIND,
+	VISIBILITY,
+	TRIVIA,
+	CUSTOM_ERROR,
+	#[doc(hidden)]
+	__LAST,
+}
+use self::SyntaxKind::*;
+impl SyntaxKind {
+	pub fn is_keyword(self) -> bool {
+		match self {
+			OR | NULL_COAELSE | 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 | TAILSTRICT_KW
+			| LOCAL_KW | IMPORTSTR_KW | IMPORTBIN_KW | IMPORT_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_enum(self) -> bool {
+		match self {
+			SUFFIX | BIND | STMT | OBJ_BODY | COMP_SPEC | EXPR_BASE | MEMBER_COMP | MEMBER
+			| FIELD_NAME | DESTRUCT | DESTRUCT_ARRAY_PART | BINARY_OPERATOR | UNARY_OPERATOR
+			| LITERAL | TEXT | NUMBER | IMPORT_KIND | VISIBILITY | TRIVIA | CUSTOM_ERROR => true,
+			_ => false,
+		}
+	}
+	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 :: NULL_COAELSE } ; [&&] => { $ 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 } ; [tailstrict] => { $ crate :: SyntaxKind :: TAILSTRICT_KW } ; [local] => { $ crate :: SyntaxKind :: LOCAL_KW } ; [importstr] => { $ crate :: SyntaxKind :: IMPORTSTR_KW } ; [importbin] => { $ crate :: SyntaxKind :: IMPORTBIN_KW } ; [import] => { $ crate :: SyntaxKind :: IMPORT_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 } }
+pub use T;
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>;
addedcrates/jrsonnet-rowan-parser/src/lex.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/lex.rs
@@ -0,0 +1,80 @@
+use core::ops::Range;
+use std::convert::TryFrom;
+
+use logos::Logos;
+use rowan::{TextRange, TextSize};
+
+use crate::{
+	string_block::{lex_str_block, StringBlockError},
+	SyntaxKind,
+};
+
+pub struct Lexer<'a> {
+	inner: logos::Lexer<'a, SyntaxKind>,
+}
+
+impl<'a> Lexer<'a> {
+	pub fn new(input: &'a str) -> Self {
+		Self {
+			inner: SyntaxKind::lexer(input),
+		}
+	}
+}
+
+impl<'a> Iterator for Lexer<'a> {
+	type Item = Lexeme<'a>;
+
+	fn next(&mut self) -> Option<Self::Item> {
+		use SyntaxKind::*;
+
+		let mut kind = self.inner.next()?;
+		let text = self.inner.slice();
+
+		if kind == STRING_BLOCK {
+			// We use custom lexer, which skips enough bytes, but not returns error
+			// Instead we should call lexer again to verify if there is something wrong with string block
+			let mut lexer = logos::Lexer::<SyntaxKind>::new(text);
+			// In kinds, string blocks is parsed at least as `|||`
+			lexer.bump(3);
+			let res = lex_str_block(&mut lexer);
+			debug_assert!(lexer.next().is_none(), "str_block is lexed");
+			match res {
+				Ok(_) => {}
+				Err(e) => {
+					kind = match e {
+						StringBlockError::UnexpectedEnd => ERROR_STRING_BLOCK_UNEXPECTED_END,
+						StringBlockError::MissingNewLine => ERROR_STRING_BLOCK_MISSING_NEW_LINE,
+						StringBlockError::MissingTermination => {
+							ERROR_STRING_BLOCK_MISSING_TERMINATION
+						}
+						StringBlockError::MissingIndent => ERROR_STRING_BLOCK_MISSING_INDENT,
+					}
+				}
+			}
+		}
+
+		Some(Self::Item {
+			kind,
+			text,
+			range: {
+				let Range { start, end } = self.inner.span();
+
+				TextRange::new(
+					TextSize::try_from(start).unwrap(),
+					TextSize::try_from(end).unwrap(),
+				)
+			},
+		})
+	}
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct Lexeme<'i> {
+	pub kind: SyntaxKind,
+	pub text: &'i str,
+	pub range: TextRange,
+}
+
+pub fn lex(input: &str) -> Vec<Lexeme<'_>> {
+	Lexer::new(input).collect()
+}
addedcrates/jrsonnet-rowan-parser/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/lib.rs
@@ -0,0 +1,71 @@
+#![deny(unused_must_use)]
+
+use event::Sink;
+use generated::nodes::{SourceFile, Trivia};
+use lex::lex;
+use parser::{LocatedSyntaxError, Parser};
+pub use rowan;
+
+mod ast;
+mod event;
+mod generated;
+mod language;
+mod lex;
+mod marker;
+mod parser;
+mod precedence;
+mod string_block;
+mod tests;
+mod token_set;
+
+pub use ast::{AstChildren, AstNode, AstToken};
+pub use generated::{nodes, syntax_kinds::SyntaxKind};
+pub use language::*;
+pub use token_set::SyntaxKindSet;
+
+use self::{
+	ast::support,
+	generated::nodes::{Expr, ExprBinary, ExprObjExtend},
+};
+
+pub fn parse(input: &str) -> (SourceFile, Vec<LocatedSyntaxError>) {
+	let lexemes = lex(input);
+	let kinds = lexemes
+		.iter()
+		.map(|l| l.kind)
+		.filter(|k| !Trivia::can_cast(*k))
+		.collect();
+	let parser = Parser::new(kinds);
+	let events = parser.parse();
+	let sink = Sink::new(events, &lexemes);
+
+	let parse = sink.finish();
+	(
+		SourceFile {
+			syntax: parse.syntax(),
+		},
+		parse.errors,
+	)
+}
+impl ExprBinary {
+	pub fn lhs_work(&self) -> Option<Expr> {
+		support::child(self.syntax())
+	}
+	pub fn rhs_work(&self) -> Option<Expr> {
+		let mut children = support::children(self.syntax());
+		// skip lhs
+		children.next()?;
+		children.next()
+	}
+}
+impl ExprObjExtend {
+	pub fn lhs_work(&self) -> Option<Expr> {
+		support::child(self.syntax())
+	}
+	pub fn rhs_work(&self) -> Option<Expr> {
+		let mut children = support::children(self.syntax());
+		// skip lhs
+		children.next()?;
+		children.next()
+	}
+}
addedcrates/jrsonnet-rowan-parser/src/marker.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/marker.rs
@@ -0,0 +1,187 @@
+use std::num::NonZeroUsize;
+
+use drop_bomb::DropBomb;
+
+use crate::{
+	event::Event,
+	parser::{ExpectedSyntax, Parser, SyntaxError},
+	SyntaxKind,
+};
+
+pub struct Ranger {
+	pub pos: usize,
+}
+impl Ranger {
+	pub fn finish(self, p: &Parser) -> FinishedRanger {
+		FinishedRanger {
+			start_token: self.pos,
+			end_token: self.pos.max(p.offset.saturating_sub(1)),
+		}
+	}
+}
+
+pub struct FinishedRanger {
+	pub start_token: usize,
+	pub end_token: usize,
+}
+impl FinishedRanger {
+	#[allow(dead_code)]
+	pub fn had_error_since(&self, p: &Parser) -> bool {
+		p.last_error_token >= self.start_token
+	}
+}
+
+#[must_use]
+pub struct Marker {
+	pub start_event_idx: usize,
+	bomb: DropBomb,
+}
+impl Marker {
+	pub fn new(pos: usize) -> Self {
+		Self {
+			start_event_idx: pos,
+			bomb: DropBomb::new("marked dropped while not completed"),
+		}
+	}
+	fn complete_raw(
+		mut self,
+		p: &mut Parser,
+		kind: SyntaxKind,
+		error: Option<SyntaxError>,
+	) -> CompletedMarker {
+		self.bomb.defuse();
+		assert!(
+			!kind.is_enum(),
+			"{kind:?} is a enum kind, you should use variant kinds instead"
+		);
+		// TODO: is_lexer should return true if enum variant has #[regex]/#[token] over it, or it is defined as lexer error explicitly
+		// debug_assert!(
+		// 	!kind.is_lexer(),
+		// 	"{kind:?} should be only emitted by lexer, not used directly"
+		// );
+		let event_at_pos = &mut p.events[self.start_event_idx];
+		assert!(matches!(event_at_pos, Event::Pending));
+
+		*event_at_pos = Event::Start {
+			kind,
+			forward_parent: None,
+		};
+
+		let finish_event_idx = p.events.len();
+		p.events.push(Event::Finish {
+			wrapper: None,
+			error: error.map(Box::new),
+		});
+		p.entered -= 1;
+		p.clear_outdated_hints();
+		CompletedMarker {
+			start_event_idx: self.start_event_idx,
+			finish_event_idx,
+		}
+	}
+	pub fn complete(mut self, p: &mut Parser, kind: SyntaxKind) -> CompletedMarker {
+		self.complete_raw(p, kind, None)
+	}
+	pub fn complete_error(mut self, p: &mut Parser, msg: impl AsRef<str>) -> CompletedMarker {
+		self.complete_raw(
+			p,
+			SyntaxKind::ERROR_CUSTOM,
+			Some(SyntaxError::Custom {
+				error: msg.as_ref().to_owned(),
+			}),
+		)
+	}
+	pub fn complete_missing(mut self, p: &mut Parser, expected: ExpectedSyntax) -> CompletedMarker {
+		self.complete_raw(
+			p,
+			SyntaxKind::ERROR_MISSING_TOKEN,
+			Some(SyntaxError::Missing { expected }),
+		)
+	}
+	pub fn complete_unexpected(
+		mut self,
+		p: &mut Parser,
+		expected: ExpectedSyntax,
+		found: SyntaxKind,
+	) -> CompletedMarker {
+		self.complete_raw(
+			p,
+			SyntaxKind::ERROR_UNEXPECTED_TOKEN,
+			Some(SyntaxError::Unexpected { expected, found }),
+		)
+	}
+
+	pub fn forget(mut self, p: &mut Parser) {
+		self.bomb.defuse();
+		let event_at_pos = &mut p.events[self.start_event_idx];
+		assert!(matches!(event_at_pos, Event::Pending));
+
+		*event_at_pos = Event::Noop;
+		p.entered -= 1;
+		p.clear_outdated_hints();
+	}
+}
+pub struct CompletedMarker {
+	start_event_idx: usize,
+	finish_event_idx: usize,
+}
+impl CompletedMarker {
+	pub(super) fn precede(self, p: &mut Parser) -> Marker {
+		let new_m = p.start();
+		match &mut p.events[self.start_event_idx] {
+			Event::Start { forward_parent, .. } => {
+				*forward_parent = Some(
+					NonZeroUsize::new(new_m.start_event_idx - self.start_event_idx).expect("!= 0"),
+				);
+			}
+			_ => unreachable!(),
+		}
+
+		new_m
+	}
+	/// Create new node around existing marker, not counting anything that comes after it
+	fn wrap_raw(
+		self,
+		p: &mut Parser,
+		kind: SyntaxKind,
+		error: Option<SyntaxError>,
+	) -> CompletedMarker {
+		let new_m = p.start();
+		match &mut p.events[self.start_event_idx] {
+			Event::Start { forward_parent, .. } => {
+				*forward_parent = Some(
+					NonZeroUsize::new(new_m.start_event_idx - self.start_event_idx).expect("!= 0"),
+				);
+			}
+			_ => unreachable!(),
+		}
+
+		let completed = new_m.complete_raw(p, kind, error);
+
+		match &mut p.events[self.finish_event_idx] {
+			Event::Finish {
+				wrapper,
+				error: _error,
+			} => {
+				*wrapper = Some(
+					NonZeroUsize::new(completed.finish_event_idx - self.finish_event_idx)
+						.expect("!= 0"),
+				);
+			}
+			_ => unreachable!(),
+		}
+		completed
+	}
+	pub fn wrap(self, p: &mut Parser, kind: SyntaxKind) -> CompletedMarker {
+		self.wrap_raw(p, kind, None)
+	}
+	pub fn wrap_error(self, p: &mut Parser, msg: impl AsRef<str>) -> CompletedMarker {
+		self.wrap_raw(
+			p,
+			SyntaxKind::ERROR_CUSTOM,
+			Some(SyntaxError::Custom {
+				error: msg.as_ref().to_owned(),
+			}),
+		)
+	}
+}
addedcrates/jrsonnet-rowan-parser/src/parser.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/parser.rs
@@ -0,0 +1,940 @@
+use std::{cell::Cell, fmt, rc::Rc};
+
+use miette::{LabeledSpan, SourceOffset, SourceSpan};
+use rowan::{GreenNode, TextRange};
+
+use crate::{
+	event::Event,
+	marker::{CompletedMarker, Marker, Ranger},
+	nodes::{BinaryOperatorKind, Literal, Number, Text, UnaryOperatorKind},
+	token_set::SyntaxKindSet,
+	AstToken, SyntaxKind,
+	SyntaxKind::*,
+	SyntaxNode, T, TS,
+};
+
+pub struct Parse {
+	pub green_node: GreenNode,
+	pub errors: Vec<LocatedSyntaxError>,
+}
+
+pub struct Parser {
+	// TODO: remove all trivia before feeding to parser?
+	kinds: Vec<SyntaxKind>,
+	pub offset: usize,
+	pub events: Vec<Event>,
+	pub entered: u32,
+	pub hints: Vec<(u32, TextRange, String)>,
+	pub last_error_token: usize,
+	expected_syntax_tracking_state: Rc<Cell<ExpectedSyntax>>,
+	steps: Cell<u64>,
+}
+
+#[derive(Clone, Debug)]
+pub enum SyntaxError {
+	Unexpected {
+		expected: ExpectedSyntax,
+		found: SyntaxKind,
+	},
+	Missing {
+		expected: ExpectedSyntax,
+	},
+	Custom {
+		error: String,
+	},
+	Hint {
+		error: String,
+	},
+}
+
+#[derive(Debug)]
+pub struct LocatedSyntaxError {
+	pub error: SyntaxError,
+	pub range: TextRange,
+}
+
+impl From<LocatedSyntaxError> for LabeledSpan {
+	fn from(val: LocatedSyntaxError) -> Self {
+		let span = SourceSpan::new(
+			SourceOffset::from(usize::from(val.range.start())),
+			SourceOffset::from(usize::from(val.range.end() - val.range.start())),
+		);
+		dbg!(&val);
+		match val.error {
+			SyntaxError::Unexpected { expected, found } => LabeledSpan::new_with_span(
+				Some(format!("expected {expected}, found {found:?}")),
+				span,
+			),
+			SyntaxError::Missing { expected } => {
+				LabeledSpan::new_with_span(Some(format!("missing {expected}")), span)
+			}
+			SyntaxError::Custom { error } | SyntaxError::Hint { error } => {
+				LabeledSpan::new_with_span(Some(error), span)
+			}
+		}
+	}
+}
+
+impl Parser {
+	pub fn new(kinds: Vec<SyntaxKind>) -> Self {
+		Self {
+			kinds,
+			offset: 0,
+			events: vec![],
+			entered: 0,
+			last_error_token: 0,
+			hints: vec![],
+			expected_syntax_tracking_state: Rc::new(Cell::new(ExpectedSyntax::Unnamed(TS![]))),
+			steps: Cell::new(0),
+		}
+	}
+	pub fn clear_outdated_hints(&mut self) {
+		let amount = self
+			.hints
+			.iter()
+			.rev()
+			.take_while(|h| h.0 > self.entered)
+			.count();
+		self.hints.truncate(self.hints.len() - amount)
+	}
+	fn clear_expected_syntaxes(&mut self) {
+		self.expected_syntax_tracking_state
+			.set(ExpectedSyntax::Unnamed(TS![]));
+	}
+	pub fn start(&mut self) -> Marker {
+		let start_event_idx = self.events.len();
+		self.events.push(Event::Pending);
+		self.entered += 1;
+		Marker::new(start_event_idx)
+	}
+	pub fn start_ranger(&mut self) -> Ranger {
+		let pos = self.offset;
+		Ranger { pos }
+	}
+	pub fn parse(mut self) -> Vec<Event> {
+		let m = self.start();
+		expr(&mut self);
+		if !self.at(EOF) {
+			let m = self.start();
+			while !self.at(EOF) {
+				self.bump();
+			}
+			m.complete_error(&mut self, "unexpected tokens after end");
+		}
+		m.complete(&mut self, SOURCE_FILE);
+
+		self.events
+	}
+
+	pub(crate) fn expect(&mut self, kind: SyntaxKind) {
+		self.expect_with_recovery_set(kind, TS![])
+	}
+
+	pub(crate) fn expect_with_recovery_set(
+		&mut self,
+		kind: SyntaxKind,
+		recovery_set: SyntaxKindSet,
+	) {
+		if self.at(kind) {
+			if kind != EOF {
+				self.bump();
+			}
+		} else {
+			self.error_with_recovery_set(recovery_set);
+		}
+	}
+
+	pub(crate) fn expect_with_no_skip(&mut self, kind: SyntaxKind) {
+		if self.at(kind) {
+			self.bump();
+		} else {
+			self.error_with_no_skip();
+		}
+	}
+	pub fn error_with_no_skip(&mut self) -> CompletedMarker {
+		self.error_with_recovery_set(SyntaxKindSet::ALL)
+	}
+
+	pub fn error_with_recovery_set(&mut self, recovery_set: SyntaxKindSet) -> CompletedMarker {
+		let expected = self.expected_syntax_tracking_state.get();
+		self.expected_syntax_tracking_state
+			.set(ExpectedSyntax::Unnamed(TS![]));
+
+		if self.at_end() || self.at_ts(recovery_set) {
+			let m = self.start();
+			return m.complete_missing(self, expected);
+		}
+
+		let current_token = self.current();
+
+		self.last_error_token = self.offset;
+
+		let m = self.start();
+		self.bump();
+		let m = m.complete_unexpected(self, expected, current_token);
+		self.clear_expected_syntaxes();
+		m
+	}
+	fn bump_assert(&mut self, kind: SyntaxKind) {
+		assert!(self.at(kind), "expected {:?}", kind);
+		self.bump_remap(self.current());
+	}
+	fn bump(&mut self) {
+		self.bump_remap(self.current());
+	}
+	fn bump_remap(&mut self, kind: SyntaxKind) {
+		assert_ne!(self.offset, self.kinds.len(), "already at end");
+		self.events.push(Event::Token { kind });
+		self.offset += 1;
+		self.clear_expected_syntaxes();
+	}
+	fn step(&self) {
+		use std::fmt::Write;
+		let steps = self.steps.get();
+		if steps >= 15000000 {
+			let mut out = "seems like parsing is stuck".to_owned();
+			{
+				let last = 20;
+				write!(out, "\n\nLast {} events:", last).unwrap();
+				for (i, event) in self
+					.events
+					.iter()
+					.skip(self.events.len().saturating_sub(last))
+					.enumerate()
+				{
+					write!(out, "\n{i}. {event:?}").unwrap();
+				}
+			}
+			{
+				let next = 20;
+				write!(out, "\n\nNext {next} tokens:").unwrap();
+				for (i, tok) in self.kinds.iter().skip(self.offset).take(next).enumerate() {
+					write!(out, "\n{i}. {tok:?}").unwrap();
+				}
+			}
+			panic!("{out}")
+		}
+		self.steps.set(steps + 1);
+	}
+	fn nth(&self, i: usize) -> SyntaxKind {
+		self.step();
+		let mut offset = self.offset;
+		for _ in 0..i {
+			offset += 1;
+		}
+		self.kinds.get(offset).copied().unwrap_or(EOF)
+	}
+	fn current(&self) -> SyntaxKind {
+		self.nth(0)
+	}
+	#[must_use]
+	pub(crate) fn expected_syntax_name(&mut self, name: &'static str) -> ExpectedSyntaxGuard {
+		self.expected_syntax_tracking_state
+			.set(ExpectedSyntax::Named(name));
+
+		ExpectedSyntaxGuard::new(Rc::clone(&self.expected_syntax_tracking_state))
+	}
+	pub fn at(&mut self, kind: SyntaxKind) -> bool {
+		self.nth_at(0, kind)
+	}
+	pub fn nth_at(&mut self, n: usize, kind: SyntaxKind) -> bool {
+		if n == 0 {
+			if let ExpectedSyntax::Unnamed(kinds) = self.expected_syntax_tracking_state.get() {
+				let kinds = kinds.with(kind);
+				self.expected_syntax_tracking_state
+					.set(ExpectedSyntax::Unnamed(kinds))
+			}
+		}
+		self.nth(n) == kind
+	}
+	pub fn at_ts(&mut self, set: SyntaxKindSet) -> bool {
+		if let ExpectedSyntax::Unnamed(kinds) = self.expected_syntax_tracking_state.get() {
+			let kinds = kinds.union(set);
+			self.expected_syntax_tracking_state
+				.set(ExpectedSyntax::Unnamed(kinds))
+		}
+		set.contains(self.current())
+	}
+	pub fn at_end(&mut self) -> bool {
+		self.at(EOF)
+	}
+}
+pub(crate) struct ExpectedSyntaxGuard {
+	expected_syntax_tracking_state: Rc<Cell<ExpectedSyntax>>,
+}
+
+impl ExpectedSyntaxGuard {
+	fn new(expected_syntax_tracking_state: Rc<Cell<ExpectedSyntax>>) -> Self {
+		Self {
+			expected_syntax_tracking_state,
+		}
+	}
+}
+
+impl Drop for ExpectedSyntaxGuard {
+	fn drop(&mut self) {
+		self.expected_syntax_tracking_state
+			.set(ExpectedSyntax::Unnamed(TS![]));
+	}
+}
+
+#[derive(Clone, Debug, Copy)]
+pub enum ExpectedSyntax {
+	Named(&'static str),
+	Unnamed(SyntaxKindSet),
+}
+impl fmt::Display for ExpectedSyntax {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		match self {
+			ExpectedSyntax::Named(name) => write!(f, "{name}"),
+			ExpectedSyntax::Unnamed(set) => write!(f, "{set}"),
+		}
+	}
+}
+
+fn expr(p: &mut Parser) -> CompletedMarker {
+	let m = p.start();
+	while p.at(T![local]) || p.at(T![assert]) {
+		let m = p.start();
+
+		if p.at(T![local]) {
+			p.bump();
+			loop {
+				if p.at(T![;]) {
+					p.bump();
+					break;
+				}
+				bind(p);
+
+				if p.at(T![,]) {
+					p.bump();
+					continue;
+				}
+				p.expect(T![;]);
+				break;
+			}
+			m.complete(p, STMT_LOCAL);
+		} else {
+			assertion(p);
+			p.expect(T![;]);
+			m.complete(p, STMT_ASSERT);
+		}
+	}
+	match expr_binding_power(p, 0) {
+		Ok(m) => m,
+		Err(m) => m,
+	};
+	m.complete(p, EXPR)
+}
+fn expr_binding_power(
+	p: &mut Parser,
+	minimum_binding_power: u8,
+) -> Result<CompletedMarker, CompletedMarker> {
+	let mut lhs = lhs(p)?;
+
+	while let Some(op) = BinaryOperatorKind::cast(p.current())
+		.or_else(|| p.at(T!['{']).then_some(BinaryOperatorKind::MetaObjectApply))
+	{
+		let (left_binding_power, right_binding_power) = op.binding_power();
+		if left_binding_power < minimum_binding_power {
+			break;
+		}
+
+		// Object apply is not a real operator, we dont have something to bump
+		if op != BinaryOperatorKind::MetaObjectApply {
+			p.bump();
+		}
+
+		let m = lhs.wrap(p, EXPR).precede(p);
+		let parsed_rhs = expr_binding_power(p, right_binding_power)
+			.map(|v| v.precede(p).complete(p, EXPR))
+			.is_ok();
+		lhs = m.complete(
+			p,
+			if op == BinaryOperatorKind::MetaObjectApply {
+				EXPR_OBJ_EXTEND
+			} else {
+				EXPR_BINARY
+			},
+		);
+
+		if !parsed_rhs {
+			break;
+		}
+	}
+	Ok(lhs)
+}
+
+const COMPSPEC: SyntaxKindSet = TS![for if];
+fn compspec(p: &mut Parser) -> CompletedMarker {
+	assert!(p.at_ts(COMPSPEC));
+	if p.at(T![for]) {
+		let m = p.start();
+		p.bump();
+		destruct(p);
+		p.expect(T![in]);
+		expr(p);
+		m.complete(p, FOR_SPEC)
+	} else if p.at(T![if]) {
+		let m = p.start();
+		p.bump();
+		expr(p);
+		m.complete(p, IF_SPEC)
+	} else {
+		unreachable!()
+	}
+}
+
+fn comma(p: &mut Parser) -> bool {
+	comma_with_alternatives(p, TS![])
+}
+fn comma_with_alternatives(p: &mut Parser, set: SyntaxKindSet) -> bool {
+	if p.at(T![,]) {
+		p.bump();
+		true
+	} else if p.at_ts(set) {
+		let _ex = p.expected_syntax_name("comma");
+		p.expect_with_recovery_set(T![,], TS![]);
+		true
+	} else {
+		false
+	}
+}
+
+fn field_name(p: &mut Parser) {
+	let _e = p.expected_syntax_name("field name");
+	let m = p.start();
+	if p.at(T!['[']) {
+		p.bump();
+		expr(p);
+		p.expect(T![']']);
+		m.complete(p, FIELD_NAME_DYNAMIC);
+	} else if p.at(IDENT) {
+		name(p);
+		m.complete(p, FIELD_NAME_FIXED);
+	} else if Text::can_cast(p.current()) {
+		text(p);
+		m.complete(p, FIELD_NAME_FIXED);
+	} else {
+		m.forget(p);
+		p.error_with_recovery_set(TS![; : :: ::: '(']);
+	}
+}
+fn visibility(p: &mut Parser) {
+	if p.at_ts(TS![: :: :::]) {
+		p.bump()
+	} else {
+		p.error_with_recovery_set(TS![=]);
+	}
+}
+fn assertion(p: &mut Parser) {
+	let m = p.start();
+	p.bump_assert(T![assert]);
+	expr(p);
+	if p.at(T![:]) {
+		p.bump();
+		expr(p);
+	}
+	m.complete(p, ASSERTION);
+}
+fn object(p: &mut Parser) -> CompletedMarker {
+	let m_t = p.start();
+	let m = p.start();
+	p.bump_assert(T!['{']);
+
+	let mut elems = 0;
+	let mut compspecs = Vec::new();
+	let mut asserts = Vec::new();
+	loop {
+		if p.at(T!['}']) {
+			p.bump();
+			break;
+		}
+		if p.at_ts(COMPSPEC) {
+			if elems == 0 {
+				let m = p.start();
+				m.complete_missing(p, ExpectedSyntax::Named("field definition"));
+			}
+			while p.at_ts(COMPSPEC) {
+				compspecs.push(compspec(p));
+			}
+			if comma_with_alternatives(p, TS![;]) {
+				continue;
+			}
+			p.expect(R_BRACE);
+			break;
+		}
+		let m = p.start();
+		if p.at(T![local]) {
+			obj_local(p);
+			m.complete(p, MEMBER_BIND_STMT);
+		} else if p.at(T![assert]) {
+			assertion(p);
+			asserts.push(m.complete(p, MEMBER_ASSERT_STMT));
+		} else {
+			field_name(p);
+			if p.at(T![+]) {
+				p.bump();
+			}
+			let params = if p.at(T!['(']) {
+				params_desc(p);
+				visibility(p);
+				expr(p);
+				true
+			} else if p.at_ts(TS![: :: :::]) && p.nth_at(1, T![function]) {
+				visibility(p);
+				p.bump_assert(T![function]);
+				params_desc(p);
+				expr(p);
+				true
+			} else {
+				visibility(p);
+				expr(p);
+				false
+			};
+			elems += 1;
+
+			if params {
+				m.complete(p, MEMBER_FIELD_METHOD)
+			} else {
+				m.complete(p, MEMBER_FIELD_NORMAL)
+			};
+		};
+		while p.at_ts(COMPSPEC) {
+			compspecs.push(compspec(p));
+		}
+		if comma_with_alternatives(p, TS![;]) {
+			continue;
+		}
+		p.expect(R_BRACE);
+		break;
+	}
+
+	if elems > 1 && !compspecs.is_empty() {
+		for errored in compspecs {
+			errored.wrap_error(
+				p,
+				"compspec may only be used if there is only one object element",
+			);
+		}
+		m.complete(p, OBJ_BODY_MEMBER_LIST);
+	} else if !compspecs.is_empty() {
+		for errored in asserts {
+			errored.wrap_error(p, "asserts can't be used in object comprehensions");
+		}
+		m.complete(p, OBJ_BODY_COMP);
+	} else {
+		m.complete(p, OBJ_BODY_MEMBER_LIST);
+	}
+	m_t.complete(p, EXPR_OBJECT)
+}
+fn param(p: &mut Parser) {
+	let m = p.start();
+	destruct(p);
+	if p.at(T![=]) {
+		p.bump();
+		expr(p);
+	}
+	m.complete(p, PARAM);
+}
+fn params_desc(p: &mut Parser) -> CompletedMarker {
+	let m = p.start();
+	p.bump_assert(T!['(']);
+
+	loop {
+		if p.at(T![')']) {
+			p.bump();
+			break;
+		}
+		param(p);
+		if comma(p) {
+			continue;
+		}
+		p.expect(T![')']);
+		break;
+	}
+
+	m.complete(p, PARAMS_DESC)
+}
+fn args_desc(p: &mut Parser) {
+	let m = p.start();
+	p.bump_assert(T!['(']);
+
+	let started_named = Cell::new(false);
+	let mut unnamed_after_named = Vec::new();
+
+	loop {
+		if p.at(T![')']) {
+			break;
+		}
+
+		let m = p.start();
+		if p.at(IDENT) && p.nth_at(1, T![=]) {
+			name(p);
+			p.bump();
+			expr(p);
+			m.complete(p, ARG);
+			started_named.set(true);
+		} else {
+			expr(p);
+			let arg = m.complete(p, ARG);
+			if started_named.get() {
+				unnamed_after_named.push(arg)
+			}
+		}
+		if comma(p) {
+			continue;
+		}
+		break;
+	}
+	p.expect(T![')']);
+	if p.at(T![tailstrict]) {
+		p.bump()
+	}
+
+	for errored in unnamed_after_named {
+		errored.wrap_error(p, "can't use positional arguments after named");
+	}
+
+	m.complete(p, ARGS_DESC);
+}
+
+fn array(p: &mut Parser) -> CompletedMarker {
+	// Start the list node
+	let m = p.start();
+	p.bump_assert(T!['[']);
+
+	let mut compspecs = Vec::new();
+	let mut elems = 0;
+
+	loop {
+		if p.at(T![']']) {
+			p.bump();
+			break;
+		}
+		if elems != 0 && p.at_ts(COMPSPEC) {
+			while p.at_ts(COMPSPEC) {
+				compspecs.push(compspec(p));
+			}
+			if comma(p) {
+				continue;
+			}
+			p.expect(T![']']);
+			break;
+		}
+		expr(p);
+		elems += 1;
+		while p.at_ts(COMPSPEC) {
+			compspecs.push(compspec(p));
+		}
+		if comma(p) {
+			continue;
+		}
+		p.expect(T![']']);
+		break;
+	}
+
+	if elems > 1 && !compspecs.is_empty() {
+		for spec in compspecs {
+			spec.wrap_error(
+				p,
+				"compspec may only be used if there is only one array element",
+			);
+		}
+
+		m.complete(p, EXPR_ARRAY)
+	} else if !compspecs.is_empty() {
+		m.complete(p, EXPR_ARRAY_COMP)
+	} else {
+		m.complete(p, EXPR_ARRAY)
+	}
+}
+/// Returns true if it was slice, false if just index
+#[must_use]
+fn slice_desc_or_index(p: &mut Parser) -> bool {
+	let m = p.start();
+	p.bump();
+	// TODO: do not treat :, ::, ::: as full tokens?
+	// Start
+	if !p.at(T![:]) && !p.at(T![::]) {
+		expr(p);
+	}
+	if p.at(T![:]) {
+		p.bump();
+		// End
+		if !p.at(T![']']) {
+			expr(p).wrap(p, SLICE_DESC_END);
+		}
+		if p.at(T![:]) {
+			p.bump();
+			// Step
+			if !p.at(T![']']) {
+				expr(p).wrap(p, SLICE_DESC_STEP);
+			}
+		}
+	} else if p.at(T![::]) {
+		p.bump();
+		// End
+		if !p.at(T![']']) {
+			expr(p).wrap(p, SLICE_DESC_END);
+		}
+	} else {
+		// It was not a slice
+		p.expect(T![']']);
+		m.forget(p);
+		return false;
+	}
+	p.expect(T![']']);
+	m.complete(p, SLICE_DESC);
+	true
+}
+
+fn suffix(p: &mut Parser) {
+	loop {
+		let start = p.start();
+		let _marker: CompletedMarker = if p.at(T![?]) {
+			p.bump();
+			p.expect(T![.]);
+			if p.at(IDENT) {
+				name(p);
+				start.complete(p, SUFFIX_INDEX)
+			} else if p.at(T!['[']) {
+				p.bump();
+				expr(p);
+				p.expect(T![']']);
+				start.complete(p, SUFFIX_INDEX_EXPR)
+			} else {
+				start.complete_missing(p, ExpectedSyntax::Named("index"))
+			}
+		} else if p.at(T![.]) {
+			p.bump();
+			name(p);
+			start.complete(p, SUFFIX_INDEX)
+		} else if p.at(T!['[']) {
+			if slice_desc_or_index(p) {
+				start.complete(p, SUFFIX_SLICE)
+			} else {
+				start.complete(p, SUFFIX_INDEX_EXPR)
+			}
+		} else if p.at(T!['(']) {
+			args_desc(p);
+			start.complete(p, SUFFIX_APPLY)
+		} else {
+			start.forget(p);
+			break;
+		};
+	}
+}
+
+fn lhs(p: &mut Parser) -> Result<CompletedMarker, CompletedMarker> {
+	let lhs = lhs_basic(p)?;
+
+	suffix(p);
+
+	Ok(lhs)
+}
+fn name(p: &mut Parser) {
+	let m = p.start();
+	p.expect(IDENT);
+	m.complete(p, NAME);
+}
+fn destruct_rest(p: &mut Parser) {
+	let m = p.start();
+	p.bump_assert(T![...]);
+	if p.at(IDENT) {
+		p.bump()
+	}
+	m.complete(p, DESTRUCT_REST);
+}
+fn destruct_object_field(p: &mut Parser) {
+	let m = p.start();
+	name(p);
+	if p.at(T![:]) {
+		p.bump();
+		destruct(p);
+	};
+	if p.at(T![=]) {
+		p.bump();
+		expr(p);
+	}
+	m.complete(p, DESTRUCT_OBJECT_FIELD);
+}
+fn obj_local(p: &mut Parser) {
+	let m = p.start();
+	p.bump_assert(T![local]);
+	bind(p);
+	m.complete(p, OBJ_LOCAL);
+}
+fn destruct(p: &mut Parser) -> CompletedMarker {
+	let m = p.start();
+	let _ex = p.expected_syntax_name("destruction specifier");
+	if p.at(T![?]) {
+		p.bump();
+		m.complete(p, DESTRUCT_SKIP)
+	} else if p.at(T!['[']) {
+		p.bump();
+		let mut had_rest = false;
+		loop {
+			if p.at(T![']']) {
+				p.bump();
+				break;
+			} else if p.at(T![...]) {
+				let m_err = p.start_ranger();
+				destruct_rest(p);
+				// if had_rest {
+				// 	p.custom_error(m_err.finish(p), "only one rest can be present in array");
+				// }
+				had_rest = true;
+			} else {
+				destruct(p);
+			}
+			if p.at(T![,]) {
+				p.bump();
+				continue;
+			}
+			p.expect(T![']']);
+			break;
+		}
+		m.complete(p, DESTRUCT_ARRAY)
+	} else if p.at(T!['{']) {
+		p.bump();
+		let mut had_rest = false;
+		loop {
+			if p.at(T!['}']) {
+				p.bump();
+				break;
+			} else if p.at(T![...]) {
+				let m_err = p.start_ranger();
+				destruct_rest(p);
+				// if had_rest {
+				// 	p.custom_error(m_err.finish(p), "only one rest can be present in object");
+				// }
+				had_rest = true;
+			} else {
+				if had_rest {
+					p.error_with_recovery_set(TS![]);
+				}
+				destruct_object_field(p);
+			}
+			if p.at(T![,]) {
+				p.bump();
+				continue;
+			}
+			p.expect(T!['}']);
+			break;
+		}
+		m.complete(p, DESTRUCT_OBJECT)
+	} else if p.at(IDENT) {
+		name(p);
+		m.complete(p, DESTRUCT_FULL)
+	} else {
+		m.forget(p);
+		p.error_with_recovery_set(TS![; , '}', '(', :])
+	}
+}
+fn bind(p: &mut Parser) {
+	let m = p.start();
+	if p.at(IDENT) && p.nth_at(1, T!['(']) {
+		name(p);
+		params_desc(p);
+		p.expect(T![=]);
+		expr(p);
+		m.complete(p, BIND_FUNCTION)
+	} else if p.at(IDENT) && p.nth_at(1, T![=]) && p.nth_at(2, T![function]) {
+		name(p);
+		p.expect(T![=]);
+		p.expect(T![function]);
+		params_desc(p);
+		expr(p);
+		m.complete(p, BIND_FUNCTION)
+	} else {
+		destruct(p);
+		p.expect(T![=]);
+		expr(p);
+		m.complete(p, BIND_DESTRUCT)
+	};
+}
+fn text(p: &mut Parser) {
+	assert!(Text::can_cast(p.current()));
+	p.bump();
+}
+fn number(p: &mut Parser) {
+	assert!(Number::can_cast(p.current()));
+	p.bump();
+}
+fn literal(p: &mut Parser) {
+	assert!(Literal::can_cast(p.current()));
+	p.bump();
+}
+fn lhs_basic(p: &mut Parser) -> Result<CompletedMarker, CompletedMarker> {
+	let _e = p.expected_syntax_name("expression");
+	Ok(if Literal::can_cast(p.current()) {
+		let m = p.start();
+		literal(p);
+		m.complete(p, EXPR_LITERAL)
+	} else if Text::can_cast(p.current()) {
+		let m = p.start();
+		text(p);
+		m.complete(p, EXPR_STRING)
+	} else if Number::can_cast(p.current()) {
+		let m = p.start();
+		number(p);
+		m.complete(p, EXPR_NUMBER)
+	} else if p.at(IDENT) {
+		let m = p.start();
+		name(p);
+		m.complete(p, EXPR_VAR)
+	} else if p.at(T![if]) {
+		let m = p.start();
+		p.bump();
+		expr(p);
+		p.expect(T![then]);
+		expr(p).wrap(p, TRUE_EXPR);
+		if p.at(T![else]) {
+			p.bump();
+			expr(p).wrap(p, FALSE_EXPR);
+		}
+		m.complete(p, EXPR_IF_THEN_ELSE)
+	} else if p.at(T!['[']) {
+		array(p)
+	} else if p.at(T!['{']) {
+		object(p)
+	} else if p.at(T![function]) {
+		let m = p.start();
+		p.bump();
+		params_desc(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![import]) || p.at(T![importstr]) || p.at(T![importbin]) {
+		let m = p.start();
+		p.bump();
+		text(p);
+		m.complete(p, EXPR_IMPORT)
+	} else if let Some(op) = UnaryOperatorKind::cast(p.current()) {
+		let ((), right_binding_power) = op.binding_power();
+
+		let m = p.start();
+		p.bump();
+		let _ = 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);
+		p.expect(T![')']);
+		m.complete(p, EXPR_PARENED)
+	} else {
+		return Err(p.error_with_no_skip());
+	})
+}
+
+impl Parse {
+	pub fn syntax(&self) -> SyntaxNode {
+		SyntaxNode::new_root(self.green_node.clone())
+	}
+}
addedcrates/jrsonnet-rowan-parser/src/precedence.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/precedence.rs
@@ -0,0 +1,31 @@
+use crate::nodes::{BinaryOperatorKind, UnaryOperatorKind};
+
+impl BinaryOperatorKind {
+	pub fn binding_power(&self) -> (u8, u8) {
+		match self {
+			Self::MetaObjectApply => (22, 23),
+			Self::Mul | Self::Div | Self::Modulo => (20, 21),
+			Self::Plus | Self::Minus => (18, 19),
+			Self::Lhs | Self::Rhs => (16, 17),
+			Self::Lt | Self::Gt | Self::Le | Self::Ge | Self::InKw => (14, 15),
+			Self::Eq | Self::Ne => (12, 13),
+			Self::BitAnd => (10, 11),
+			Self::BitXor => (8, 9),
+			Self::BitOr => (6, 7),
+			Self::And => (4, 5),
+			Self::NullCoaelse => (2, 3),
+			Self::Or => (2, 3),
+			Self::ErrorNoOperator => (0, 1),
+		}
+	}
+}
+
+impl UnaryOperatorKind {
+	pub fn binding_power(&self) -> ((), u8) {
+		match self {
+			Self::Minus => ((), 20),
+			Self::Not => ((), 20),
+			Self::BitNot => ((), 20),
+		}
+	}
+}
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__arr_compspec.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__arr_compspec.snap
@@ -0,0 +1,41 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "[a for a in [1, 2, 3]]\n"
+---
+SOURCE_FILE@0..23
+  EXPR@0..22
+    EXPR_ARRAY_COMP@0..22
+      L_BRACK@0..1 "["
+      EXPR@1..2
+        EXPR_VAR@1..2
+          NAME@1..2
+            IDENT@1..2 "a"
+      WHITESPACE@2..3 " "
+      FOR_SPEC@3..21
+        FOR_KW@3..6 "for"
+        WHITESPACE@6..7 " "
+        DESTRUCT_FULL@7..8
+          NAME@7..8
+            IDENT@7..8 "a"
+        WHITESPACE@8..9 " "
+        IN_KW@9..11 "in"
+        WHITESPACE@11..12 " "
+        EXPR@12..21
+          EXPR_ARRAY@12..21
+            L_BRACK@12..13 "["
+            EXPR@13..14
+              EXPR_NUMBER@13..14
+                FLOAT@13..14 "1"
+            COMMA@14..15 ","
+            WHITESPACE@15..16 " "
+            EXPR@16..17
+              EXPR_NUMBER@16..17
+                FLOAT@16..17 "2"
+            COMMA@17..18 ","
+            WHITESPACE@18..19 " "
+            EXPR@19..20
+              EXPR_NUMBER@19..20
+                FLOAT@19..20 "3"
+            R_BRACK@20..21 "]"
+      R_BRACK@21..22 "]"
+  WHITESPACE@22..23 "\n"
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__arr_compspec_comma.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__arr_compspec_comma.snap
@@ -0,0 +1,42 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "[a, for a in [1, 2, 3]]\n"
+---
+SOURCE_FILE@0..24
+  EXPR@0..23
+    EXPR_ARRAY_COMP@0..23
+      L_BRACK@0..1 "["
+      EXPR@1..2
+        EXPR_VAR@1..2
+          NAME@1..2
+            IDENT@1..2 "a"
+      COMMA@2..3 ","
+      WHITESPACE@3..4 " "
+      FOR_SPEC@4..22
+        FOR_KW@4..7 "for"
+        WHITESPACE@7..8 " "
+        DESTRUCT_FULL@8..9
+          NAME@8..9
+            IDENT@8..9 "a"
+        WHITESPACE@9..10 " "
+        IN_KW@10..12 "in"
+        WHITESPACE@12..13 " "
+        EXPR@13..22
+          EXPR_ARRAY@13..22
+            L_BRACK@13..14 "["
+            EXPR@14..15
+              EXPR_NUMBER@14..15
+                FLOAT@14..15 "1"
+            COMMA@15..16 ","
+            WHITESPACE@16..17 " "
+            EXPR@17..18
+              EXPR_NUMBER@17..18
+                FLOAT@17..18 "2"
+            COMMA@18..19 ","
+            WHITESPACE@19..20 " "
+            EXPR@20..21
+              EXPR_NUMBER@20..21
+                FLOAT@20..21 "3"
+            R_BRACK@21..22 "]"
+      R_BRACK@22..23 "]"
+  WHITESPACE@23..24 "\n"
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__arr_compspec_incompatible_with_multiple_elems.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__arr_compspec_incompatible_with_multiple_elems.snap
@@ -0,0 +1,57 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "[a for a in [1, 2, 3], b]\n"
+---
+SOURCE_FILE@0..26
+  EXPR@0..25
+    EXPR_ARRAY@0..25
+      L_BRACK@0..1 "["
+      EXPR@1..2
+        EXPR_VAR@1..2
+          NAME@1..2
+            IDENT@1..2 "a"
+      WHITESPACE@2..3 " "
+      ERROR_CUSTOM@3..21
+        FOR_SPEC@3..21
+          FOR_KW@3..6 "for"
+          WHITESPACE@6..7 " "
+          DESTRUCT_FULL@7..8
+            NAME@7..8
+              IDENT@7..8 "a"
+          WHITESPACE@8..9 " "
+          IN_KW@9..11 "in"
+          WHITESPACE@11..12 " "
+          EXPR@12..21
+            EXPR_ARRAY@12..21
+              L_BRACK@12..13 "["
+              EXPR@13..14
+                EXPR_NUMBER@13..14
+                  FLOAT@13..14 "1"
+              COMMA@14..15 ","
+              WHITESPACE@15..16 " "
+              EXPR@16..17
+                EXPR_NUMBER@16..17
+                  FLOAT@16..17 "2"
+              COMMA@17..18 ","
+              WHITESPACE@18..19 " "
+              EXPR@19..20
+                EXPR_NUMBER@19..20
+                  FLOAT@19..20 "3"
+              R_BRACK@20..21 "]"
+      COMMA@21..22 ","
+      WHITESPACE@22..23 " "
+      EXPR@23..24
+        EXPR_VAR@23..24
+          NAME@23..24
+            IDENT@23..24 "b"
+      R_BRACK@24..25 "]"
+  WHITESPACE@25..26 "\n"
+===
+LocatedSyntaxError { error: Custom { error: "compspec may only be used if there is only one array element" }, range: 3..21 }
+===
+  x syntax error
+   ,----
+ 1 | [a for a in [1, 2, 3], b]
+   :    ^^^^^^^^^|^^^^^^^^
+   :             `-- compspec may only be used if there is only one array element
+   `----
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__arr_compspec_incompatible_with_multiple_elems_w.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__arr_compspec_incompatible_with_multiple_elems_w.snap
@@ -0,0 +1,64 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "[a, b, for a in [1, 2, 3], c]\n"
+---
+SOURCE_FILE@0..30
+  EXPR@0..29
+    EXPR_ARRAY@0..29
+      L_BRACK@0..1 "["
+      EXPR@1..2
+        EXPR_VAR@1..2
+          NAME@1..2
+            IDENT@1..2 "a"
+      COMMA@2..3 ","
+      WHITESPACE@3..4 " "
+      EXPR@4..5
+        EXPR_VAR@4..5
+          NAME@4..5
+            IDENT@4..5 "b"
+      COMMA@5..6 ","
+      WHITESPACE@6..7 " "
+      ERROR_CUSTOM@7..25
+        FOR_SPEC@7..25
+          FOR_KW@7..10 "for"
+          WHITESPACE@10..11 " "
+          DESTRUCT_FULL@11..12
+            NAME@11..12
+              IDENT@11..12 "a"
+          WHITESPACE@12..13 " "
+          IN_KW@13..15 "in"
+          WHITESPACE@15..16 " "
+          EXPR@16..25
+            EXPR_ARRAY@16..25
+              L_BRACK@16..17 "["
+              EXPR@17..18
+                EXPR_NUMBER@17..18
+                  FLOAT@17..18 "1"
+              COMMA@18..19 ","
+              WHITESPACE@19..20 " "
+              EXPR@20..21
+                EXPR_NUMBER@20..21
+                  FLOAT@20..21 "2"
+              COMMA@21..22 ","
+              WHITESPACE@22..23 " "
+              EXPR@23..24
+                EXPR_NUMBER@23..24
+                  FLOAT@23..24 "3"
+              R_BRACK@24..25 "]"
+      COMMA@25..26 ","
+      WHITESPACE@26..27 " "
+      EXPR@27..28
+        EXPR_VAR@27..28
+          NAME@27..28
+            IDENT@27..28 "c"
+      R_BRACK@28..29 "]"
+  WHITESPACE@29..30 "\n"
+===
+LocatedSyntaxError { error: Custom { error: "compspec may only be used if there is only one array element" }, range: 7..25 }
+===
+  x syntax error
+   ,----
+ 1 | [a, b, for a in [1, 2, 3], c]
+   :        ^^^^^^^^^|^^^^^^^^
+   :                 `-- compspec may only be used if there is only one array element
+   `----
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__arr_compspec_no_elems.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__arr_compspec_no_elems.snap
@@ -0,0 +1,47 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "[for a in [1, 2, 3]]\n"
+---
+SOURCE_FILE@0..21
+  EXPR@0..20
+    EXPR_ARRAY_COMP@0..20
+      L_BRACK@0..1 "["
+      EXPR@1..1
+        ERROR_MISSING_TOKEN@1..1
+      FOR_SPEC@1..19
+        FOR_KW@1..4 "for"
+        WHITESPACE@4..5 " "
+        DESTRUCT_FULL@5..6
+          NAME@5..6
+            IDENT@5..6 "a"
+        WHITESPACE@6..7 " "
+        IN_KW@7..9 "in"
+        WHITESPACE@9..10 " "
+        EXPR@10..19
+          EXPR_ARRAY@10..19
+            L_BRACK@10..11 "["
+            EXPR@11..12
+              EXPR_NUMBER@11..12
+                FLOAT@11..12 "1"
+            COMMA@12..13 ","
+            WHITESPACE@13..14 " "
+            EXPR@14..15
+              EXPR_NUMBER@14..15
+                FLOAT@14..15 "2"
+            COMMA@15..16 ","
+            WHITESPACE@16..17 " "
+            EXPR@17..18
+              EXPR_NUMBER@17..18
+                FLOAT@17..18 "3"
+            R_BRACK@18..19 "]"
+      R_BRACK@19..20 "]"
+  WHITESPACE@20..21 "\n"
+===
+LocatedSyntaxError { error: Missing { expected: Named("expression") }, range: 1..1 }
+===
+  x syntax error
+   ,----
+ 1 | [for a in [1, 2, 3]]
+   :  ^
+   :  `-- missing expression
+   `----
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__array_comp.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__array_comp.snap
@@ -0,0 +1,35 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "[a for a in [1, 2, 3]]\n"
+---
+SOURCE_FILE@0..23
+  EXPR_ARRAY_COMP@0..22
+    L_BRACK@0..1 "["
+    EXPR_VAR@1..2
+      NAME@1..2
+        IDENT@1..2 "a"
+    WHITESPACE@2..3 " "
+    FOR_SPEC@3..21
+      FOR_KW@3..6 "for"
+      WHITESPACE@6..7 " "
+      NAME@7..8
+        IDENT@7..8 "a"
+      WHITESPACE@8..9 " "
+      IN_KW@9..11 "in"
+      WHITESPACE@11..12 " "
+      EXPR_ARRAY@12..21
+        L_BRACK@12..13 "["
+        EXPR_NUMBER@13..14
+          FLOAT@13..14 "1"
+        COMMA@14..15 ","
+        WHITESPACE@15..16 " "
+        EXPR_NUMBER@16..17
+          FLOAT@16..17 "2"
+        COMMA@17..18 ","
+        WHITESPACE@18..19 " "
+        EXPR_NUMBER@19..20
+          FLOAT@19..20 "3"
+        R_BRACK@20..21 "]"
+    R_BRACK@21..22 "]"
+  WHITESPACE@22..23 "\n"
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__array_comp_incompatible_with_multiple_elems.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__array_comp_incompatible_with_multiple_elems.snap
@@ -0,0 +1,48 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "[a for a in [1, 2, 3], b]\n"
+---
+SOURCE_FILE@0..25
+  EXPR_ARRAY@0..25
+    L_BRACK@0..1 "["
+    EXPR_VAR@1..2
+      NAME@1..2
+        IDENT@1..2 "a"
+    WHITESPACE@2..3 " "
+    FOR_SPEC@3..21
+      FOR_KW@3..6 "for"
+      WHITESPACE@6..7 " "
+      NAME@7..8
+        IDENT@7..8 "a"
+      WHITESPACE@8..9 " "
+      IN_KW@9..11 "in"
+      WHITESPACE@11..12 " "
+      EXPR_ARRAY@12..21
+        L_BRACK@12..13 "["
+        EXPR_NUMBER@13..14
+          FLOAT@13..14 "1"
+        COMMA@14..15 ","
+        WHITESPACE@15..16 " "
+        EXPR_NUMBER@16..17
+          FLOAT@16..17 "2"
+        COMMA@17..18 ","
+        WHITESPACE@18..19 " "
+        EXPR_NUMBER@19..20
+          FLOAT@19..20 "3"
+        R_BRACK@20..21 "]"
+    COMMA@21..22 ","
+    WHITESPACE@22..23 " "
+    EXPR_VAR@23..24
+      NAME@23..24
+        IDENT@23..24 "b"
+    R_BRACK@24..25 "]"
+===
+Custom { error: "compspec may only be used if there is only one array element", range: 3..21 }
+===
+  x syntax error
+   ,----
+ 1 | [a for a in [1, 2, 3], b]
+   :    ^^^^^^^^^|^^^^^^^^
+   :             `-- compspec may only be used if there is only one array element
+   `----
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__continue_after_total_failure.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__continue_after_total_failure.snap
@@ -0,0 +1,77 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "local intr = $intrinsic(test);\n\nlocal a = 1, b = 2, c = a + b;\n\n[c]\n"
+---
+SOURCE_FILE@0..68
+  EXPR@0..29
+    STMT_LOCAL@0..23
+      LOCAL_KW@0..5 "local"
+      WHITESPACE@5..6 " "
+      BIND_DESTRUCT@6..14
+        DESTRUCT_FULL@6..10
+          NAME@6..10
+            IDENT@6..10 "intr"
+        WHITESPACE@10..11 " "
+        ASSIGN@11..12 "="
+        WHITESPACE@12..13 " "
+        EXPR@13..14
+          EXPR_LITERAL@13..14
+            DOLLAR@13..14 "$"
+      ERROR_UNEXPECTED_TOKEN@14..23
+        IDENT@14..23 "intrinsic"
+    EXPR_PARENED@23..29
+      L_PAREN@23..24 "("
+      EXPR@24..28
+        EXPR_VAR@24..28
+          NAME@24..28
+            IDENT@24..28 "test"
+      R_PAREN@28..29 ")"
+  ERROR_CUSTOM@29..67
+    SEMI@29..30 ";"
+    WHITESPACE@30..32 "\n\n"
+    LOCAL_KW@32..37 "local"
+    WHITESPACE@37..38 " "
+    IDENT@38..39 "a"
+    WHITESPACE@39..40 " "
+    ASSIGN@40..41 "="
+    WHITESPACE@41..42 " "
+    FLOAT@42..43 "1"
+    COMMA@43..44 ","
+    WHITESPACE@44..45 " "
+    IDENT@45..46 "b"
+    WHITESPACE@46..47 " "
+    ASSIGN@47..48 "="
+    WHITESPACE@48..49 " "
+    FLOAT@49..50 "2"
+    COMMA@50..51 ","
+    WHITESPACE@51..52 " "
+    IDENT@52..53 "c"
+    WHITESPACE@53..54 " "
+    ASSIGN@54..55 "="
+    WHITESPACE@55..56 " "
+    IDENT@56..57 "a"
+    WHITESPACE@57..58 " "
+    PLUS@58..59 "+"
+    WHITESPACE@59..60 " "
+    IDENT@60..61 "b"
+    SEMI@61..62 ";"
+    WHITESPACE@62..64 "\n\n"
+    L_BRACK@64..65 "["
+    IDENT@65..66 "c"
+    R_BRACK@66..67 "]"
+  WHITESPACE@67..68 "\n"
+===
+LocatedSyntaxError { error: Unexpected { expected: Unnamed(SyntaxKindSet([L_BRACK, L_PAREN, L_BRACE, SEMI, DOT, COMMA, QUESTION_MARK])), found: IDENT }, range: 14..23 }
+LocatedSyntaxError { error: Custom { error: "unexpected tokens after end" }, range: 29..67 }
+===
+  x syntax error
+   ,-[1:1]
+ 1 | ,-> local intr = $intrinsic(test);
+   : ||              ^^^^|^^^^
+   : ||                  `-- expected L_BRACK, L_PAREN, L_BRACE, SEMI, DOT, COMMA or QUESTION_MARK, found IDENT
+ 2 | |
+ 3 | |   local a = 1, b = 2, c = a + b;
+ 4 | |
+ 5 | |-> [c]
+   : `---- unexpected tokens after end
+   `----
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__destruct.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__destruct.snap
@@ -0,0 +1,265 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "local [a, b, c] = arr;\nlocal [a, ...] = arr_rest;\nlocal [..., a] = rest_arr;\nlocal [...] = rest_in_arr;\nlocal [a, ...n] = arr_rest_n;\nlocal [...n, a] = rest_arr_n;\nlocal [...n] = rest_in_arr_n;\n\nlocal {a, b, c} = obj;\nlocal {a, b, c, ...} = obj_rest;\nlocal {a, b, c, ...n} = obj_rest_n;\n\nnull\n"
+---
+SOURCE_FILE@0..293
+  EXPR@0..292
+    STMT_LOCAL@0..22
+      LOCAL_KW@0..5 "local"
+      WHITESPACE@5..6 " "
+      BIND_DESTRUCT@6..21
+        DESTRUCT_ARRAY@6..15
+          L_BRACK@6..7 "["
+          DESTRUCT_FULL@7..8
+            NAME@7..8
+              IDENT@7..8 "a"
+          COMMA@8..9 ","
+          WHITESPACE@9..10 " "
+          DESTRUCT_FULL@10..11
+            NAME@10..11
+              IDENT@10..11 "b"
+          COMMA@11..12 ","
+          WHITESPACE@12..13 " "
+          DESTRUCT_FULL@13..14
+            NAME@13..14
+              IDENT@13..14 "c"
+          R_BRACK@14..15 "]"
+        WHITESPACE@15..16 " "
+        ASSIGN@16..17 "="
+        WHITESPACE@17..18 " "
+        EXPR@18..21
+          EXPR_VAR@18..21
+            NAME@18..21
+              IDENT@18..21 "arr"
+      SEMI@21..22 ";"
+    WHITESPACE@22..23 "\n"
+    STMT_LOCAL@23..49
+      LOCAL_KW@23..28 "local"
+      WHITESPACE@28..29 " "
+      BIND_DESTRUCT@29..48
+        DESTRUCT_ARRAY@29..37
+          L_BRACK@29..30 "["
+          DESTRUCT_FULL@30..31
+            NAME@30..31
+              IDENT@30..31 "a"
+          COMMA@31..32 ","
+          WHITESPACE@32..33 " "
+          DESTRUCT_REST@33..36
+            DOTDOTDOT@33..36 "..."
+          R_BRACK@36..37 "]"
+        WHITESPACE@37..38 " "
+        ASSIGN@38..39 "="
+        WHITESPACE@39..40 " "
+        EXPR@40..48
+          EXPR_VAR@40..48
+            NAME@40..48
+              IDENT@40..48 "arr_rest"
+      SEMI@48..49 ";"
+    WHITESPACE@49..50 "\n"
+    STMT_LOCAL@50..76
+      LOCAL_KW@50..55 "local"
+      WHITESPACE@55..56 " "
+      BIND_DESTRUCT@56..75
+        DESTRUCT_ARRAY@56..64
+          L_BRACK@56..57 "["
+          DESTRUCT_REST@57..60
+            DOTDOTDOT@57..60 "..."
+          COMMA@60..61 ","
+          WHITESPACE@61..62 " "
+          DESTRUCT_FULL@62..63
+            NAME@62..63
+              IDENT@62..63 "a"
+          R_BRACK@63..64 "]"
+        WHITESPACE@64..65 " "
+        ASSIGN@65..66 "="
+        WHITESPACE@66..67 " "
+        EXPR@67..75
+          EXPR_VAR@67..75
+            NAME@67..75
+              IDENT@67..75 "rest_arr"
+      SEMI@75..76 ";"
+    WHITESPACE@76..77 "\n"
+    STMT_LOCAL@77..103
+      LOCAL_KW@77..82 "local"
+      WHITESPACE@82..83 " "
+      BIND_DESTRUCT@83..102
+        DESTRUCT_ARRAY@83..88
+          L_BRACK@83..84 "["
+          DESTRUCT_REST@84..87
+            DOTDOTDOT@84..87 "..."
+          R_BRACK@87..88 "]"
+        WHITESPACE@88..89 " "
+        ASSIGN@89..90 "="
+        WHITESPACE@90..91 " "
+        EXPR@91..102
+          EXPR_VAR@91..102
+            NAME@91..102
+              IDENT@91..102 "rest_in_arr"
+      SEMI@102..103 ";"
+    WHITESPACE@103..104 "\n"
+    STMT_LOCAL@104..133
+      LOCAL_KW@104..109 "local"
+      WHITESPACE@109..110 " "
+      BIND_DESTRUCT@110..132
+        DESTRUCT_ARRAY@110..119
+          L_BRACK@110..111 "["
+          DESTRUCT_FULL@111..112
+            NAME@111..112
+              IDENT@111..112 "a"
+          COMMA@112..113 ","
+          WHITESPACE@113..114 " "
+          DESTRUCT_REST@114..118
+            DOTDOTDOT@114..117 "..."
+            IDENT@117..118 "n"
+          R_BRACK@118..119 "]"
+        WHITESPACE@119..120 " "
+        ASSIGN@120..121 "="
+        WHITESPACE@121..122 " "
+        EXPR@122..132
+          EXPR_VAR@122..132
+            NAME@122..132
+              IDENT@122..132 "arr_rest_n"
+      SEMI@132..133 ";"
+    WHITESPACE@133..134 "\n"
+    STMT_LOCAL@134..163
+      LOCAL_KW@134..139 "local"
+      WHITESPACE@139..140 " "
+      BIND_DESTRUCT@140..162
+        DESTRUCT_ARRAY@140..149
+          L_BRACK@140..141 "["
+          DESTRUCT_REST@141..145
+            DOTDOTDOT@141..144 "..."
+            IDENT@144..145 "n"
+          COMMA@145..146 ","
+          WHITESPACE@146..147 " "
+          DESTRUCT_FULL@147..148
+            NAME@147..148
+              IDENT@147..148 "a"
+          R_BRACK@148..149 "]"
+        WHITESPACE@149..150 " "
+        ASSIGN@150..151 "="
+        WHITESPACE@151..152 " "
+        EXPR@152..162
+          EXPR_VAR@152..162
+            NAME@152..162
+              IDENT@152..162 "rest_arr_n"
+      SEMI@162..163 ";"
+    WHITESPACE@163..164 "\n"
+    STMT_LOCAL@164..193
+      LOCAL_KW@164..169 "local"
+      WHITESPACE@169..170 " "
+      BIND_DESTRUCT@170..192
+        DESTRUCT_ARRAY@170..176
+          L_BRACK@170..171 "["
+          DESTRUCT_REST@171..175
+            DOTDOTDOT@171..174 "..."
+            IDENT@174..175 "n"
+          R_BRACK@175..176 "]"
+        WHITESPACE@176..177 " "
+        ASSIGN@177..178 "="
+        WHITESPACE@178..179 " "
+        EXPR@179..192
+          EXPR_VAR@179..192
+            NAME@179..192
+              IDENT@179..192 "rest_in_arr_n"
+      SEMI@192..193 ";"
+    WHITESPACE@193..195 "\n\n"
+    STMT_LOCAL@195..217
+      LOCAL_KW@195..200 "local"
+      WHITESPACE@200..201 " "
+      BIND_DESTRUCT@201..216
+        DESTRUCT_OBJECT@201..210
+          L_BRACE@201..202 "{"
+          DESTRUCT_OBJECT_FIELD@202..203
+            NAME@202..203
+              IDENT@202..203 "a"
+          COMMA@203..204 ","
+          WHITESPACE@204..205 " "
+          DESTRUCT_OBJECT_FIELD@205..206
+            NAME@205..206
+              IDENT@205..206 "b"
+          COMMA@206..207 ","
+          WHITESPACE@207..208 " "
+          DESTRUCT_OBJECT_FIELD@208..209
+            NAME@208..209
+              IDENT@208..209 "c"
+          R_BRACE@209..210 "}"
+        WHITESPACE@210..211 " "
+        ASSIGN@211..212 "="
+        WHITESPACE@212..213 " "
+        EXPR@213..216
+          EXPR_VAR@213..216
+            NAME@213..216
+              IDENT@213..216 "obj"
+      SEMI@216..217 ";"
+    WHITESPACE@217..218 "\n"
+    STMT_LOCAL@218..250
+      LOCAL_KW@218..223 "local"
+      WHITESPACE@223..224 " "
+      BIND_DESTRUCT@224..249
+        DESTRUCT_OBJECT@224..238
+          L_BRACE@224..225 "{"
+          DESTRUCT_OBJECT_FIELD@225..226
+            NAME@225..226
+              IDENT@225..226 "a"
+          COMMA@226..227 ","
+          WHITESPACE@227..228 " "
+          DESTRUCT_OBJECT_FIELD@228..229
+            NAME@228..229
+              IDENT@228..229 "b"
+          COMMA@229..230 ","
+          WHITESPACE@230..231 " "
+          DESTRUCT_OBJECT_FIELD@231..232
+            NAME@231..232
+              IDENT@231..232 "c"
+          COMMA@232..233 ","
+          WHITESPACE@233..234 " "
+          DESTRUCT_REST@234..237
+            DOTDOTDOT@234..237 "..."
+          R_BRACE@237..238 "}"
+        WHITESPACE@238..239 " "
+        ASSIGN@239..240 "="
+        WHITESPACE@240..241 " "
+        EXPR@241..249
+          EXPR_VAR@241..249
+            NAME@241..249
+              IDENT@241..249 "obj_rest"
+      SEMI@249..250 ";"
+    WHITESPACE@250..251 "\n"
+    STMT_LOCAL@251..286
+      LOCAL_KW@251..256 "local"
+      WHITESPACE@256..257 " "
+      BIND_DESTRUCT@257..285
+        DESTRUCT_OBJECT@257..272
+          L_BRACE@257..258 "{"
+          DESTRUCT_OBJECT_FIELD@258..259
+            NAME@258..259
+              IDENT@258..259 "a"
+          COMMA@259..260 ","
+          WHITESPACE@260..261 " "
+          DESTRUCT_OBJECT_FIELD@261..262
+            NAME@261..262
+              IDENT@261..262 "b"
+          COMMA@262..263 ","
+          WHITESPACE@263..264 " "
+          DESTRUCT_OBJECT_FIELD@264..265
+            NAME@264..265
+              IDENT@264..265 "c"
+          COMMA@265..266 ","
+          WHITESPACE@266..267 " "
+          DESTRUCT_REST@267..271
+            DOTDOTDOT@267..270 "..."
+            IDENT@270..271 "n"
+          R_BRACE@271..272 "}"
+        WHITESPACE@272..273 " "
+        ASSIGN@273..274 "="
+        WHITESPACE@274..275 " "
+        EXPR@275..285
+          EXPR_VAR@275..285
+            NAME@275..285
+              IDENT@275..285 "obj_rest_n"
+      SEMI@285..286 ";"
+    WHITESPACE@286..288 "\n\n"
+    EXPR_LITERAL@288..292
+      NULL_KW@288..292 "null"
+  WHITESPACE@292..293 "\n"
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__empty.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__empty.snap
@@ -0,0 +1,17 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: " "
+---
+SOURCE_FILE@0..1
+  WHITESPACE@0..1 " "
+  EXPR@1..1
+    ERROR_MISSING_TOKEN@1..1
+===
+LocatedSyntaxError { error: Missing { expected: Named("expression") }, range: 1..1 }
+===
+  x syntax error
+   ,----
+ 1 |
+   :  ^
+   :  `-- missing expression
+   `----
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__function.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__function.snap
@@ -0,0 +1,42 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "function(a, b = 1) a + b\n"
+---
+SOURCE_FILE@0..25
+  EXPR@0..24
+    EXPR_FUNCTION@0..24
+      FUNCTION_KW@0..8 "function"
+      PARAMS_DESC@8..18
+        L_PAREN@8..9 "("
+        PARAM@9..10
+          DESTRUCT_FULL@9..10
+            NAME@9..10
+              IDENT@9..10 "a"
+        COMMA@10..11 ","
+        WHITESPACE@11..12 " "
+        PARAM@12..17
+          DESTRUCT_FULL@12..13
+            NAME@12..13
+              IDENT@12..13 "b"
+          WHITESPACE@13..14 " "
+          ASSIGN@14..15 "="
+          WHITESPACE@15..16 " "
+          EXPR@16..17
+            EXPR_NUMBER@16..17
+              FLOAT@16..17 "1"
+        R_PAREN@17..18 ")"
+      WHITESPACE@18..19 " "
+      EXPR@19..24
+        EXPR_BINARY@19..24
+          EXPR@19..20
+            EXPR_VAR@19..20
+              NAME@19..20
+                IDENT@19..20 "a"
+          WHITESPACE@20..21 " "
+          PLUS@21..22 "+"
+          WHITESPACE@22..23 " "
+          EXPR@23..24
+            EXPR_VAR@23..24
+              NAME@23..24
+                IDENT@23..24 "b"
+  WHITESPACE@24..25 "\n"
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__function_error_body.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__function_error_body.snap
@@ -0,0 +1,33 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "function(a, b)\n"
+---
+SOURCE_FILE@0..15
+  EXPR@0..15
+    EXPR_FUNCTION@0..15
+      FUNCTION_KW@0..8 "function"
+      PARAMS_DESC@8..14
+        L_PAREN@8..9 "("
+        PARAM@9..10
+          DESTRUCT_FULL@9..10
+            NAME@9..10
+              IDENT@9..10 "a"
+        COMMA@10..11 ","
+        WHITESPACE@11..12 " "
+        PARAM@12..13
+          DESTRUCT_FULL@12..13
+            NAME@12..13
+              IDENT@12..13 "b"
+        R_PAREN@13..14 ")"
+      WHITESPACE@14..15 "\n"
+      EXPR@15..15
+        ERROR_MISSING_TOKEN@15..15
+===
+LocatedSyntaxError { error: Missing { expected: Named("expression") }, range: 15..15 }
+===
+  x syntax error
+   ,----
+ 1 | function(a, b)
+   :                ^
+   :                `-- missing expression
+   `----
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__function_error_no_value.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__function_error_no_value.snap
@@ -0,0 +1,50 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "function(a, b = ) a + b\n"
+---
+SOURCE_FILE@0..24
+  EXPR@0..23
+    EXPR_FUNCTION@0..23
+      FUNCTION_KW@0..8 "function"
+      PARAMS_DESC@8..17
+        L_PAREN@8..9 "("
+        PARAM@9..10
+          DESTRUCT_FULL@9..10
+            NAME@9..10
+              IDENT@9..10 "a"
+        COMMA@10..11 ","
+        WHITESPACE@11..12 " "
+        PARAM@12..16
+          DESTRUCT_FULL@12..13
+            NAME@12..13
+              IDENT@12..13 "b"
+          WHITESPACE@13..14 " "
+          ASSIGN@14..15 "="
+          WHITESPACE@15..16 " "
+          EXPR@16..16
+            ERROR_MISSING_TOKEN@16..16
+        R_PAREN@16..17 ")"
+      WHITESPACE@17..18 " "
+      EXPR@18..23
+        EXPR_BINARY@18..23
+          EXPR@18..19
+            EXPR_VAR@18..19
+              NAME@18..19
+                IDENT@18..19 "a"
+          WHITESPACE@19..20 " "
+          PLUS@20..21 "+"
+          WHITESPACE@21..22 " "
+          EXPR@22..23
+            EXPR_VAR@22..23
+              NAME@22..23
+                IDENT@22..23 "b"
+  WHITESPACE@23..24 "\n"
+===
+LocatedSyntaxError { error: Missing { expected: Named("expression") }, range: 16..16 }
+===
+  x syntax error
+   ,----
+ 1 | function(a, b = ) a + b
+   :                 ^
+   :                 `-- missing expression
+   `----
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__function_error_rparen.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__function_error_rparen.snap
@@ -0,0 +1,35 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "function(a, b\n"
+---
+SOURCE_FILE@0..14
+  EXPR@0..14
+    EXPR_FUNCTION@0..14
+      FUNCTION_KW@0..8 "function"
+      PARAMS_DESC@8..14
+        L_PAREN@8..9 "("
+        PARAM@9..10
+          DESTRUCT_FULL@9..10
+            NAME@9..10
+              IDENT@9..10 "a"
+        COMMA@10..11 ","
+        WHITESPACE@11..12 " "
+        PARAM@12..13
+          DESTRUCT_FULL@12..13
+            NAME@12..13
+              IDENT@12..13 "b"
+        WHITESPACE@13..14 "\n"
+        ERROR_MISSING_TOKEN@14..14
+      EXPR@14..14
+        ERROR_MISSING_TOKEN@14..14
+===
+LocatedSyntaxError { error: Missing { expected: Unnamed(SyntaxKindSet([R_PAREN, COMMA, ASSIGN])) }, range: 14..14 }
+LocatedSyntaxError { error: Missing { expected: Named("expression") }, range: 14..14 }
+===
+  x syntax error
+   ,----
+ 1 | function(a, b
+   :               ^^
+   :               |`-- missing expression
+   :               `-- missing R_PAREN, COMMA or ASSIGN
+   `----
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__local_method.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__local_method.snap
@@ -0,0 +1,55 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "local\n\ta(x) = x,\n\ta = function(x) x,\n; c\n"
+---
+SOURCE_FILE@0..41
+  EXPR@0..40
+    STMT_LOCAL@0..38
+      LOCAL_KW@0..5 "local"
+      WHITESPACE@5..7 "\n\t"
+      BIND_FUNCTION@7..15
+        NAME@7..8
+          IDENT@7..8 "a"
+        PARAMS_DESC@8..11
+          L_PAREN@8..9 "("
+          PARAM@9..10
+            DESTRUCT_FULL@9..10
+              NAME@9..10
+                IDENT@9..10 "x"
+          R_PAREN@10..11 ")"
+        WHITESPACE@11..12 " "
+        ASSIGN@12..13 "="
+        WHITESPACE@13..14 " "
+        EXPR@14..15
+          EXPR_VAR@14..15
+            NAME@14..15
+              IDENT@14..15 "x"
+      COMMA@15..16 ","
+      WHITESPACE@16..18 "\n\t"
+      BIND_FUNCTION@18..35
+        NAME@18..19
+          IDENT@18..19 "a"
+        WHITESPACE@19..20 " "
+        ASSIGN@20..21 "="
+        WHITESPACE@21..22 " "
+        FUNCTION_KW@22..30 "function"
+        PARAMS_DESC@30..33
+          L_PAREN@30..31 "("
+          PARAM@31..32
+            DESTRUCT_FULL@31..32
+              NAME@31..32
+                IDENT@31..32 "x"
+          R_PAREN@32..33 ")"
+        WHITESPACE@33..34 " "
+        EXPR@34..35
+          EXPR_VAR@34..35
+            NAME@34..35
+              IDENT@34..35 "x"
+      COMMA@35..36 ","
+      WHITESPACE@36..37 "\n"
+      SEMI@37..38 ";"
+    WHITESPACE@38..39 " "
+    EXPR_VAR@39..40
+      NAME@39..40
+        IDENT@39..40 "c"
+  WHITESPACE@40..41 "\n"
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__local_no_value_recovery.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__local_no_value_recovery.snap
@@ -0,0 +1,49 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "local a =\nlocal b = 3;\n1\n"
+---
+SOURCE_FILE@0..25
+  EXPR@0..25
+    STMT_LOCAL@0..25
+      LOCAL_KW@0..5 "local"
+      WHITESPACE@5..6 " "
+      BIND_DESTRUCT@6..24
+        DESTRUCT_FULL@6..7
+          NAME@6..7
+            IDENT@6..7 "a"
+        WHITESPACE@7..8 " "
+        ASSIGN@8..9 "="
+        WHITESPACE@9..10 "\n"
+        EXPR@10..24
+          STMT_LOCAL@10..22
+            LOCAL_KW@10..15 "local"
+            WHITESPACE@15..16 " "
+            BIND_DESTRUCT@16..21
+              DESTRUCT_FULL@16..17
+                NAME@16..17
+                  IDENT@16..17 "b"
+              WHITESPACE@17..18 " "
+              ASSIGN@18..19 "="
+              WHITESPACE@19..20 " "
+              EXPR@20..21
+                EXPR_NUMBER@20..21
+                  FLOAT@20..21 "3"
+            SEMI@21..22 ";"
+          WHITESPACE@22..23 "\n"
+          EXPR_NUMBER@23..24
+            FLOAT@23..24 "1"
+      WHITESPACE@24..25 "\n"
+      ERROR_MISSING_TOKEN@25..25
+    ERROR_MISSING_TOKEN@25..25
+===
+LocatedSyntaxError { error: Missing { expected: Unnamed(SyntaxKindSet([L_BRACK, L_PAREN, L_BRACE, SEMI, DOT, COMMA, QUESTION_MARK])) }, range: 25..25 }
+LocatedSyntaxError { error: Missing { expected: Named("expression") }, range: 25..25 }
+===
+  x syntax error
+   ,-[2:1]
+ 2 | local b = 3;
+ 3 | 1
+   :   ^^
+   :   |`-- missing expression
+   :   `-- missing L_BRACK, L_PAREN, L_BRACE, SEMI, DOT, COMMA or QUESTION_MARK
+   `----
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__local_novalue.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__local_novalue.snap
@@ -0,0 +1,33 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "local a =\n"
+---
+SOURCE_FILE@0..10
+  EXPR@0..10
+    STMT_LOCAL@0..10
+      LOCAL_KW@0..5 "local"
+      WHITESPACE@5..6 " "
+      BIND_DESTRUCT@6..10
+        DESTRUCT_FULL@6..7
+          NAME@6..7
+            IDENT@6..7 "a"
+        WHITESPACE@7..8 " "
+        ASSIGN@8..9 "="
+        WHITESPACE@9..10 "\n"
+        EXPR@10..10
+          ERROR_MISSING_TOKEN@10..10
+      ERROR_MISSING_TOKEN@10..10
+    ERROR_MISSING_TOKEN@10..10
+===
+LocatedSyntaxError { error: Missing { expected: Named("expression") }, range: 10..10 }
+LocatedSyntaxError { error: Missing { expected: Unnamed(SyntaxKindSet([SEMI, COMMA])) }, range: 10..10 }
+LocatedSyntaxError { error: Missing { expected: Named("expression") }, range: 10..10 }
+===
+  x syntax error
+   ,----
+ 1 | local a =
+   :           ^^^
+   :           `-- missing expression
+   :           |`-- missing SEMI or COMMA
+   :           `-- missing expression
+   `----
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__named_before_positional.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__named_before_positional.snap
@@ -0,0 +1,78 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "a(1, 2, b=4, 3, 5, k = 12, 6)\n"
+---
+SOURCE_FILE@0..30
+  EXPR@0..29
+    EXPR_VAR@0..1
+      NAME@0..1
+        IDENT@0..1 "a"
+    SUFFIX_APPLY@1..29
+      ARGS_DESC@1..29
+        L_PAREN@1..2 "("
+        ARG@2..3
+          EXPR@2..3
+            EXPR_NUMBER@2..3
+              FLOAT@2..3 "1"
+        COMMA@3..4 ","
+        WHITESPACE@4..5 " "
+        ARG@5..6
+          EXPR@5..6
+            EXPR_NUMBER@5..6
+              FLOAT@5..6 "2"
+        COMMA@6..7 ","
+        WHITESPACE@7..8 " "
+        ARG@8..11
+          NAME@8..9
+            IDENT@8..9 "b"
+          ASSIGN@9..10 "="
+          EXPR@10..11
+            EXPR_NUMBER@10..11
+              FLOAT@10..11 "4"
+        COMMA@11..12 ","
+        WHITESPACE@12..13 " "
+        ERROR_CUSTOM@13..14
+          ARG@13..14
+            EXPR@13..14
+              EXPR_NUMBER@13..14
+                FLOAT@13..14 "3"
+        COMMA@14..15 ","
+        WHITESPACE@15..16 " "
+        ERROR_CUSTOM@16..17
+          ARG@16..17
+            EXPR@16..17
+              EXPR_NUMBER@16..17
+                FLOAT@16..17 "5"
+        COMMA@17..18 ","
+        WHITESPACE@18..19 " "
+        ARG@19..25
+          NAME@19..20
+            IDENT@19..20 "k"
+          WHITESPACE@20..21 " "
+          ASSIGN@21..22 "="
+          WHITESPACE@22..23 " "
+          EXPR@23..25
+            EXPR_NUMBER@23..25
+              FLOAT@23..25 "12"
+        COMMA@25..26 ","
+        WHITESPACE@26..27 " "
+        ERROR_CUSTOM@27..28
+          ARG@27..28
+            EXPR@27..28
+              EXPR_NUMBER@27..28
+                FLOAT@27..28 "6"
+        R_PAREN@28..29 ")"
+  WHITESPACE@29..30 "\n"
+===
+LocatedSyntaxError { error: Custom { error: "can't use positional arguments after named" }, range: 13..14 }
+LocatedSyntaxError { error: Custom { error: "can't use positional arguments after named" }, range: 16..17 }
+LocatedSyntaxError { error: Custom { error: "can't use positional arguments after named" }, range: 27..28 }
+===
+  x syntax error
+   ,----
+ 1 | a(1, 2, b=4, 3, 5, k = 12, 6)
+   :              |  |          |
+   :              |  |          `-- can't use positional arguments after named
+   :              |  `-- can't use positional arguments after named
+   :              `-- can't use positional arguments after named
+   `----
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__no_lhs.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__no_lhs.snap
@@ -0,0 +1,23 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "+ 2\n"
+---
+SOURCE_FILE@0..4
+  EXPR@0..0
+    ERROR_MISSING_TOKEN@0..0
+  ERROR_CUSTOM@0..3
+    PLUS@0..1 "+"
+    WHITESPACE@1..2 " "
+    FLOAT@2..3 "2"
+  WHITESPACE@3..4 "\n"
+===
+LocatedSyntaxError { error: Missing { expected: Named("expression") }, range: 0..0 }
+LocatedSyntaxError { error: Custom { error: "unexpected tokens after end" }, range: 0..3 }
+===
+  x syntax error
+   ,----
+ 1 | + 2
+   : ^^|
+   : | `-- unexpected tokens after end
+   : `-- missing expression
+   `----
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__no_operator.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__no_operator.snap
@@ -0,0 +1,21 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "2 2\n"
+---
+SOURCE_FILE@0..4
+  EXPR@0..1
+    EXPR_NUMBER@0..1
+      FLOAT@0..1 "2"
+  WHITESPACE@1..2 " "
+  ERROR_CUSTOM@2..3
+    FLOAT@2..3 "2"
+  WHITESPACE@3..4 "\n"
+===
+LocatedSyntaxError { error: Custom { error: "unexpected tokens after end" }, range: 2..3 }
+===
+  x syntax error
+   ,----
+ 1 | 2 2
+   :   |
+   :   `-- unexpected tokens after end
+   `----
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__no_rhs.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__no_rhs.snap
@@ -0,0 +1,24 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "a +\n"
+---
+SOURCE_FILE@0..4
+  EXPR@0..4
+    EXPR_BINARY@0..4
+      EXPR@0..1
+        EXPR_VAR@0..1
+          NAME@0..1
+            IDENT@0..1 "a"
+      WHITESPACE@1..2 " "
+      PLUS@2..3 "+"
+      WHITESPACE@3..4 "\n"
+      ERROR_MISSING_TOKEN@4..4
+===
+LocatedSyntaxError { error: Missing { expected: Named("expression") }, range: 4..4 }
+===
+  x syntax error
+   ,----
+ 1 | a +
+   :     ^
+   :     `-- missing expression
+   `----
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__obj_compspec.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__obj_compspec.snap
@@ -0,0 +1,46 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "{a:1 for a in [1, 2, 3]}\n"
+---
+SOURCE_FILE@0..25
+  EXPR@0..24
+    EXPR_OBJECT@0..24
+      OBJ_BODY_COMP@0..24
+        L_BRACE@0..1 "{"
+        MEMBER_FIELD_NORMAL@1..4
+          FIELD_NAME_FIXED@1..2
+            NAME@1..2
+              IDENT@1..2 "a"
+          COLON@2..3 ":"
+          EXPR@3..4
+            EXPR_NUMBER@3..4
+              FLOAT@3..4 "1"
+        WHITESPACE@4..5 " "
+        FOR_SPEC@5..23
+          FOR_KW@5..8 "for"
+          WHITESPACE@8..9 " "
+          DESTRUCT_FULL@9..10
+            NAME@9..10
+              IDENT@9..10 "a"
+          WHITESPACE@10..11 " "
+          IN_KW@11..13 "in"
+          WHITESPACE@13..14 " "
+          EXPR@14..23
+            EXPR_ARRAY@14..23
+              L_BRACK@14..15 "["
+              EXPR@15..16
+                EXPR_NUMBER@15..16
+                  FLOAT@15..16 "1"
+              COMMA@16..17 ","
+              WHITESPACE@17..18 " "
+              EXPR@18..19
+                EXPR_NUMBER@18..19
+                  FLOAT@18..19 "2"
+              COMMA@19..20 ","
+              WHITESPACE@20..21 " "
+              EXPR@21..22
+                EXPR_NUMBER@21..22
+                  FLOAT@21..22 "3"
+              R_BRACK@22..23 "]"
+        R_BRACE@23..24 "}"
+  WHITESPACE@24..25 "\n"
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__obj_compspec_comma.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__obj_compspec_comma.snap
@@ -0,0 +1,47 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "{a:1, for a in [1, 2, 3]}\n"
+---
+SOURCE_FILE@0..26
+  EXPR@0..25
+    EXPR_OBJECT@0..25
+      OBJ_BODY_COMP@0..25
+        L_BRACE@0..1 "{"
+        MEMBER_FIELD_NORMAL@1..4
+          FIELD_NAME_FIXED@1..2
+            NAME@1..2
+              IDENT@1..2 "a"
+          COLON@2..3 ":"
+          EXPR@3..4
+            EXPR_NUMBER@3..4
+              FLOAT@3..4 "1"
+        COMMA@4..5 ","
+        WHITESPACE@5..6 " "
+        FOR_SPEC@6..24
+          FOR_KW@6..9 "for"
+          WHITESPACE@9..10 " "
+          DESTRUCT_FULL@10..11
+            NAME@10..11
+              IDENT@10..11 "a"
+          WHITESPACE@11..12 " "
+          IN_KW@12..14 "in"
+          WHITESPACE@14..15 " "
+          EXPR@15..24
+            EXPR_ARRAY@15..24
+              L_BRACK@15..16 "["
+              EXPR@16..17
+                EXPR_NUMBER@16..17
+                  FLOAT@16..17 "1"
+              COMMA@17..18 ","
+              WHITESPACE@18..19 " "
+              EXPR@19..20
+                EXPR_NUMBER@19..20
+                  FLOAT@19..20 "2"
+              COMMA@20..21 ","
+              WHITESPACE@21..22 " "
+              EXPR@22..23
+                EXPR_NUMBER@22..23
+                  FLOAT@22..23 "3"
+              R_BRACK@23..24 "]"
+        R_BRACE@24..25 "}"
+  WHITESPACE@25..26 "\n"
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__obj_compspec_incompatible_with_asserts.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__obj_compspec_incompatible_with_multiple_elems.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__obj_compspec_incompatible_with_multiple_elems.snap
@@ -0,0 +1,66 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "{a:1 for a in [1, 2, 3], b:1}\n"
+---
+SOURCE_FILE@0..30
+  EXPR@0..29
+    EXPR_OBJECT@0..29
+      OBJ_BODY_MEMBER_LIST@0..29
+        L_BRACE@0..1 "{"
+        MEMBER_FIELD_NORMAL@1..4
+          FIELD_NAME_FIXED@1..2
+            NAME@1..2
+              IDENT@1..2 "a"
+          COLON@2..3 ":"
+          EXPR@3..4
+            EXPR_NUMBER@3..4
+              FLOAT@3..4 "1"
+        WHITESPACE@4..5 " "
+        ERROR_CUSTOM@5..23
+          FOR_SPEC@5..23
+            FOR_KW@5..8 "for"
+            WHITESPACE@8..9 " "
+            DESTRUCT_FULL@9..10
+              NAME@9..10
+                IDENT@9..10 "a"
+            WHITESPACE@10..11 " "
+            IN_KW@11..13 "in"
+            WHITESPACE@13..14 " "
+            EXPR@14..23
+              EXPR_ARRAY@14..23
+                L_BRACK@14..15 "["
+                EXPR@15..16
+                  EXPR_NUMBER@15..16
+                    FLOAT@15..16 "1"
+                COMMA@16..17 ","
+                WHITESPACE@17..18 " "
+                EXPR@18..19
+                  EXPR_NUMBER@18..19
+                    FLOAT@18..19 "2"
+                COMMA@19..20 ","
+                WHITESPACE@20..21 " "
+                EXPR@21..22
+                  EXPR_NUMBER@21..22
+                    FLOAT@21..22 "3"
+                R_BRACK@22..23 "]"
+        COMMA@23..24 ","
+        WHITESPACE@24..25 " "
+        MEMBER_FIELD_NORMAL@25..28
+          FIELD_NAME_FIXED@25..26
+            NAME@25..26
+              IDENT@25..26 "b"
+          COLON@26..27 ":"
+          EXPR@27..28
+            EXPR_NUMBER@27..28
+              FLOAT@27..28 "1"
+        R_BRACE@28..29 "}"
+  WHITESPACE@29..30 "\n"
+===
+LocatedSyntaxError { error: Custom { error: "compspec may only be used if there is only one object element" }, range: 5..23 }
+===
+  x syntax error
+   ,----
+ 1 | {a:1 for a in [1, 2, 3], b:1}
+   :      ^^^^^^^^^|^^^^^^^^
+   :               `-- compspec may only be used if there is only one object element
+   `----
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__obj_compspec_incompatible_with_multiple_elems_w.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__obj_compspec_incompatible_with_multiple_elems_w.snap
@@ -0,0 +1,77 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "{a:1, b:1, for a in [1, 2, 3], c:1}\n"
+---
+SOURCE_FILE@0..36
+  EXPR@0..35
+    EXPR_OBJECT@0..35
+      OBJ_BODY_MEMBER_LIST@0..35
+        L_BRACE@0..1 "{"
+        MEMBER_FIELD_NORMAL@1..4
+          FIELD_NAME_FIXED@1..2
+            NAME@1..2
+              IDENT@1..2 "a"
+          COLON@2..3 ":"
+          EXPR@3..4
+            EXPR_NUMBER@3..4
+              FLOAT@3..4 "1"
+        COMMA@4..5 ","
+        WHITESPACE@5..6 " "
+        MEMBER_FIELD_NORMAL@6..9
+          FIELD_NAME_FIXED@6..7
+            NAME@6..7
+              IDENT@6..7 "b"
+          COLON@7..8 ":"
+          EXPR@8..9
+            EXPR_NUMBER@8..9
+              FLOAT@8..9 "1"
+        COMMA@9..10 ","
+        WHITESPACE@10..11 " "
+        ERROR_CUSTOM@11..29
+          FOR_SPEC@11..29
+            FOR_KW@11..14 "for"
+            WHITESPACE@14..15 " "
+            DESTRUCT_FULL@15..16
+              NAME@15..16
+                IDENT@15..16 "a"
+            WHITESPACE@16..17 " "
+            IN_KW@17..19 "in"
+            WHITESPACE@19..20 " "
+            EXPR@20..29
+              EXPR_ARRAY@20..29
+                L_BRACK@20..21 "["
+                EXPR@21..22
+                  EXPR_NUMBER@21..22
+                    FLOAT@21..22 "1"
+                COMMA@22..23 ","
+                WHITESPACE@23..24 " "
+                EXPR@24..25
+                  EXPR_NUMBER@24..25
+                    FLOAT@24..25 "2"
+                COMMA@25..26 ","
+                WHITESPACE@26..27 " "
+                EXPR@27..28
+                  EXPR_NUMBER@27..28
+                    FLOAT@27..28 "3"
+                R_BRACK@28..29 "]"
+        COMMA@29..30 ","
+        WHITESPACE@30..31 " "
+        MEMBER_FIELD_NORMAL@31..34
+          FIELD_NAME_FIXED@31..32
+            NAME@31..32
+              IDENT@31..32 "c"
+          COLON@32..33 ":"
+          EXPR@33..34
+            EXPR_NUMBER@33..34
+              FLOAT@33..34 "1"
+        R_BRACE@34..35 "}"
+  WHITESPACE@35..36 "\n"
+===
+LocatedSyntaxError { error: Custom { error: "compspec may only be used if there is only one object element" }, range: 11..29 }
+===
+  x syntax error
+   ,----
+ 1 | {a:1, b:1, for a in [1, 2, 3], c:1}
+   :            ^^^^^^^^^|^^^^^^^^
+   :                     `-- compspec may only be used if there is only one object element
+   `----
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__obj_compspec_no_elems.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__obj_compspec_no_elems.snap
@@ -0,0 +1,47 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "{for a in [1, 2, 3]}\n"
+---
+SOURCE_FILE@0..21
+  EXPR@0..20
+    EXPR_OBJECT@0..20
+      OBJ_BODY_COMP@0..20
+        L_BRACE@0..1 "{"
+        ERROR_MISSING_TOKEN@1..1
+        FOR_SPEC@1..19
+          FOR_KW@1..4 "for"
+          WHITESPACE@4..5 " "
+          DESTRUCT_FULL@5..6
+            NAME@5..6
+              IDENT@5..6 "a"
+          WHITESPACE@6..7 " "
+          IN_KW@7..9 "in"
+          WHITESPACE@9..10 " "
+          EXPR@10..19
+            EXPR_ARRAY@10..19
+              L_BRACK@10..11 "["
+              EXPR@11..12
+                EXPR_NUMBER@11..12
+                  FLOAT@11..12 "1"
+              COMMA@12..13 ","
+              WHITESPACE@13..14 " "
+              EXPR@14..15
+                EXPR_NUMBER@14..15
+                  FLOAT@14..15 "2"
+              COMMA@15..16 ","
+              WHITESPACE@16..17 " "
+              EXPR@17..18
+                EXPR_NUMBER@17..18
+                  FLOAT@17..18 "3"
+              R_BRACK@18..19 "]"
+        R_BRACE@19..20 "}"
+  WHITESPACE@20..21 "\n"
+===
+LocatedSyntaxError { error: Missing { expected: Named("field definition") }, range: 1..1 }
+===
+  x syntax error
+   ,----
+ 1 | {for a in [1, 2, 3]}
+   :  ^
+   :  `-- missing field definition
+   `----
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__obj_method.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__obj_method.snap
@@ -0,0 +1,52 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "{\n\ta(x): x,\n\ta: function(x) x,\n}\n"
+---
+SOURCE_FILE@0..33
+  EXPR@0..32
+    EXPR_OBJECT@0..32
+      OBJ_BODY_MEMBER_LIST@0..32
+        L_BRACE@0..1 "{"
+        WHITESPACE@1..3 "\n\t"
+        MEMBER_FIELD_METHOD@3..10
+          FIELD_NAME_FIXED@3..4
+            NAME@3..4
+              IDENT@3..4 "a"
+          PARAMS_DESC@4..7
+            L_PAREN@4..5 "("
+            PARAM@5..6
+              DESTRUCT_FULL@5..6
+                NAME@5..6
+                  IDENT@5..6 "x"
+            R_PAREN@6..7 ")"
+          COLON@7..8 ":"
+          WHITESPACE@8..9 " "
+          EXPR@9..10
+            EXPR_VAR@9..10
+              NAME@9..10
+                IDENT@9..10 "x"
+        COMMA@10..11 ","
+        WHITESPACE@11..13 "\n\t"
+        MEMBER_FIELD_METHOD@13..29
+          FIELD_NAME_FIXED@13..14
+            NAME@13..14
+              IDENT@13..14 "a"
+          COLON@14..15 ":"
+          WHITESPACE@15..16 " "
+          FUNCTION_KW@16..24 "function"
+          PARAMS_DESC@24..27
+            L_PAREN@24..25 "("
+            PARAM@25..26
+              DESTRUCT_FULL@25..26
+                NAME@25..26
+                  IDENT@25..26 "x"
+            R_PAREN@26..27 ")"
+          WHITESPACE@27..28 " "
+          EXPR@28..29
+            EXPR_VAR@28..29
+              NAME@28..29
+                IDENT@28..29 "x"
+        COMMA@29..30 ","
+        WHITESPACE@30..31 "\n"
+        R_BRACE@31..32 "}"
+  WHITESPACE@32..33 "\n"
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__plain_call.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__plain_call.snap
@@ -0,0 +1,58 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "std.substr(a, 0, std.length(b)) == b\n"
+---
+SOURCE_FILE@0..37
+  EXPR@0..36
+    EXPR_BINARY@0..36
+      EXPR@0..3
+        EXPR_VAR@0..3
+          NAME@0..3
+            IDENT@0..3 "std"
+      SUFFIX_INDEX@3..10
+        DOT@3..4 "."
+        NAME@4..10
+          IDENT@4..10 "substr"
+      SUFFIX_APPLY@10..31
+        ARGS_DESC@10..31
+          L_PAREN@10..11 "("
+          ARG@11..12
+            EXPR@11..12
+              EXPR_VAR@11..12
+                NAME@11..12
+                  IDENT@11..12 "a"
+          COMMA@12..13 ","
+          WHITESPACE@13..14 " "
+          ARG@14..15
+            EXPR@14..15
+              EXPR_NUMBER@14..15
+                FLOAT@14..15 "0"
+          COMMA@15..16 ","
+          WHITESPACE@16..17 " "
+          ARG@17..30
+            EXPR@17..30
+              EXPR_VAR@17..20
+                NAME@17..20
+                  IDENT@17..20 "std"
+              SUFFIX_INDEX@20..27
+                DOT@20..21 "."
+                NAME@21..27
+                  IDENT@21..27 "length"
+              SUFFIX_APPLY@27..30
+                ARGS_DESC@27..30
+                  L_PAREN@27..28 "("
+                  ARG@28..29
+                    EXPR@28..29
+                      EXPR_VAR@28..29
+                        NAME@28..29
+                          IDENT@28..29 "b"
+                  R_PAREN@29..30 ")"
+          R_PAREN@30..31 ")"
+      WHITESPACE@31..32 " "
+      EQ@32..34 "=="
+      WHITESPACE@34..35 " "
+      EXPR@35..36
+        EXPR_VAR@35..36
+          NAME@35..36
+            IDENT@35..36 "b"
+  WHITESPACE@36..37 "\n"
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__stdlib.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__stdlib.snap
@@ -0,0 +1,5686 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "{\n  local std = self,\n  local id = std.id,\n\n  thisFile:: error 'std.thisFile is deprecated, to enable its support in jrsonnet - recompile it with \"legacy-this-file\" support.\\nThis will slow down stdlib caching a bit, though',\n\n  lstripChars(str, chars)::\n    if std.length(str) > 0 && std.member(chars, str[0]) then\n      std.lstripChars(str[1:], chars)\n    else\n      str,\n\n  rstripChars(str, chars)::\n    local len = std.length(str);\n    if len > 0 && std.member(chars, str[len - 1]) then\n      std.rstripChars(str[:len - 1], chars)\n    else\n      str,\n\n  stripChars(str, chars)::\n    std.lstripChars(std.rstripChars(str, chars), chars),\n\n  splitLimitR(str, c, maxsplits)::\n    if maxsplits == -1 then\n      std.splitLimit(str, c, -1)\n    else\n      local revStr(str) = std.join('', std.reverse(std.stringChars(str)));\n      std.map(function(e) revStr(e), std.reverse(std.splitLimit(revStr(str), revStr(c), maxsplits))),\n\n  split(str, c):: std.splitLimit(str, c, -1),\n\n  mapWithIndex(func, arr)::\n    if !std.isFunction(func) then\n      error ('std.mapWithIndex first param must be function, got ' + std.type(func))\n    else if !std.isArray(arr) && !std.isString(arr) then\n      error ('std.mapWithIndex second param must be array, got ' + std.type(arr))\n    else\n      std.makeArray(std.length(arr), function(i) func(i, arr[i])),\n\n  mapWithKey(func, obj)::\n    if !std.isFunction(func) then\n      error ('std.mapWithKey first param must be function, got ' + std.type(func))\n    else if !std.isObject(obj) then\n      error ('std.mapWithKey second param must be object, got ' + std.type(obj))\n    else\n      { [k]: func(k, obj[k]) for k in std.objectFields(obj) },\n\n  lines(arr)::\n    std.join('\\n', arr + ['']),\n\n  deepJoin(arr)::\n    if std.isString(arr) then\n      arr\n    else if std.isArray(arr) then\n      std.join('', [std.deepJoin(x) for x in arr])\n    else\n      error 'Expected string or array, got %s' % std.type(arr),\n\n  assertEqual(a, b)::\n    if a == b then\n      true\n    else\n      error 'Assertion failed. ' + a + ' != ' + b,\n\n  clamp(x, minVal, maxVal)::\n    if x < minVal then minVal\n    else if x > maxVal then maxVal\n    else x,\n\n  manifestIni(ini)::\n    local body_lines(body) =\n      std.join([], [\n        local value_or_values = body[k];\n        if std.isArray(value_or_values) then\n          ['%s = %s' % [k, value] for value in value_or_values]\n        else\n          ['%s = %s' % [k, value_or_values]]\n\n        for k in std.objectFields(body)\n      ]);\n\n    local section_lines(sname, sbody) = ['[%s]' % [sname]] + body_lines(sbody),\n          main_body = if std.objectHas(ini, 'main') then body_lines(ini.main) else [],\n          all_sections = [\n      section_lines(k, ini.sections[k])\n      for k in std.objectFields(ini.sections)\n    ];\n    std.join('\\n', main_body + std.flattenArrays(all_sections) + ['']),\n\n  manifestToml(value):: std.manifestTomlEx(value, '  '),\n\n  escapeStringPython(str)::\n    std.escapeStringJson(str),\n\n  escapeStringBash(str_)::\n    local str = std.toString(str_);\n    local trans(ch) =\n      if ch == \"'\" then\n        \"'\\\"'\\\"'\"\n      else\n        ch;\n    \"'%s'\" % std.join('', [trans(ch) for ch in std.stringChars(str)]),\n\n  escapeStringDollars(str_)::\n    local str = std.toString(str_);\n    local trans(ch) =\n      if ch == '$' then\n        '$$'\n      else\n        ch;\n    std.foldl(function(a, b) a + trans(b), std.stringChars(str), ''),\n\n  local xml_escapes = {\n    '<': '&lt;',\n    '>': '&gt;',\n    '&': '&amp;',\n    '\"': '&quot;',\n    \"'\": '&apos;',\n  },\n\n  escapeStringXML(str_)::\n    local str = std.toString(str_);\n    std.join('', [std.get(xml_escapes, ch, ch) for ch in std.stringChars(str)]),\n\n  manifestJson(value):: std.manifestJsonEx(value, '    ') tailstrict,\n\n  manifestJsonMinified(value):: std.manifestJsonEx(value, '', '', ':'),\n\n  manifestYamlStream(value, indent_array_in_object=false, c_document_end=true, quote_keys=true)::\n    if !std.isArray(value) then\n      error 'manifestYamlStream only takes arrays, got ' + std.type(value)\n    else\n      '---\\n' + std.join(\n        '\\n---\\n', [std.manifestYamlDoc(e, indent_array_in_object, quote_keys) for e in value]\n      ) + if c_document_end then '\\n...\\n' else '\\n',\n\n  manifestPython(v)::\n    if std.isObject(v) then\n      local fields = [\n        '%s: %s' % [std.escapeStringPython(k), std.manifestPython(v[k])]\n        for k in std.objectFields(v)\n      ];\n      '{%s}' % [std.join(', ', fields)]\n    else if std.isArray(v) then\n      '[%s]' % [std.join(', ', [std.manifestPython(v2) for v2 in v])]\n    else if std.isString(v) then\n      '%s' % [std.escapeStringPython(v)]\n    else if std.isFunction(v) then\n      error 'cannot manifest function'\n    else if std.isNumber(v) then\n      std.toString(v)\n    else if v == true then\n      'True'\n    else if v == false then\n      'False'\n    else if v == null then\n      'None',\n\n  manifestPythonVars(conf)::\n    local vars = ['%s = %s' % [k, std.manifestPython(conf[k])] for k in std.objectFields(conf)];\n    std.join('\\n', vars + ['']),\n\n  manifestXmlJsonml(value)::\n    if !std.isArray(value) then\n      error 'Expected a JSONML value (an array), got %s' % std.type(value)\n    else\n      local aux(v) =\n        if std.isString(v) then\n          v\n        else\n          local tag = v[0];\n          local has_attrs = std.length(v) > 1 && std.isObject(v[1]);\n          local attrs = if has_attrs then v[1] else {};\n          local children = if has_attrs then v[2:] else v[1:];\n          local attrs_str =\n            std.join('', [' %s=\"%s\"' % [k, attrs[k]] for k in std.objectFields(attrs)]);\n          std.deepJoin(['<', tag, attrs_str, '>', [aux(x) for x in children], '</', tag, '>']);\n\n      aux(value),\n\n  mergePatch(target, patch)::\n    if std.isObject(patch) then\n      local target_object =\n        if std.isObject(target) then target else {};\n\n      local target_fields =\n        if std.isObject(target_object) then std.objectFields(target_object) else [];\n\n      local null_fields = [k for k in std.objectFields(patch) if patch[k] == null];\n      local both_fields = std.setUnion(target_fields, std.objectFields(patch));\n\n      {\n        [k]:\n          if !std.objectHas(patch, k) then\n            target_object[k]\n          else if !std.objectHas(target_object, k) then\n            std.mergePatch(null, patch[k]) tailstrict\n          else\n            std.mergePatch(target_object[k], patch[k]) tailstrict\n        for k in std.setDiff(both_fields, null_fields)\n      }\n    else\n      patch,\n\n  get(o, f, default=null, inc_hidden=true)::\n    if std.objectHasEx(o, f, inc_hidden) then o[f] else default,\n\n  resolvePath(f, r)::\n    local arr = std.split(f, '/');\n    std.join('/', std.makeArray(std.length(arr) - 1, function(i) arr[i]) + [r]),\n\n  prune(a)::\n    local isContent(b) =\n      if b == null then\n        false\n      else if std.isArray(b) then\n        std.length(b) > 0\n      else if std.isObject(b) then\n        std.length(b) > 0\n      else\n        true;\n    if std.isArray(a) then\n      [std.prune(x) for x in a if isContent($.prune(x))]\n    else if std.isObject(a) then {\n      [x]: $.prune(a[x])\n      for x in std.objectFields(a)\n      if isContent(std.prune(a[x]))\n    } else\n      a,\n\n  find(value, arr)::\n    if !std.isArray(arr) then\n      error 'find second parameter should be an array, got ' + std.type(arr)\n    else\n      std.filter(function(i) arr[i] == value, std.range(0, std.length(arr) - 1)),\n\n  // Compat\n  __compare_array(arr1, arr2)::\n    assert std.isArray(arr1) && std.isArray(arr2);\n    std.__compare(arr1, arr2),\n  __array_less(arr1, arr2):: std.__compare_array(arr1, arr2) == -1,\n  __array_greater(arr1, arr2):: std.__compare_array(arr1, arr2) == 1,\n  __array_less_or_equal(arr1, arr2):: std.__compare_array(arr1, arr2) <= 0,\n  __array_greater_or_equal(arr1, arr2):: std.__compare_array(arr1, arr2) >= 0,\n}\n"
+---
+SOURCE_FILE@0..7835
+  EXPR@0..7834
+    EXPR_OBJECT@0..7834
+      OBJ_BODY_MEMBER_LIST@0..7834
+        L_BRACE@0..1 "{"
+        WHITESPACE@1..4 "\n  "
+        MEMBER_BIND_STMT@4..20
+          OBJ_LOCAL@4..20
+            LOCAL_KW@4..9 "local"
+            WHITESPACE@9..10 " "
+            BIND_DESTRUCT@10..20
+              DESTRUCT_FULL@10..13
+                NAME@10..13
+                  IDENT@10..13 "std"
+              WHITESPACE@13..14 " "
+              ASSIGN@14..15 "="
+              WHITESPACE@15..16 " "
+              EXPR@16..20
+                EXPR_LITERAL@16..20
+                  SELF_KW@16..20 "self"
+        COMMA@20..21 ","
+        WHITESPACE@21..24 "\n  "
+        MEMBER_BIND_STMT@24..41
+          OBJ_LOCAL@24..41
+            LOCAL_KW@24..29 "local"
+            WHITESPACE@29..30 " "
+            BIND_DESTRUCT@30..41
+              DESTRUCT_FULL@30..32
+                NAME@30..32
+                  IDENT@30..32 "id"
+              WHITESPACE@32..33 " "
+              ASSIGN@33..34 "="
+              WHITESPACE@34..35 " "
+              EXPR@35..41
+                EXPR_VAR@35..38
+                  NAME@35..38
+                    IDENT@35..38 "std"
+                SUFFIX_INDEX@38..41
+                  DOT@38..39 "."
+                  NAME@39..41
+                    IDENT@39..41 "id"
+        COMMA@41..42 ","
+        WHITESPACE@42..46 "\n\n  "
+        MEMBER_FIELD_NORMAL@46..224
+          FIELD_NAME_FIXED@46..54
+            NAME@46..54
+              IDENT@46..54 "thisFile"
+          COLONCOLON@54..56 "::"
+          WHITESPACE@56..57 " "
+          EXPR@57..224
+            EXPR_ERROR@57..224
+              ERROR_KW@57..62 "error"
+              WHITESPACE@62..63 " "
+              EXPR@63..224
+                EXPR_STRING@63..224
+                  STRING_SINGLE@63..224 "'std.thisFile is depr ..."
+        COMMA@224..225 ","
+        WHITESPACE@225..229 "\n\n  "
+        MEMBER_FIELD_METHOD@229..372
+          FIELD_NAME_FIXED@229..240
+            NAME@229..240
+              IDENT@229..240 "lstripChars"
+          PARAMS_DESC@240..252
+            L_PAREN@240..241 "("
+            PARAM@241..244
+              DESTRUCT_FULL@241..244
+                NAME@241..244
+                  IDENT@241..244 "str"
+            COMMA@244..245 ","
+            WHITESPACE@245..246 " "
+            PARAM@246..251
+              DESTRUCT_FULL@246..251
+                NAME@246..251
+                  IDENT@246..251 "chars"
+            R_PAREN@251..252 ")"
+          COLONCOLON@252..254 "::"
+          WHITESPACE@254..259 "\n    "
+          EXPR@259..372
+            EXPR_IF_THEN_ELSE@259..372
+              IF_KW@259..261 "if"
+              WHITESPACE@261..262 " "
+              EXPR@262..310
+                EXPR_BINARY@262..310
+                  EXPR@262..281
+                    EXPR_BINARY@262..281
+                      EXPR@262..265
+                        EXPR_VAR@262..265
+                          NAME@262..265
+                            IDENT@262..265 "std"
+                      SUFFIX_INDEX@265..272
+                        DOT@265..266 "."
+                        NAME@266..272
+                          IDENT@266..272 "length"
+                      SUFFIX_APPLY@272..277
+                        ARGS_DESC@272..277
+                          L_PAREN@272..273 "("
+                          ARG@273..276
+                            EXPR@273..276
+                              EXPR_VAR@273..276
+                                NAME@273..276
+                                  IDENT@273..276 "str"
+                          R_PAREN@276..277 ")"
+                      WHITESPACE@277..278 " "
+                      GT@278..279 ">"
+                      WHITESPACE@279..280 " "
+                      EXPR@280..281
+                        EXPR_NUMBER@280..281
+                          FLOAT@280..281 "0"
+                  WHITESPACE@281..282 " "
+                  AND@282..284 "&&"
+                  WHITESPACE@284..285 " "
+                  EXPR@285..310
+                    EXPR_VAR@285..288
+                      NAME@285..288
+                        IDENT@285..288 "std"
+                    SUFFIX_INDEX@288..295
+                      DOT@288..289 "."
+                      NAME@289..295
+                        IDENT@289..295 "member"
+                    SUFFIX_APPLY@295..310
+                      ARGS_DESC@295..310
+                        L_PAREN@295..296 "("
+                        ARG@296..301
+                          EXPR@296..301
+                            EXPR_VAR@296..301
+                              NAME@296..301
+                                IDENT@296..301 "chars"
+                        COMMA@301..302 ","
+                        WHITESPACE@302..303 " "
+                        ARG@303..309
+                          EXPR@303..309
+                            EXPR_VAR@303..306
+                              NAME@303..306
+                                IDENT@303..306 "str"
+                            SUFFIX_INDEX_EXPR@306..309
+                              L_BRACK@306..307 "["
+                              EXPR@307..308
+                                EXPR_NUMBER@307..308
+                                  FLOAT@307..308 "0"
+                              R_BRACK@308..309 "]"
+                        R_PAREN@309..310 ")"
+              WHITESPACE@310..311 " "
+              THEN_KW@311..315 "then"
+              WHITESPACE@315..322 "\n      "
+              TRUE_EXPR@322..353
+                EXPR@322..353
+                  EXPR_VAR@322..325
+                    NAME@322..325
+                      IDENT@322..325 "std"
+                  SUFFIX_INDEX@325..337
+                    DOT@325..326 "."
+                    NAME@326..337
+                      IDENT@326..337 "lstripChars"
+                  SUFFIX_APPLY@337..353
+                    ARGS_DESC@337..353
+                      L_PAREN@337..338 "("
+                      ARG@338..345
+                        EXPR@338..345
+                          EXPR_VAR@338..341
+                            NAME@338..341
+                              IDENT@338..341 "str"
+                          SUFFIX_SLICE@341..345
+                            SLICE_DESC@341..345
+                              L_BRACK@341..342 "["
+                              EXPR@342..343
+                                EXPR_NUMBER@342..343
+                                  FLOAT@342..343 "1"
+                              COLON@343..344 ":"
+                              R_BRACK@344..345 "]"
+                      COMMA@345..346 ","
+                      WHITESPACE@346..347 " "
+                      ARG@347..352
+                        EXPR@347..352
+                          EXPR_VAR@347..352
+                            NAME@347..352
+                              IDENT@347..352 "chars"
+                      R_PAREN@352..353 ")"
+              WHITESPACE@353..358 "\n    "
+              ELSE_KW@358..362 "else"
+              WHITESPACE@362..369 "\n      "
+              FALSE_EXPR@369..372
+                EXPR@369..372
+                  EXPR_VAR@369..372
+                    NAME@369..372
+                      IDENT@369..372 "str"
+        COMMA@372..373 ","
+        WHITESPACE@373..377 "\n\n  "
+        MEMBER_FIELD_METHOD@377..553
+          FIELD_NAME_FIXED@377..388
+            NAME@377..388
+              IDENT@377..388 "rstripChars"
+          PARAMS_DESC@388..400
+            L_PAREN@388..389 "("
+            PARAM@389..392
+              DESTRUCT_FULL@389..392
+                NAME@389..392
+                  IDENT@389..392 "str"
+            COMMA@392..393 ","
+            WHITESPACE@393..394 " "
+            PARAM@394..399
+              DESTRUCT_FULL@394..399
+                NAME@394..399
+                  IDENT@394..399 "chars"
+            R_PAREN@399..400 ")"
+          COLONCOLON@400..402 "::"
+          WHITESPACE@402..407 "\n    "
+          EXPR@407..553
+            STMT_LOCAL@407..435
+              LOCAL_KW@407..412 "local"
+              WHITESPACE@412..413 " "
+              BIND_DESTRUCT@413..434
+                DESTRUCT_FULL@413..416
+                  NAME@413..416
+                    IDENT@413..416 "len"
+                WHITESPACE@416..417 " "
+                ASSIGN@417..418 "="
+                WHITESPACE@418..419 " "
+                EXPR@419..434
+                  EXPR_VAR@419..422
+                    NAME@419..422
+                      IDENT@419..422 "std"
+                  SUFFIX_INDEX@422..429
+                    DOT@422..423 "."
+                    NAME@423..429
+                      IDENT@423..429 "length"
+                  SUFFIX_APPLY@429..434
+                    ARGS_DESC@429..434
+                      L_PAREN@429..430 "("
+                      ARG@430..433
+                        EXPR@430..433
+                          EXPR_VAR@430..433
+                            NAME@430..433
+                              IDENT@430..433 "str"
+                      R_PAREN@433..434 ")"
+              SEMI@434..435 ";"
+            WHITESPACE@435..440 "\n    "
+            EXPR_IF_THEN_ELSE@440..553
+              IF_KW@440..442 "if"
+              WHITESPACE@442..443 " "
+              EXPR@443..485
+                EXPR_BINARY@443..485
+                  EXPR@443..450
+                    EXPR_BINARY@443..450
+                      EXPR@443..446
+                        EXPR_VAR@443..446
+                          NAME@443..446
+                            IDENT@443..446 "len"
+                      WHITESPACE@446..447 " "
+                      GT@447..448 ">"
+                      WHITESPACE@448..449 " "
+                      EXPR@449..450
+                        EXPR_NUMBER@449..450
+                          FLOAT@449..450 "0"
+                  WHITESPACE@450..451 " "
+                  AND@451..453 "&&"
+                  WHITESPACE@453..454 " "
+                  EXPR@454..485
+                    EXPR_VAR@454..457
+                      NAME@454..457
+                        IDENT@454..457 "std"
+                    SUFFIX_INDEX@457..464
+                      DOT@457..458 "."
+                      NAME@458..464
+                        IDENT@458..464 "member"
+                    SUFFIX_APPLY@464..485
+                      ARGS_DESC@464..485
+                        L_PAREN@464..465 "("
+                        ARG@465..470
+                          EXPR@465..470
+                            EXPR_VAR@465..470
+                              NAME@465..470
+                                IDENT@465..470 "chars"
+                        COMMA@470..471 ","
+                        WHITESPACE@471..472 " "
+                        ARG@472..484
+                          EXPR@472..484
+                            EXPR_VAR@472..475
+                              NAME@472..475
+                                IDENT@472..475 "str"
+                            SUFFIX_INDEX_EXPR@475..484
+                              L_BRACK@475..476 "["
+                              EXPR@476..483
+                                EXPR_BINARY@476..483
+                                  EXPR@476..479
+                                    EXPR_VAR@476..479
+                                      NAME@476..479
+                                        IDENT@476..479 "len"
+                                  WHITESPACE@479..480 " "
+                                  MINUS@480..481 "-"
+                                  WHITESPACE@481..482 " "
+                                  EXPR@482..483
+                                    EXPR_NUMBER@482..483
+                                      FLOAT@482..483 "1"
+                              R_BRACK@483..484 "]"
+                        R_PAREN@484..485 ")"
+              WHITESPACE@485..486 " "
+              THEN_KW@486..490 "then"
+              WHITESPACE@490..497 "\n      "
+              TRUE_EXPR@497..534
+                EXPR@497..534
+                  EXPR_VAR@497..500
+                    NAME@497..500
+                      IDENT@497..500 "std"
+                  SUFFIX_INDEX@500..512
+                    DOT@500..501 "."
+                    NAME@501..512
+                      IDENT@501..512 "rstripChars"
+                  SUFFIX_APPLY@512..534
+                    ARGS_DESC@512..534
+                      L_PAREN@512..513 "("
+                      ARG@513..526
+                        EXPR@513..526
+                          EXPR_VAR@513..516
+                            NAME@513..516
+                              IDENT@513..516 "str"
+                          SUFFIX_SLICE@516..526
+                            SLICE_DESC@516..526
+                              L_BRACK@516..517 "["
+                              COLON@517..518 ":"
+                              SLICE_DESC_END@518..525
+                                EXPR@518..525
+                                  EXPR_BINARY@518..525
+                                    EXPR@518..521
+                                      EXPR_VAR@518..521
+                                        NAME@518..521
+                                          IDENT@518..521 "len"
+                                    WHITESPACE@521..522 " "
+                                    MINUS@522..523 "-"
+                                    WHITESPACE@523..524 " "
+                                    EXPR@524..525
+                                      EXPR_NUMBER@524..525
+                                        FLOAT@524..525 "1"
+                              R_BRACK@525..526 "]"
+                      COMMA@526..527 ","
+                      WHITESPACE@527..528 " "
+                      ARG@528..533
+                        EXPR@528..533
+                          EXPR_VAR@528..533
+                            NAME@528..533
+                              IDENT@528..533 "chars"
+                      R_PAREN@533..534 ")"
+              WHITESPACE@534..539 "\n    "
+              ELSE_KW@539..543 "else"
+              WHITESPACE@543..550 "\n      "
+              FALSE_EXPR@550..553
+                EXPR@550..553
+                  EXPR_VAR@550..553
+                    NAME@550..553
+                      IDENT@550..553 "str"
+        COMMA@553..554 ","
+        WHITESPACE@554..558 "\n\n  "
+        MEMBER_FIELD_METHOD@558..638
+          FIELD_NAME_FIXED@558..568
+            NAME@558..568
+              IDENT@558..568 "stripChars"
+          PARAMS_DESC@568..580
+            L_PAREN@568..569 "("
+            PARAM@569..572
+              DESTRUCT_FULL@569..572
+                NAME@569..572
+                  IDENT@569..572 "str"
+            COMMA@572..573 ","
+            WHITESPACE@573..574 " "
+            PARAM@574..579
+              DESTRUCT_FULL@574..579
+                NAME@574..579
+                  IDENT@574..579 "chars"
+            R_PAREN@579..580 ")"
+          COLONCOLON@580..582 "::"
+          WHITESPACE@582..587 "\n    "
+          EXPR@587..638
+            EXPR_VAR@587..590
+              NAME@587..590
+                IDENT@587..590 "std"
+            SUFFIX_INDEX@590..602
+              DOT@590..591 "."
+              NAME@591..602
+                IDENT@591..602 "lstripChars"
+            SUFFIX_APPLY@602..638
+              ARGS_DESC@602..638
+                L_PAREN@602..603 "("
+                ARG@603..630
+                  EXPR@603..630
+                    EXPR_VAR@603..606
+                      NAME@603..606
+                        IDENT@603..606 "std"
+                    SUFFIX_INDEX@606..618
+                      DOT@606..607 "."
+                      NAME@607..618
+                        IDENT@607..618 "rstripChars"
+                    SUFFIX_APPLY@618..630
+                      ARGS_DESC@618..630
+                        L_PAREN@618..619 "("
+                        ARG@619..622
+                          EXPR@619..622
+                            EXPR_VAR@619..622
+                              NAME@619..622
+                                IDENT@619..622 "str"
+                        COMMA@622..623 ","
+                        WHITESPACE@623..624 " "
+                        ARG@624..629
+                          EXPR@624..629
+                            EXPR_VAR@624..629
+                              NAME@624..629
+                                IDENT@624..629 "chars"
+                        R_PAREN@629..630 ")"
+                COMMA@630..631 ","
+                WHITESPACE@631..632 " "
+                ARG@632..637
+                  EXPR@632..637
+                    EXPR_VAR@632..637
+                      NAME@632..637
+                        IDENT@632..637 "chars"
+                R_PAREN@637..638 ")"
+        COMMA@638..639 ","
+        WHITESPACE@639..643 "\n\n  "
+        MEMBER_FIELD_METHOD@643..921
+          FIELD_NAME_FIXED@643..654
+            NAME@643..654
+              IDENT@643..654 "splitLimitR"
+          PARAMS_DESC@654..673
+            L_PAREN@654..655 "("
+            PARAM@655..658
+              DESTRUCT_FULL@655..658
+                NAME@655..658
+                  IDENT@655..658 "str"
+            COMMA@658..659 ","
+            WHITESPACE@659..660 " "
+            PARAM@660..661
+              DESTRUCT_FULL@660..661
+                NAME@660..661
+                  IDENT@660..661 "c"
+            COMMA@661..662 ","
+            WHITESPACE@662..663 " "
+            PARAM@663..672
+              DESTRUCT_FULL@663..672
+                NAME@663..672
+                  IDENT@663..672 "maxsplits"
+            R_PAREN@672..673 ")"
+          COLONCOLON@673..675 "::"
+          WHITESPACE@675..680 "\n    "
+          EXPR@680..921
+            EXPR_IF_THEN_ELSE@680..921
+              IF_KW@680..682 "if"
+              WHITESPACE@682..683 " "
+              EXPR@683..698
+                EXPR_BINARY@683..698
+                  EXPR@683..692
+                    EXPR_VAR@683..692
+                      NAME@683..692
+                        IDENT@683..692 "maxsplits"
+                  WHITESPACE@692..693 " "
+                  EQ@693..695 "=="
+                  WHITESPACE@695..696 " "
+                  EXPR@696..698
+                    EXPR_UNARY@696..698
+                      MINUS@696..697 "-"
+                      EXPR_NUMBER@697..698
+                        FLOAT@697..698 "1"
+              WHITESPACE@698..699 " "
+              THEN_KW@699..703 "then"
+              WHITESPACE@703..710 "\n      "
+              TRUE_EXPR@710..736
+                EXPR@710..736
+                  EXPR_VAR@710..713
+                    NAME@710..713
+                      IDENT@710..713 "std"
+                  SUFFIX_INDEX@713..724
+                    DOT@713..714 "."
+                    NAME@714..724
+                      IDENT@714..724 "splitLimit"
+                  SUFFIX_APPLY@724..736
+                    ARGS_DESC@724..736
+                      L_PAREN@724..725 "("
+                      ARG@725..728
+                        EXPR@725..728
+                          EXPR_VAR@725..728
+                            NAME@725..728
+                              IDENT@725..728 "str"
+                      COMMA@728..729 ","
+                      WHITESPACE@729..730 " "
+                      ARG@730..731
+                        EXPR@730..731
+                          EXPR_VAR@730..731
+                            NAME@730..731
+                              IDENT@730..731 "c"
+                      COMMA@731..732 ","
+                      WHITESPACE@732..733 " "
+                      ARG@733..735
+                        EXPR@733..735
+                          EXPR_UNARY@733..735
+                            MINUS@733..734 "-"
+                            EXPR_NUMBER@734..735
+                              FLOAT@734..735 "1"
+                      R_PAREN@735..736 ")"
+              WHITESPACE@736..741 "\n    "
+              ELSE_KW@741..745 "else"
+              WHITESPACE@745..752 "\n      "
+              FALSE_EXPR@752..921
+                EXPR@752..921
+                  STMT_LOCAL@752..820
+                    LOCAL_KW@752..757 "local"
+                    WHITESPACE@757..758 " "
+                    BIND_FUNCTION@758..819
+                      NAME@758..764
+                        IDENT@758..764 "revStr"
+                      PARAMS_DESC@764..769
+                        L_PAREN@764..765 "("
+                        PARAM@765..768
+                          DESTRUCT_FULL@765..768
+                            NAME@765..768
+                              IDENT@765..768 "str"
+                        R_PAREN@768..769 ")"
+                      WHITESPACE@769..770 " "
+                      ASSIGN@770..771 "="
+                      WHITESPACE@771..772 " "
+                      EXPR@772..819
+                        EXPR_VAR@772..775
+                          NAME@772..775
+                            IDENT@772..775 "std"
+                        SUFFIX_INDEX@775..780
+                          DOT@775..776 "."
+                          NAME@776..780
+                            IDENT@776..780 "join"
+                        SUFFIX_APPLY@780..819
+                          ARGS_DESC@780..819
+                            L_PAREN@780..781 "("
+                            ARG@781..783
+                              EXPR@781..783
+                                EXPR_STRING@781..783
+                                  STRING_SINGLE@781..783 "''"
+                            COMMA@783..784 ","
+                            WHITESPACE@784..785 " "
+                            ARG@785..818
+                              EXPR@785..818
+                                EXPR_VAR@785..788
+                                  NAME@785..788
+                                    IDENT@785..788 "std"
+                                SUFFIX_INDEX@788..796
+                                  DOT@788..789 "."
+                                  NAME@789..796
+                                    IDENT@789..796 "reverse"
+                                SUFFIX_APPLY@796..818
+                                  ARGS_DESC@796..818
+                                    L_PAREN@796..797 "("
+                                    ARG@797..817
+                                      EXPR@797..817
+                                        EXPR_VAR@797..800
+                                          NAME@797..800
+                                            IDENT@797..800 "std"
+                                        SUFFIX_INDEX@800..812
+                                          DOT@800..801 "."
+                                          NAME@801..812
+                                            IDENT@801..812 "stringChars"
+                                        SUFFIX_APPLY@812..817
+                                          ARGS_DESC@812..817
+                                            L_PAREN@812..813 "("
+                                            ARG@813..816
+                                              EXPR@813..816
+                                                EXPR_VAR@813..816
+                                                  NAME@813..816
+                                                    IDENT@813..816 "str"
+                                            R_PAREN@816..817 ")"
+                                    R_PAREN@817..818 ")"
+                            R_PAREN@818..819 ")"
+                    SEMI@819..820 ";"
+                  WHITESPACE@820..827 "\n      "
+                  EXPR_VAR@827..830
+                    NAME@827..830
+                      IDENT@827..830 "std"
+                  SUFFIX_INDEX@830..834
+                    DOT@830..831 "."
+                    NAME@831..834
+                      IDENT@831..834 "map"
+                  SUFFIX_APPLY@834..921
+                    ARGS_DESC@834..921
+                      L_PAREN@834..835 "("
+                      ARG@835..856
+                        EXPR@835..856
+                          EXPR_FUNCTION@835..856
+                            FUNCTION_KW@835..843 "function"
+                            PARAMS_DESC@843..846
+                              L_PAREN@843..844 "("
+                              PARAM@844..845
+                                DESTRUCT_FULL@844..845
+                                  NAME@844..845
+                                    IDENT@844..845 "e"
+                              R_PAREN@845..846 ")"
+                            WHITESPACE@846..847 " "
+                            EXPR@847..856
+                              EXPR_VAR@847..853
+                                NAME@847..853
+                                  IDENT@847..853 "revStr"
+                              SUFFIX_APPLY@853..856
+                                ARGS_DESC@853..856
+                                  L_PAREN@853..854 "("
+                                  ARG@854..855
+                                    EXPR@854..855
+                                      EXPR_VAR@854..855
+                                        NAME@854..855
+                                          IDENT@854..855 "e"
+                                  R_PAREN@855..856 ")"
+                      COMMA@856..857 ","
+                      WHITESPACE@857..858 " "
+                      ARG@858..920
+                        EXPR@858..920
+                          EXPR_VAR@858..861
+                            NAME@858..861
+                              IDENT@858..861 "std"
+                          SUFFIX_INDEX@861..869
+                            DOT@861..862 "."
+                            NAME@862..869
+                              IDENT@862..869 "reverse"
+                          SUFFIX_APPLY@869..920
+                            ARGS_DESC@869..920
+                              L_PAREN@869..870 "("
+                              ARG@870..919
+                                EXPR@870..919
+                                  EXPR_VAR@870..873
+                                    NAME@870..873
+                                      IDENT@870..873 "std"
+                                  SUFFIX_INDEX@873..884
+                                    DOT@873..874 "."
+                                    NAME@874..884
+                                      IDENT@874..884 "splitLimit"
+                                  SUFFIX_APPLY@884..919
+                                    ARGS_DESC@884..919
+                                      L_PAREN@884..885 "("
+                                      ARG@885..896
+                                        EXPR@885..896
+                                          EXPR_VAR@885..891
+                                            NAME@885..891
+                                              IDENT@885..891 "revStr"
+                                          SUFFIX_APPLY@891..896
+                                            ARGS_DESC@891..896
+                                              L_PAREN@891..892 "("
+                                              ARG@892..895
+                                                EXPR@892..895
+                                                  EXPR_VAR@892..895
+                                                    NAME@892..895
+                                                      IDENT@892..895 "str"
+                                              R_PAREN@895..896 ")"
+                                      COMMA@896..897 ","
+                                      WHITESPACE@897..898 " "
+                                      ARG@898..907
+                                        EXPR@898..907
+                                          EXPR_VAR@898..904
+                                            NAME@898..904
+                                              IDENT@898..904 "revStr"
+                                          SUFFIX_APPLY@904..907
+                                            ARGS_DESC@904..907
+                                              L_PAREN@904..905 "("
+                                              ARG@905..906
+                                                EXPR@905..906
+                                                  EXPR_VAR@905..906
+                                                    NAME@905..906
+                                                      IDENT@905..906 "c"
+                                              R_PAREN@906..907 ")"
+                                      COMMA@907..908 ","
+                                      WHITESPACE@908..909 " "
+                                      ARG@909..918
+                                        EXPR@909..918
+                                          EXPR_VAR@909..918
+                                            NAME@909..918
+                                              IDENT@909..918 "maxsplits"
+                                      R_PAREN@918..919 ")"
+                              R_PAREN@919..920 ")"
+                      R_PAREN@920..921 ")"
+        COMMA@921..922 ","
+        WHITESPACE@922..926 "\n\n  "
+        MEMBER_FIELD_METHOD@926..968
+          FIELD_NAME_FIXED@926..931
+            NAME@926..931
+              IDENT@926..931 "split"
+          PARAMS_DESC@931..939
+            L_PAREN@931..932 "("
+            PARAM@932..935
+              DESTRUCT_FULL@932..935
+                NAME@932..935
+                  IDENT@932..935 "str"
+            COMMA@935..936 ","
+            WHITESPACE@936..937 " "
+            PARAM@937..938
+              DESTRUCT_FULL@937..938
+                NAME@937..938
+                  IDENT@937..938 "c"
+            R_PAREN@938..939 ")"
+          COLONCOLON@939..941 "::"
+          WHITESPACE@941..942 " "
+          EXPR@942..968
+            EXPR_VAR@942..945
+              NAME@942..945
+                IDENT@942..945 "std"
+            SUFFIX_INDEX@945..956
+              DOT@945..946 "."
+              NAME@946..956
+                IDENT@946..956 "splitLimit"
+            SUFFIX_APPLY@956..968
+              ARGS_DESC@956..968
+                L_PAREN@956..957 "("
+                ARG@957..960
+                  EXPR@957..960
+                    EXPR_VAR@957..960
+                      NAME@957..960
+                        IDENT@957..960 "str"
+                COMMA@960..961 ","
+                WHITESPACE@961..962 " "
+                ARG@962..963
+                  EXPR@962..963
+                    EXPR_VAR@962..963
+                      NAME@962..963
+                        IDENT@962..963 "c"
+                COMMA@963..964 ","
+                WHITESPACE@964..965 " "
+                ARG@965..967
+                  EXPR@965..967
+                    EXPR_UNARY@965..967
+                      MINUS@965..966 "-"
+                      EXPR_NUMBER@966..967
+                        FLOAT@966..967 "1"
+                R_PAREN@967..968 ")"
+        COMMA@968..969 ","
+        WHITESPACE@969..973 "\n\n  "
+        MEMBER_FIELD_METHOD@973..1331
+          FIELD_NAME_FIXED@973..985
+            NAME@973..985
+              IDENT@973..985 "mapWithIndex"
+          PARAMS_DESC@985..996
+            L_PAREN@985..986 "("
+            PARAM@986..990
+              DESTRUCT_FULL@986..990
+                NAME@986..990
+                  IDENT@986..990 "func"
+            COMMA@990..991 ","
+            WHITESPACE@991..992 " "
+            PARAM@992..995
+              DESTRUCT_FULL@992..995
+                NAME@992..995
+                  IDENT@992..995 "arr"
+            R_PAREN@995..996 ")"
+          COLONCOLON@996..998 "::"
+          WHITESPACE@998..1003 "\n    "
+          EXPR@1003..1331
+            EXPR_IF_THEN_ELSE@1003..1331
+              IF_KW@1003..1005 "if"
+              WHITESPACE@1005..1006 " "
+              EXPR@1006..1027
+                EXPR_UNARY@1006..1027
+                  NOT@1006..1007 "!"
+                  EXPR_VAR@1007..1010
+                    NAME@1007..1010
+                      IDENT@1007..1010 "std"
+                  SUFFIX_INDEX@1010..1021
+                    DOT@1010..1011 "."
+                    NAME@1011..1021
+                      IDENT@1011..1021 "isFunction"
+                  SUFFIX_APPLY@1021..1027
+                    ARGS_DESC@1021..1027
+                      L_PAREN@1021..1022 "("
+                      ARG@1022..1026
+                        EXPR@1022..1026
+                          EXPR_VAR@1022..1026
+                            NAME@1022..1026
+                              IDENT@1022..1026 "func"
+                      R_PAREN@1026..1027 ")"
+              WHITESPACE@1027..1028 " "
+              THEN_KW@1028..1032 "then"
+              WHITESPACE@1032..1039 "\n      "
+              TRUE_EXPR@1039..1117
+                EXPR@1039..1117
+                  EXPR_ERROR@1039..1117
+                    ERROR_KW@1039..1044 "error"
+                    WHITESPACE@1044..1045 " "
+                    EXPR@1045..1117
+                      EXPR_PARENED@1045..1117
+                        L_PAREN@1045..1046 "("
+                        EXPR@1046..1116
+                          EXPR_BINARY@1046..1116
+                            EXPR@1046..1099
+                              EXPR_STRING@1046..1099
+                                STRING_SINGLE@1046..1099 "'std.mapWithIndex fir ..."
+                            WHITESPACE@1099..1100 " "
+                            PLUS@1100..1101 "+"
+                            WHITESPACE@1101..1102 " "
+                            EXPR@1102..1116
+                              EXPR_VAR@1102..1105
+                                NAME@1102..1105
+                                  IDENT@1102..1105 "std"
+                              SUFFIX_INDEX@1105..1110
+                                DOT@1105..1106 "."
+                                NAME@1106..1110
+                                  IDENT@1106..1110 "type"
+                              SUFFIX_APPLY@1110..1116
+                                ARGS_DESC@1110..1116
+                                  L_PAREN@1110..1111 "("
+                                  ARG@1111..1115
+                                    EXPR@1111..1115
+                                      EXPR_VAR@1111..1115
+                                        NAME@1111..1115
+                                          IDENT@1111..1115 "func"
+                                  R_PAREN@1115..1116 ")"
+                        R_PAREN@1116..1117 ")"
+              WHITESPACE@1117..1122 "\n    "
+              ELSE_KW@1122..1126 "else"
+              WHITESPACE@1126..1127 " "
+              FALSE_EXPR@1127..1331
+                EXPR@1127..1331
+                  EXPR_IF_THEN_ELSE@1127..1331
+                    IF_KW@1127..1129 "if"
+                    WHITESPACE@1129..1130 " "
+                    EXPR@1130..1169
+                      EXPR_BINARY@1130..1169
+                        EXPR@1130..1147
+                          EXPR_UNARY@1130..1147
+                            NOT@1130..1131 "!"
+                            EXPR_VAR@1131..1134
+                              NAME@1131..1134
+                                IDENT@1131..1134 "std"
+                            SUFFIX_INDEX@1134..1142
+                              DOT@1134..1135 "."
+                              NAME@1135..1142
+                                IDENT@1135..1142 "isArray"
+                            SUFFIX_APPLY@1142..1147
+                              ARGS_DESC@1142..1147
+                                L_PAREN@1142..1143 "("
+                                ARG@1143..1146
+                                  EXPR@1143..1146
+                                    EXPR_VAR@1143..1146
+                                      NAME@1143..1146
+                                        IDENT@1143..1146 "arr"
+                                R_PAREN@1146..1147 ")"
+                        WHITESPACE@1147..1148 " "
+                        AND@1148..1150 "&&"
+                        WHITESPACE@1150..1151 " "
+                        EXPR@1151..1169
+                          EXPR_UNARY@1151..1169
+                            NOT@1151..1152 "!"
+                            EXPR_VAR@1152..1155
+                              NAME@1152..1155
+                                IDENT@1152..1155 "std"
+                            SUFFIX_INDEX@1155..1164
+                              DOT@1155..1156 "."
+                              NAME@1156..1164
+                                IDENT@1156..1164 "isString"
+                            SUFFIX_APPLY@1164..1169
+                              ARGS_DESC@1164..1169
+                                L_PAREN@1164..1165 "("
+                                ARG@1165..1168
+                                  EXPR@1165..1168
+                                    EXPR_VAR@1165..1168
+                                      NAME@1165..1168
+                                        IDENT@1165..1168 "arr"
+                                R_PAREN@1168..1169 ")"
+                    WHITESPACE@1169..1170 " "
+                    THEN_KW@1170..1174 "then"
+                    WHITESPACE@1174..1181 "\n      "
+                    TRUE_EXPR@1181..1256
+                      EXPR@1181..1256
+                        EXPR_ERROR@1181..1256
+                          ERROR_KW@1181..1186 "error"
+                          WHITESPACE@1186..1187 " "
+                          EXPR@1187..1256
+                            EXPR_PARENED@1187..1256
+                              L_PAREN@1187..1188 "("
+                              EXPR@1188..1255
+                                EXPR_BINARY@1188..1255
+                                  EXPR@1188..1239
+                                    EXPR_STRING@1188..1239
+                                      STRING_SINGLE@1188..1239 "'std.mapWithIndex sec ..."
+                                  WHITESPACE@1239..1240 " "
+                                  PLUS@1240..1241 "+"
+                                  WHITESPACE@1241..1242 " "
+                                  EXPR@1242..1255
+                                    EXPR_VAR@1242..1245
+                                      NAME@1242..1245
+                                        IDENT@1242..1245 "std"
+                                    SUFFIX_INDEX@1245..1250
+                                      DOT@1245..1246 "."
+                                      NAME@1246..1250
+                                        IDENT@1246..1250 "type"
+                                    SUFFIX_APPLY@1250..1255
+                                      ARGS_DESC@1250..1255
+                                        L_PAREN@1250..1251 "("
+                                        ARG@1251..1254
+                                          EXPR@1251..1254
+                                            EXPR_VAR@1251..1254
+                                              NAME@1251..1254
+                                                IDENT@1251..1254 "arr"
+                                        R_PAREN@1254..1255 ")"
+                              R_PAREN@1255..1256 ")"
+                    WHITESPACE@1256..1261 "\n    "
+                    ELSE_KW@1261..1265 "else"
+                    WHITESPACE@1265..1272 "\n      "
+                    FALSE_EXPR@1272..1331
+                      EXPR@1272..1331
+                        EXPR_VAR@1272..1275
+                          NAME@1272..1275
+                            IDENT@1272..1275 "std"
+                        SUFFIX_INDEX@1275..1285
+                          DOT@1275..1276 "."
+                          NAME@1276..1285
+                            IDENT@1276..1285 "makeArray"
+                        SUFFIX_APPLY@1285..1331
+                          ARGS_DESC@1285..1331
+                            L_PAREN@1285..1286 "("
+                            ARG@1286..1301
+                              EXPR@1286..1301
+                                EXPR_VAR@1286..1289
+                                  NAME@1286..1289
+                                    IDENT@1286..1289 "std"
+                                SUFFIX_INDEX@1289..1296
+                                  DOT@1289..1290 "."
+                                  NAME@1290..1296
+                                    IDENT@1290..1296 "length"
+                                SUFFIX_APPLY@1296..1301
+                                  ARGS_DESC@1296..1301
+                                    L_PAREN@1296..1297 "("
+                                    ARG@1297..1300
+                                      EXPR@1297..1300
+                                        EXPR_VAR@1297..1300
+                                          NAME@1297..1300
+                                            IDENT@1297..1300 "arr"
+                                    R_PAREN@1300..1301 ")"
+                            COMMA@1301..1302 ","
+                            WHITESPACE@1302..1303 " "
+                            ARG@1303..1330
+                              EXPR@1303..1330
+                                EXPR_FUNCTION@1303..1330
+                                  FUNCTION_KW@1303..1311 "function"
+                                  PARAMS_DESC@1311..1314
+                                    L_PAREN@1311..1312 "("
+                                    PARAM@1312..1313
+                                      DESTRUCT_FULL@1312..1313
+                                        NAME@1312..1313
+                                          IDENT@1312..1313 "i"
+                                    R_PAREN@1313..1314 ")"
+                                  WHITESPACE@1314..1315 " "
+                                  EXPR@1315..1330
+                                    EXPR_VAR@1315..1319
+                                      NAME@1315..1319
+                                        IDENT@1315..1319 "func"
+                                    SUFFIX_APPLY@1319..1330
+                                      ARGS_DESC@1319..1330
+                                        L_PAREN@1319..1320 "("
+                                        ARG@1320..1321
+                                          EXPR@1320..1321
+                                            EXPR_VAR@1320..1321
+                                              NAME@1320..1321
+                                                IDENT@1320..1321 "i"
+                                        COMMA@1321..1322 ","
+                                        WHITESPACE@1322..1323 " "
+                                        ARG@1323..1329
+                                          EXPR@1323..1329
+                                            EXPR_VAR@1323..1326
+                                              NAME@1323..1326
+                                                IDENT@1323..1326 "arr"
+                                            SUFFIX_INDEX_EXPR@1326..1329
+                                              L_BRACK@1326..1327 "["
+                                              EXPR@1327..1328
+                                                EXPR_VAR@1327..1328
+                                                  NAME@1327..1328
+                                                    IDENT@1327..1328 "i"
+                                              R_BRACK@1328..1329 "]"
+                                        R_PAREN@1329..1330 ")"
+                            R_PAREN@1330..1331 ")"
+        COMMA@1331..1332 ","
+        WHITESPACE@1332..1336 "\n\n  "
+        MEMBER_FIELD_METHOD@1336..1664
+          FIELD_NAME_FIXED@1336..1346
+            NAME@1336..1346
+              IDENT@1336..1346 "mapWithKey"
+          PARAMS_DESC@1346..1357
+            L_PAREN@1346..1347 "("
+            PARAM@1347..1351
+              DESTRUCT_FULL@1347..1351
+                NAME@1347..1351
+                  IDENT@1347..1351 "func"
+            COMMA@1351..1352 ","
+            WHITESPACE@1352..1353 " "
+            PARAM@1353..1356
+              DESTRUCT_FULL@1353..1356
+                NAME@1353..1356
+                  IDENT@1353..1356 "obj"
+            R_PAREN@1356..1357 ")"
+          COLONCOLON@1357..1359 "::"
+          WHITESPACE@1359..1364 "\n    "
+          EXPR@1364..1664
+            EXPR_IF_THEN_ELSE@1364..1664
+              IF_KW@1364..1366 "if"
+              WHITESPACE@1366..1367 " "
+              EXPR@1367..1388
+                EXPR_UNARY@1367..1388
+                  NOT@1367..1368 "!"
+                  EXPR_VAR@1368..1371
+                    NAME@1368..1371
+                      IDENT@1368..1371 "std"
+                  SUFFIX_INDEX@1371..1382
+                    DOT@1371..1372 "."
+                    NAME@1372..1382
+                      IDENT@1372..1382 "isFunction"
+                  SUFFIX_APPLY@1382..1388
+                    ARGS_DESC@1382..1388
+                      L_PAREN@1382..1383 "("
+                      ARG@1383..1387
+                        EXPR@1383..1387
+                          EXPR_VAR@1383..1387
+                            NAME@1383..1387
+                              IDENT@1383..1387 "func"
+                      R_PAREN@1387..1388 ")"
+              WHITESPACE@1388..1389 " "
+              THEN_KW@1389..1393 "then"
+              WHITESPACE@1393..1400 "\n      "
+              TRUE_EXPR@1400..1476
+                EXPR@1400..1476
+                  EXPR_ERROR@1400..1476
+                    ERROR_KW@1400..1405 "error"
+                    WHITESPACE@1405..1406 " "
+                    EXPR@1406..1476
+                      EXPR_PARENED@1406..1476
+                        L_PAREN@1406..1407 "("
+                        EXPR@1407..1475
+                          EXPR_BINARY@1407..1475
+                            EXPR@1407..1458
+                              EXPR_STRING@1407..1458
+                                STRING_SINGLE@1407..1458 "'std.mapWithKey first ..."
+                            WHITESPACE@1458..1459 " "
+                            PLUS@1459..1460 "+"
+                            WHITESPACE@1460..1461 " "
+                            EXPR@1461..1475
+                              EXPR_VAR@1461..1464
+                                NAME@1461..1464
+                                  IDENT@1461..1464 "std"
+                              SUFFIX_INDEX@1464..1469
+                                DOT@1464..1465 "."
+                                NAME@1465..1469
+                                  IDENT@1465..1469 "type"
+                              SUFFIX_APPLY@1469..1475
+                                ARGS_DESC@1469..1475
+                                  L_PAREN@1469..1470 "("
+                                  ARG@1470..1474
+                                    EXPR@1470..1474
+                                      EXPR_VAR@1470..1474
+                                        NAME@1470..1474
+                                          IDENT@1470..1474 "func"
+                                  R_PAREN@1474..1475 ")"
+                        R_PAREN@1475..1476 ")"
+              WHITESPACE@1476..1481 "\n    "
+              ELSE_KW@1481..1485 "else"
+              WHITESPACE@1485..1486 " "
+              FALSE_EXPR@1486..1664
+                EXPR@1486..1664
+                  EXPR_IF_THEN_ELSE@1486..1664
+                    IF_KW@1486..1488 "if"
+                    WHITESPACE@1488..1489 " "
+                    EXPR@1489..1507
+                      EXPR_UNARY@1489..1507
+                        NOT@1489..1490 "!"
+                        EXPR_VAR@1490..1493
+                          NAME@1490..1493
+                            IDENT@1490..1493 "std"
+                        SUFFIX_INDEX@1493..1502
+                          DOT@1493..1494 "."
+                          NAME@1494..1502
+                            IDENT@1494..1502 "isObject"
+                        SUFFIX_APPLY@1502..1507
+                          ARGS_DESC@1502..1507
+                            L_PAREN@1502..1503 "("
+                            ARG@1503..1506
+                              EXPR@1503..1506
+                                EXPR_VAR@1503..1506
+                                  NAME@1503..1506
+                                    IDENT@1503..1506 "obj"
+                            R_PAREN@1506..1507 ")"
+                    WHITESPACE@1507..1508 " "
+                    THEN_KW@1508..1512 "then"
+                    WHITESPACE@1512..1519 "\n      "
+                    TRUE_EXPR@1519..1593
+                      EXPR@1519..1593
+                        EXPR_ERROR@1519..1593
+                          ERROR_KW@1519..1524 "error"
+                          WHITESPACE@1524..1525 " "
+                          EXPR@1525..1593
+                            EXPR_PARENED@1525..1593
+                              L_PAREN@1525..1526 "("
+                              EXPR@1526..1592
+                                EXPR_BINARY@1526..1592
+                                  EXPR@1526..1576
+                                    EXPR_STRING@1526..1576
+                                      STRING_SINGLE@1526..1576 "'std.mapWithKey secon ..."
+                                  WHITESPACE@1576..1577 " "
+                                  PLUS@1577..1578 "+"
+                                  WHITESPACE@1578..1579 " "
+                                  EXPR@1579..1592
+                                    EXPR_VAR@1579..1582
+                                      NAME@1579..1582
+                                        IDENT@1579..1582 "std"
+                                    SUFFIX_INDEX@1582..1587
+                                      DOT@1582..1583 "."
+                                      NAME@1583..1587
+                                        IDENT@1583..1587 "type"
+                                    SUFFIX_APPLY@1587..1592
+                                      ARGS_DESC@1587..1592
+                                        L_PAREN@1587..1588 "("
+                                        ARG@1588..1591
+                                          EXPR@1588..1591
+                                            EXPR_VAR@1588..1591
+                                              NAME@1588..1591
+                                                IDENT@1588..1591 "obj"
+                                        R_PAREN@1591..1592 ")"
+                              R_PAREN@1592..1593 ")"
+                    WHITESPACE@1593..1598 "\n    "
+                    ELSE_KW@1598..1602 "else"
+                    WHITESPACE@1602..1609 "\n      "
+                    FALSE_EXPR@1609..1664
+                      EXPR@1609..1664
+                        EXPR_OBJECT@1609..1664
+                          OBJ_BODY_COMP@1609..1664
+                            L_BRACE@1609..1610 "{"
+                            WHITESPACE@1610..1611 " "
+                            MEMBER_FIELD_NORMAL@1611..1631
+                              FIELD_NAME_DYNAMIC@1611..1614
+                                L_BRACK@1611..1612 "["
+                                EXPR@1612..1613
+                                  EXPR_VAR@1612..1613
+                                    NAME@1612..1613
+                                      IDENT@1612..1613 "k"
+                                R_BRACK@1613..1614 "]"
+                              COLON@1614..1615 ":"
+                              WHITESPACE@1615..1616 " "
+                              EXPR@1616..1631
+                                EXPR_VAR@1616..1620
+                                  NAME@1616..1620
+                                    IDENT@1616..1620 "func"
+                                SUFFIX_APPLY@1620..1631
+                                  ARGS_DESC@1620..1631
+                                    L_PAREN@1620..1621 "("
+                                    ARG@1621..1622
+                                      EXPR@1621..1622
+                                        EXPR_VAR@1621..1622
+                                          NAME@1621..1622
+                                            IDENT@1621..1622 "k"
+                                    COMMA@1622..1623 ","
+                                    WHITESPACE@1623..1624 " "
+                                    ARG@1624..1630
+                                      EXPR@1624..1630
+                                        EXPR_VAR@1624..1627
+                                          NAME@1624..1627
+                                            IDENT@1624..1627 "obj"
+                                        SUFFIX_INDEX_EXPR@1627..1630
+                                          L_BRACK@1627..1628 "["
+                                          EXPR@1628..1629
+                                            EXPR_VAR@1628..1629
+                                              NAME@1628..1629
+                                                IDENT@1628..1629 "k"
+                                          R_BRACK@1629..1630 "]"
+                                    R_PAREN@1630..1631 ")"
+                            WHITESPACE@1631..1632 " "
+                            FOR_SPEC@1632..1662
+                              FOR_KW@1632..1635 "for"
+                              WHITESPACE@1635..1636 " "
+                              DESTRUCT_FULL@1636..1637
+                                NAME@1636..1637
+                                  IDENT@1636..1637 "k"
+                              WHITESPACE@1637..1638 " "
+                              IN_KW@1638..1640 "in"
+                              WHITESPACE@1640..1641 " "
+                              EXPR@1641..1662
+                                EXPR_VAR@1641..1644
+                                  NAME@1641..1644
+                                    IDENT@1641..1644 "std"
+                                SUFFIX_INDEX@1644..1657
+                                  DOT@1644..1645 "."
+                                  NAME@1645..1657
+                                    IDENT@1645..1657 "objectFields"
+                                SUFFIX_APPLY@1657..1662
+                                  ARGS_DESC@1657..1662
+                                    L_PAREN@1657..1658 "("
+                                    ARG@1658..1661
+                                      EXPR@1658..1661
+                                        EXPR_VAR@1658..1661
+                                          NAME@1658..1661
+                                            IDENT@1658..1661 "obj"
+                                    R_PAREN@1661..1662 ")"
+                            WHITESPACE@1662..1663 " "
+                            R_BRACE@1663..1664 "}"
+        COMMA@1664..1665 ","
+        WHITESPACE@1665..1669 "\n\n  "
+        MEMBER_FIELD_METHOD@1669..1712
+          FIELD_NAME_FIXED@1669..1674
+            NAME@1669..1674
+              IDENT@1669..1674 "lines"
+          PARAMS_DESC@1674..1679
+            L_PAREN@1674..1675 "("
+            PARAM@1675..1678
+              DESTRUCT_FULL@1675..1678
+                NAME@1675..1678
+                  IDENT@1675..1678 "arr"
+            R_PAREN@1678..1679 ")"
+          COLONCOLON@1679..1681 "::"
+          WHITESPACE@1681..1686 "\n    "
+          EXPR@1686..1712
+            EXPR_VAR@1686..1689
+              NAME@1686..1689
+                IDENT@1686..1689 "std"
+            SUFFIX_INDEX@1689..1694
+              DOT@1689..1690 "."
+              NAME@1690..1694
+                IDENT@1690..1694 "join"
+            SUFFIX_APPLY@1694..1712
+              ARGS_DESC@1694..1712
+                L_PAREN@1694..1695 "("
+                ARG@1695..1699
+                  EXPR@1695..1699
+                    EXPR_STRING@1695..1699
+                      STRING_SINGLE@1695..1699 "'\\n'"
+                COMMA@1699..1700 ","
+                WHITESPACE@1700..1701 " "
+                ARG@1701..1711
+                  EXPR@1701..1711
+                    EXPR_BINARY@1701..1711
+                      EXPR@1701..1704
+                        EXPR_VAR@1701..1704
+                          NAME@1701..1704
+                            IDENT@1701..1704 "arr"
+                      WHITESPACE@1704..1705 " "
+                      PLUS@1705..1706 "+"
+                      WHITESPACE@1706..1707 " "
+                      EXPR@1707..1711
+                        EXPR_ARRAY@1707..1711
+                          L_BRACK@1707..1708 "["
+                          EXPR@1708..1710
+                            EXPR_STRING@1708..1710
+                              STRING_SINGLE@1708..1710 "''"
+                          R_BRACK@1710..1711 "]"
+                R_PAREN@1711..1712 ")"
+        COMMA@1712..1713 ","
+        WHITESPACE@1713..1717 "\n\n  "
+        MEMBER_FIELD_METHOD@1717..1929
+          FIELD_NAME_FIXED@1717..1725
+            NAME@1717..1725
+              IDENT@1717..1725 "deepJoin"
+          PARAMS_DESC@1725..1730
+            L_PAREN@1725..1726 "("
+            PARAM@1726..1729
+              DESTRUCT_FULL@1726..1729
+                NAME@1726..1729
+                  IDENT@1726..1729 "arr"
+            R_PAREN@1729..1730 ")"
+          COLONCOLON@1730..1732 "::"
+          WHITESPACE@1732..1737 "\n    "
+          EXPR@1737..1929
+            EXPR_IF_THEN_ELSE@1737..1929
+              IF_KW@1737..1739 "if"
+              WHITESPACE@1739..1740 " "
+              EXPR@1740..1757
+                EXPR_VAR@1740..1743
+                  NAME@1740..1743
+                    IDENT@1740..1743 "std"
+                SUFFIX_INDEX@1743..1752
+                  DOT@1743..1744 "."
+                  NAME@1744..1752
+                    IDENT@1744..1752 "isString"
+                SUFFIX_APPLY@1752..1757
+                  ARGS_DESC@1752..1757
+                    L_PAREN@1752..1753 "("
+                    ARG@1753..1756
+                      EXPR@1753..1756
+                        EXPR_VAR@1753..1756
+                          NAME@1753..1756
+                            IDENT@1753..1756 "arr"
+                    R_PAREN@1756..1757 ")"
+              WHITESPACE@1757..1758 " "
+              THEN_KW@1758..1762 "then"
+              WHITESPACE@1762..1769 "\n      "
+              TRUE_EXPR@1769..1772
+                EXPR@1769..1772
+                  EXPR_VAR@1769..1772
+                    NAME@1769..1772
+                      IDENT@1769..1772 "arr"
+              WHITESPACE@1772..1777 "\n    "
+              ELSE_KW@1777..1781 "else"
+              WHITESPACE@1781..1782 " "
+              FALSE_EXPR@1782..1929
+                EXPR@1782..1929
+                  EXPR_IF_THEN_ELSE@1782..1929
+                    IF_KW@1782..1784 "if"
+                    WHITESPACE@1784..1785 " "
+                    EXPR@1785..1801
+                      EXPR_VAR@1785..1788
+                        NAME@1785..1788
+                          IDENT@1785..1788 "std"
+                      SUFFIX_INDEX@1788..1796
+                        DOT@1788..1789 "."
+                        NAME@1789..1796
+                          IDENT@1789..1796 "isArray"
+                      SUFFIX_APPLY@1796..1801
+                        ARGS_DESC@1796..1801
+                          L_PAREN@1796..1797 "("
+                          ARG@1797..1800
+                            EXPR@1797..1800
+                              EXPR_VAR@1797..1800
+                                NAME@1797..1800
+                                  IDENT@1797..1800 "arr"
+                          R_PAREN@1800..1801 ")"
+                    WHITESPACE@1801..1802 " "
+                    THEN_KW@1802..1806 "then"
+                    WHITESPACE@1806..1813 "\n      "
+                    TRUE_EXPR@1813..1857
+                      EXPR@1813..1857
+                        EXPR_VAR@1813..1816
+                          NAME@1813..1816
+                            IDENT@1813..1816 "std"
+                        SUFFIX_INDEX@1816..1821
+                          DOT@1816..1817 "."
+                          NAME@1817..1821
+                            IDENT@1817..1821 "join"
+                        SUFFIX_APPLY@1821..1857
+                          ARGS_DESC@1821..1857
+                            L_PAREN@1821..1822 "("
+                            ARG@1822..1824
+                              EXPR@1822..1824
+                                EXPR_STRING@1822..1824
+                                  STRING_SINGLE@1822..1824 "''"
+                            COMMA@1824..1825 ","
+                            WHITESPACE@1825..1826 " "
+                            ARG@1826..1856
+                              EXPR@1826..1856
+                                EXPR_ARRAY_COMP@1826..1856
+                                  L_BRACK@1826..1827 "["
+                                  EXPR@1827..1842
+                                    EXPR_VAR@1827..1830
+                                      NAME@1827..1830
+                                        IDENT@1827..1830 "std"
+                                    SUFFIX_INDEX@1830..1839
+                                      DOT@1830..1831 "."
+                                      NAME@1831..1839
+                                        IDENT@1831..1839 "deepJoin"
+                                    SUFFIX_APPLY@1839..1842
+                                      ARGS_DESC@1839..1842
+                                        L_PAREN@1839..1840 "("
+                                        ARG@1840..1841
+                                          EXPR@1840..1841
+                                            EXPR_VAR@1840..1841
+                                              NAME@1840..1841
+                                                IDENT@1840..1841 "x"
+                                        R_PAREN@1841..1842 ")"
+                                  WHITESPACE@1842..1843 " "
+                                  FOR_SPEC@1843..1855
+                                    FOR_KW@1843..1846 "for"
+                                    WHITESPACE@1846..1847 " "
+                                    DESTRUCT_FULL@1847..1848
+                                      NAME@1847..1848
+                                        IDENT@1847..1848 "x"
+                                    WHITESPACE@1848..1849 " "
+                                    IN_KW@1849..1851 "in"
+                                    WHITESPACE@1851..1852 " "
+                                    EXPR@1852..1855
+                                      EXPR_VAR@1852..1855
+                                        NAME@1852..1855
+                                          IDENT@1852..1855 "arr"
+                                  R_BRACK@1855..1856 "]"
+                            R_PAREN@1856..1857 ")"
+                    WHITESPACE@1857..1862 "\n    "
+                    ELSE_KW@1862..1866 "else"
+                    WHITESPACE@1866..1873 "\n      "
+                    FALSE_EXPR@1873..1929
+                      EXPR@1873..1929
+                        EXPR_ERROR@1873..1929
+                          ERROR_KW@1873..1878 "error"
+                          WHITESPACE@1878..1879 " "
+                          EXPR@1879..1929
+                            EXPR_BINARY@1879..1929
+                              EXPR@1879..1913
+                                EXPR_STRING@1879..1913
+                                  STRING_SINGLE@1879..1913 "'Expected string or a ..."
+                              WHITESPACE@1913..1914 " "
+                              MODULO@1914..1915 "%"
+                              WHITESPACE@1915..1916 " "
+                              EXPR@1916..1929
+                                EXPR_VAR@1916..1919
+                                  NAME@1916..1919
+                                    IDENT@1916..1919 "std"
+                                SUFFIX_INDEX@1919..1924
+                                  DOT@1919..1920 "."
+                                  NAME@1920..1924
+                                    IDENT@1920..1924 "type"
+                                SUFFIX_APPLY@1924..1929
+                                  ARGS_DESC@1924..1929
+                                    L_PAREN@1924..1925 "("
+                                    ARG@1925..1928
+                                      EXPR@1925..1928
+                                        EXPR_VAR@1925..1928
+                                          NAME@1925..1928
+                                            IDENT@1925..1928 "arr"
+                                    R_PAREN@1928..1929 ")"
+        COMMA@1929..1930 ","
+        WHITESPACE@1930..1934 "\n\n  "
+        MEMBER_FIELD_METHOD@1934..2042
+          FIELD_NAME_FIXED@1934..1945
+            NAME@1934..1945
+              IDENT@1934..1945 "assertEqual"
+          PARAMS_DESC@1945..1951
+            L_PAREN@1945..1946 "("
+            PARAM@1946..1947
+              DESTRUCT_FULL@1946..1947
+                NAME@1946..1947
+                  IDENT@1946..1947 "a"
+            COMMA@1947..1948 ","
+            WHITESPACE@1948..1949 " "
+            PARAM@1949..1950
+              DESTRUCT_FULL@1949..1950
+                NAME@1949..1950
+                  IDENT@1949..1950 "b"
+            R_PAREN@1950..1951 ")"
+          COLONCOLON@1951..1953 "::"
+          WHITESPACE@1953..1958 "\n    "
+          EXPR@1958..2042
+            EXPR_IF_THEN_ELSE@1958..2042
+              IF_KW@1958..1960 "if"
+              WHITESPACE@1960..1961 " "
+              EXPR@1961..1967
+                EXPR_BINARY@1961..1967
+                  EXPR@1961..1962
+                    EXPR_VAR@1961..1962
+                      NAME@1961..1962
+                        IDENT@1961..1962 "a"
+                  WHITESPACE@1962..1963 " "
+                  EQ@1963..1965 "=="
+                  WHITESPACE@1965..1966 " "
+                  EXPR@1966..1967
+                    EXPR_VAR@1966..1967
+                      NAME@1966..1967
+                        IDENT@1966..1967 "b"
+              WHITESPACE@1967..1968 " "
+              THEN_KW@1968..1972 "then"
+              WHITESPACE@1972..1979 "\n      "
+              TRUE_EXPR@1979..1983
+                EXPR@1979..1983
+                  EXPR_LITERAL@1979..1983
+                    TRUE_KW@1979..1983 "true"
+              WHITESPACE@1983..1988 "\n    "
+              ELSE_KW@1988..1992 "else"
+              WHITESPACE@1992..1999 "\n      "
+              FALSE_EXPR@1999..2042
+                EXPR@1999..2042
+                  EXPR_ERROR@1999..2042
+                    ERROR_KW@1999..2004 "error"
+                    WHITESPACE@2004..2005 " "
+                    EXPR@2005..2042
+                      EXPR_BINARY@2005..2042
+                        EXPR@2005..2038
+                          EXPR_BINARY@2005..2038
+                            EXPR@2005..2029
+                              EXPR_BINARY@2005..2029
+                                EXPR@2005..2025
+                                  EXPR_STRING@2005..2025
+                                    STRING_SINGLE@2005..2025 "'Assertion failed. '"
+                                WHITESPACE@2025..2026 " "
+                                PLUS@2026..2027 "+"
+                                WHITESPACE@2027..2028 " "
+                                EXPR@2028..2029
+                                  EXPR_VAR@2028..2029
+                                    NAME@2028..2029
+                                      IDENT@2028..2029 "a"
+                            WHITESPACE@2029..2030 " "
+                            PLUS@2030..2031 "+"
+                            WHITESPACE@2031..2032 " "
+                            EXPR@2032..2038
+                              EXPR_STRING@2032..2038
+                                STRING_SINGLE@2032..2038 "' != '"
+                        WHITESPACE@2038..2039 " "
+                        PLUS@2039..2040 "+"
+                        WHITESPACE@2040..2041 " "
+                        EXPR@2041..2042
+                          EXPR_VAR@2041..2042
+                            NAME@2041..2042
+                              IDENT@2041..2042 "b"
+        COMMA@2042..2043 ","
+        WHITESPACE@2043..2047 "\n\n  "
+        MEMBER_FIELD_METHOD@2047..2149
+          FIELD_NAME_FIXED@2047..2052
+            NAME@2047..2052
+              IDENT@2047..2052 "clamp"
+          PARAMS_DESC@2052..2071
+            L_PAREN@2052..2053 "("
+            PARAM@2053..2054
+              DESTRUCT_FULL@2053..2054
+                NAME@2053..2054
+                  IDENT@2053..2054 "x"
+            COMMA@2054..2055 ","
+            WHITESPACE@2055..2056 " "
+            PARAM@2056..2062
+              DESTRUCT_FULL@2056..2062
+                NAME@2056..2062
+                  IDENT@2056..2062 "minVal"
+            COMMA@2062..2063 ","
+            WHITESPACE@2063..2064 " "
+            PARAM@2064..2070
+              DESTRUCT_FULL@2064..2070
+                NAME@2064..2070
+                  IDENT@2064..2070 "maxVal"
+            R_PAREN@2070..2071 ")"
+          COLONCOLON@2071..2073 "::"
+          WHITESPACE@2073..2078 "\n    "
+          EXPR@2078..2149
+            EXPR_IF_THEN_ELSE@2078..2149
+              IF_KW@2078..2080 "if"
+              WHITESPACE@2080..2081 " "
+              EXPR@2081..2091
+                EXPR_BINARY@2081..2091
+                  EXPR@2081..2082
+                    EXPR_VAR@2081..2082
+                      NAME@2081..2082
+                        IDENT@2081..2082 "x"
+                  WHITESPACE@2082..2083 " "
+                  LT@2083..2084 "<"
+                  WHITESPACE@2084..2085 " "
+                  EXPR@2085..2091
+                    EXPR_VAR@2085..2091
+                      NAME@2085..2091
+                        IDENT@2085..2091 "minVal"
+              WHITESPACE@2091..2092 " "
+              THEN_KW@2092..2096 "then"
+              WHITESPACE@2096..2097 " "
+              TRUE_EXPR@2097..2103
+                EXPR@2097..2103
+                  EXPR_VAR@2097..2103
+                    NAME@2097..2103
+                      IDENT@2097..2103 "minVal"
+              WHITESPACE@2103..2108 "\n    "
+              ELSE_KW@2108..2112 "else"
+              WHITESPACE@2112..2113 " "
+              FALSE_EXPR@2113..2149
+                EXPR@2113..2149
+                  EXPR_IF_THEN_ELSE@2113..2149
+                    IF_KW@2113..2115 "if"
+                    WHITESPACE@2115..2116 " "
+                    EXPR@2116..2126
+                      EXPR_BINARY@2116..2126
+                        EXPR@2116..2117
+                          EXPR_VAR@2116..2117
+                            NAME@2116..2117
+                              IDENT@2116..2117 "x"
+                        WHITESPACE@2117..2118 " "
+                        GT@2118..2119 ">"
+                        WHITESPACE@2119..2120 " "
+                        EXPR@2120..2126
+                          EXPR_VAR@2120..2126
+                            NAME@2120..2126
+                              IDENT@2120..2126 "maxVal"
+                    WHITESPACE@2126..2127 " "
+                    THEN_KW@2127..2131 "then"
+                    WHITESPACE@2131..2132 " "
+                    TRUE_EXPR@2132..2138
+                      EXPR@2132..2138
+                        EXPR_VAR@2132..2138
+                          NAME@2132..2138
+                            IDENT@2132..2138 "maxVal"
+                    WHITESPACE@2138..2143 "\n    "
+                    ELSE_KW@2143..2147 "else"
+                    WHITESPACE@2147..2148 " "
+                    FALSE_EXPR@2148..2149
+                      EXPR@2148..2149
+                        EXPR_VAR@2148..2149
+                          NAME@2148..2149
+                            IDENT@2148..2149 "x"
+        COMMA@2149..2150 ","
+        WHITESPACE@2150..2154 "\n\n  "
+        MEMBER_FIELD_METHOD@2154..2840
+          FIELD_NAME_FIXED@2154..2165
+            NAME@2154..2165
+              IDENT@2154..2165 "manifestIni"
+          PARAMS_DESC@2165..2170
+            L_PAREN@2165..2166 "("
+            PARAM@2166..2169
+              DESTRUCT_FULL@2166..2169
+                NAME@2166..2169
+                  IDENT@2166..2169 "ini"
+            R_PAREN@2169..2170 ")"
+          COLONCOLON@2170..2172 "::"
+          WHITESPACE@2172..2177 "\n    "
+          EXPR@2177..2840
+            STMT_LOCAL@2177..2481
+              LOCAL_KW@2177..2182 "local"
+              WHITESPACE@2182..2183 " "
+              BIND_FUNCTION@2183..2480
+                NAME@2183..2193
+                  IDENT@2183..2193 "body_lines"
+                PARAMS_DESC@2193..2199
+                  L_PAREN@2193..2194 "("
+                  PARAM@2194..2198
+                    DESTRUCT_FULL@2194..2198
+                      NAME@2194..2198
+                        IDENT@2194..2198 "body"
+                  R_PAREN@2198..2199 ")"
+                WHITESPACE@2199..2200 " "
+                ASSIGN@2200..2201 "="
+                WHITESPACE@2201..2208 "\n      "
+                EXPR@2208..2480
+                  EXPR_VAR@2208..2211
+                    NAME@2208..2211
+                      IDENT@2208..2211 "std"
+                  SUFFIX_INDEX@2211..2216
+                    DOT@2211..2212 "."
+                    NAME@2212..2216
+                      IDENT@2212..2216 "join"
+                  SUFFIX_APPLY@2216..2480
+                    ARGS_DESC@2216..2480
+                      L_PAREN@2216..2217 "("
+                      ARG@2217..2219
+                        EXPR@2217..2219
+                          EXPR_ARRAY@2217..2219
+                            L_BRACK@2217..2218 "["
+                            R_BRACK@2218..2219 "]"
+                      COMMA@2219..2220 ","
+                      WHITESPACE@2220..2221 " "
+                      ARG@2221..2479
+                        EXPR@2221..2479
+                          EXPR_ARRAY_COMP@2221..2479
+                            L_BRACK@2221..2222 "["
+                            WHITESPACE@2222..2231 "\n        "
+                            EXPR@2231..2430
+                              STMT_LOCAL@2231..2263
+                                LOCAL_KW@2231..2236 "local"
+                                WHITESPACE@2236..2237 " "
+                                BIND_DESTRUCT@2237..2262
+                                  DESTRUCT_FULL@2237..2252
+                                    NAME@2237..2252
+                                      IDENT@2237..2252 "value_or_values"
+                                  WHITESPACE@2252..2253 " "
+                                  ASSIGN@2253..2254 "="
+                                  WHITESPACE@2254..2255 " "
+                                  EXPR@2255..2262
+                                    EXPR_VAR@2255..2259
+                                      NAME@2255..2259
+                                        IDENT@2255..2259 "body"
+                                    SUFFIX_INDEX_EXPR@2259..2262
+                                      L_BRACK@2259..2260 "["
+                                      EXPR@2260..2261
+                                        EXPR_VAR@2260..2261
+                                          NAME@2260..2261
+                                            IDENT@2260..2261 "k"
+                                      R_BRACK@2261..2262 "]"
+                                SEMI@2262..2263 ";"
+                              WHITESPACE@2263..2272 "\n        "
+                              EXPR_IF_THEN_ELSE@2272..2430
+                                IF_KW@2272..2274 "if"
+                                WHITESPACE@2274..2275 " "
+                                EXPR@2275..2303
+                                  EXPR_VAR@2275..2278
+                                    NAME@2275..2278
+                                      IDENT@2275..2278 "std"
+                                  SUFFIX_INDEX@2278..2286
+                                    DOT@2278..2279 "."
+                                    NAME@2279..2286
+                                      IDENT@2279..2286 "isArray"
+                                  SUFFIX_APPLY@2286..2303
+                                    ARGS_DESC@2286..2303
+                                      L_PAREN@2286..2287 "("
+                                      ARG@2287..2302
+                                        EXPR@2287..2302
+                                          EXPR_VAR@2287..2302
+                                            NAME@2287..2302
+                                              IDENT@2287..2302 "value_or_values"
+                                      R_PAREN@2302..2303 ")"
+                                WHITESPACE@2303..2304 " "
+                                THEN_KW@2304..2308 "then"
+                                WHITESPACE@2308..2319 "\n          "
+                                TRUE_EXPR@2319..2372
+                                  EXPR@2319..2372
+                                    EXPR_ARRAY_COMP@2319..2372
+                                      L_BRACK@2319..2320 "["
+                                      EXPR@2320..2342
+                                        EXPR_BINARY@2320..2342
+                                          EXPR@2320..2329
+                                            EXPR_STRING@2320..2329
+                                              STRING_SINGLE@2320..2329 "'%s = %s'"
+                                          WHITESPACE@2329..2330 " "
+                                          MODULO@2330..2331 "%"
+                                          WHITESPACE@2331..2332 " "
+                                          EXPR@2332..2342
+                                            EXPR_ARRAY@2332..2342
+                                              L_BRACK@2332..2333 "["
+                                              EXPR@2333..2334
+                                                EXPR_VAR@2333..2334
+                                                  NAME@2333..2334
+                                                    IDENT@2333..2334 "k"
+                                              COMMA@2334..2335 ","
+                                              WHITESPACE@2335..2336 " "
+                                              EXPR@2336..2341
+                                                EXPR_VAR@2336..2341
+                                                  NAME@2336..2341
+                                                    IDENT@2336..2341 "value"
+                                              R_BRACK@2341..2342 "]"
+                                      WHITESPACE@2342..2343 " "
+                                      FOR_SPEC@2343..2371
+                                        FOR_KW@2343..2346 "for"
+                                        WHITESPACE@2346..2347 " "
+                                        DESTRUCT_FULL@2347..2352
+                                          NAME@2347..2352
+                                            IDENT@2347..2352 "value"
+                                        WHITESPACE@2352..2353 " "
+                                        IN_KW@2353..2355 "in"
+                                        WHITESPACE@2355..2356 " "
+                                        EXPR@2356..2371
+                                          EXPR_VAR@2356..2371
+                                            NAME@2356..2371
+                                              IDENT@2356..2371 "value_or_values"
+                                      R_BRACK@2371..2372 "]"
+                                WHITESPACE@2372..2381 "\n        "
+                                ELSE_KW@2381..2385 "else"
+                                WHITESPACE@2385..2396 "\n          "
+                                FALSE_EXPR@2396..2430
+                                  EXPR@2396..2430
+                                    EXPR_ARRAY@2396..2430
+                                      L_BRACK@2396..2397 "["
+                                      EXPR@2397..2429
+                                        EXPR_BINARY@2397..2429
+                                          EXPR@2397..2406
+                                            EXPR_STRING@2397..2406
+                                              STRING_SINGLE@2397..2406 "'%s = %s'"
+                                          WHITESPACE@2406..2407 " "
+                                          MODULO@2407..2408 "%"
+                                          WHITESPACE@2408..2409 " "
+                                          EXPR@2409..2429
+                                            EXPR_ARRAY@2409..2429
+                                              L_BRACK@2409..2410 "["
+                                              EXPR@2410..2411
+                                                EXPR_VAR@2410..2411
+                                                  NAME@2410..2411
+                                                    IDENT@2410..2411 "k"
+                                              COMMA@2411..2412 ","
+                                              WHITESPACE@2412..2413 " "
+                                              EXPR@2413..2428
+                                                EXPR_VAR@2413..2428
+                                                  NAME@2413..2428
+                                                    IDENT@2413..2428 "value_or_values"
+                                              R_BRACK@2428..2429 "]"
+                                      R_BRACK@2429..2430 "]"
+                            WHITESPACE@2430..2440 "\n\n        "
+                            FOR_SPEC@2440..2471
+                              FOR_KW@2440..2443 "for"
+                              WHITESPACE@2443..2444 " "
+                              DESTRUCT_FULL@2444..2445
+                                NAME@2444..2445
+                                  IDENT@2444..2445 "k"
+                              WHITESPACE@2445..2446 " "
+                              IN_KW@2446..2448 "in"
+                              WHITESPACE@2448..2449 " "
+                              EXPR@2449..2471
+                                EXPR_VAR@2449..2452
+                                  NAME@2449..2452
+                                    IDENT@2449..2452 "std"
+                                SUFFIX_INDEX@2452..2465
+                                  DOT@2452..2453 "."
+                                  NAME@2453..2465
+                                    IDENT@2453..2465 "objectFields"
+                                SUFFIX_APPLY@2465..2471
+                                  ARGS_DESC@2465..2471
+                                    L_PAREN@2465..2466 "("
+                                    ARG@2466..2470
+                                      EXPR@2466..2470
+                                        EXPR_VAR@2466..2470
+                                          NAME@2466..2470
+                                            IDENT@2466..2470 "body"
+                                    R_PAREN@2470..2471 ")"
+                            WHITESPACE@2471..2478 "\n      "
+                            R_BRACK@2478..2479 "]"
+                      R_PAREN@2479..2480 ")"
+              SEMI@2480..2481 ";"
+            WHITESPACE@2481..2487 "\n\n    "
+            STMT_LOCAL@2487..2769
+              LOCAL_KW@2487..2492 "local"
+              WHITESPACE@2492..2493 " "
+              BIND_FUNCTION@2493..2561
+                NAME@2493..2506
+                  IDENT@2493..2506 "section_lines"
+                PARAMS_DESC@2506..2520
+                  L_PAREN@2506..2507 "("
+                  PARAM@2507..2512
+                    DESTRUCT_FULL@2507..2512
+                      NAME@2507..2512
+                        IDENT@2507..2512 "sname"
+                  COMMA@2512..2513 ","
+                  WHITESPACE@2513..2514 " "
+                  PARAM@2514..2519
+                    DESTRUCT_FULL@2514..2519
+                      NAME@2514..2519
+                        IDENT@2514..2519 "sbody"
+                  R_PAREN@2519..2520 ")"
+                WHITESPACE@2520..2521 " "
+                ASSIGN@2521..2522 "="
+                WHITESPACE@2522..2523 " "
+                EXPR@2523..2561
+                  EXPR_BINARY@2523..2561
+                    EXPR@2523..2541
+                      EXPR_ARRAY@2523..2541
+                        L_BRACK@2523..2524 "["
+                        EXPR@2524..2540
+                          EXPR_BINARY@2524..2540
+                            EXPR@2524..2530
+                              EXPR_STRING@2524..2530
+                                STRING_SINGLE@2524..2530 "'[%s]'"
+                            WHITESPACE@2530..2531 " "
+                            MODULO@2531..2532 "%"
+                            WHITESPACE@2532..2533 " "
+                            EXPR@2533..2540
+                              EXPR_ARRAY@2533..2540
+                                L_BRACK@2533..2534 "["
+                                EXPR@2534..2539
+                                  EXPR_VAR@2534..2539
+                                    NAME@2534..2539
+                                      IDENT@2534..2539 "sname"
+                                R_BRACK@2539..2540 "]"
+                        R_BRACK@2540..2541 "]"
+                    WHITESPACE@2541..2542 " "
+                    PLUS@2542..2543 "+"
+                    WHITESPACE@2543..2544 " "
+                    EXPR@2544..2561
+                      EXPR_VAR@2544..2554
+                        NAME@2544..2554
+                          IDENT@2544..2554 "body_lines"
+                      SUFFIX_APPLY@2554..2561
+                        ARGS_DESC@2554..2561
+                          L_PAREN@2554..2555 "("
+                          ARG@2555..2560
+                            EXPR@2555..2560
+                              EXPR_VAR@2555..2560
+                                NAME@2555..2560
+                                  IDENT@2555..2560 "sbody"
+                          R_PAREN@2560..2561 ")"
+              COMMA@2561..2562 ","
+              WHITESPACE@2562..2573 "\n          "
+              BIND_DESTRUCT@2573..2648
+                DESTRUCT_FULL@2573..2582
+                  NAME@2573..2582
+                    IDENT@2573..2582 "main_body"
+                WHITESPACE@2582..2583 " "
+                ASSIGN@2583..2584 "="
+                WHITESPACE@2584..2585 " "
+                EXPR@2585..2648
+                  EXPR_IF_THEN_ELSE@2585..2648
+                    IF_KW@2585..2587 "if"
+                    WHITESPACE@2587..2588 " "
+                    EXPR@2588..2614
+                      EXPR_VAR@2588..2591
+                        NAME@2588..2591
+                          IDENT@2588..2591 "std"
+                      SUFFIX_INDEX@2591..2601
+                        DOT@2591..2592 "."
+                        NAME@2592..2601
+                          IDENT@2592..2601 "objectHas"
+                      SUFFIX_APPLY@2601..2614
+                        ARGS_DESC@2601..2614
+                          L_PAREN@2601..2602 "("
+                          ARG@2602..2605
+                            EXPR@2602..2605
+                              EXPR_VAR@2602..2605
+                                NAME@2602..2605
+                                  IDENT@2602..2605 "ini"
+                          COMMA@2605..2606 ","
+                          WHITESPACE@2606..2607 " "
+                          ARG@2607..2613
+                            EXPR@2607..2613
+                              EXPR_STRING@2607..2613
+                                STRING_SINGLE@2607..2613 "'main'"
+                          R_PAREN@2613..2614 ")"
+                    WHITESPACE@2614..2615 " "
+                    THEN_KW@2615..2619 "then"
+                    WHITESPACE@2619..2620 " "
+                    TRUE_EXPR@2620..2640
+                      EXPR@2620..2640
+                        EXPR_VAR@2620..2630
+                          NAME@2620..2630
+                            IDENT@2620..2630 "body_lines"
+                        SUFFIX_APPLY@2630..2640
+                          ARGS_DESC@2630..2640
+                            L_PAREN@2630..2631 "("
+                            ARG@2631..2639
+                              EXPR@2631..2639
+                                EXPR_VAR@2631..2634
+                                  NAME@2631..2634
+                                    IDENT@2631..2634 "ini"
+                                SUFFIX_INDEX@2634..2639
+                                  DOT@2634..2635 "."
+                                  NAME@2635..2639
+                                    IDENT@2635..2639 "main"
+                            R_PAREN@2639..2640 ")"
+                    WHITESPACE@2640..2641 " "
+                    ELSE_KW@2641..2645 "else"
+                    WHITESPACE@2645..2646 " "
+                    FALSE_EXPR@2646..2648
+                      EXPR@2646..2648
+                        EXPR_ARRAY@2646..2648
+                          L_BRACK@2646..2647 "["
+                          R_BRACK@2647..2648 "]"
+              COMMA@2648..2649 ","
+              WHITESPACE@2649..2660 "\n          "
+              BIND_DESTRUCT@2660..2768
+                DESTRUCT_FULL@2660..2672
+                  NAME@2660..2672
+                    IDENT@2660..2672 "all_sections"
+                WHITESPACE@2672..2673 " "
+                ASSIGN@2673..2674 "="
+                WHITESPACE@2674..2675 " "
+                EXPR@2675..2768
+                  EXPR_ARRAY_COMP@2675..2768
+                    L_BRACK@2675..2676 "["
+                    WHITESPACE@2676..2683 "\n      "
+                    EXPR@2683..2716
+                      EXPR_VAR@2683..2696
+                        NAME@2683..2696
+                          IDENT@2683..2696 "section_lines"
+                      SUFFIX_APPLY@2696..2716
+                        ARGS_DESC@2696..2716
+                          L_PAREN@2696..2697 "("
+                          ARG@2697..2698
+                            EXPR@2697..2698
+                              EXPR_VAR@2697..2698
+                                NAME@2697..2698
+                                  IDENT@2697..2698 "k"
+                          COMMA@2698..2699 ","
+                          WHITESPACE@2699..2700 " "
+                          ARG@2700..2715
+                            EXPR@2700..2715
+                              EXPR_VAR@2700..2703
+                                NAME@2700..2703
+                                  IDENT@2700..2703 "ini"
+                              SUFFIX_INDEX@2703..2712
+                                DOT@2703..2704 "."
+                                NAME@2704..2712
+                                  IDENT@2704..2712 "sections"
+                              SUFFIX_INDEX_EXPR@2712..2715
+                                L_BRACK@2712..2713 "["
+                                EXPR@2713..2714
+                                  EXPR_VAR@2713..2714
+                                    NAME@2713..2714
+                                      IDENT@2713..2714 "k"
+                                R_BRACK@2714..2715 "]"
+                          R_PAREN@2715..2716 ")"
+                    WHITESPACE@2716..2723 "\n      "
+                    FOR_SPEC@2723..2762
+                      FOR_KW@2723..2726 "for"
+                      WHITESPACE@2726..2727 " "
+                      DESTRUCT_FULL@2727..2728
+                        NAME@2727..2728
+                          IDENT@2727..2728 "k"
+                      WHITESPACE@2728..2729 " "
+                      IN_KW@2729..2731 "in"
+                      WHITESPACE@2731..2732 " "
+                      EXPR@2732..2762
+                        EXPR_VAR@2732..2735
+                          NAME@2732..2735
+                            IDENT@2732..2735 "std"
+                        SUFFIX_INDEX@2735..2748
+                          DOT@2735..2736 "."
+                          NAME@2736..2748
+                            IDENT@2736..2748 "objectFields"
+                        SUFFIX_APPLY@2748..2762
+                          ARGS_DESC@2748..2762
+                            L_PAREN@2748..2749 "("
+                            ARG@2749..2761
+                              EXPR@2749..2761
+                                EXPR_VAR@2749..2752
+                                  NAME@2749..2752
+                                    IDENT@2749..2752 "ini"
+                                SUFFIX_INDEX@2752..2761
+                                  DOT@2752..2753 "."
+                                  NAME@2753..2761
+                                    IDENT@2753..2761 "sections"
+                            R_PAREN@2761..2762 ")"
+                    WHITESPACE@2762..2767 "\n    "
+                    R_BRACK@2767..2768 "]"
+              SEMI@2768..2769 ";"
+            WHITESPACE@2769..2774 "\n    "
+            EXPR_VAR@2774..2777
+              NAME@2774..2777
+                IDENT@2774..2777 "std"
+            SUFFIX_INDEX@2777..2782
+              DOT@2777..2778 "."
+              NAME@2778..2782
+                IDENT@2778..2782 "join"
+            SUFFIX_APPLY@2782..2840
+              ARGS_DESC@2782..2840
+                L_PAREN@2782..2783 "("
+                ARG@2783..2787
+                  EXPR@2783..2787
+                    EXPR_STRING@2783..2787
+                      STRING_SINGLE@2783..2787 "'\\n'"
+                COMMA@2787..2788 ","
+                WHITESPACE@2788..2789 " "
+                ARG@2789..2839
+                  EXPR@2789..2839
+                    EXPR_BINARY@2789..2839
+                      EXPR@2789..2832
+                        EXPR_BINARY@2789..2832
+                          EXPR@2789..2798
+                            EXPR_VAR@2789..2798
+                              NAME@2789..2798
+                                IDENT@2789..2798 "main_body"
+                          WHITESPACE@2798..2799 " "
+                          PLUS@2799..2800 "+"
+                          WHITESPACE@2800..2801 " "
+                          EXPR@2801..2832
+                            EXPR_VAR@2801..2804
+                              NAME@2801..2804
+                                IDENT@2801..2804 "std"
+                            SUFFIX_INDEX@2804..2818
+                              DOT@2804..2805 "."
+                              NAME@2805..2818
+                                IDENT@2805..2818 "flattenArrays"
+                            SUFFIX_APPLY@2818..2832
+                              ARGS_DESC@2818..2832
+                                L_PAREN@2818..2819 "("
+                                ARG@2819..2831
+                                  EXPR@2819..2831
+                                    EXPR_VAR@2819..2831
+                                      NAME@2819..2831
+                                        IDENT@2819..2831 "all_sections"
+                                R_PAREN@2831..2832 ")"
+                      WHITESPACE@2832..2833 " "
+                      PLUS@2833..2834 "+"
+                      WHITESPACE@2834..2835 " "
+                      EXPR@2835..2839
+                        EXPR_ARRAY@2835..2839
+                          L_BRACK@2835..2836 "["
+                          EXPR@2836..2838
+                            EXPR_STRING@2836..2838
+                              STRING_SINGLE@2836..2838 "''"
+                          R_BRACK@2838..2839 "]"
+                R_PAREN@2839..2840 ")"
+        COMMA@2840..2841 ","
+        WHITESPACE@2841..2845 "\n\n  "
+        MEMBER_FIELD_METHOD@2845..2898
+          FIELD_NAME_FIXED@2845..2857
+            NAME@2845..2857
+              IDENT@2845..2857 "manifestToml"
+          PARAMS_DESC@2857..2864
+            L_PAREN@2857..2858 "("
+            PARAM@2858..2863
+              DESTRUCT_FULL@2858..2863
+                NAME@2858..2863
+                  IDENT@2858..2863 "value"
+            R_PAREN@2863..2864 ")"
+          COLONCOLON@2864..2866 "::"
+          WHITESPACE@2866..2867 " "
+          EXPR@2867..2898
+            EXPR_VAR@2867..2870
+              NAME@2867..2870
+                IDENT@2867..2870 "std"
+            SUFFIX_INDEX@2870..2885
+              DOT@2870..2871 "."
+              NAME@2871..2885
+                IDENT@2871..2885 "manifestTomlEx"
+            SUFFIX_APPLY@2885..2898
+              ARGS_DESC@2885..2898
+                L_PAREN@2885..2886 "("
+                ARG@2886..2891
+                  EXPR@2886..2891
+                    EXPR_VAR@2886..2891
+                      NAME@2886..2891
+                        IDENT@2886..2891 "value"
+                COMMA@2891..2892 ","
+                WHITESPACE@2892..2893 " "
+                ARG@2893..2897
+                  EXPR@2893..2897
+                    EXPR_STRING@2893..2897
+                      STRING_SINGLE@2893..2897 "'  '"
+                R_PAREN@2897..2898 ")"
+        COMMA@2898..2899 ","
+        WHITESPACE@2899..2903 "\n\n  "
+        MEMBER_FIELD_METHOD@2903..2958
+          FIELD_NAME_FIXED@2903..2921
+            NAME@2903..2921
+              IDENT@2903..2921 "escapeStringPython"
+          PARAMS_DESC@2921..2926
+            L_PAREN@2921..2922 "("
+            PARAM@2922..2925
+              DESTRUCT_FULL@2922..2925
+                NAME@2922..2925
+                  IDENT@2922..2925 "str"
+            R_PAREN@2925..2926 ")"
+          COLONCOLON@2926..2928 "::"
+          WHITESPACE@2928..2933 "\n    "
+          EXPR@2933..2958
+            EXPR_VAR@2933..2936
+              NAME@2933..2936
+                IDENT@2933..2936 "std"
+            SUFFIX_INDEX@2936..2953
+              DOT@2936..2937 "."
+              NAME@2937..2953
+                IDENT@2937..2953 "escapeStringJson"
+            SUFFIX_APPLY@2953..2958
+              ARGS_DESC@2953..2958
+                L_PAREN@2953..2954 "("
+                ARG@2954..2957
+                  EXPR@2954..2957
+                    EXPR_VAR@2954..2957
+                      NAME@2954..2957
+                        IDENT@2954..2957 "str"
+                R_PAREN@2957..2958 ")"
+        COMMA@2958..2959 ","
+        WHITESPACE@2959..2963 "\n\n  "
+        MEMBER_FIELD_METHOD@2963..3180
+          FIELD_NAME_FIXED@2963..2979
+            NAME@2963..2979
+              IDENT@2963..2979 "escapeStringBash"
+          PARAMS_DESC@2979..2985
+            L_PAREN@2979..2980 "("
+            PARAM@2980..2984
+              DESTRUCT_FULL@2980..2984
+                NAME@2980..2984
+                  IDENT@2980..2984 "str_"
+            R_PAREN@2984..2985 ")"
+          COLONCOLON@2985..2987 "::"
+          WHITESPACE@2987..2992 "\n    "
+          EXPR@2992..3180
+            STMT_LOCAL@2992..3023
+              LOCAL_KW@2992..2997 "local"
+              WHITESPACE@2997..2998 " "
+              BIND_DESTRUCT@2998..3022
+                DESTRUCT_FULL@2998..3001
+                  NAME@2998..3001
+                    IDENT@2998..3001 "str"
+                WHITESPACE@3001..3002 " "
+                ASSIGN@3002..3003 "="
+                WHITESPACE@3003..3004 " "
+                EXPR@3004..3022
+                  EXPR_VAR@3004..3007
+                    NAME@3004..3007
+                      IDENT@3004..3007 "std"
+                  SUFFIX_INDEX@3007..3016
+                    DOT@3007..3008 "."
+                    NAME@3008..3016
+                      IDENT@3008..3016 "toString"
+                  SUFFIX_APPLY@3016..3022
+                    ARGS_DESC@3016..3022
+                      L_PAREN@3016..3017 "("
+                      ARG@3017..3021
+                        EXPR@3017..3021
+                          EXPR_VAR@3017..3021
+                            NAME@3017..3021
+                              IDENT@3017..3021 "str_"
+                      R_PAREN@3021..3022 ")"
+              SEMI@3022..3023 ";"
+            WHITESPACE@3023..3028 "\n    "
+            STMT_LOCAL@3028..3110
+              LOCAL_KW@3028..3033 "local"
+              WHITESPACE@3033..3034 " "
+              BIND_FUNCTION@3034..3109
+                NAME@3034..3039
+                  IDENT@3034..3039 "trans"
+                PARAMS_DESC@3039..3043
+                  L_PAREN@3039..3040 "("
+                  PARAM@3040..3042
+                    DESTRUCT_FULL@3040..3042
+                      NAME@3040..3042
+                        IDENT@3040..3042 "ch"
+                  R_PAREN@3042..3043 ")"
+                WHITESPACE@3043..3044 " "
+                ASSIGN@3044..3045 "="
+                WHITESPACE@3045..3052 "\n      "
+                EXPR@3052..3109
+                  EXPR_IF_THEN_ELSE@3052..3109
+                    IF_KW@3052..3054 "if"
+                    WHITESPACE@3054..3055 " "
+                    EXPR@3055..3064
+                      EXPR_BINARY@3055..3064
+                        EXPR@3055..3057
+                          EXPR_VAR@3055..3057
+                            NAME@3055..3057
+                              IDENT@3055..3057 "ch"
+                        WHITESPACE@3057..3058 " "
+                        EQ@3058..3060 "=="
+                        WHITESPACE@3060..3061 " "
+                        EXPR@3061..3064
+                          EXPR_STRING@3061..3064
+                            STRING_DOUBLE@3061..3064 "\"'\""
+                    WHITESPACE@3064..3065 " "
+                    THEN_KW@3065..3069 "then"
+                    WHITESPACE@3069..3078 "\n        "
+                    TRUE_EXPR@3078..3087
+                      EXPR@3078..3087
+                        EXPR_STRING@3078..3087
+                          STRING_DOUBLE@3078..3087 "\"'\\\"'\\\"'\""
+                    WHITESPACE@3087..3094 "\n      "
+                    ELSE_KW@3094..3098 "else"
+                    WHITESPACE@3098..3107 "\n        "
+                    FALSE_EXPR@3107..3109
+                      EXPR@3107..3109
+                        EXPR_VAR@3107..3109
+                          NAME@3107..3109
+                            IDENT@3107..3109 "ch"
+              SEMI@3109..3110 ";"
+            WHITESPACE@3110..3115 "\n    "
+            EXPR_BINARY@3115..3180
+              EXPR@3115..3121
+                EXPR_STRING@3115..3121
+                  STRING_DOUBLE@3115..3121 "\"'%s'\""
+              WHITESPACE@3121..3122 " "
+              MODULO@3122..3123 "%"
+              WHITESPACE@3123..3124 " "
+              EXPR@3124..3180
+                EXPR_VAR@3124..3127
+                  NAME@3124..3127
+                    IDENT@3124..3127 "std"
+                SUFFIX_INDEX@3127..3132
+                  DOT@3127..3128 "."
+                  NAME@3128..3132
+                    IDENT@3128..3132 "join"
+                SUFFIX_APPLY@3132..3180
+                  ARGS_DESC@3132..3180
+                    L_PAREN@3132..3133 "("
+                    ARG@3133..3135
+                      EXPR@3133..3135
+                        EXPR_STRING@3133..3135
+                          STRING_SINGLE@3133..3135 "''"
+                    COMMA@3135..3136 ","
+                    WHITESPACE@3136..3137 " "
+                    ARG@3137..3179
+                      EXPR@3137..3179
+                        EXPR_ARRAY_COMP@3137..3179
+                          L_BRACK@3137..3138 "["
+                          EXPR@3138..3147
+                            EXPR_VAR@3138..3143
+                              NAME@3138..3143
+                                IDENT@3138..3143 "trans"
+                            SUFFIX_APPLY@3143..3147
+                              ARGS_DESC@3143..3147
+                                L_PAREN@3143..3144 "("
+                                ARG@3144..3146
+                                  EXPR@3144..3146
+                                    EXPR_VAR@3144..3146
+                                      NAME@3144..3146
+                                        IDENT@3144..3146 "ch"
+                                R_PAREN@3146..3147 ")"
+                          WHITESPACE@3147..3148 " "
+                          FOR_SPEC@3148..3178
+                            FOR_KW@3148..3151 "for"
+                            WHITESPACE@3151..3152 " "
+                            DESTRUCT_FULL@3152..3154
+                              NAME@3152..3154
+                                IDENT@3152..3154 "ch"
+                            WHITESPACE@3154..3155 " "
+                            IN_KW@3155..3157 "in"
+                            WHITESPACE@3157..3158 " "
+                            EXPR@3158..3178
+                              EXPR_VAR@3158..3161
+                                NAME@3158..3161
+                                  IDENT@3158..3161 "std"
+                              SUFFIX_INDEX@3161..3173
+                                DOT@3161..3162 "."
+                                NAME@3162..3173
+                                  IDENT@3162..3173 "stringChars"
+                              SUFFIX_APPLY@3173..3178
+                                ARGS_DESC@3173..3178
+                                  L_PAREN@3173..3174 "("
+                                  ARG@3174..3177
+                                    EXPR@3174..3177
+                                      EXPR_VAR@3174..3177
+                                        NAME@3174..3177
+                                          IDENT@3174..3177 "str"
+                                  R_PAREN@3177..3178 ")"
+                          R_BRACK@3178..3179 "]"
+                    R_PAREN@3179..3180 ")"
+        COMMA@3180..3181 ","
+        WHITESPACE@3181..3185 "\n\n  "
+        MEMBER_FIELD_METHOD@3185..3399
+          FIELD_NAME_FIXED@3185..3204
+            NAME@3185..3204
+              IDENT@3185..3204 "escapeStringDollars"
+          PARAMS_DESC@3204..3210
+            L_PAREN@3204..3205 "("
+            PARAM@3205..3209
+              DESTRUCT_FULL@3205..3209
+                NAME@3205..3209
+                  IDENT@3205..3209 "str_"
+            R_PAREN@3209..3210 ")"
+          COLONCOLON@3210..3212 "::"
+          WHITESPACE@3212..3217 "\n    "
+          EXPR@3217..3399
+            STMT_LOCAL@3217..3248
+              LOCAL_KW@3217..3222 "local"
+              WHITESPACE@3222..3223 " "
+              BIND_DESTRUCT@3223..3247
+                DESTRUCT_FULL@3223..3226
+                  NAME@3223..3226
+                    IDENT@3223..3226 "str"
+                WHITESPACE@3226..3227 " "
+                ASSIGN@3227..3228 "="
+                WHITESPACE@3228..3229 " "
+                EXPR@3229..3247
+                  EXPR_VAR@3229..3232
+                    NAME@3229..3232
+                      IDENT@3229..3232 "std"
+                  SUFFIX_INDEX@3232..3241
+                    DOT@3232..3233 "."
+                    NAME@3233..3241
+                      IDENT@3233..3241 "toString"
+                  SUFFIX_APPLY@3241..3247
+                    ARGS_DESC@3241..3247
+                      L_PAREN@3241..3242 "("
+                      ARG@3242..3246
+                        EXPR@3242..3246
+                          EXPR_VAR@3242..3246
+                            NAME@3242..3246
+                              IDENT@3242..3246 "str_"
+                      R_PAREN@3246..3247 ")"
+              SEMI@3247..3248 ";"
+            WHITESPACE@3248..3253 "\n    "
+            STMT_LOCAL@3253..3330
+              LOCAL_KW@3253..3258 "local"
+              WHITESPACE@3258..3259 " "
+              BIND_FUNCTION@3259..3329
+                NAME@3259..3264
+                  IDENT@3259..3264 "trans"
+                PARAMS_DESC@3264..3268
+                  L_PAREN@3264..3265 "("
+                  PARAM@3265..3267
+                    DESTRUCT_FULL@3265..3267
+                      NAME@3265..3267
+                        IDENT@3265..3267 "ch"
+                  R_PAREN@3267..3268 ")"
+                WHITESPACE@3268..3269 " "
+                ASSIGN@3269..3270 "="
+                WHITESPACE@3270..3277 "\n      "
+                EXPR@3277..3329
+                  EXPR_IF_THEN_ELSE@3277..3329
+                    IF_KW@3277..3279 "if"
+                    WHITESPACE@3279..3280 " "
+                    EXPR@3280..3289
+                      EXPR_BINARY@3280..3289
+                        EXPR@3280..3282
+                          EXPR_VAR@3280..3282
+                            NAME@3280..3282
+                              IDENT@3280..3282 "ch"
+                        WHITESPACE@3282..3283 " "
+                        EQ@3283..3285 "=="
+                        WHITESPACE@3285..3286 " "
+                        EXPR@3286..3289
+                          EXPR_STRING@3286..3289
+                            STRING_SINGLE@3286..3289 "'$'"
+                    WHITESPACE@3289..3290 " "
+                    THEN_KW@3290..3294 "then"
+                    WHITESPACE@3294..3303 "\n        "
+                    TRUE_EXPR@3303..3307
+                      EXPR@3303..3307
+                        EXPR_STRING@3303..3307
+                          STRING_SINGLE@3303..3307 "'$$'"
+                    WHITESPACE@3307..3314 "\n      "
+                    ELSE_KW@3314..3318 "else"
+                    WHITESPACE@3318..3327 "\n        "
+                    FALSE_EXPR@3327..3329
+                      EXPR@3327..3329
+                        EXPR_VAR@3327..3329
+                          NAME@3327..3329
+                            IDENT@3327..3329 "ch"
+              SEMI@3329..3330 ";"
+            WHITESPACE@3330..3335 "\n    "
+            EXPR_VAR@3335..3338
+              NAME@3335..3338
+                IDENT@3335..3338 "std"
+            SUFFIX_INDEX@3338..3344
+              DOT@3338..3339 "."
+              NAME@3339..3344
+                IDENT@3339..3344 "foldl"
+            SUFFIX_APPLY@3344..3399
+              ARGS_DESC@3344..3399
+                L_PAREN@3344..3345 "("
+                ARG@3345..3372
+                  EXPR@3345..3372
+                    EXPR_FUNCTION@3345..3372
+                      FUNCTION_KW@3345..3353 "function"
+                      PARAMS_DESC@3353..3359
+                        L_PAREN@3353..3354 "("
+                        PARAM@3354..3355
+                          DESTRUCT_FULL@3354..3355
+                            NAME@3354..3355
+                              IDENT@3354..3355 "a"
+                        COMMA@3355..3356 ","
+                        WHITESPACE@3356..3357 " "
+                        PARAM@3357..3358
+                          DESTRUCT_FULL@3357..3358
+                            NAME@3357..3358
+                              IDENT@3357..3358 "b"
+                        R_PAREN@3358..3359 ")"
+                      WHITESPACE@3359..3360 " "
+                      EXPR@3360..3372
+                        EXPR_BINARY@3360..3372
+                          EXPR@3360..3361
+                            EXPR_VAR@3360..3361
+                              NAME@3360..3361
+                                IDENT@3360..3361 "a"
+                          WHITESPACE@3361..3362 " "
+                          PLUS@3362..3363 "+"
+                          WHITESPACE@3363..3364 " "
+                          EXPR@3364..3372
+                            EXPR_VAR@3364..3369
+                              NAME@3364..3369
+                                IDENT@3364..3369 "trans"
+                            SUFFIX_APPLY@3369..3372
+                              ARGS_DESC@3369..3372
+                                L_PAREN@3369..3370 "("
+                                ARG@3370..3371
+                                  EXPR@3370..3371
+                                    EXPR_VAR@3370..3371
+                                      NAME@3370..3371
+                                        IDENT@3370..3371 "b"
+                                R_PAREN@3371..3372 ")"
+                COMMA@3372..3373 ","
+                WHITESPACE@3373..3374 " "
+                ARG@3374..3394
+                  EXPR@3374..3394
+                    EXPR_VAR@3374..3377
+                      NAME@3374..3377
+                        IDENT@3374..3377 "std"
+                    SUFFIX_INDEX@3377..3389
+                      DOT@3377..3378 "."
+                      NAME@3378..3389
+                        IDENT@3378..3389 "stringChars"
+                    SUFFIX_APPLY@3389..3394
+                      ARGS_DESC@3389..3394
+                        L_PAREN@3389..3390 "("
+                        ARG@3390..3393
+                          EXPR@3390..3393
+                            EXPR_VAR@3390..3393
+                              NAME@3390..3393
+                                IDENT@3390..3393 "str"
+                        R_PAREN@3393..3394 ")"
+                COMMA@3394..3395 ","
+                WHITESPACE@3395..3396 " "
+                ARG@3396..3398
+                  EXPR@3396..3398
+                    EXPR_STRING@3396..3398
+                      STRING_SINGLE@3396..3398 "''"
+                R_PAREN@3398..3399 ")"
+        COMMA@3399..3400 ","
+        WHITESPACE@3400..3404 "\n\n  "
+        MEMBER_BIND_STMT@3404..3519
+          OBJ_LOCAL@3404..3519
+            LOCAL_KW@3404..3409 "local"
+            WHITESPACE@3409..3410 " "
+            BIND_DESTRUCT@3410..3519
+              DESTRUCT_FULL@3410..3421
+                NAME@3410..3421
+                  IDENT@3410..3421 "xml_escapes"
+              WHITESPACE@3421..3422 " "
+              ASSIGN@3422..3423 "="
+              WHITESPACE@3423..3424 " "
+              EXPR@3424..3519
+                EXPR_OBJECT@3424..3519
+                  OBJ_BODY_MEMBER_LIST@3424..3519
+                    L_BRACE@3424..3425 "{"
+                    WHITESPACE@3425..3430 "\n    "
+                    MEMBER_FIELD_NORMAL@3430..3441
+                      FIELD_NAME_FIXED@3430..3433
+                        STRING_SINGLE@3430..3433 "'<'"
+                      COLON@3433..3434 ":"
+                      WHITESPACE@3434..3435 " "
+                      EXPR@3435..3441
+                        EXPR_STRING@3435..3441
+                          STRING_SINGLE@3435..3441 "'&lt;'"
+                    COMMA@3441..3442 ","
+                    WHITESPACE@3442..3447 "\n    "
+                    MEMBER_FIELD_NORMAL@3447..3458
+                      FIELD_NAME_FIXED@3447..3450
+                        STRING_SINGLE@3447..3450 "'>'"
+                      COLON@3450..3451 ":"
+                      WHITESPACE@3451..3452 " "
+                      EXPR@3452..3458
+                        EXPR_STRING@3452..3458
+                          STRING_SINGLE@3452..3458 "'&gt;'"
+                    COMMA@3458..3459 ","
+                    WHITESPACE@3459..3464 "\n    "
+                    MEMBER_FIELD_NORMAL@3464..3476
+                      FIELD_NAME_FIXED@3464..3467
+                        STRING_SINGLE@3464..3467 "'&'"
+                      COLON@3467..3468 ":"
+                      WHITESPACE@3468..3469 " "
+                      EXPR@3469..3476
+                        EXPR_STRING@3469..3476
+                          STRING_SINGLE@3469..3476 "'&amp;'"
+                    COMMA@3476..3477 ","
+                    WHITESPACE@3477..3482 "\n    "
+                    MEMBER_FIELD_NORMAL@3482..3495
+                      FIELD_NAME_FIXED@3482..3485
+                        STRING_SINGLE@3482..3485 "'\"'"
+                      COLON@3485..3486 ":"
+                      WHITESPACE@3486..3487 " "
+                      EXPR@3487..3495
+                        EXPR_STRING@3487..3495
+                          STRING_SINGLE@3487..3495 "'&quot;'"
+                    COMMA@3495..3496 ","
+                    WHITESPACE@3496..3501 "\n    "
+                    MEMBER_FIELD_NORMAL@3501..3514
+                      FIELD_NAME_FIXED@3501..3504
+                        STRING_DOUBLE@3501..3504 "\"'\""
+                      COLON@3504..3505 ":"
+                      WHITESPACE@3505..3506 " "
+                      EXPR@3506..3514
+                        EXPR_STRING@3506..3514
+                          STRING_SINGLE@3506..3514 "'&apos;'"
+                    COMMA@3514..3515 ","
+                    WHITESPACE@3515..3518 "\n  "
+                    R_BRACE@3518..3519 "}"
+        COMMA@3519..3520 ","
+        WHITESPACE@3520..3524 "\n\n  "
+        MEMBER_FIELD_METHOD@3524..3663
+          FIELD_NAME_FIXED@3524..3539
+            NAME@3524..3539
+              IDENT@3524..3539 "escapeStringXML"
+          PARAMS_DESC@3539..3545
+            L_PAREN@3539..3540 "("
+            PARAM@3540..3544
+              DESTRUCT_FULL@3540..3544
+                NAME@3540..3544
+                  IDENT@3540..3544 "str_"
+            R_PAREN@3544..3545 ")"
+          COLONCOLON@3545..3547 "::"
+          WHITESPACE@3547..3552 "\n    "
+          EXPR@3552..3663
+            STMT_LOCAL@3552..3583
+              LOCAL_KW@3552..3557 "local"
+              WHITESPACE@3557..3558 " "
+              BIND_DESTRUCT@3558..3582
+                DESTRUCT_FULL@3558..3561
+                  NAME@3558..3561
+                    IDENT@3558..3561 "str"
+                WHITESPACE@3561..3562 " "
+                ASSIGN@3562..3563 "="
+                WHITESPACE@3563..3564 " "
+                EXPR@3564..3582
+                  EXPR_VAR@3564..3567
+                    NAME@3564..3567
+                      IDENT@3564..3567 "std"
+                  SUFFIX_INDEX@3567..3576
+                    DOT@3567..3568 "."
+                    NAME@3568..3576
+                      IDENT@3568..3576 "toString"
+                  SUFFIX_APPLY@3576..3582
+                    ARGS_DESC@3576..3582
+                      L_PAREN@3576..3577 "("
+                      ARG@3577..3581
+                        EXPR@3577..3581
+                          EXPR_VAR@3577..3581
+                            NAME@3577..3581
+                              IDENT@3577..3581 "str_"
+                      R_PAREN@3581..3582 ")"
+              SEMI@3582..3583 ";"
+            WHITESPACE@3583..3588 "\n    "
+            EXPR_VAR@3588..3591
+              NAME@3588..3591
+                IDENT@3588..3591 "std"
+            SUFFIX_INDEX@3591..3596
+              DOT@3591..3592 "."
+              NAME@3592..3596
+                IDENT@3592..3596 "join"
+            SUFFIX_APPLY@3596..3663
+              ARGS_DESC@3596..3663
+                L_PAREN@3596..3597 "("
+                ARG@3597..3599
+                  EXPR@3597..3599
+                    EXPR_STRING@3597..3599
+                      STRING_SINGLE@3597..3599 "''"
+                COMMA@3599..3600 ","
+                WHITESPACE@3600..3601 " "
+                ARG@3601..3662
+                  EXPR@3601..3662
+                    EXPR_ARRAY_COMP@3601..3662
+                      L_BRACK@3601..3602 "["
+                      EXPR@3602..3630
+                        EXPR_VAR@3602..3605
+                          NAME@3602..3605
+                            IDENT@3602..3605 "std"
+                        SUFFIX_INDEX@3605..3609
+                          DOT@3605..3606 "."
+                          NAME@3606..3609
+                            IDENT@3606..3609 "get"
+                        SUFFIX_APPLY@3609..3630
+                          ARGS_DESC@3609..3630
+                            L_PAREN@3609..3610 "("
+                            ARG@3610..3621
+                              EXPR@3610..3621
+                                EXPR_VAR@3610..3621
+                                  NAME@3610..3621
+                                    IDENT@3610..3621 "xml_escapes"
+                            COMMA@3621..3622 ","
+                            WHITESPACE@3622..3623 " "
+                            ARG@3623..3625
+                              EXPR@3623..3625
+                                EXPR_VAR@3623..3625
+                                  NAME@3623..3625
+                                    IDENT@3623..3625 "ch"
+                            COMMA@3625..3626 ","
+                            WHITESPACE@3626..3627 " "
+                            ARG@3627..3629
+                              EXPR@3627..3629
+                                EXPR_VAR@3627..3629
+                                  NAME@3627..3629
+                                    IDENT@3627..3629 "ch"
+                            R_PAREN@3629..3630 ")"
+                      WHITESPACE@3630..3631 " "
+                      FOR_SPEC@3631..3661
+                        FOR_KW@3631..3634 "for"
+                        WHITESPACE@3634..3635 " "
+                        DESTRUCT_FULL@3635..3637
+                          NAME@3635..3637
+                            IDENT@3635..3637 "ch"
+                        WHITESPACE@3637..3638 " "
+                        IN_KW@3638..3640 "in"
+                        WHITESPACE@3640..3641 " "
+                        EXPR@3641..3661
+                          EXPR_VAR@3641..3644
+                            NAME@3641..3644
+                              IDENT@3641..3644 "std"
+                          SUFFIX_INDEX@3644..3656
+                            DOT@3644..3645 "."
+                            NAME@3645..3656
+                              IDENT@3645..3656 "stringChars"
+                          SUFFIX_APPLY@3656..3661
+                            ARGS_DESC@3656..3661
+                              L_PAREN@3656..3657 "("
+                              ARG@3657..3660
+                                EXPR@3657..3660
+                                  EXPR_VAR@3657..3660
+                                    NAME@3657..3660
+                                      IDENT@3657..3660 "str"
+                              R_PAREN@3660..3661 ")"
+                      R_BRACK@3661..3662 "]"
+                R_PAREN@3662..3663 ")"
+        COMMA@3663..3664 ","
+        WHITESPACE@3664..3668 "\n\n  "
+        MEMBER_FIELD_METHOD@3668..3734
+          FIELD_NAME_FIXED@3668..3680
+            NAME@3668..3680
+              IDENT@3668..3680 "manifestJson"
+          PARAMS_DESC@3680..3687
+            L_PAREN@3680..3681 "("
+            PARAM@3681..3686
+              DESTRUCT_FULL@3681..3686
+                NAME@3681..3686
+                  IDENT@3681..3686 "value"
+            R_PAREN@3686..3687 ")"
+          COLONCOLON@3687..3689 "::"
+          WHITESPACE@3689..3690 " "
+          EXPR@3690..3734
+            EXPR_VAR@3690..3693
+              NAME@3690..3693
+                IDENT@3690..3693 "std"
+            SUFFIX_INDEX@3693..3708
+              DOT@3693..3694 "."
+              NAME@3694..3708
+                IDENT@3694..3708 "manifestJsonEx"
+            SUFFIX_APPLY@3708..3734
+              ARGS_DESC@3708..3734
+                L_PAREN@3708..3709 "("
+                ARG@3709..3714
+                  EXPR@3709..3714
+                    EXPR_VAR@3709..3714
+                      NAME@3709..3714
+                        IDENT@3709..3714 "value"
+                COMMA@3714..3715 ","
+                WHITESPACE@3715..3716 " "
+                ARG@3716..3722
+                  EXPR@3716..3722
+                    EXPR_STRING@3716..3722
+                      STRING_SINGLE@3716..3722 "'    '"
+                R_PAREN@3722..3723 ")"
+                WHITESPACE@3723..3724 " "
+                TAILSTRICT_KW@3724..3734 "tailstrict"
+        COMMA@3734..3735 ","
+        WHITESPACE@3735..3739 "\n\n  "
+        MEMBER_FIELD_METHOD@3739..3807
+          FIELD_NAME_FIXED@3739..3759
+            NAME@3739..3759
+              IDENT@3739..3759 "manifestJsonMinified"
+          PARAMS_DESC@3759..3766
+            L_PAREN@3759..3760 "("
+            PARAM@3760..3765
+              DESTRUCT_FULL@3760..3765
+                NAME@3760..3765
+                  IDENT@3760..3765 "value"
+            R_PAREN@3765..3766 ")"
+          COLONCOLON@3766..3768 "::"
+          WHITESPACE@3768..3769 " "
+          EXPR@3769..3807
+            EXPR_VAR@3769..3772
+              NAME@3769..3772
+                IDENT@3769..3772 "std"
+            SUFFIX_INDEX@3772..3787
+              DOT@3772..3773 "."
+              NAME@3773..3787
+                IDENT@3773..3787 "manifestJsonEx"
+            SUFFIX_APPLY@3787..3807
+              ARGS_DESC@3787..3807
+                L_PAREN@3787..3788 "("
+                ARG@3788..3793
+                  EXPR@3788..3793
+                    EXPR_VAR@3788..3793
+                      NAME@3788..3793
+                        IDENT@3788..3793 "value"
+                COMMA@3793..3794 ","
+                WHITESPACE@3794..3795 " "
+                ARG@3795..3797
+                  EXPR@3795..3797
+                    EXPR_STRING@3795..3797
+                      STRING_SINGLE@3795..3797 "''"
+                COMMA@3797..3798 ","
+                WHITESPACE@3798..3799 " "
+                ARG@3799..3801
+                  EXPR@3799..3801
+                    EXPR_STRING@3799..3801
+                      STRING_SINGLE@3799..3801 "''"
+                COMMA@3801..3802 ","
+                WHITESPACE@3802..3803 " "
+                ARG@3803..3806
+                  EXPR@3803..3806
+                    EXPR_STRING@3803..3806
+                      STRING_SINGLE@3803..3806 "':'"
+                R_PAREN@3806..3807 ")"
+        COMMA@3807..3808 ","
+        WHITESPACE@3808..3812 "\n\n  "
+        MEMBER_FIELD_METHOD@3812..4197
+          FIELD_NAME_FIXED@3812..3830
+            NAME@3812..3830
+              IDENT@3812..3830 "manifestYamlStream"
+          PARAMS_DESC@3830..3905
+            L_PAREN@3830..3831 "("
+            PARAM@3831..3836
+              DESTRUCT_FULL@3831..3836
+                NAME@3831..3836
+                  IDENT@3831..3836 "value"
+            COMMA@3836..3837 ","
+            WHITESPACE@3837..3838 " "
+            PARAM@3838..3866
+              DESTRUCT_FULL@3838..3860
+                NAME@3838..3860
+                  IDENT@3838..3860 "indent_array_in_object"
+              ASSIGN@3860..3861 "="
+              EXPR@3861..3866
+                EXPR_LITERAL@3861..3866
+                  FALSE_KW@3861..3866 "false"
+            COMMA@3866..3867 ","
+            WHITESPACE@3867..3868 " "
+            PARAM@3868..3887
+              DESTRUCT_FULL@3868..3882
+                NAME@3868..3882
+                  IDENT@3868..3882 "c_document_end"
+              ASSIGN@3882..3883 "="
+              EXPR@3883..3887
+                EXPR_LITERAL@3883..3887
+                  TRUE_KW@3883..3887 "true"
+            COMMA@3887..3888 ","
+            WHITESPACE@3888..3889 " "
+            PARAM@3889..3904
+              DESTRUCT_FULL@3889..3899
+                NAME@3889..3899
+                  IDENT@3889..3899 "quote_keys"
+              ASSIGN@3899..3900 "="
+              EXPR@3900..3904
+                EXPR_LITERAL@3900..3904
+                  TRUE_KW@3900..3904 "true"
+            R_PAREN@3904..3905 ")"
+          COLONCOLON@3905..3907 "::"
+          WHITESPACE@3907..3912 "\n    "
+          EXPR@3912..4197
+            EXPR_IF_THEN_ELSE@3912..4197
+              IF_KW@3912..3914 "if"
+              WHITESPACE@3914..3915 " "
+              EXPR@3915..3934
+                EXPR_UNARY@3915..3934
+                  NOT@3915..3916 "!"
+                  EXPR_VAR@3916..3919
+                    NAME@3916..3919
+                      IDENT@3916..3919 "std"
+                  SUFFIX_INDEX@3919..3927
+                    DOT@3919..3920 "."
+                    NAME@3920..3927
+                      IDENT@3920..3927 "isArray"
+                  SUFFIX_APPLY@3927..3934
+                    ARGS_DESC@3927..3934
+                      L_PAREN@3927..3928 "("
+                      ARG@3928..3933
+                        EXPR@3928..3933
+                          EXPR_VAR@3928..3933
+                            NAME@3928..3933
+                              IDENT@3928..3933 "value"
+                      R_PAREN@3933..3934 ")"
+              WHITESPACE@3934..3935 " "
+              THEN_KW@3935..3939 "then"
+              WHITESPACE@3939..3946 "\n      "
+              TRUE_EXPR@3946..4014
+                EXPR@3946..4014
+                  EXPR_ERROR@3946..4014
+                    ERROR_KW@3946..3951 "error"
+                    WHITESPACE@3951..3952 " "
+                    EXPR@3952..4014
+                      EXPR_BINARY@3952..4014
+                        EXPR@3952..3996
+                          EXPR_STRING@3952..3996
+                            STRING_SINGLE@3952..3996 "'manifestYamlStream o ..."
+                        WHITESPACE@3996..3997 " "
+                        PLUS@3997..3998 "+"
+                        WHITESPACE@3998..3999 " "
+                        EXPR@3999..4014
+                          EXPR_VAR@3999..4002
+                            NAME@3999..4002
+                              IDENT@3999..4002 "std"
+                          SUFFIX_INDEX@4002..4007
+                            DOT@4002..4003 "."
+                            NAME@4003..4007
+                              IDENT@4003..4007 "type"
+                          SUFFIX_APPLY@4007..4014
+                            ARGS_DESC@4007..4014
+                              L_PAREN@4007..4008 "("
+                              ARG@4008..4013
+                                EXPR@4008..4013
+                                  EXPR_VAR@4008..4013
+                                    NAME@4008..4013
+                                      IDENT@4008..4013 "value"
+                              R_PAREN@4013..4014 ")"
+              WHITESPACE@4014..4019 "\n    "
+              ELSE_KW@4019..4023 "else"
+              WHITESPACE@4023..4030 "\n      "
+              FALSE_EXPR@4030..4197
+                EXPR@4030..4197
+                  EXPR_BINARY@4030..4197
+                    EXPR@4030..4152
+                      EXPR_BINARY@4030..4152
+                        EXPR@4030..4037
+                          EXPR_STRING@4030..4037
+                            STRING_SINGLE@4030..4037 "'---\\n'"
+                        WHITESPACE@4037..4038 " "
+                        PLUS@4038..4039 "+"
+                        WHITESPACE@4039..4040 " "
+                        EXPR@4040..4152
+                          EXPR_VAR@4040..4043
+                            NAME@4040..4043
+                              IDENT@4040..4043 "std"
+                          SUFFIX_INDEX@4043..4048
+                            DOT@4043..4044 "."
+                            NAME@4044..4048
+                              IDENT@4044..4048 "join"
+                          SUFFIX_APPLY@4048..4152
+                            ARGS_DESC@4048..4152
+                              L_PAREN@4048..4049 "("
+                              WHITESPACE@4049..4058 "\n        "
+                              ARG@4058..4067
+                                EXPR@4058..4067
+                                  EXPR_STRING@4058..4067
+                                    STRING_SINGLE@4058..4067 "'\\n---\\n'"
+                              COMMA@4067..4068 ","
+                              WHITESPACE@4068..4069 " "
+                              ARG@4069..4144
+                                EXPR@4069..4144
+                                  EXPR_ARRAY_COMP@4069..4144
+                                    L_BRACK@4069..4070 "["
+                                    EXPR@4070..4128
+                                      EXPR_VAR@4070..4073
+                                        NAME@4070..4073
+                                          IDENT@4070..4073 "std"
+                                      SUFFIX_INDEX@4073..4089
+                                        DOT@4073..4074 "."
+                                        NAME@4074..4089
+                                          IDENT@4074..4089 "manifestYamlDoc"
+                                      SUFFIX_APPLY@4089..4128
+                                        ARGS_DESC@4089..4128
+                                          L_PAREN@4089..4090 "("
+                                          ARG@4090..4091
+                                            EXPR@4090..4091
+                                              EXPR_VAR@4090..4091
+                                                NAME@4090..4091
+                                                  IDENT@4090..4091 "e"
+                                          COMMA@4091..4092 ","
+                                          WHITESPACE@4092..4093 " "
+                                          ARG@4093..4115
+                                            EXPR@4093..4115
+                                              EXPR_VAR@4093..4115
+                                                NAME@4093..4115
+                                                  IDENT@4093..4115 "indent_array_in_object"
+                                          COMMA@4115..4116 ","
+                                          WHITESPACE@4116..4117 " "
+                                          ARG@4117..4127
+                                            EXPR@4117..4127
+                                              EXPR_VAR@4117..4127
+                                                NAME@4117..4127
+                                                  IDENT@4117..4127 "quote_keys"
+                                          R_PAREN@4127..4128 ")"
+                                    WHITESPACE@4128..4129 " "
+                                    FOR_SPEC@4129..4143
+                                      FOR_KW@4129..4132 "for"
+                                      WHITESPACE@4132..4133 " "
+                                      DESTRUCT_FULL@4133..4134
+                                        NAME@4133..4134
+                                          IDENT@4133..4134 "e"
+                                      WHITESPACE@4134..4135 " "
+                                      IN_KW@4135..4137 "in"
+                                      WHITESPACE@4137..4138 " "
+                                      EXPR@4138..4143
+                                        EXPR_VAR@4138..4143
+                                          NAME@4138..4143
+                                            IDENT@4138..4143 "value"
+                                    R_BRACK@4143..4144 "]"
+                              WHITESPACE@4144..4151 "\n      "
+                              R_PAREN@4151..4152 ")"
+                    WHITESPACE@4152..4153 " "
+                    PLUS@4153..4154 "+"
+                    WHITESPACE@4154..4155 " "
+                    EXPR@4155..4197
+                      EXPR_IF_THEN_ELSE@4155..4197
+                        IF_KW@4155..4157 "if"
+                        WHITESPACE@4157..4158 " "
+                        EXPR@4158..4172
+                          EXPR_VAR@4158..4172
+                            NAME@4158..4172
+                              IDENT@4158..4172 "c_document_end"
+                        WHITESPACE@4172..4173 " "
+                        THEN_KW@4173..4177 "then"
+                        WHITESPACE@4177..4178 " "
+                        TRUE_EXPR@4178..4187
+                          EXPR@4178..4187
+                            EXPR_STRING@4178..4187
+                              STRING_SINGLE@4178..4187 "'\\n...\\n'"
+                        WHITESPACE@4187..4188 " "
+                        ELSE_KW@4188..4192 "else"
+                        WHITESPACE@4192..4193 " "
+                        FALSE_EXPR@4193..4197
+                          EXPR@4193..4197
+                            EXPR_STRING@4193..4197
+                              STRING_SINGLE@4193..4197 "'\\n'"
+        COMMA@4197..4198 ","
+        WHITESPACE@4198..4202 "\n\n  "
+        MEMBER_FIELD_METHOD@4202..4858
+          FIELD_NAME_FIXED@4202..4216
+            NAME@4202..4216
+              IDENT@4202..4216 "manifestPython"
+          PARAMS_DESC@4216..4219
+            L_PAREN@4216..4217 "("
+            PARAM@4217..4218
+              DESTRUCT_FULL@4217..4218
+                NAME@4217..4218
+                  IDENT@4217..4218 "v"
+            R_PAREN@4218..4219 ")"
+          COLONCOLON@4219..4221 "::"
+          WHITESPACE@4221..4226 "\n    "
+          EXPR@4226..4858
+            EXPR_IF_THEN_ELSE@4226..4858
+              IF_KW@4226..4228 "if"
+              WHITESPACE@4228..4229 " "
+              EXPR@4229..4244
+                EXPR_VAR@4229..4232
+                  NAME@4229..4232
+                    IDENT@4229..4232 "std"
+                SUFFIX_INDEX@4232..4241
+                  DOT@4232..4233 "."
+                  NAME@4233..4241
+                    IDENT@4233..4241 "isObject"
+                SUFFIX_APPLY@4241..4244
+                  ARGS_DESC@4241..4244
+                    L_PAREN@4241..4242 "("
+                    ARG@4242..4243
+                      EXPR@4242..4243
+                        EXPR_VAR@4242..4243
+                          NAME@4242..4243
+                            IDENT@4242..4243 "v"
+                    R_PAREN@4243..4244 ")"
+              WHITESPACE@4244..4245 " "
+              THEN_KW@4245..4249 "then"
+              WHITESPACE@4249..4256 "\n      "
+              TRUE_EXPR@4256..4431
+                EXPR@4256..4431
+                  STMT_LOCAL@4256..4391
+                    LOCAL_KW@4256..4261 "local"
+                    WHITESPACE@4261..4262 " "
+                    BIND_DESTRUCT@4262..4390
+                      DESTRUCT_FULL@4262..4268
+                        NAME@4262..4268
+                          IDENT@4262..4268 "fields"
+                      WHITESPACE@4268..4269 " "
+                      ASSIGN@4269..4270 "="
+                      WHITESPACE@4270..4271 " "
+                      EXPR@4271..4390
+                        EXPR_ARRAY_COMP@4271..4390
+                          L_BRACK@4271..4272 "["
+                          WHITESPACE@4272..4281 "\n        "
+                          EXPR@4281..4345
+                            EXPR_BINARY@4281..4345
+                              EXPR@4281..4289
+                                EXPR_STRING@4281..4289
+                                  STRING_SINGLE@4281..4289 "'%s: %s'"
+                              WHITESPACE@4289..4290 " "
+                              MODULO@4290..4291 "%"
+                              WHITESPACE@4291..4292 " "
+                              EXPR@4292..4345
+                                EXPR_ARRAY@4292..4345
+                                  L_BRACK@4292..4293 "["
+                                  EXPR@4293..4318
+                                    EXPR_VAR@4293..4296
+                                      NAME@4293..4296
+                                        IDENT@4293..4296 "std"
+                                    SUFFIX_INDEX@4296..4315
+                                      DOT@4296..4297 "."
+                                      NAME@4297..4315
+                                        IDENT@4297..4315 "escapeStringPython"
+                                    SUFFIX_APPLY@4315..4318
+                                      ARGS_DESC@4315..4318
+                                        L_PAREN@4315..4316 "("
+                                        ARG@4316..4317
+                                          EXPR@4316..4317
+                                            EXPR_VAR@4316..4317
+                                              NAME@4316..4317
+                                                IDENT@4316..4317 "k"
+                                        R_PAREN@4317..4318 ")"
+                                  COMMA@4318..4319 ","
+                                  WHITESPACE@4319..4320 " "
+                                  EXPR@4320..4344
+                                    EXPR_VAR@4320..4323
+                                      NAME@4320..4323
+                                        IDENT@4320..4323 "std"
+                                    SUFFIX_INDEX@4323..4338
+                                      DOT@4323..4324 "."
+                                      NAME@4324..4338
+                                        IDENT@4324..4338 "manifestPython"
+                                    SUFFIX_APPLY@4338..4344
+                                      ARGS_DESC@4338..4344
+                                        L_PAREN@4338..4339 "("
+                                        ARG@4339..4343
+                                          EXPR@4339..4343
+                                            EXPR_VAR@4339..4340
+                                              NAME@4339..4340
+                                                IDENT@4339..4340 "v"
+                                            SUFFIX_INDEX_EXPR@4340..4343
+                                              L_BRACK@4340..4341 "["
+                                              EXPR@4341..4342
+                                                EXPR_VAR@4341..4342
+                                                  NAME@4341..4342
+                                                    IDENT@4341..4342 "k"
+                                              R_BRACK@4342..4343 "]"
+                                        R_PAREN@4343..4344 ")"
+                                  R_BRACK@4344..4345 "]"
+                          WHITESPACE@4345..4354 "\n        "
+                          FOR_SPEC@4354..4382
+                            FOR_KW@4354..4357 "for"
+                            WHITESPACE@4357..4358 " "
+                            DESTRUCT_FULL@4358..4359
+                              NAME@4358..4359
+                                IDENT@4358..4359 "k"
+                            WHITESPACE@4359..4360 " "
+                            IN_KW@4360..4362 "in"
+                            WHITESPACE@4362..4363 " "
+                            EXPR@4363..4382
+                              EXPR_VAR@4363..4366
+                                NAME@4363..4366
+                                  IDENT@4363..4366 "std"
+                              SUFFIX_INDEX@4366..4379
+                                DOT@4366..4367 "."
+                                NAME@4367..4379
+                                  IDENT@4367..4379 "objectFields"
+                              SUFFIX_APPLY@4379..4382
+                                ARGS_DESC@4379..4382
+                                  L_PAREN@4379..4380 "("
+                                  ARG@4380..4381
+                                    EXPR@4380..4381
+                                      EXPR_VAR@4380..4381
+                                        NAME@4380..4381
+                                          IDENT@4380..4381 "v"
+                                  R_PAREN@4381..4382 ")"
+                          WHITESPACE@4382..4389 "\n      "
+                          R_BRACK@4389..4390 "]"
+                    SEMI@4390..4391 ";"
+                  WHITESPACE@4391..4398 "\n      "
+                  EXPR_BINARY@4398..4431
+                    EXPR@4398..4404
+                      EXPR_STRING@4398..4404
+                        STRING_SINGLE@4398..4404 "'{%s}'"
+                    WHITESPACE@4404..4405 " "
+                    MODULO@4405..4406 "%"
+                    WHITESPACE@4406..4407 " "
+                    EXPR@4407..4431
+                      EXPR_ARRAY@4407..4431
+                        L_BRACK@4407..4408 "["
+                        EXPR@4408..4430
+                          EXPR_VAR@4408..4411
+                            NAME@4408..4411
+                              IDENT@4408..4411 "std"
+                          SUFFIX_INDEX@4411..4416
+                            DOT@4411..4412 "."
+                            NAME@4412..4416
+                              IDENT@4412..4416 "join"
+                          SUFFIX_APPLY@4416..4430
+                            ARGS_DESC@4416..4430
+                              L_PAREN@4416..4417 "("
+                              ARG@4417..4421
+                                EXPR@4417..4421
+                                  EXPR_STRING@4417..4421
+                                    STRING_SINGLE@4417..4421 "', '"
+                              COMMA@4421..4422 ","
+                              WHITESPACE@4422..4423 " "
+                              ARG@4423..4429
+                                EXPR@4423..4429
+                                  EXPR_VAR@4423..4429
+                                    NAME@4423..4429
+                                      IDENT@4423..4429 "fields"
+                              R_PAREN@4429..4430 ")"
+                        R_BRACK@4430..4431 "]"
+              WHITESPACE@4431..4436 "\n    "
+              ELSE_KW@4436..4440 "else"
+              WHITESPACE@4440..4441 " "
+              FALSE_EXPR@4441..4858
+                EXPR@4441..4858
+                  EXPR_IF_THEN_ELSE@4441..4858
+                    IF_KW@4441..4443 "if"
+                    WHITESPACE@4443..4444 " "
+                    EXPR@4444..4458
+                      EXPR_VAR@4444..4447
+                        NAME@4444..4447
+                          IDENT@4444..4447 "std"
+                      SUFFIX_INDEX@4447..4455
+                        DOT@4447..4448 "."
+                        NAME@4448..4455
+                          IDENT@4448..4455 "isArray"
+                      SUFFIX_APPLY@4455..4458
+                        ARGS_DESC@4455..4458
+                          L_PAREN@4455..4456 "("
+                          ARG@4456..4457
+                            EXPR@4456..4457
+                              EXPR_VAR@4456..4457
+                                NAME@4456..4457
+                                  IDENT@4456..4457 "v"
+                          R_PAREN@4457..4458 ")"
+                    WHITESPACE@4458..4459 " "
+                    THEN_KW@4459..4463 "then"
+                    WHITESPACE@4463..4470 "\n      "
+                    TRUE_EXPR@4470..4533
+                      EXPR@4470..4533
+                        EXPR_BINARY@4470..4533
+                          EXPR@4470..4476
+                            EXPR_STRING@4470..4476
+                              STRING_SINGLE@4470..4476 "'[%s]'"
+                          WHITESPACE@4476..4477 " "
+                          MODULO@4477..4478 "%"
+                          WHITESPACE@4478..4479 " "
+                          EXPR@4479..4533
+                            EXPR_ARRAY@4479..4533
+                              L_BRACK@4479..4480 "["
+                              EXPR@4480..4532
+                                EXPR_VAR@4480..4483
+                                  NAME@4480..4483
+                                    IDENT@4480..4483 "std"
+                                SUFFIX_INDEX@4483..4488
+                                  DOT@4483..4484 "."
+                                  NAME@4484..4488
+                                    IDENT@4484..4488 "join"
+                                SUFFIX_APPLY@4488..4532
+                                  ARGS_DESC@4488..4532
+                                    L_PAREN@4488..4489 "("
+                                    ARG@4489..4493
+                                      EXPR@4489..4493
+                                        EXPR_STRING@4489..4493
+                                          STRING_SINGLE@4489..4493 "', '"
+                                    COMMA@4493..4494 ","
+                                    WHITESPACE@4494..4495 " "
+                                    ARG@4495..4531
+                                      EXPR@4495..4531
+                                        EXPR_ARRAY_COMP@4495..4531
+                                          L_BRACK@4495..4496 "["
+                                          EXPR@4496..4518
+                                            EXPR_VAR@4496..4499
+                                              NAME@4496..4499
+                                                IDENT@4496..4499 "std"
+                                            SUFFIX_INDEX@4499..4514
+                                              DOT@4499..4500 "."
+                                              NAME@4500..4514
+                                                IDENT@4500..4514 "manifestPython"
+                                            SUFFIX_APPLY@4514..4518
+                                              ARGS_DESC@4514..4518
+                                                L_PAREN@4514..4515 "("
+                                                ARG@4515..4517
+                                                  EXPR@4515..4517
+                                                    EXPR_VAR@4515..4517
+                                                      NAME@4515..4517
+                                                        IDENT@4515..4517 "v2"
+                                                R_PAREN@4517..4518 ")"
+                                          WHITESPACE@4518..4519 " "
+                                          FOR_SPEC@4519..4530
+                                            FOR_KW@4519..4522 "for"
+                                            WHITESPACE@4522..4523 " "
+                                            DESTRUCT_FULL@4523..4525
+                                              NAME@4523..4525
+                                                IDENT@4523..4525 "v2"
+                                            WHITESPACE@4525..4526 " "
+                                            IN_KW@4526..4528 "in"
+                                            WHITESPACE@4528..4529 " "
+                                            EXPR@4529..4530
+                                              EXPR_VAR@4529..4530
+                                                NAME@4529..4530
+                                                  IDENT@4529..4530 "v"
+                                          R_BRACK@4530..4531 "]"
+                                    R_PAREN@4531..4532 ")"
+                              R_BRACK@4532..4533 "]"
+                    WHITESPACE@4533..4538 "\n    "
+                    ELSE_KW@4538..4542 "else"
+                    WHITESPACE@4542..4543 " "
+                    FALSE_EXPR@4543..4858
+                      EXPR@4543..4858
+                        EXPR_IF_THEN_ELSE@4543..4858
+                          IF_KW@4543..4545 "if"
+                          WHITESPACE@4545..4546 " "
+                          EXPR@4546..4561
+                            EXPR_VAR@4546..4549
+                              NAME@4546..4549
+                                IDENT@4546..4549 "std"
+                            SUFFIX_INDEX@4549..4558
+                              DOT@4549..4550 "."
+                              NAME@4550..4558
+                                IDENT@4550..4558 "isString"
+                            SUFFIX_APPLY@4558..4561
+                              ARGS_DESC@4558..4561
+                                L_PAREN@4558..4559 "("
+                                ARG@4559..4560
+                                  EXPR@4559..4560
+                                    EXPR_VAR@4559..4560
+                                      NAME@4559..4560
+                                        IDENT@4559..4560 "v"
+                                R_PAREN@4560..4561 ")"
+                          WHITESPACE@4561..4562 " "
+                          THEN_KW@4562..4566 "then"
+                          WHITESPACE@4566..4573 "\n      "
+                          TRUE_EXPR@4573..4607
+                            EXPR@4573..4607
+                              EXPR_BINARY@4573..4607
+                                EXPR@4573..4577
+                                  EXPR_STRING@4573..4577
+                                    STRING_SINGLE@4573..4577 "'%s'"
+                                WHITESPACE@4577..4578 " "
+                                MODULO@4578..4579 "%"
+                                WHITESPACE@4579..4580 " "
+                                EXPR@4580..4607
+                                  EXPR_ARRAY@4580..4607
+                                    L_BRACK@4580..4581 "["
+                                    EXPR@4581..4606
+                                      EXPR_VAR@4581..4584
+                                        NAME@4581..4584
+                                          IDENT@4581..4584 "std"
+                                      SUFFIX_INDEX@4584..4603
+                                        DOT@4584..4585 "."
+                                        NAME@4585..4603
+                                          IDENT@4585..4603 "escapeStringPython"
+                                      SUFFIX_APPLY@4603..4606
+                                        ARGS_DESC@4603..4606
+                                          L_PAREN@4603..4604 "("
+                                          ARG@4604..4605
+                                            EXPR@4604..4605
+                                              EXPR_VAR@4604..4605
+                                                NAME@4604..4605
+                                                  IDENT@4604..4605 "v"
+                                          R_PAREN@4605..4606 ")"
+                                    R_BRACK@4606..4607 "]"
+                          WHITESPACE@4607..4612 "\n    "
+                          ELSE_KW@4612..4616 "else"
+                          WHITESPACE@4616..4617 " "
+                          FALSE_EXPR@4617..4858
+                            EXPR@4617..4858
+                              EXPR_IF_THEN_ELSE@4617..4858
+                                IF_KW@4617..4619 "if"
+                                WHITESPACE@4619..4620 " "
+                                EXPR@4620..4637
+                                  EXPR_VAR@4620..4623
+                                    NAME@4620..4623
+                                      IDENT@4620..4623 "std"
+                                  SUFFIX_INDEX@4623..4634
+                                    DOT@4623..4624 "."
+                                    NAME@4624..4634
+                                      IDENT@4624..4634 "isFunction"
+                                  SUFFIX_APPLY@4634..4637
+                                    ARGS_DESC@4634..4637
+                                      L_PAREN@4634..4635 "("
+                                      ARG@4635..4636
+                                        EXPR@4635..4636
+                                          EXPR_VAR@4635..4636
+                                            NAME@4635..4636
+                                              IDENT@4635..4636 "v"
+                                      R_PAREN@4636..4637 ")"
+                                WHITESPACE@4637..4638 " "
+                                THEN_KW@4638..4642 "then"
+                                WHITESPACE@4642..4649 "\n      "
+                                TRUE_EXPR@4649..4681
+                                  EXPR@4649..4681
+                                    EXPR_ERROR@4649..4681
+                                      ERROR_KW@4649..4654 "error"
+                                      WHITESPACE@4654..4655 " "
+                                      EXPR@4655..4681
+                                        EXPR_STRING@4655..4681
+                                          STRING_SINGLE@4655..4681 "'cannot manifest func ..."
+                                WHITESPACE@4681..4686 "\n    "
+                                ELSE_KW@4686..4690 "else"
+                                WHITESPACE@4690..4691 " "
+                                FALSE_EXPR@4691..4858
+                                  EXPR@4691..4858
+                                    EXPR_IF_THEN_ELSE@4691..4858
+                                      IF_KW@4691..4693 "if"
+                                      WHITESPACE@4693..4694 " "
+                                      EXPR@4694..4709
+                                        EXPR_VAR@4694..4697
+                                          NAME@4694..4697
+                                            IDENT@4694..4697 "std"
+                                        SUFFIX_INDEX@4697..4706
+                                          DOT@4697..4698 "."
+                                          NAME@4698..4706
+                                            IDENT@4698..4706 "isNumber"
+                                        SUFFIX_APPLY@4706..4709
+                                          ARGS_DESC@4706..4709
+                                            L_PAREN@4706..4707 "("
+                                            ARG@4707..4708
+                                              EXPR@4707..4708
+                                                EXPR_VAR@4707..4708
+                                                  NAME@4707..4708
+                                                    IDENT@4707..4708 "v"
+                                            R_PAREN@4708..4709 ")"
+                                      WHITESPACE@4709..4710 " "
+                                      THEN_KW@4710..4714 "then"
+                                      WHITESPACE@4714..4721 "\n      "
+                                      TRUE_EXPR@4721..4736
+                                        EXPR@4721..4736
+                                          EXPR_VAR@4721..4724
+                                            NAME@4721..4724
+                                              IDENT@4721..4724 "std"
+                                          SUFFIX_INDEX@4724..4733
+                                            DOT@4724..4725 "."
+                                            NAME@4725..4733
+                                              IDENT@4725..4733 "toString"
+                                          SUFFIX_APPLY@4733..4736
+                                            ARGS_DESC@4733..4736
+                                              L_PAREN@4733..4734 "("
+                                              ARG@4734..4735
+                                                EXPR@4734..4735
+                                                  EXPR_VAR@4734..4735
+                                                    NAME@4734..4735
+                                                      IDENT@4734..4735 "v"
+                                              R_PAREN@4735..4736 ")"
+                                      WHITESPACE@4736..4741 "\n    "
+                                      ELSE_KW@4741..4745 "else"
+                                      WHITESPACE@4745..4746 " "
+                                      FALSE_EXPR@4746..4858
+                                        EXPR@4746..4858
+                                          EXPR_IF_THEN_ELSE@4746..4858
+                                            IF_KW@4746..4748 "if"
+                                            WHITESPACE@4748..4749 " "
+                                            EXPR@4749..4758
+                                              EXPR_BINARY@4749..4758
+                                                EXPR@4749..4750
+                                                  EXPR_VAR@4749..4750
+                                                    NAME@4749..4750
+                                                      IDENT@4749..4750 "v"
+                                                WHITESPACE@4750..4751 " "
+                                                EQ@4751..4753 "=="
+                                                WHITESPACE@4753..4754 " "
+                                                EXPR@4754..4758
+                                                  EXPR_LITERAL@4754..4758
+                                                    TRUE_KW@4754..4758 "true"
+                                            WHITESPACE@4758..4759 " "
+                                            THEN_KW@4759..4763 "then"
+                                            WHITESPACE@4763..4770 "\n      "
+                                            TRUE_EXPR@4770..4776
+                                              EXPR@4770..4776
+                                                EXPR_STRING@4770..4776
+                                                  STRING_SINGLE@4770..4776 "'True'"
+                                            WHITESPACE@4776..4781 "\n    "
+                                            ELSE_KW@4781..4785 "else"
+                                            WHITESPACE@4785..4786 " "
+                                            FALSE_EXPR@4786..4858
+                                              EXPR@4786..4858
+                                                EXPR_IF_THEN_ELSE@4786..4858
+                                                  IF_KW@4786..4788 "if"
+                                                  WHITESPACE@4788..4789 " "
+                                                  EXPR@4789..4799
+                                                    EXPR_BINARY@4789..4799
+                                                      EXPR@4789..4790
+                                                        EXPR_VAR@4789..4790
+                                                          NAME@4789..4790
+                                                            IDENT@4789..4790 "v"
+                                                      WHITESPACE@4790..4791 " "
+                                                      EQ@4791..4793 "=="
+                                                      WHITESPACE@4793..4794 " "
+                                                      EXPR@4794..4799
+                                                        EXPR_LITERAL@4794..4799
+                                                          FALSE_KW@4794..4799 "false"
+                                                  WHITESPACE@4799..4800 " "
+                                                  THEN_KW@4800..4804 "then"
+                                                  WHITESPACE@4804..4811 "\n      "
+                                                  TRUE_EXPR@4811..4818
+                                                    EXPR@4811..4818
+                                                      EXPR_STRING@4811..4818
+                                                        STRING_SINGLE@4811..4818 "'False'"
+                                                  WHITESPACE@4818..4823 "\n    "
+                                                  ELSE_KW@4823..4827 "else"
+                                                  WHITESPACE@4827..4828 " "
+                                                  FALSE_EXPR@4828..4858
+                                                    EXPR@4828..4858
+                                                      EXPR_IF_THEN_ELSE@4828..4858
+                                                        IF_KW@4828..4830 "if"
+                                                        WHITESPACE@4830..4831 " "
+                                                        EXPR@4831..4840
+                                                          EXPR_BINARY@4831..4840
+                                                            EXPR@4831..4832
+                                                              EXPR_VAR@4831..4832
+                                                                NAME@4831..4832
+                                                                  IDENT@4831..4832 "v"
+                                                            WHITESPACE@4832..4833 " "
+                                                            EQ@4833..4835 "=="
+                                                            WHITESPACE@4835..4836 " "
+                                                            EXPR@4836..4840
+                                                              EXPR_LITERAL@4836..4840
+                                                                NULL_KW@4836..4840 "null"
+                                                        WHITESPACE@4840..4841 " "
+                                                        THEN_KW@4841..4845 "then"
+                                                        WHITESPACE@4845..4852 "\n      "
+                                                        TRUE_EXPR@4852..4858
+                                                          EXPR@4852..4858
+                                                            EXPR_STRING@4852..4858
+                                                              STRING_SINGLE@4852..4858 "'None'"
+        COMMA@4858..4859 ","
+        WHITESPACE@4859..4863 "\n\n  "
+        MEMBER_FIELD_METHOD@4863..5018
+          FIELD_NAME_FIXED@4863..4881
+            NAME@4863..4881
+              IDENT@4863..4881 "manifestPythonVars"
+          PARAMS_DESC@4881..4887
+            L_PAREN@4881..4882 "("
+            PARAM@4882..4886
+              DESTRUCT_FULL@4882..4886
+                NAME@4882..4886
+                  IDENT@4882..4886 "conf"
+            R_PAREN@4886..4887 ")"
+          COLONCOLON@4887..4889 "::"
+          WHITESPACE@4889..4894 "\n    "
+          EXPR@4894..5018
+            STMT_LOCAL@4894..4986
+              LOCAL_KW@4894..4899 "local"
+              WHITESPACE@4899..4900 " "
+              BIND_DESTRUCT@4900..4985
+                DESTRUCT_FULL@4900..4904
+                  NAME@4900..4904
+                    IDENT@4900..4904 "vars"
+                WHITESPACE@4904..4905 " "
+                ASSIGN@4905..4906 "="
+                WHITESPACE@4906..4907 " "
+                EXPR@4907..4985
+                  EXPR_ARRAY_COMP@4907..4985
+                    L_BRACK@4907..4908 "["
+                    EXPR@4908..4952
+                      EXPR_BINARY@4908..4952
+                        EXPR@4908..4917
+                          EXPR_STRING@4908..4917
+                            STRING_SINGLE@4908..4917 "'%s = %s'"
+                        WHITESPACE@4917..4918 " "
+                        MODULO@4918..4919 "%"
+                        WHITESPACE@4919..4920 " "
+                        EXPR@4920..4952
+                          EXPR_ARRAY@4920..4952
+                            L_BRACK@4920..4921 "["
+                            EXPR@4921..4922
+                              EXPR_VAR@4921..4922
+                                NAME@4921..4922
+                                  IDENT@4921..4922 "k"
+                            COMMA@4922..4923 ","
+                            WHITESPACE@4923..4924 " "
+                            EXPR@4924..4951
+                              EXPR_VAR@4924..4927
+                                NAME@4924..4927
+                                  IDENT@4924..4927 "std"
+                              SUFFIX_INDEX@4927..4942
+                                DOT@4927..4928 "."
+                                NAME@4928..4942
+                                  IDENT@4928..4942 "manifestPython"
+                              SUFFIX_APPLY@4942..4951
+                                ARGS_DESC@4942..4951
+                                  L_PAREN@4942..4943 "("
+                                  ARG@4943..4950
+                                    EXPR@4943..4950
+                                      EXPR_VAR@4943..4947
+                                        NAME@4943..4947
+                                          IDENT@4943..4947 "conf"
+                                      SUFFIX_INDEX_EXPR@4947..4950
+                                        L_BRACK@4947..4948 "["
+                                        EXPR@4948..4949
+                                          EXPR_VAR@4948..4949
+                                            NAME@4948..4949
+                                              IDENT@4948..4949 "k"
+                                        R_BRACK@4949..4950 "]"
+                                  R_PAREN@4950..4951 ")"
+                            R_BRACK@4951..4952 "]"
+                    WHITESPACE@4952..4953 " "
+                    FOR_SPEC@4953..4984
+                      FOR_KW@4953..4956 "for"
+                      WHITESPACE@4956..4957 " "
+                      DESTRUCT_FULL@4957..4958
+                        NAME@4957..4958
+                          IDENT@4957..4958 "k"
+                      WHITESPACE@4958..4959 " "
+                      IN_KW@4959..4961 "in"
+                      WHITESPACE@4961..4962 " "
+                      EXPR@4962..4984
+                        EXPR_VAR@4962..4965
+                          NAME@4962..4965
+                            IDENT@4962..4965 "std"
+                        SUFFIX_INDEX@4965..4978
+                          DOT@4965..4966 "."
+                          NAME@4966..4978
+                            IDENT@4966..4978 "objectFields"
+                        SUFFIX_APPLY@4978..4984
+                          ARGS_DESC@4978..4984
+                            L_PAREN@4978..4979 "("
+                            ARG@4979..4983
+                              EXPR@4979..4983
+                                EXPR_VAR@4979..4983
+                                  NAME@4979..4983
+                                    IDENT@4979..4983 "conf"
+                            R_PAREN@4983..4984 ")"
+                    R_BRACK@4984..4985 "]"
+              SEMI@4985..4986 ";"
+            WHITESPACE@4986..4991 "\n    "
+            EXPR_VAR@4991..4994
+              NAME@4991..4994
+                IDENT@4991..4994 "std"
+            SUFFIX_INDEX@4994..4999
+              DOT@4994..4995 "."
+              NAME@4995..4999
+                IDENT@4995..4999 "join"
+            SUFFIX_APPLY@4999..5018
+              ARGS_DESC@4999..5018
+                L_PAREN@4999..5000 "("
+                ARG@5000..5004
+                  EXPR@5000..5004
+                    EXPR_STRING@5000..5004
+                      STRING_SINGLE@5000..5004 "'\\n'"
+                COMMA@5004..5005 ","
+                WHITESPACE@5005..5006 " "
+                ARG@5006..5017
+                  EXPR@5006..5017
+                    EXPR_BINARY@5006..5017
+                      EXPR@5006..5010
+                        EXPR_VAR@5006..5010
+                          NAME@5006..5010
+                            IDENT@5006..5010 "vars"
+                      WHITESPACE@5010..5011 " "
+                      PLUS@5011..5012 "+"
+                      WHITESPACE@5012..5013 " "
+                      EXPR@5013..5017
+                        EXPR_ARRAY@5013..5017
+                          L_BRACK@5013..5014 "["
+                          EXPR@5014..5016
+                            EXPR_STRING@5014..5016
+                              STRING_SINGLE@5014..5016 "''"
+                          R_BRACK@5016..5017 "]"
+                R_PAREN@5017..5018 ")"
+        COMMA@5018..5019 ","
+        WHITESPACE@5019..5023 "\n\n  "
+        MEMBER_FIELD_METHOD@5023..5690
+          FIELD_NAME_FIXED@5023..5040
+            NAME@5023..5040
+              IDENT@5023..5040 "manifestXmlJsonml"
+          PARAMS_DESC@5040..5047
+            L_PAREN@5040..5041 "("
+            PARAM@5041..5046
+              DESTRUCT_FULL@5041..5046
+                NAME@5041..5046
+                  IDENT@5041..5046 "value"
+            R_PAREN@5046..5047 ")"
+          COLONCOLON@5047..5049 "::"
+          WHITESPACE@5049..5054 "\n    "
+          EXPR@5054..5690
+            EXPR_IF_THEN_ELSE@5054..5690
+              IF_KW@5054..5056 "if"
+              WHITESPACE@5056..5057 " "
+              EXPR@5057..5076
+                EXPR_UNARY@5057..5076
+                  NOT@5057..5058 "!"
+                  EXPR_VAR@5058..5061
+                    NAME@5058..5061
+                      IDENT@5058..5061 "std"
+                  SUFFIX_INDEX@5061..5069
+                    DOT@5061..5062 "."
+                    NAME@5062..5069
+                      IDENT@5062..5069 "isArray"
+                  SUFFIX_APPLY@5069..5076
+                    ARGS_DESC@5069..5076
+                      L_PAREN@5069..5070 "("
+                      ARG@5070..5075
+                        EXPR@5070..5075
+                          EXPR_VAR@5070..5075
+                            NAME@5070..5075
+                              IDENT@5070..5075 "value"
+                      R_PAREN@5075..5076 ")"
+              WHITESPACE@5076..5077 " "
+              THEN_KW@5077..5081 "then"
+              WHITESPACE@5081..5088 "\n      "
+              TRUE_EXPR@5088..5156
+                EXPR@5088..5156
+                  EXPR_ERROR@5088..5156
+                    ERROR_KW@5088..5093 "error"
+                    WHITESPACE@5093..5094 " "
+                    EXPR@5094..5156
+                      EXPR_BINARY@5094..5156
+                        EXPR@5094..5138
+                          EXPR_STRING@5094..5138
+                            STRING_SINGLE@5094..5138 "'Expected a JSONML va ..."
+                        WHITESPACE@5138..5139 " "
+                        MODULO@5139..5140 "%"
+                        WHITESPACE@5140..5141 " "
+                        EXPR@5141..5156
+                          EXPR_VAR@5141..5144
+                            NAME@5141..5144
+                              IDENT@5141..5144 "std"
+                          SUFFIX_INDEX@5144..5149
+                            DOT@5144..5145 "."
+                            NAME@5145..5149
+                              IDENT@5145..5149 "type"
+                          SUFFIX_APPLY@5149..5156
+                            ARGS_DESC@5149..5156
+                              L_PAREN@5149..5150 "("
+                              ARG@5150..5155
+                                EXPR@5150..5155
+                                  EXPR_VAR@5150..5155
+                                    NAME@5150..5155
+                                      IDENT@5150..5155 "value"
+                              R_PAREN@5155..5156 ")"
+              WHITESPACE@5156..5161 "\n    "
+              ELSE_KW@5161..5165 "else"
+              WHITESPACE@5165..5172 "\n      "
+              FALSE_EXPR@5172..5690
+                EXPR@5172..5690
+                  STMT_LOCAL@5172..5672
+                    LOCAL_KW@5172..5177 "local"
+                    WHITESPACE@5177..5178 " "
+                    BIND_FUNCTION@5178..5671
+                      NAME@5178..5181
+                        IDENT@5178..5181 "aux"
+                      PARAMS_DESC@5181..5184
+                        L_PAREN@5181..5182 "("
+                        PARAM@5182..5183
+                          DESTRUCT_FULL@5182..5183
+                            NAME@5182..5183
+                              IDENT@5182..5183 "v"
+                        R_PAREN@5183..5184 ")"
+                      WHITESPACE@5184..5185 " "
+                      ASSIGN@5185..5186 "="
+                      WHITESPACE@5186..5195 "\n        "
+                      EXPR@5195..5671
+                        EXPR_IF_THEN_ELSE@5195..5671
+                          IF_KW@5195..5197 "if"
+                          WHITESPACE@5197..5198 " "
+                          EXPR@5198..5213
+                            EXPR_VAR@5198..5201
+                              NAME@5198..5201
+                                IDENT@5198..5201 "std"
+                            SUFFIX_INDEX@5201..5210
+                              DOT@5201..5202 "."
+                              NAME@5202..5210
+                                IDENT@5202..5210 "isString"
+                            SUFFIX_APPLY@5210..5213
+                              ARGS_DESC@5210..5213
+                                L_PAREN@5210..5211 "("
+                                ARG@5211..5212
+                                  EXPR@5211..5212
+                                    EXPR_VAR@5211..5212
+                                      NAME@5211..5212
+                                        IDENT@5211..5212 "v"
+                                R_PAREN@5212..5213 ")"
+                          WHITESPACE@5213..5214 " "
+                          THEN_KW@5214..5218 "then"
+                          WHITESPACE@5218..5229 "\n          "
+                          TRUE_EXPR@5229..5230
+                            EXPR@5229..5230
+                              EXPR_VAR@5229..5230
+                                NAME@5229..5230
+                                  IDENT@5229..5230 "v"
+                          WHITESPACE@5230..5239 "\n        "
+                          ELSE_KW@5239..5243 "else"
+                          WHITESPACE@5243..5254 "\n          "
+                          FALSE_EXPR@5254..5671
+                            EXPR@5254..5671
+                              STMT_LOCAL@5254..5271
+                                LOCAL_KW@5254..5259 "local"
+                                WHITESPACE@5259..5260 " "
+                                BIND_DESTRUCT@5260..5270
+                                  DESTRUCT_FULL@5260..5263
+                                    NAME@5260..5263
+                                      IDENT@5260..5263 "tag"
+                                  WHITESPACE@5263..5264 " "
+                                  ASSIGN@5264..5265 "="
+                                  WHITESPACE@5265..5266 " "
+                                  EXPR@5266..5270
+                                    EXPR_VAR@5266..5267
+                                      NAME@5266..5267
+                                        IDENT@5266..5267 "v"
+                                    SUFFIX_INDEX_EXPR@5267..5270
+                                      L_BRACK@5267..5268 "["
+                                      EXPR@5268..5269
+                                        EXPR_NUMBER@5268..5269
+                                          FLOAT@5268..5269 "0"
+                                      R_BRACK@5269..5270 "]"
+                                SEMI@5270..5271 ";"
+                              WHITESPACE@5271..5282 "\n          "
+                              STMT_LOCAL@5282..5340
+                                LOCAL_KW@5282..5287 "local"
+                                WHITESPACE@5287..5288 " "
+                                BIND_DESTRUCT@5288..5339
+                                  DESTRUCT_FULL@5288..5297
+                                    NAME@5288..5297
+                                      IDENT@5288..5297 "has_attrs"
+                                  WHITESPACE@5297..5298 " "
+                                  ASSIGN@5298..5299 "="
+                                  WHITESPACE@5299..5300 " "
+                                  EXPR@5300..5339
+                                    EXPR_BINARY@5300..5339
+                                      EXPR@5300..5317
+                                        EXPR_BINARY@5300..5317
+                                          EXPR@5300..5303
+                                            EXPR_VAR@5300..5303
+                                              NAME@5300..5303
+                                                IDENT@5300..5303 "std"
+                                          SUFFIX_INDEX@5303..5310
+                                            DOT@5303..5304 "."
+                                            NAME@5304..5310
+                                              IDENT@5304..5310 "length"
+                                          SUFFIX_APPLY@5310..5313
+                                            ARGS_DESC@5310..5313
+                                              L_PAREN@5310..5311 "("
+                                              ARG@5311..5312
+                                                EXPR@5311..5312
+                                                  EXPR_VAR@5311..5312
+                                                    NAME@5311..5312
+                                                      IDENT@5311..5312 "v"
+                                              R_PAREN@5312..5313 ")"
+                                          WHITESPACE@5313..5314 " "
+                                          GT@5314..5315 ">"
+                                          WHITESPACE@5315..5316 " "
+                                          EXPR@5316..5317
+                                            EXPR_NUMBER@5316..5317
+                                              FLOAT@5316..5317 "1"
+                                      WHITESPACE@5317..5318 " "
+                                      AND@5318..5320 "&&"
+                                      WHITESPACE@5320..5321 " "
+                                      EXPR@5321..5339
+                                        EXPR_VAR@5321..5324
+                                          NAME@5321..5324
+                                            IDENT@5321..5324 "std"
+                                        SUFFIX_INDEX@5324..5333
+                                          DOT@5324..5325 "."
+                                          NAME@5325..5333
+                                            IDENT@5325..5333 "isObject"
+                                        SUFFIX_APPLY@5333..5339
+                                          ARGS_DESC@5333..5339
+                                            L_PAREN@5333..5334 "("
+                                            ARG@5334..5338
+                                              EXPR@5334..5338
+                                                EXPR_VAR@5334..5335
+                                                  NAME@5334..5335
+                                                    IDENT@5334..5335 "v"
+                                                SUFFIX_INDEX_EXPR@5335..5338
+                                                  L_BRACK@5335..5336 "["
+                                                  EXPR@5336..5337
+                                                    EXPR_NUMBER@5336..5337
+                                                      FLOAT@5336..5337 "1"
+                                                  R_BRACK@5337..5338 "]"
+                                            R_PAREN@5338..5339 ")"
+                                SEMI@5339..5340 ";"
+                              WHITESPACE@5340..5351 "\n          "
+                              STMT_LOCAL@5351..5396
+                                LOCAL_KW@5351..5356 "local"
+                                WHITESPACE@5356..5357 " "
+                                BIND_DESTRUCT@5357..5395
+                                  DESTRUCT_FULL@5357..5362
+                                    NAME@5357..5362
+                                      IDENT@5357..5362 "attrs"
+                                  WHITESPACE@5362..5363 " "
+                                  ASSIGN@5363..5364 "="
+                                  WHITESPACE@5364..5365 " "
+                                  EXPR@5365..5395
+                                    EXPR_IF_THEN_ELSE@5365..5395
+                                      IF_KW@5365..5367 "if"
+                                      WHITESPACE@5367..5368 " "
+                                      EXPR@5368..5377
+                                        EXPR_VAR@5368..5377
+                                          NAME@5368..5377
+                                            IDENT@5368..5377 "has_attrs"
+                                      WHITESPACE@5377..5378 " "
+                                      THEN_KW@5378..5382 "then"
+                                      WHITESPACE@5382..5383 " "
+                                      TRUE_EXPR@5383..5387
+                                        EXPR@5383..5387
+                                          EXPR_VAR@5383..5384
+                                            NAME@5383..5384
+                                              IDENT@5383..5384 "v"
+                                          SUFFIX_INDEX_EXPR@5384..5387
+                                            L_BRACK@5384..5385 "["
+                                            EXPR@5385..5386
+                                              EXPR_NUMBER@5385..5386
+                                                FLOAT@5385..5386 "1"
+                                            R_BRACK@5386..5387 "]"
+                                      WHITESPACE@5387..5388 " "
+                                      ELSE_KW@5388..5392 "else"
+                                      WHITESPACE@5392..5393 " "
+                                      FALSE_EXPR@5393..5395
+                                        EXPR@5393..5395
+                                          EXPR_OBJECT@5393..5395
+                                            OBJ_BODY_MEMBER_LIST@5393..5395
+                                              L_BRACE@5393..5394 "{"
+                                              R_BRACE@5394..5395 "}"
+                                SEMI@5395..5396 ";"
+                              WHITESPACE@5396..5407 "\n          "
+                              STMT_LOCAL@5407..5459
+                                LOCAL_KW@5407..5412 "local"
+                                WHITESPACE@5412..5413 " "
+                                BIND_DESTRUCT@5413..5458
+                                  DESTRUCT_FULL@5413..5421
+                                    NAME@5413..5421
+                                      IDENT@5413..5421 "children"
+                                  WHITESPACE@5421..5422 " "
+                                  ASSIGN@5422..5423 "="
+                                  WHITESPACE@5423..5424 " "
+                                  EXPR@5424..5458
+                                    EXPR_IF_THEN_ELSE@5424..5458
+                                      IF_KW@5424..5426 "if"
+                                      WHITESPACE@5426..5427 " "
+                                      EXPR@5427..5436
+                                        EXPR_VAR@5427..5436
+                                          NAME@5427..5436
+                                            IDENT@5427..5436 "has_attrs"
+                                      WHITESPACE@5436..5437 " "
+                                      THEN_KW@5437..5441 "then"
+                                      WHITESPACE@5441..5442 " "
+                                      TRUE_EXPR@5442..5447
+                                        EXPR@5442..5447
+                                          EXPR_VAR@5442..5443
+                                            NAME@5442..5443
+                                              IDENT@5442..5443 "v"
+                                          SUFFIX_SLICE@5443..5447
+                                            SLICE_DESC@5443..5447
+                                              L_BRACK@5443..5444 "["
+                                              EXPR@5444..5445
+                                                EXPR_NUMBER@5444..5445
+                                                  FLOAT@5444..5445 "2"
+                                              COLON@5445..5446 ":"
+                                              R_BRACK@5446..5447 "]"
+                                      WHITESPACE@5447..5448 " "
+                                      ELSE_KW@5448..5452 "else"
+                                      WHITESPACE@5452..5453 " "
+                                      FALSE_EXPR@5453..5458
+                                        EXPR@5453..5458
+                                          EXPR_VAR@5453..5454
+                                            NAME@5453..5454
+                                              IDENT@5453..5454 "v"
+                                          SUFFIX_SLICE@5454..5458
+                                            SLICE_DESC@5454..5458
+                                              L_BRACK@5454..5455 "["
+                                              EXPR@5455..5456
+                                                EXPR_NUMBER@5455..5456
+                                                  FLOAT@5455..5456 "1"
+                                              COLON@5456..5457 ":"
+                                              R_BRACK@5457..5458 "]"
+                                SEMI@5458..5459 ";"
+                              WHITESPACE@5459..5470 "\n          "
+                              STMT_LOCAL@5470..5576
+                                LOCAL_KW@5470..5475 "local"
+                                WHITESPACE@5475..5476 " "
+                                BIND_DESTRUCT@5476..5575
+                                  DESTRUCT_FULL@5476..5485
+                                    NAME@5476..5485
+                                      IDENT@5476..5485 "attrs_str"
+                                  WHITESPACE@5485..5486 " "
+                                  ASSIGN@5486..5487 "="
+                                  WHITESPACE@5487..5500 "\n            "
+                                  EXPR@5500..5575
+                                    EXPR_VAR@5500..5503
+                                      NAME@5500..5503
+                                        IDENT@5500..5503 "std"
+                                    SUFFIX_INDEX@5503..5508
+                                      DOT@5503..5504 "."
+                                      NAME@5504..5508
+                                        IDENT@5504..5508 "join"
+                                    SUFFIX_APPLY@5508..5575
+                                      ARGS_DESC@5508..5575
+                                        L_PAREN@5508..5509 "("
+                                        ARG@5509..5511
+                                          EXPR@5509..5511
+                                            EXPR_STRING@5509..5511
+                                              STRING_SINGLE@5509..5511 "''"
+                                        COMMA@5511..5512 ","
+                                        WHITESPACE@5512..5513 " "
+                                        ARG@5513..5574
+                                          EXPR@5513..5574
+                                            EXPR_ARRAY_COMP@5513..5574
+                                              L_BRACK@5513..5514 "["
+                                              EXPR@5514..5540
+                                                EXPR_BINARY@5514..5540
+                                                  EXPR@5514..5524
+                                                    EXPR_STRING@5514..5524
+                                                      STRING_SINGLE@5514..5524 "' %s=\"%s\"'"
+                                                  WHITESPACE@5524..5525 " "
+                                                  MODULO@5525..5526 "%"
+                                                  WHITESPACE@5526..5527 " "
+                                                  EXPR@5527..5540
+                                                    EXPR_ARRAY@5527..5540
+                                                      L_BRACK@5527..5528 "["
+                                                      EXPR@5528..5529
+                                                        EXPR_VAR@5528..5529
+                                                          NAME@5528..5529
+                                                            IDENT@5528..5529 "k"
+                                                      COMMA@5529..5530 ","
+                                                      WHITESPACE@5530..5531 " "
+                                                      EXPR@5531..5539
+                                                        EXPR_VAR@5531..5536
+                                                          NAME@5531..5536
+                                                            IDENT@5531..5536 "attrs"
+                                                        SUFFIX_INDEX_EXPR@5536..5539
+                                                          L_BRACK@5536..5537 "["
+                                                          EXPR@5537..5538
+                                                            EXPR_VAR@5537..5538
+                                                              NAME@5537..5538
+                                                                IDENT@5537..5538 "k"
+                                                          R_BRACK@5538..5539 "]"
+                                                      R_BRACK@5539..5540 "]"
+                                              WHITESPACE@5540..5541 " "
+                                              FOR_SPEC@5541..5573
+                                                FOR_KW@5541..5544 "for"
+                                                WHITESPACE@5544..5545 " "
+                                                DESTRUCT_FULL@5545..5546
+                                                  NAME@5545..5546
+                                                    IDENT@5545..5546 "k"
+                                                WHITESPACE@5546..5547 " "
+                                                IN_KW@5547..5549 "in"
+                                                WHITESPACE@5549..5550 " "
+                                                EXPR@5550..5573
+                                                  EXPR_VAR@5550..5553
+                                                    NAME@5550..5553
+                                                      IDENT@5550..5553 "std"
+                                                  SUFFIX_INDEX@5553..5566
+                                                    DOT@5553..5554 "."
+                                                    NAME@5554..5566
+                                                      IDENT@5554..5566 "objectFields"
+                                                  SUFFIX_APPLY@5566..5573
+                                                    ARGS_DESC@5566..5573
+                                                      L_PAREN@5566..5567 "("
+                                                      ARG@5567..5572
+                                                        EXPR@5567..5572
+                                                          EXPR_VAR@5567..5572
+                                                            NAME@5567..5572
+                                                              IDENT@5567..5572 "attrs"
+                                                      R_PAREN@5572..5573 ")"
+                                              R_BRACK@5573..5574 "]"
+                                        R_PAREN@5574..5575 ")"
+                                SEMI@5575..5576 ";"
+                              WHITESPACE@5576..5587 "\n          "
+                              EXPR_VAR@5587..5590
+                                NAME@5587..5590
+                                  IDENT@5587..5590 "std"
+                              SUFFIX_INDEX@5590..5599
+                                DOT@5590..5591 "."
+                                NAME@5591..5599
+                                  IDENT@5591..5599 "deepJoin"
+                              SUFFIX_APPLY@5599..5671
+                                ARGS_DESC@5599..5671
+                                  L_PAREN@5599..5600 "("
+                                  ARG@5600..5670
+                                    EXPR@5600..5670
+                                      EXPR_ARRAY@5600..5670
+                                        L_BRACK@5600..5601 "["
+                                        EXPR@5601..5604
+                                          EXPR_STRING@5601..5604
+                                            STRING_SINGLE@5601..5604 "'<'"
+                                        COMMA@5604..5605 ","
+                                        WHITESPACE@5605..5606 " "
+                                        EXPR@5606..5609
+                                          EXPR_VAR@5606..5609
+                                            NAME@5606..5609
+                                              IDENT@5606..5609 "tag"
+                                        COMMA@5609..5610 ","
+                                        WHITESPACE@5610..5611 " "
+                                        EXPR@5611..5620
+                                          EXPR_VAR@5611..5620
+                                            NAME@5611..5620
+                                              IDENT@5611..5620 "attrs_str"
+                                        COMMA@5620..5621 ","
+                                        WHITESPACE@5621..5622 " "
+                                        EXPR@5622..5625
+                                          EXPR_STRING@5622..5625
+                                            STRING_SINGLE@5622..5625 "'>'"
+                                        COMMA@5625..5626 ","
+                                        WHITESPACE@5626..5627 " "
+                                        EXPR@5627..5653
+                                          EXPR_ARRAY_COMP@5627..5653
+                                            L_BRACK@5627..5628 "["
+                                            EXPR@5628..5634
+                                              EXPR_VAR@5628..5631
+                                                NAME@5628..5631
+                                                  IDENT@5628..5631 "aux"
+                                              SUFFIX_APPLY@5631..5634
+                                                ARGS_DESC@5631..5634
+                                                  L_PAREN@5631..5632 "("
+                                                  ARG@5632..5633
+                                                    EXPR@5632..5633
+                                                      EXPR_VAR@5632..5633
+                                                        NAME@5632..5633
+                                                          IDENT@5632..5633 "x"
+                                                  R_PAREN@5633..5634 ")"
+                                            WHITESPACE@5634..5635 " "
+                                            FOR_SPEC@5635..5652
+                                              FOR_KW@5635..5638 "for"
+                                              WHITESPACE@5638..5639 " "
+                                              DESTRUCT_FULL@5639..5640
+                                                NAME@5639..5640
+                                                  IDENT@5639..5640 "x"
+                                              WHITESPACE@5640..5641 " "
+                                              IN_KW@5641..5643 "in"
+                                              WHITESPACE@5643..5644 " "
+                                              EXPR@5644..5652
+                                                EXPR_VAR@5644..5652
+                                                  NAME@5644..5652
+                                                    IDENT@5644..5652 "children"
+                                            R_BRACK@5652..5653 "]"
+                                        COMMA@5653..5654 ","
+                                        WHITESPACE@5654..5655 " "
+                                        EXPR@5655..5659
+                                          EXPR_STRING@5655..5659
+                                            STRING_SINGLE@5655..5659 "'</'"
+                                        COMMA@5659..5660 ","
+                                        WHITESPACE@5660..5661 " "
+                                        EXPR@5661..5664
+                                          EXPR_VAR@5661..5664
+                                            NAME@5661..5664
+                                              IDENT@5661..5664 "tag"
+                                        COMMA@5664..5665 ","
+                                        WHITESPACE@5665..5666 " "
+                                        EXPR@5666..5669
+                                          EXPR_STRING@5666..5669
+                                            STRING_SINGLE@5666..5669 "'>'"
+                                        R_BRACK@5669..5670 "]"
+                                  R_PAREN@5670..5671 ")"
+                    SEMI@5671..5672 ";"
+                  WHITESPACE@5672..5680 "\n\n      "
+                  EXPR_VAR@5680..5683
+                    NAME@5680..5683
+                      IDENT@5680..5683 "aux"
+                  SUFFIX_APPLY@5683..5690
+                    ARGS_DESC@5683..5690
+                      L_PAREN@5683..5684 "("
+                      ARG@5684..5689
+                        EXPR@5684..5689
+                          EXPR_VAR@5684..5689
+                            NAME@5684..5689
+                              IDENT@5684..5689 "value"
+                      R_PAREN@5689..5690 ")"
+        COMMA@5690..5691 ","
+        WHITESPACE@5691..5695 "\n\n  "
+        MEMBER_FIELD_METHOD@5695..6483
+          FIELD_NAME_FIXED@5695..5705
+            NAME@5695..5705
+              IDENT@5695..5705 "mergePatch"
+          PARAMS_DESC@5705..5720
+            L_PAREN@5705..5706 "("
+            PARAM@5706..5712
+              DESTRUCT_FULL@5706..5712
+                NAME@5706..5712
+                  IDENT@5706..5712 "target"
+            COMMA@5712..5713 ","
+            WHITESPACE@5713..5714 " "
+            PARAM@5714..5719
+              DESTRUCT_FULL@5714..5719
+                NAME@5714..5719
+                  IDENT@5714..5719 "patch"
+            R_PAREN@5719..5720 ")"
+          COLONCOLON@5720..5722 "::"
+          WHITESPACE@5722..5727 "\n    "
+          EXPR@5727..6483
+            EXPR_IF_THEN_ELSE@5727..6483
+              IF_KW@5727..5729 "if"
+              WHITESPACE@5729..5730 " "
+              EXPR@5730..5749
+                EXPR_VAR@5730..5733
+                  NAME@5730..5733
+                    IDENT@5730..5733 "std"
+                SUFFIX_INDEX@5733..5742
+                  DOT@5733..5734 "."
+                  NAME@5734..5742
+                    IDENT@5734..5742 "isObject"
+                SUFFIX_APPLY@5742..5749
+                  ARGS_DESC@5742..5749
+                    L_PAREN@5742..5743 "("
+                    ARG@5743..5748
+                      EXPR@5743..5748
+                        EXPR_VAR@5743..5748
+                          NAME@5743..5748
+                            IDENT@5743..5748 "patch"
+                    R_PAREN@5748..5749 ")"
+              WHITESPACE@5749..5750 " "
+              THEN_KW@5750..5754 "then"
+              WHITESPACE@5754..5761 "\n      "
+              TRUE_EXPR@5761..6462
+                EXPR@5761..6462
+                  STMT_LOCAL@5761..5835
+                    LOCAL_KW@5761..5766 "local"
+                    WHITESPACE@5766..5767 " "
+                    BIND_DESTRUCT@5767..5834
+                      DESTRUCT_FULL@5767..5780
+                        NAME@5767..5780
+                          IDENT@5767..5780 "target_object"
+                      WHITESPACE@5780..5781 " "
+                      ASSIGN@5781..5782 "="
+                      WHITESPACE@5782..5791 "\n        "
+                      EXPR@5791..5834
+                        EXPR_IF_THEN_ELSE@5791..5834
+                          IF_KW@5791..5793 "if"
+                          WHITESPACE@5793..5794 " "
+                          EXPR@5794..5814
+                            EXPR_VAR@5794..5797
+                              NAME@5794..5797
+                                IDENT@5794..5797 "std"
+                            SUFFIX_INDEX@5797..5806
+                              DOT@5797..5798 "."
+                              NAME@5798..5806
+                                IDENT@5798..5806 "isObject"
+                            SUFFIX_APPLY@5806..5814
+                              ARGS_DESC@5806..5814
+                                L_PAREN@5806..5807 "("
+                                ARG@5807..5813
+                                  EXPR@5807..5813
+                                    EXPR_VAR@5807..5813
+                                      NAME@5807..5813
+                                        IDENT@5807..5813 "target"
+                                R_PAREN@5813..5814 ")"
+                          WHITESPACE@5814..5815 " "
+                          THEN_KW@5815..5819 "then"
+                          WHITESPACE@5819..5820 " "
+                          TRUE_EXPR@5820..5826
+                            EXPR@5820..5826
+                              EXPR_VAR@5820..5826
+                                NAME@5820..5826
+                                  IDENT@5820..5826 "target"
+                          WHITESPACE@5826..5827 " "
+                          ELSE_KW@5827..5831 "else"
+                          WHITESPACE@5831..5832 " "
+                          FALSE_EXPR@5832..5834
+                            EXPR@5832..5834
+                              EXPR_OBJECT@5832..5834
+                                OBJ_BODY_MEMBER_LIST@5832..5834
+                                  L_BRACE@5832..5833 "{"
+                                  R_BRACE@5833..5834 "}"
+                    SEMI@5834..5835 ";"
+                  WHITESPACE@5835..5843 "\n\n      "
+                  STMT_LOCAL@5843..5949
+                    LOCAL_KW@5843..5848 "local"
+                    WHITESPACE@5848..5849 " "
+                    BIND_DESTRUCT@5849..5948
+                      DESTRUCT_FULL@5849..5862
+                        NAME@5849..5862
+                          IDENT@5849..5862 "target_fields"
+                      WHITESPACE@5862..5863 " "
+                      ASSIGN@5863..5864 "="
+                      WHITESPACE@5864..5873 "\n        "
+                      EXPR@5873..5948
+                        EXPR_IF_THEN_ELSE@5873..5948
+                          IF_KW@5873..5875 "if"
+                          WHITESPACE@5875..5876 " "
+                          EXPR@5876..5903
+                            EXPR_VAR@5876..5879
+                              NAME@5876..5879
+                                IDENT@5876..5879 "std"
+                            SUFFIX_INDEX@5879..5888
+                              DOT@5879..5880 "."
+                              NAME@5880..5888
+                                IDENT@5880..5888 "isObject"
+                            SUFFIX_APPLY@5888..5903
+                              ARGS_DESC@5888..5903
+                                L_PAREN@5888..5889 "("
+                                ARG@5889..5902
+                                  EXPR@5889..5902
+                                    EXPR_VAR@5889..5902
+                                      NAME@5889..5902
+                                        IDENT@5889..5902 "target_object"
+                                R_PAREN@5902..5903 ")"
+                          WHITESPACE@5903..5904 " "
+                          THEN_KW@5904..5908 "then"
+                          WHITESPACE@5908..5909 " "
+                          TRUE_EXPR@5909..5940
+                            EXPR@5909..5940
+                              EXPR_VAR@5909..5912
+                                NAME@5909..5912
+                                  IDENT@5909..5912 "std"
+                              SUFFIX_INDEX@5912..5925
+                                DOT@5912..5913 "."
+                                NAME@5913..5925
+                                  IDENT@5913..5925 "objectFields"
+                              SUFFIX_APPLY@5925..5940
+                                ARGS_DESC@5925..5940
+                                  L_PAREN@5925..5926 "("
+                                  ARG@5926..5939
+                                    EXPR@5926..5939
+                                      EXPR_VAR@5926..5939
+                                        NAME@5926..5939
+                                          IDENT@5926..5939 "target_object"
+                                  R_PAREN@5939..5940 ")"
+                          WHITESPACE@5940..5941 " "
+                          ELSE_KW@5941..5945 "else"
+                          WHITESPACE@5945..5946 " "
+                          FALSE_EXPR@5946..5948
+                            EXPR@5946..5948
+                              EXPR_ARRAY@5946..5948
+                                L_BRACK@5946..5947 "["
+                                R_BRACK@5947..5948 "]"
+                    SEMI@5948..5949 ";"
+                  WHITESPACE@5949..5957 "\n\n      "
+                  STMT_LOCAL@5957..6034
+                    LOCAL_KW@5957..5962 "local"
+                    WHITESPACE@5962..5963 " "
+                    BIND_DESTRUCT@5963..6033
+                      DESTRUCT_FULL@5963..5974
+                        NAME@5963..5974
+                          IDENT@5963..5974 "null_fields"
+                      WHITESPACE@5974..5975 " "
+                      ASSIGN@5975..5976 "="
+                      WHITESPACE@5976..5977 " "
+                      EXPR@5977..6033
+                        EXPR_ARRAY_COMP@5977..6033
+                          L_BRACK@5977..5978 "["
+                          EXPR@5978..5979
+                            EXPR_VAR@5978..5979
+                              NAME@5978..5979
+                                IDENT@5978..5979 "k"
+                          WHITESPACE@5979..5980 " "
+                          FOR_SPEC@5980..6012
+                            FOR_KW@5980..5983 "for"
+                            WHITESPACE@5983..5984 " "
+                            DESTRUCT_FULL@5984..5985
+                              NAME@5984..5985
+                                IDENT@5984..5985 "k"
+                            WHITESPACE@5985..5986 " "
+                            IN_KW@5986..5988 "in"
+                            WHITESPACE@5988..5989 " "
+                            EXPR@5989..6012
+                              EXPR_VAR@5989..5992
+                                NAME@5989..5992
+                                  IDENT@5989..5992 "std"
+                              SUFFIX_INDEX@5992..6005
+                                DOT@5992..5993 "."
+                                NAME@5993..6005
+                                  IDENT@5993..6005 "objectFields"
+                              SUFFIX_APPLY@6005..6012
+                                ARGS_DESC@6005..6012
+                                  L_PAREN@6005..6006 "("
+                                  ARG@6006..6011
+                                    EXPR@6006..6011
+                                      EXPR_VAR@6006..6011
+                                        NAME@6006..6011
+                                          IDENT@6006..6011 "patch"
+                                  R_PAREN@6011..6012 ")"
+                          WHITESPACE@6012..6013 " "
+                          IF_SPEC@6013..6032
+                            IF_KW@6013..6015 "if"
+                            WHITESPACE@6015..6016 " "
+                            EXPR@6016..6032
+                              EXPR_BINARY@6016..6032
+                                EXPR@6016..6021
+                                  EXPR_VAR@6016..6021
+                                    NAME@6016..6021
+                                      IDENT@6016..6021 "patch"
+                                SUFFIX_INDEX_EXPR@6021..6024
+                                  L_BRACK@6021..6022 "["
+                                  EXPR@6022..6023
+                                    EXPR_VAR@6022..6023
+                                      NAME@6022..6023
+                                        IDENT@6022..6023 "k"
+                                  R_BRACK@6023..6024 "]"
+                                WHITESPACE@6024..6025 " "
+                                EQ@6025..6027 "=="
+                                WHITESPACE@6027..6028 " "
+                                EXPR@6028..6032
+                                  EXPR_LITERAL@6028..6032
+                                    NULL_KW@6028..6032 "null"
+                          R_BRACK@6032..6033 "]"
+                    SEMI@6033..6034 ";"
+                  WHITESPACE@6034..6041 "\n      "
+                  STMT_LOCAL@6041..6114
+                    LOCAL_KW@6041..6046 "local"
+                    WHITESPACE@6046..6047 " "
+                    BIND_DESTRUCT@6047..6113
+                      DESTRUCT_FULL@6047..6058
+                        NAME@6047..6058
+                          IDENT@6047..6058 "both_fields"
+                      WHITESPACE@6058..6059 " "
+                      ASSIGN@6059..6060 "="
+                      WHITESPACE@6060..6061 " "
+                      EXPR@6061..6113
+                        EXPR_VAR@6061..6064
+                          NAME@6061..6064
+                            IDENT@6061..6064 "std"
+                        SUFFIX_INDEX@6064..6073
+                          DOT@6064..6065 "."
+                          NAME@6065..6073
+                            IDENT@6065..6073 "setUnion"
+                        SUFFIX_APPLY@6073..6113
+                          ARGS_DESC@6073..6113
+                            L_PAREN@6073..6074 "("
+                            ARG@6074..6087
+                              EXPR@6074..6087
+                                EXPR_VAR@6074..6087
+                                  NAME@6074..6087
+                                    IDENT@6074..6087 "target_fields"
+                            COMMA@6087..6088 ","
+                            WHITESPACE@6088..6089 " "
+                            ARG@6089..6112
+                              EXPR@6089..6112
+                                EXPR_VAR@6089..6092
+                                  NAME@6089..6092
+                                    IDENT@6089..6092 "std"
+                                SUFFIX_INDEX@6092..6105
+                                  DOT@6092..6093 "."
+                                  NAME@6093..6105
+                                    IDENT@6093..6105 "objectFields"
+                                SUFFIX_APPLY@6105..6112
+                                  ARGS_DESC@6105..6112
+                                    L_PAREN@6105..6106 "("
+                                    ARG@6106..6111
+                                      EXPR@6106..6111
+                                        EXPR_VAR@6106..6111
+                                          NAME@6106..6111
+                                            IDENT@6106..6111 "patch"
+                                    R_PAREN@6111..6112 ")"
+                            R_PAREN@6112..6113 ")"
+                    SEMI@6113..6114 ";"
+                  WHITESPACE@6114..6122 "\n\n      "
+                  EXPR_OBJECT@6122..6462
+                    OBJ_BODY_COMP@6122..6462
+                      L_BRACE@6122..6123 "{"
+                      WHITESPACE@6123..6132 "\n        "
+                      MEMBER_FIELD_NORMAL@6132..6399
+                        FIELD_NAME_DYNAMIC@6132..6135
+                          L_BRACK@6132..6133 "["
+                          EXPR@6133..6134
+                            EXPR_VAR@6133..6134
+                              NAME@6133..6134
+                                IDENT@6133..6134 "k"
+                          R_BRACK@6134..6135 "]"
+                        COLON@6135..6136 ":"
+                        WHITESPACE@6136..6147 "\n          "
+                        EXPR@6147..6399
+                          EXPR_IF_THEN_ELSE@6147..6399
+                            IF_KW@6147..6149 "if"
+                            WHITESPACE@6149..6150 " "
+                            EXPR@6150..6174
+                              EXPR_UNARY@6150..6174
+                                NOT@6150..6151 "!"
+                                EXPR_VAR@6151..6154
+                                  NAME@6151..6154
+                                    IDENT@6151..6154 "std"
+                                SUFFIX_INDEX@6154..6164
+                                  DOT@6154..6155 "."
+                                  NAME@6155..6164
+                                    IDENT@6155..6164 "objectHas"
+                                SUFFIX_APPLY@6164..6174
+                                  ARGS_DESC@6164..6174
+                                    L_PAREN@6164..6165 "("
+                                    ARG@6165..6170
+                                      EXPR@6165..6170
+                                        EXPR_VAR@6165..6170
+                                          NAME@6165..6170
+                                            IDENT@6165..6170 "patch"
+                                    COMMA@6170..6171 ","
+                                    WHITESPACE@6171..6172 " "
+                                    ARG@6172..6173
+                                      EXPR@6172..6173
+                                        EXPR_VAR@6172..6173
+                                          NAME@6172..6173
+                                            IDENT@6172..6173 "k"
+                                    R_PAREN@6173..6174 ")"
+                            WHITESPACE@6174..6175 " "
+                            THEN_KW@6175..6179 "then"
+                            WHITESPACE@6179..6192 "\n            "
+                            TRUE_EXPR@6192..6208
+                              EXPR@6192..6208
+                                EXPR_VAR@6192..6205
+                                  NAME@6192..6205
+                                    IDENT@6192..6205 "target_object"
+                                SUFFIX_INDEX_EXPR@6205..6208
+                                  L_BRACK@6205..6206 "["
+                                  EXPR@6206..6207
+                                    EXPR_VAR@6206..6207
+                                      NAME@6206..6207
+                                        IDENT@6206..6207 "k"
+                                  R_BRACK@6207..6208 "]"
+                            WHITESPACE@6208..6219 "\n          "
+                            ELSE_KW@6219..6223 "else"
+                            WHITESPACE@6223..6224 " "
+                            FALSE_EXPR@6224..6399
+                              EXPR@6224..6399
+                                EXPR_IF_THEN_ELSE@6224..6399
+                                  IF_KW@6224..6226 "if"
+                                  WHITESPACE@6226..6227 " "
+                                  EXPR@6227..6259
+                                    EXPR_UNARY@6227..6259
+                                      NOT@6227..6228 "!"
+                                      EXPR_VAR@6228..6231
+                                        NAME@6228..6231
+                                          IDENT@6228..6231 "std"
+                                      SUFFIX_INDEX@6231..6241
+                                        DOT@6231..6232 "."
+                                        NAME@6232..6241
+                                          IDENT@6232..6241 "objectHas"
+                                      SUFFIX_APPLY@6241..6259
+                                        ARGS_DESC@6241..6259
+                                          L_PAREN@6241..6242 "("
+                                          ARG@6242..6255
+                                            EXPR@6242..6255
+                                              EXPR_VAR@6242..6255
+                                                NAME@6242..6255
+                                                  IDENT@6242..6255 "target_object"
+                                          COMMA@6255..6256 ","
+                                          WHITESPACE@6256..6257 " "
+                                          ARG@6257..6258
+                                            EXPR@6257..6258
+                                              EXPR_VAR@6257..6258
+                                                NAME@6257..6258
+                                                  IDENT@6257..6258 "k"
+                                          R_PAREN@6258..6259 ")"
+                                  WHITESPACE@6259..6260 " "
+                                  THEN_KW@6260..6264 "then"
+                                  WHITESPACE@6264..6277 "\n            "
+                                  TRUE_EXPR@6277..6318
+                                    EXPR@6277..6318
+                                      EXPR_VAR@6277..6280
+                                        NAME@6277..6280
+                                          IDENT@6277..6280 "std"
+                                      SUFFIX_INDEX@6280..6291
+                                        DOT@6280..6281 "."
+                                        NAME@6281..6291
+                                          IDENT@6281..6291 "mergePatch"
+                                      SUFFIX_APPLY@6291..6318
+                                        ARGS_DESC@6291..6318
+                                          L_PAREN@6291..6292 "("
+                                          ARG@6292..6296
+                                            EXPR@6292..6296
+                                              EXPR_LITERAL@6292..6296
+                                                NULL_KW@6292..6296 "null"
+                                          COMMA@6296..6297 ","
+                                          WHITESPACE@6297..6298 " "
+                                          ARG@6298..6306
+                                            EXPR@6298..6306
+                                              EXPR_VAR@6298..6303
+                                                NAME@6298..6303
+                                                  IDENT@6298..6303 "patch"
+                                              SUFFIX_INDEX_EXPR@6303..6306
+                                                L_BRACK@6303..6304 "["
+                                                EXPR@6304..6305
+                                                  EXPR_VAR@6304..6305
+                                                    NAME@6304..6305
+                                                      IDENT@6304..6305 "k"
+                                                R_BRACK@6305..6306 "]"
+                                          R_PAREN@6306..6307 ")"
+                                          WHITESPACE@6307..6308 " "
+                                          TAILSTRICT_KW@6308..6318 "tailstrict"
+                                  WHITESPACE@6318..6329 "\n          "
+                                  ELSE_KW@6329..6333 "else"
+                                  WHITESPACE@6333..6346 "\n            "
+                                  FALSE_EXPR@6346..6399
+                                    EXPR@6346..6399
+                                      EXPR_VAR@6346..6349
+                                        NAME@6346..6349
+                                          IDENT@6346..6349 "std"
+                                      SUFFIX_INDEX@6349..6360
+                                        DOT@6349..6350 "."
+                                        NAME@6350..6360
+                                          IDENT@6350..6360 "mergePatch"
+                                      SUFFIX_APPLY@6360..6399
+                                        ARGS_DESC@6360..6399
+                                          L_PAREN@6360..6361 "("
+                                          ARG@6361..6377
+                                            EXPR@6361..6377
+                                              EXPR_VAR@6361..6374
+                                                NAME@6361..6374
+                                                  IDENT@6361..6374 "target_object"
+                                              SUFFIX_INDEX_EXPR@6374..6377
+                                                L_BRACK@6374..6375 "["
+                                                EXPR@6375..6376
+                                                  EXPR_VAR@6375..6376
+                                                    NAME@6375..6376
+                                                      IDENT@6375..6376 "k"
+                                                R_BRACK@6376..6377 "]"
+                                          COMMA@6377..6378 ","
+                                          WHITESPACE@6378..6379 " "
+                                          ARG@6379..6387
+                                            EXPR@6379..6387
+                                              EXPR_VAR@6379..6384
+                                                NAME@6379..6384
+                                                  IDENT@6379..6384 "patch"
+                                              SUFFIX_INDEX_EXPR@6384..6387
+                                                L_BRACK@6384..6385 "["
+                                                EXPR@6385..6386
+                                                  EXPR_VAR@6385..6386
+                                                    NAME@6385..6386
+                                                      IDENT@6385..6386 "k"
+                                                R_BRACK@6386..6387 "]"
+                                          R_PAREN@6387..6388 ")"
+                                          WHITESPACE@6388..6389 " "
+                                          TAILSTRICT_KW@6389..6399 "tailstrict"
+                      WHITESPACE@6399..6408 "\n        "
+                      FOR_SPEC@6408..6454
+                        FOR_KW@6408..6411 "for"
+                        WHITESPACE@6411..6412 " "
+                        DESTRUCT_FULL@6412..6413
+                          NAME@6412..6413
+                            IDENT@6412..6413 "k"
+                        WHITESPACE@6413..6414 " "
+                        IN_KW@6414..6416 "in"
+                        WHITESPACE@6416..6417 " "
+                        EXPR@6417..6454
+                          EXPR_VAR@6417..6420
+                            NAME@6417..6420
+                              IDENT@6417..6420 "std"
+                          SUFFIX_INDEX@6420..6428
+                            DOT@6420..6421 "."
+                            NAME@6421..6428
+                              IDENT@6421..6428 "setDiff"
+                          SUFFIX_APPLY@6428..6454
+                            ARGS_DESC@6428..6454
+                              L_PAREN@6428..6429 "("
+                              ARG@6429..6440
+                                EXPR@6429..6440
+                                  EXPR_VAR@6429..6440
+                                    NAME@6429..6440
+                                      IDENT@6429..6440 "both_fields"
+                              COMMA@6440..6441 ","
+                              WHITESPACE@6441..6442 " "
+                              ARG@6442..6453
+                                EXPR@6442..6453
+                                  EXPR_VAR@6442..6453
+                                    NAME@6442..6453
+                                      IDENT@6442..6453 "null_fields"
+                              R_PAREN@6453..6454 ")"
+                      WHITESPACE@6454..6461 "\n      "
+                      R_BRACE@6461..6462 "}"
+              WHITESPACE@6462..6467 "\n    "
+              ELSE_KW@6467..6471 "else"
+              WHITESPACE@6471..6478 "\n      "
+              FALSE_EXPR@6478..6483
+                EXPR@6478..6483
+                  EXPR_VAR@6478..6483
+                    NAME@6478..6483
+                      IDENT@6478..6483 "patch"
+        COMMA@6483..6484 ","
+        WHITESPACE@6484..6488 "\n\n  "
+        MEMBER_FIELD_METHOD@6488..6594
+          FIELD_NAME_FIXED@6488..6491
+            NAME@6488..6491
+              IDENT@6488..6491 "get"
+          PARAMS_DESC@6491..6528
+            L_PAREN@6491..6492 "("
+            PARAM@6492..6493
+              DESTRUCT_FULL@6492..6493
+                NAME@6492..6493
+                  IDENT@6492..6493 "o"
+            COMMA@6493..6494 ","
+            WHITESPACE@6494..6495 " "
+            PARAM@6495..6496
+              DESTRUCT_FULL@6495..6496
+                NAME@6495..6496
+                  IDENT@6495..6496 "f"
+            COMMA@6496..6497 ","
+            WHITESPACE@6497..6498 " "
+            PARAM@6498..6510
+              DESTRUCT_FULL@6498..6505
+                NAME@6498..6505
+                  IDENT@6498..6505 "default"
+              ASSIGN@6505..6506 "="
+              EXPR@6506..6510
+                EXPR_LITERAL@6506..6510
+                  NULL_KW@6506..6510 "null"
+            COMMA@6510..6511 ","
+            WHITESPACE@6511..6512 " "
+            PARAM@6512..6527
+              DESTRUCT_FULL@6512..6522
+                NAME@6512..6522
+                  IDENT@6512..6522 "inc_hidden"
+              ASSIGN@6522..6523 "="
+              EXPR@6523..6527
+                EXPR_LITERAL@6523..6527
+                  TRUE_KW@6523..6527 "true"
+            R_PAREN@6527..6528 ")"
+          COLONCOLON@6528..6530 "::"
+          WHITESPACE@6530..6535 "\n    "
+          EXPR@6535..6594
+            EXPR_IF_THEN_ELSE@6535..6594
+              IF_KW@6535..6537 "if"
+              WHITESPACE@6537..6538 " "
+              EXPR@6538..6571
+                EXPR_VAR@6538..6541
+                  NAME@6538..6541
+                    IDENT@6538..6541 "std"
+                SUFFIX_INDEX@6541..6553
+                  DOT@6541..6542 "."
+                  NAME@6542..6553
+                    IDENT@6542..6553 "objectHasEx"
+                SUFFIX_APPLY@6553..6571
+                  ARGS_DESC@6553..6571
+                    L_PAREN@6553..6554 "("
+                    ARG@6554..6555
+                      EXPR@6554..6555
+                        EXPR_VAR@6554..6555
+                          NAME@6554..6555
+                            IDENT@6554..6555 "o"
+                    COMMA@6555..6556 ","
+                    WHITESPACE@6556..6557 " "
+                    ARG@6557..6558
+                      EXPR@6557..6558
+                        EXPR_VAR@6557..6558
+                          NAME@6557..6558
+                            IDENT@6557..6558 "f"
+                    COMMA@6558..6559 ","
+                    WHITESPACE@6559..6560 " "
+                    ARG@6560..6570
+                      EXPR@6560..6570
+                        EXPR_VAR@6560..6570
+                          NAME@6560..6570
+                            IDENT@6560..6570 "inc_hidden"
+                    R_PAREN@6570..6571 ")"
+              WHITESPACE@6571..6572 " "
+              THEN_KW@6572..6576 "then"
+              WHITESPACE@6576..6577 " "
+              TRUE_EXPR@6577..6581
+                EXPR@6577..6581
+                  EXPR_VAR@6577..6578
+                    NAME@6577..6578
+                      IDENT@6577..6578 "o"
+                  SUFFIX_INDEX_EXPR@6578..6581
+                    L_BRACK@6578..6579 "["
+                    EXPR@6579..6580
+                      EXPR_VAR@6579..6580
+                        NAME@6579..6580
+                          IDENT@6579..6580 "f"
+                    R_BRACK@6580..6581 "]"
+              WHITESPACE@6581..6582 " "
+              ELSE_KW@6582..6586 "else"
+              WHITESPACE@6586..6587 " "
+              FALSE_EXPR@6587..6594
+                EXPR@6587..6594
+                  EXPR_VAR@6587..6594
+                    NAME@6587..6594
+                      IDENT@6587..6594 "default"
+        COMMA@6594..6595 ","
+        WHITESPACE@6595..6599 "\n\n  "
+        MEMBER_FIELD_METHOD@6599..6733
+          FIELD_NAME_FIXED@6599..6610
+            NAME@6599..6610
+              IDENT@6599..6610 "resolvePath"
+          PARAMS_DESC@6610..6616
+            L_PAREN@6610..6611 "("
+            PARAM@6611..6612
+              DESTRUCT_FULL@6611..6612
+                NAME@6611..6612
+                  IDENT@6611..6612 "f"
+            COMMA@6612..6613 ","
+            WHITESPACE@6613..6614 " "
+            PARAM@6614..6615
+              DESTRUCT_FULL@6614..6615
+                NAME@6614..6615
+                  IDENT@6614..6615 "r"
+            R_PAREN@6615..6616 ")"
+          COLONCOLON@6616..6618 "::"
+          WHITESPACE@6618..6623 "\n    "
+          EXPR@6623..6733
+            STMT_LOCAL@6623..6653
+              LOCAL_KW@6623..6628 "local"
+              WHITESPACE@6628..6629 " "
+              BIND_DESTRUCT@6629..6652
+                DESTRUCT_FULL@6629..6632
+                  NAME@6629..6632
+                    IDENT@6629..6632 "arr"
+                WHITESPACE@6632..6633 " "
+                ASSIGN@6633..6634 "="
+                WHITESPACE@6634..6635 " "
+                EXPR@6635..6652
+                  EXPR_VAR@6635..6638
+                    NAME@6635..6638
+                      IDENT@6635..6638 "std"
+                  SUFFIX_INDEX@6638..6644
+                    DOT@6638..6639 "."
+                    NAME@6639..6644
+                      IDENT@6639..6644 "split"
+                  SUFFIX_APPLY@6644..6652
+                    ARGS_DESC@6644..6652
+                      L_PAREN@6644..6645 "("
+                      ARG@6645..6646
+                        EXPR@6645..6646
+                          EXPR_VAR@6645..6646
+                            NAME@6645..6646
+                              IDENT@6645..6646 "f"
+                      COMMA@6646..6647 ","
+                      WHITESPACE@6647..6648 " "
+                      ARG@6648..6651
+                        EXPR@6648..6651
+                          EXPR_STRING@6648..6651
+                            STRING_SINGLE@6648..6651 "'/'"
+                      R_PAREN@6651..6652 ")"
+              SEMI@6652..6653 ";"
+            WHITESPACE@6653..6658 "\n    "
+            EXPR_VAR@6658..6661
+              NAME@6658..6661
+                IDENT@6658..6661 "std"
+            SUFFIX_INDEX@6661..6666
+              DOT@6661..6662 "."
+              NAME@6662..6666
+                IDENT@6662..6666 "join"
+            SUFFIX_APPLY@6666..6733
+              ARGS_DESC@6666..6733
+                L_PAREN@6666..6667 "("
+                ARG@6667..6670
+                  EXPR@6667..6670
+                    EXPR_STRING@6667..6670
+                      STRING_SINGLE@6667..6670 "'/'"
+                COMMA@6670..6671 ","
+                WHITESPACE@6671..6672 " "
+                ARG@6672..6732
+                  EXPR@6672..6732
+                    EXPR_BINARY@6672..6732
+                      EXPR@6672..6675
+                        EXPR_VAR@6672..6675
+                          NAME@6672..6675
+                            IDENT@6672..6675 "std"
+                      SUFFIX_INDEX@6675..6685
+                        DOT@6675..6676 "."
+                        NAME@6676..6685
+                          IDENT@6676..6685 "makeArray"
+                      SUFFIX_APPLY@6685..6726
+                        ARGS_DESC@6685..6726
+                          L_PAREN@6685..6686 "("
+                          ARG@6686..6705
+                            EXPR@6686..6705
+                              EXPR_BINARY@6686..6705
+                                EXPR@6686..6689
+                                  EXPR_VAR@6686..6689
+                                    NAME@6686..6689
+                                      IDENT@6686..6689 "std"
+                                SUFFIX_INDEX@6689..6696
+                                  DOT@6689..6690 "."
+                                  NAME@6690..6696
+                                    IDENT@6690..6696 "length"
+                                SUFFIX_APPLY@6696..6701
+                                  ARGS_DESC@6696..6701
+                                    L_PAREN@6696..6697 "("
+                                    ARG@6697..6700
+                                      EXPR@6697..6700
+                                        EXPR_VAR@6697..6700
+                                          NAME@6697..6700
+                                            IDENT@6697..6700 "arr"
+                                    R_PAREN@6700..6701 ")"
+                                WHITESPACE@6701..6702 " "
+                                MINUS@6702..6703 "-"
+                                WHITESPACE@6703..6704 " "
+                                EXPR@6704..6705
+                                  EXPR_NUMBER@6704..6705
+                                    FLOAT@6704..6705 "1"
+                          COMMA@6705..6706 ","
+                          WHITESPACE@6706..6707 " "
+                          ARG@6707..6725
+                            EXPR@6707..6725
+                              EXPR_FUNCTION@6707..6725
+                                FUNCTION_KW@6707..6715 "function"
+                                PARAMS_DESC@6715..6718
+                                  L_PAREN@6715..6716 "("
+                                  PARAM@6716..6717
+                                    DESTRUCT_FULL@6716..6717
+                                      NAME@6716..6717
+                                        IDENT@6716..6717 "i"
+                                  R_PAREN@6717..6718 ")"
+                                WHITESPACE@6718..6719 " "
+                                EXPR@6719..6725
+                                  EXPR_VAR@6719..6722
+                                    NAME@6719..6722
+                                      IDENT@6719..6722 "arr"
+                                  SUFFIX_INDEX_EXPR@6722..6725
+                                    L_BRACK@6722..6723 "["
+                                    EXPR@6723..6724
+                                      EXPR_VAR@6723..6724
+                                        NAME@6723..6724
+                                          IDENT@6723..6724 "i"
+                                    R_BRACK@6724..6725 "]"
+                          R_PAREN@6725..6726 ")"
+                      WHITESPACE@6726..6727 " "
+                      PLUS@6727..6728 "+"
+                      WHITESPACE@6728..6729 " "
+                      EXPR@6729..6732
+                        EXPR_ARRAY@6729..6732
+                          L_BRACK@6729..6730 "["
+                          EXPR@6730..6731
+                            EXPR_VAR@6730..6731
+                              NAME@6730..6731
+                                IDENT@6730..6731 "r"
+                          R_BRACK@6731..6732 "]"
+                R_PAREN@6732..6733 ")"
+        COMMA@6733..6734 ","
+        WHITESPACE@6734..6738 "\n\n  "
+        MEMBER_FIELD_METHOD@6738..7191
+          FIELD_NAME_FIXED@6738..6743
+            NAME@6738..6743
+              IDENT@6738..6743 "prune"
+          PARAMS_DESC@6743..6746
+            L_PAREN@6743..6744 "("
+            PARAM@6744..6745
+              DESTRUCT_FULL@6744..6745
+                NAME@6744..6745
+                  IDENT@6744..6745 "a"
+            R_PAREN@6745..6746 ")"
+          COLONCOLON@6746..6748 "::"
+          WHITESPACE@6748..6753 "\n    "
+          EXPR@6753..7191
+            STMT_LOCAL@6753..6957
+              LOCAL_KW@6753..6758 "local"
+              WHITESPACE@6758..6759 " "
+              BIND_FUNCTION@6759..6956
+                NAME@6759..6768
+                  IDENT@6759..6768 "isContent"
+                PARAMS_DESC@6768..6771
+                  L_PAREN@6768..6769 "("
+                  PARAM@6769..6770
+                    DESTRUCT_FULL@6769..6770
+                      NAME@6769..6770
+                        IDENT@6769..6770 "b"
+                  R_PAREN@6770..6771 ")"
+                WHITESPACE@6771..6772 " "
+                ASSIGN@6772..6773 "="
+                WHITESPACE@6773..6780 "\n      "
+                EXPR@6780..6956
+                  EXPR_IF_THEN_ELSE@6780..6956
+                    IF_KW@6780..6782 "if"
+                    WHITESPACE@6782..6783 " "
+                    EXPR@6783..6792
+                      EXPR_BINARY@6783..6792
+                        EXPR@6783..6784
+                          EXPR_VAR@6783..6784
+                            NAME@6783..6784
+                              IDENT@6783..6784 "b"
+                        WHITESPACE@6784..6785 " "
+                        EQ@6785..6787 "=="
+                        WHITESPACE@6787..6788 " "
+                        EXPR@6788..6792
+                          EXPR_LITERAL@6788..6792
+                            NULL_KW@6788..6792 "null"
+                    WHITESPACE@6792..6793 " "
+                    THEN_KW@6793..6797 "then"
+                    WHITESPACE@6797..6806 "\n        "
+                    TRUE_EXPR@6806..6811
+                      EXPR@6806..6811
+                        EXPR_LITERAL@6806..6811
+                          FALSE_KW@6806..6811 "false"
+                    WHITESPACE@6811..6818 "\n      "
+                    ELSE_KW@6818..6822 "else"
+                    WHITESPACE@6822..6823 " "
+                    FALSE_EXPR@6823..6956
+                      EXPR@6823..6956
+                        EXPR_IF_THEN_ELSE@6823..6956
+                          IF_KW@6823..6825 "if"
+                          WHITESPACE@6825..6826 " "
+                          EXPR@6826..6840
+                            EXPR_VAR@6826..6829
+                              NAME@6826..6829
+                                IDENT@6826..6829 "std"
+                            SUFFIX_INDEX@6829..6837
+                              DOT@6829..6830 "."
+                              NAME@6830..6837
+                                IDENT@6830..6837 "isArray"
+                            SUFFIX_APPLY@6837..6840
+                              ARGS_DESC@6837..6840
+                                L_PAREN@6837..6838 "("
+                                ARG@6838..6839
+                                  EXPR@6838..6839
+                                    EXPR_VAR@6838..6839
+                                      NAME@6838..6839
+                                        IDENT@6838..6839 "b"
+                                R_PAREN@6839..6840 ")"
+                          WHITESPACE@6840..6841 " "
+                          THEN_KW@6841..6845 "then"
+                          WHITESPACE@6845..6854 "\n        "
+                          TRUE_EXPR@6854..6871
+                            EXPR@6854..6871
+                              EXPR_BINARY@6854..6871
+                                EXPR@6854..6857
+                                  EXPR_VAR@6854..6857
+                                    NAME@6854..6857
+                                      IDENT@6854..6857 "std"
+                                SUFFIX_INDEX@6857..6864
+                                  DOT@6857..6858 "."
+                                  NAME@6858..6864
+                                    IDENT@6858..6864 "length"
+                                SUFFIX_APPLY@6864..6867
+                                  ARGS_DESC@6864..6867
+                                    L_PAREN@6864..6865 "("
+                                    ARG@6865..6866
+                                      EXPR@6865..6866
+                                        EXPR_VAR@6865..6866
+                                          NAME@6865..6866
+                                            IDENT@6865..6866 "b"
+                                    R_PAREN@6866..6867 ")"
+                                WHITESPACE@6867..6868 " "
+                                GT@6868..6869 ">"
+                                WHITESPACE@6869..6870 " "
+                                EXPR@6870..6871
+                                  EXPR_NUMBER@6870..6871
+                                    FLOAT@6870..6871 "0"
+                          WHITESPACE@6871..6878 "\n      "
+                          ELSE_KW@6878..6882 "else"
+                          WHITESPACE@6882..6883 " "
+                          FALSE_EXPR@6883..6956
+                            EXPR@6883..6956
+                              EXPR_IF_THEN_ELSE@6883..6956
+                                IF_KW@6883..6885 "if"
+                                WHITESPACE@6885..6886 " "
+                                EXPR@6886..6901
+                                  EXPR_VAR@6886..6889
+                                    NAME@6886..6889
+                                      IDENT@6886..6889 "std"
+                                  SUFFIX_INDEX@6889..6898
+                                    DOT@6889..6890 "."
+                                    NAME@6890..6898
+                                      IDENT@6890..6898 "isObject"
+                                  SUFFIX_APPLY@6898..6901
+                                    ARGS_DESC@6898..6901
+                                      L_PAREN@6898..6899 "("
+                                      ARG@6899..6900
+                                        EXPR@6899..6900
+                                          EXPR_VAR@6899..6900
+                                            NAME@6899..6900
+                                              IDENT@6899..6900 "b"
+                                      R_PAREN@6900..6901 ")"
+                                WHITESPACE@6901..6902 " "
+                                THEN_KW@6902..6906 "then"
+                                WHITESPACE@6906..6915 "\n        "
+                                TRUE_EXPR@6915..6932
+                                  EXPR@6915..6932
+                                    EXPR_BINARY@6915..6932
+                                      EXPR@6915..6918
+                                        EXPR_VAR@6915..6918
+                                          NAME@6915..6918
+                                            IDENT@6915..6918 "std"
+                                      SUFFIX_INDEX@6918..6925
+                                        DOT@6918..6919 "."
+                                        NAME@6919..6925
+                                          IDENT@6919..6925 "length"
+                                      SUFFIX_APPLY@6925..6928
+                                        ARGS_DESC@6925..6928
+                                          L_PAREN@6925..6926 "("
+                                          ARG@6926..6927
+                                            EXPR@6926..6927
+                                              EXPR_VAR@6926..6927
+                                                NAME@6926..6927
+                                                  IDENT@6926..6927 "b"
+                                          R_PAREN@6927..6928 ")"
+                                      WHITESPACE@6928..6929 " "
+                                      GT@6929..6930 ">"
+                                      WHITESPACE@6930..6931 " "
+                                      EXPR@6931..6932
+                                        EXPR_NUMBER@6931..6932
+                                          FLOAT@6931..6932 "0"
+                                WHITESPACE@6932..6939 "\n      "
+                                ELSE_KW@6939..6943 "else"
+                                WHITESPACE@6943..6952 "\n        "
+                                FALSE_EXPR@6952..6956
+                                  EXPR@6952..6956
+                                    EXPR_LITERAL@6952..6956
+                                      TRUE_KW@6952..6956 "true"
+              SEMI@6956..6957 ";"
+            WHITESPACE@6957..6962 "\n    "
+            EXPR_IF_THEN_ELSE@6962..7191
+              IF_KW@6962..6964 "if"
+              WHITESPACE@6964..6965 " "
+              EXPR@6965..6979
+                EXPR_VAR@6965..6968
+                  NAME@6965..6968
+                    IDENT@6965..6968 "std"
+                SUFFIX_INDEX@6968..6976
+                  DOT@6968..6969 "."
+                  NAME@6969..6976
+                    IDENT@6969..6976 "isArray"
+                SUFFIX_APPLY@6976..6979
+                  ARGS_DESC@6976..6979
+                    L_PAREN@6976..6977 "("
+                    ARG@6977..6978
+                      EXPR@6977..6978
+                        EXPR_VAR@6977..6978
+                          NAME@6977..6978
+                            IDENT@6977..6978 "a"
+                    R_PAREN@6978..6979 ")"
+              WHITESPACE@6979..6980 " "
+              THEN_KW@6980..6984 "then"
+              WHITESPACE@6984..6991 "\n      "
+              TRUE_EXPR@6991..7041
+                EXPR@6991..7041
+                  EXPR_ARRAY_COMP@6991..7041
+                    L_BRACK@6991..6992 "["
+                    EXPR@6992..7004
+                      EXPR_VAR@6992..6995
+                        NAME@6992..6995
+                          IDENT@6992..6995 "std"
+                      SUFFIX_INDEX@6995..7001
+                        DOT@6995..6996 "."
+                        NAME@6996..7001
+                          IDENT@6996..7001 "prune"
+                      SUFFIX_APPLY@7001..7004
+                        ARGS_DESC@7001..7004
+                          L_PAREN@7001..7002 "("
+                          ARG@7002..7003
+                            EXPR@7002..7003
+                              EXPR_VAR@7002..7003
+                                NAME@7002..7003
+                                  IDENT@7002..7003 "x"
+                          R_PAREN@7003..7004 ")"
+                    WHITESPACE@7004..7005 " "
+                    FOR_SPEC@7005..7015
+                      FOR_KW@7005..7008 "for"
+                      WHITESPACE@7008..7009 " "
+                      DESTRUCT_FULL@7009..7010
+                        NAME@7009..7010
+                          IDENT@7009..7010 "x"
+                      WHITESPACE@7010..7011 " "
+                      IN_KW@7011..7013 "in"
+                      WHITESPACE@7013..7014 " "
+                      EXPR@7014..7015
+                        EXPR_VAR@7014..7015
+                          NAME@7014..7015
+                            IDENT@7014..7015 "a"
+                    WHITESPACE@7015..7016 " "
+                    IF_SPEC@7016..7040
+                      IF_KW@7016..7018 "if"
+                      WHITESPACE@7018..7019 " "
+                      EXPR@7019..7040
+                        EXPR_VAR@7019..7028
+                          NAME@7019..7028
+                            IDENT@7019..7028 "isContent"
+                        SUFFIX_APPLY@7028..7040
+                          ARGS_DESC@7028..7040
+                            L_PAREN@7028..7029 "("
+                            ARG@7029..7039
+                              EXPR@7029..7039
+                                EXPR_LITERAL@7029..7030
+                                  DOLLAR@7029..7030 "$"
+                                SUFFIX_INDEX@7030..7036
+                                  DOT@7030..7031 "."
+                                  NAME@7031..7036
+                                    IDENT@7031..7036 "prune"
+                                SUFFIX_APPLY@7036..7039
+                                  ARGS_DESC@7036..7039
+                                    L_PAREN@7036..7037 "("
+                                    ARG@7037..7038
+                                      EXPR@7037..7038
+                                        EXPR_VAR@7037..7038
+                                          NAME@7037..7038
+                                            IDENT@7037..7038 "x"
+                                    R_PAREN@7038..7039 ")"
+                            R_PAREN@7039..7040 ")"
+                    R_BRACK@7040..7041 "]"
+              WHITESPACE@7041..7046 "\n    "
+              ELSE_KW@7046..7050 "else"
+              WHITESPACE@7050..7051 " "
+              FALSE_EXPR@7051..7191
+                EXPR@7051..7191
+                  EXPR_IF_THEN_ELSE@7051..7191
+                    IF_KW@7051..7053 "if"
+                    WHITESPACE@7053..7054 " "
+                    EXPR@7054..7069
+                      EXPR_VAR@7054..7057
+                        NAME@7054..7057
+                          IDENT@7054..7057 "std"
+                      SUFFIX_INDEX@7057..7066
+                        DOT@7057..7058 "."
+                        NAME@7058..7066
+                          IDENT@7058..7066 "isObject"
+                      SUFFIX_APPLY@7066..7069
+                        ARGS_DESC@7066..7069
+                          L_PAREN@7066..7067 "("
+                          ARG@7067..7068
+                            EXPR@7067..7068
+                              EXPR_VAR@7067..7068
+                                NAME@7067..7068
+                                  IDENT@7067..7068 "a"
+                          R_PAREN@7068..7069 ")"
+                    WHITESPACE@7069..7070 " "
+                    THEN_KW@7070..7074 "then"
+                    WHITESPACE@7074..7075 " "
+                    TRUE_EXPR@7075..7178
+                      EXPR@7075..7178
+                        EXPR_OBJECT@7075..7178
+                          OBJ_BODY_COMP@7075..7178
+                            L_BRACE@7075..7076 "{"
+                            WHITESPACE@7076..7083 "\n      "
+                            MEMBER_FIELD_NORMAL@7083..7101
+                              FIELD_NAME_DYNAMIC@7083..7086
+                                L_BRACK@7083..7084 "["
+                                EXPR@7084..7085
+                                  EXPR_VAR@7084..7085
+                                    NAME@7084..7085
+                                      IDENT@7084..7085 "x"
+                                R_BRACK@7085..7086 "]"
+                              COLON@7086..7087 ":"
+                              WHITESPACE@7087..7088 " "
+                              EXPR@7088..7101
+                                EXPR_LITERAL@7088..7089
+                                  DOLLAR@7088..7089 "$"
+                                SUFFIX_INDEX@7089..7095
+                                  DOT@7089..7090 "."
+                                  NAME@7090..7095
+                                    IDENT@7090..7095 "prune"
+                                SUFFIX_APPLY@7095..7101
+                                  ARGS_DESC@7095..7101
+                                    L_PAREN@7095..7096 "("
+                                    ARG@7096..7100
+                                      EXPR@7096..7100
+                                        EXPR_VAR@7096..7097
+                                          NAME@7096..7097
+                                            IDENT@7096..7097 "a"
+                                        SUFFIX_INDEX_EXPR@7097..7100
+                                          L_BRACK@7097..7098 "["
+                                          EXPR@7098..7099
+                                            EXPR_VAR@7098..7099
+                                              NAME@7098..7099
+                                                IDENT@7098..7099 "x"
+                                          R_BRACK@7099..7100 "]"
+                                    R_PAREN@7100..7101 ")"
+                            WHITESPACE@7101..7108 "\n      "
+                            FOR_SPEC@7108..7136
+                              FOR_KW@7108..7111 "for"
+                              WHITESPACE@7111..7112 " "
+                              DESTRUCT_FULL@7112..7113
+                                NAME@7112..7113
+                                  IDENT@7112..7113 "x"
+                              WHITESPACE@7113..7114 " "
+                              IN_KW@7114..7116 "in"
+                              WHITESPACE@7116..7117 " "
+                              EXPR@7117..7136
+                                EXPR_VAR@7117..7120
+                                  NAME@7117..7120
+                                    IDENT@7117..7120 "std"
+                                SUFFIX_INDEX@7120..7133
+                                  DOT@7120..7121 "."
+                                  NAME@7121..7133
+                                    IDENT@7121..7133 "objectFields"
+                                SUFFIX_APPLY@7133..7136
+                                  ARGS_DESC@7133..7136
+                                    L_PAREN@7133..7134 "("
+                                    ARG@7134..7135
+                                      EXPR@7134..7135
+                                        EXPR_VAR@7134..7135
+                                          NAME@7134..7135
+                                            IDENT@7134..7135 "a"
+                                    R_PAREN@7135..7136 ")"
+                            WHITESPACE@7136..7143 "\n      "
+                            IF_SPEC@7143..7172
+                              IF_KW@7143..7145 "if"
+                              WHITESPACE@7145..7146 " "
+                              EXPR@7146..7172
+                                EXPR_VAR@7146..7155
+                                  NAME@7146..7155
+                                    IDENT@7146..7155 "isContent"
+                                SUFFIX_APPLY@7155..7172
+                                  ARGS_DESC@7155..7172
+                                    L_PAREN@7155..7156 "("
+                                    ARG@7156..7171
+                                      EXPR@7156..7171
+                                        EXPR_VAR@7156..7159
+                                          NAME@7156..7159
+                                            IDENT@7156..7159 "std"
+                                        SUFFIX_INDEX@7159..7165
+                                          DOT@7159..7160 "."
+                                          NAME@7160..7165
+                                            IDENT@7160..7165 "prune"
+                                        SUFFIX_APPLY@7165..7171
+                                          ARGS_DESC@7165..7171
+                                            L_PAREN@7165..7166 "("
+                                            ARG@7166..7170
+                                              EXPR@7166..7170
+                                                EXPR_VAR@7166..7167
+                                                  NAME@7166..7167
+                                                    IDENT@7166..7167 "a"
+                                                SUFFIX_INDEX_EXPR@7167..7170
+                                                  L_BRACK@7167..7168 "["
+                                                  EXPR@7168..7169
+                                                    EXPR_VAR@7168..7169
+                                                      NAME@7168..7169
+                                                        IDENT@7168..7169 "x"
+                                                  R_BRACK@7169..7170 "]"
+                                            R_PAREN@7170..7171 ")"
+                                    R_PAREN@7171..7172 ")"
+                            WHITESPACE@7172..7177 "\n    "
+                            R_BRACE@7177..7178 "}"
+                    WHITESPACE@7178..7179 " "
+                    ELSE_KW@7179..7183 "else"
+                    WHITESPACE@7183..7190 "\n      "
+                    FALSE_EXPR@7190..7191
+                      EXPR@7190..7191
+                        EXPR_VAR@7190..7191
+                          NAME@7190..7191
+                            IDENT@7190..7191 "a"
+        COMMA@7191..7192 ","
+        WHITESPACE@7192..7196 "\n\n  "
+        MEMBER_FIELD_METHOD@7196..7411
+          FIELD_NAME_FIXED@7196..7200
+            NAME@7196..7200
+              IDENT@7196..7200 "find"
+          PARAMS_DESC@7200..7212
+            L_PAREN@7200..7201 "("
+            PARAM@7201..7206
+              DESTRUCT_FULL@7201..7206
+                NAME@7201..7206
+                  IDENT@7201..7206 "value"
+            COMMA@7206..7207 ","
+            WHITESPACE@7207..7208 " "
+            PARAM@7208..7211
+              DESTRUCT_FULL@7208..7211
+                NAME@7208..7211
+                  IDENT@7208..7211 "arr"
+            R_PAREN@7211..7212 ")"
+          COLONCOLON@7212..7214 "::"
+          WHITESPACE@7214..7219 "\n    "
+          EXPR@7219..7411
+            EXPR_IF_THEN_ELSE@7219..7411
+              IF_KW@7219..7221 "if"
+              WHITESPACE@7221..7222 " "
+              EXPR@7222..7239
+                EXPR_UNARY@7222..7239
+                  NOT@7222..7223 "!"
+                  EXPR_VAR@7223..7226
+                    NAME@7223..7226
+                      IDENT@7223..7226 "std"
+                  SUFFIX_INDEX@7226..7234
+                    DOT@7226..7227 "."
+                    NAME@7227..7234
+                      IDENT@7227..7234 "isArray"
+                  SUFFIX_APPLY@7234..7239
+                    ARGS_DESC@7234..7239
+                      L_PAREN@7234..7235 "("
+                      ARG@7235..7238
+                        EXPR@7235..7238
+                          EXPR_VAR@7235..7238
+                            NAME@7235..7238
+                              IDENT@7235..7238 "arr"
+                      R_PAREN@7238..7239 ")"
+              WHITESPACE@7239..7240 " "
+              THEN_KW@7240..7244 "then"
+              WHITESPACE@7244..7251 "\n      "
+              TRUE_EXPR@7251..7321
+                EXPR@7251..7321
+                  EXPR_ERROR@7251..7321
+                    ERROR_KW@7251..7256 "error"
+                    WHITESPACE@7256..7257 " "
+                    EXPR@7257..7321
+                      EXPR_BINARY@7257..7321
+                        EXPR@7257..7305
+                          EXPR_STRING@7257..7305
+                            STRING_SINGLE@7257..7305 "'find second paramete ..."
+                        WHITESPACE@7305..7306 " "
+                        PLUS@7306..7307 "+"
+                        WHITESPACE@7307..7308 " "
+                        EXPR@7308..7321
+                          EXPR_VAR@7308..7311
+                            NAME@7308..7311
+                              IDENT@7308..7311 "std"
+                          SUFFIX_INDEX@7311..7316
+                            DOT@7311..7312 "."
+                            NAME@7312..7316
+                              IDENT@7312..7316 "type"
+                          SUFFIX_APPLY@7316..7321
+                            ARGS_DESC@7316..7321
+                              L_PAREN@7316..7317 "("
+                              ARG@7317..7320
+                                EXPR@7317..7320
+                                  EXPR_VAR@7317..7320
+                                    NAME@7317..7320
+                                      IDENT@7317..7320 "arr"
+                              R_PAREN@7320..7321 ")"
+              WHITESPACE@7321..7326 "\n    "
+              ELSE_KW@7326..7330 "else"
+              WHITESPACE@7330..7337 "\n      "
+              FALSE_EXPR@7337..7411
+                EXPR@7337..7411
+                  EXPR_VAR@7337..7340
+                    NAME@7337..7340
+                      IDENT@7337..7340 "std"
+                  SUFFIX_INDEX@7340..7347
+                    DOT@7340..7341 "."
+                    NAME@7341..7347
+                      IDENT@7341..7347 "filter"
+                  SUFFIX_APPLY@7347..7411
+                    ARGS_DESC@7347..7411
+                      L_PAREN@7347..7348 "("
+                      ARG@7348..7375
+                        EXPR@7348..7375
+                          EXPR_FUNCTION@7348..7375
+                            FUNCTION_KW@7348..7356 "function"
+                            PARAMS_DESC@7356..7359
+                              L_PAREN@7356..7357 "("
+                              PARAM@7357..7358
+                                DESTRUCT_FULL@7357..7358
+                                  NAME@7357..7358
+                                    IDENT@7357..7358 "i"
+                              R_PAREN@7358..7359 ")"
+                            WHITESPACE@7359..7360 " "
+                            EXPR@7360..7375
+                              EXPR_BINARY@7360..7375
+                                EXPR@7360..7363
+                                  EXPR_VAR@7360..7363
+                                    NAME@7360..7363
+                                      IDENT@7360..7363 "arr"
+                                SUFFIX_INDEX_EXPR@7363..7366
+                                  L_BRACK@7363..7364 "["
+                                  EXPR@7364..7365
+                                    EXPR_VAR@7364..7365
+                                      NAME@7364..7365
+                                        IDENT@7364..7365 "i"
+                                  R_BRACK@7365..7366 "]"
+                                WHITESPACE@7366..7367 " "
+                                EQ@7367..7369 "=="
+                                WHITESPACE@7369..7370 " "
+                                EXPR@7370..7375
+                                  EXPR_VAR@7370..7375
+                                    NAME@7370..7375
+                                      IDENT@7370..7375 "value"
+                      COMMA@7375..7376 ","
+                      WHITESPACE@7376..7377 " "
+                      ARG@7377..7410
+                        EXPR@7377..7410
+                          EXPR_VAR@7377..7380
+                            NAME@7377..7380
+                              IDENT@7377..7380 "std"
+                          SUFFIX_INDEX@7380..7386
+                            DOT@7380..7381 "."
+                            NAME@7381..7386
+                              IDENT@7381..7386 "range"
+                          SUFFIX_APPLY@7386..7410
+                            ARGS_DESC@7386..7410
+                              L_PAREN@7386..7387 "("
+                              ARG@7387..7388
+                                EXPR@7387..7388
+                                  EXPR_NUMBER@7387..7388
+                                    FLOAT@7387..7388 "0"
+                              COMMA@7388..7389 ","
+                              WHITESPACE@7389..7390 " "
+                              ARG@7390..7409
+                                EXPR@7390..7409
+                                  EXPR_BINARY@7390..7409
+                                    EXPR@7390..7393
+                                      EXPR_VAR@7390..7393
+                                        NAME@7390..7393
+                                          IDENT@7390..7393 "std"
+                                    SUFFIX_INDEX@7393..7400
+                                      DOT@7393..7394 "."
+                                      NAME@7394..7400
+                                        IDENT@7394..7400 "length"
+                                    SUFFIX_APPLY@7400..7405
+                                      ARGS_DESC@7400..7405
+                                        L_PAREN@7400..7401 "("
+                                        ARG@7401..7404
+                                          EXPR@7401..7404
+                                            EXPR_VAR@7401..7404
+                                              NAME@7401..7404
+                                                IDENT@7401..7404 "arr"
+                                        R_PAREN@7404..7405 ")"
+                                    WHITESPACE@7405..7406 " "
+                                    MINUS@7406..7407 "-"
+                                    WHITESPACE@7407..7408 " "
+                                    EXPR@7408..7409
+                                      EXPR_NUMBER@7408..7409
+                                        FLOAT@7408..7409 "1"
+                              R_PAREN@7409..7410 ")"
+                      R_PAREN@7410..7411 ")"
+        COMMA@7411..7412 ","
+        WHITESPACE@7412..7416 "\n\n  "
+        SINGLE_LINE_SLASH_COMMENT@7416..7426 "// Compat\n"
+        WHITESPACE@7426..7428 "  "
+        MEMBER_FIELD_METHOD@7428..7538
+          FIELD_NAME_FIXED@7428..7443
+            NAME@7428..7443
+              IDENT@7428..7443 "__compare_array"
+          PARAMS_DESC@7443..7455
+            L_PAREN@7443..7444 "("
+            PARAM@7444..7448
+              DESTRUCT_FULL@7444..7448
+                NAME@7444..7448
+                  IDENT@7444..7448 "arr1"
+            COMMA@7448..7449 ","
+            WHITESPACE@7449..7450 " "
+            PARAM@7450..7454
+              DESTRUCT_FULL@7450..7454
+                NAME@7450..7454
+                  IDENT@7450..7454 "arr2"
+            R_PAREN@7454..7455 ")"
+          COLONCOLON@7455..7457 "::"
+          WHITESPACE@7457..7462 "\n    "
+          EXPR@7462..7538
+            STMT_ASSERT@7462..7508
+              ASSERTION@7462..7507
+                ASSERT_KW@7462..7468 "assert"
+                WHITESPACE@7468..7469 " "
+                EXPR@7469..7507
+                  EXPR_BINARY@7469..7507
+                    EXPR@7469..7472
+                      EXPR_VAR@7469..7472
+                        NAME@7469..7472
+                          IDENT@7469..7472 "std"
+                    SUFFIX_INDEX@7472..7480
+                      DOT@7472..7473 "."
+                      NAME@7473..7480
+                        IDENT@7473..7480 "isArray"
+                    SUFFIX_APPLY@7480..7486
+                      ARGS_DESC@7480..7486
+                        L_PAREN@7480..7481 "("
+                        ARG@7481..7485
+                          EXPR@7481..7485
+                            EXPR_VAR@7481..7485
+                              NAME@7481..7485
+                                IDENT@7481..7485 "arr1"
+                        R_PAREN@7485..7486 ")"
+                    WHITESPACE@7486..7487 " "
+                    AND@7487..7489 "&&"
+                    WHITESPACE@7489..7490 " "
+                    EXPR@7490..7507
+                      EXPR_VAR@7490..7493
+                        NAME@7490..7493
+                          IDENT@7490..7493 "std"
+                      SUFFIX_INDEX@7493..7501
+                        DOT@7493..7494 "."
+                        NAME@7494..7501
+                          IDENT@7494..7501 "isArray"
+                      SUFFIX_APPLY@7501..7507
+                        ARGS_DESC@7501..7507
+                          L_PAREN@7501..7502 "("
+                          ARG@7502..7506
+                            EXPR@7502..7506
+                              EXPR_VAR@7502..7506
+                                NAME@7502..7506
+                                  IDENT@7502..7506 "arr2"
+                          R_PAREN@7506..7507 ")"
+              SEMI@7507..7508 ";"
+            WHITESPACE@7508..7513 "\n    "
+            EXPR_VAR@7513..7516
+              NAME@7513..7516
+                IDENT@7513..7516 "std"
+            SUFFIX_INDEX@7516..7526
+              DOT@7516..7517 "."
+              NAME@7517..7526
+                IDENT@7517..7526 "__compare"
+            SUFFIX_APPLY@7526..7538
+              ARGS_DESC@7526..7538
+                L_PAREN@7526..7527 "("
+                ARG@7527..7531
+                  EXPR@7527..7531
+                    EXPR_VAR@7527..7531
+                      NAME@7527..7531
+                        IDENT@7527..7531 "arr1"
+                COMMA@7531..7532 ","
+                WHITESPACE@7532..7533 " "
+                ARG@7533..7537
+                  EXPR@7533..7537
+                    EXPR_VAR@7533..7537
+                      NAME@7533..7537
+                        IDENT@7533..7537 "arr2"
+                R_PAREN@7537..7538 ")"
+        COMMA@7538..7539 ","
+        WHITESPACE@7539..7542 "\n  "
+        MEMBER_FIELD_METHOD@7542..7606
+          FIELD_NAME_FIXED@7542..7554
+            NAME@7542..7554
+              IDENT@7542..7554 "__array_less"
+          PARAMS_DESC@7554..7566
+            L_PAREN@7554..7555 "("
+            PARAM@7555..7559
+              DESTRUCT_FULL@7555..7559
+                NAME@7555..7559
+                  IDENT@7555..7559 "arr1"
+            COMMA@7559..7560 ","
+            WHITESPACE@7560..7561 " "
+            PARAM@7561..7565
+              DESTRUCT_FULL@7561..7565
+                NAME@7561..7565
+                  IDENT@7561..7565 "arr2"
+            R_PAREN@7565..7566 ")"
+          COLONCOLON@7566..7568 "::"
+          WHITESPACE@7568..7569 " "
+          EXPR@7569..7606
+            EXPR_BINARY@7569..7606
+              EXPR@7569..7572
+                EXPR_VAR@7569..7572
+                  NAME@7569..7572
+                    IDENT@7569..7572 "std"
+              SUFFIX_INDEX@7572..7588
+                DOT@7572..7573 "."
+                NAME@7573..7588
+                  IDENT@7573..7588 "__compare_array"
+              SUFFIX_APPLY@7588..7600
+                ARGS_DESC@7588..7600
+                  L_PAREN@7588..7589 "("
+                  ARG@7589..7593
+                    EXPR@7589..7593
+                      EXPR_VAR@7589..7593
+                        NAME@7589..7593
+                          IDENT@7589..7593 "arr1"
+                  COMMA@7593..7594 ","
+                  WHITESPACE@7594..7595 " "
+                  ARG@7595..7599
+                    EXPR@7595..7599
+                      EXPR_VAR@7595..7599
+                        NAME@7595..7599
+                          IDENT@7595..7599 "arr2"
+                  R_PAREN@7599..7600 ")"
+              WHITESPACE@7600..7601 " "
+              EQ@7601..7603 "=="
+              WHITESPACE@7603..7604 " "
+              EXPR@7604..7606
+                EXPR_UNARY@7604..7606
+                  MINUS@7604..7605 "-"
+                  EXPR_NUMBER@7605..7606
+                    FLOAT@7605..7606 "1"
+        COMMA@7606..7607 ","
+        WHITESPACE@7607..7610 "\n  "
+        MEMBER_FIELD_METHOD@7610..7676
+          FIELD_NAME_FIXED@7610..7625
+            NAME@7610..7625
+              IDENT@7610..7625 "__array_greater"
+          PARAMS_DESC@7625..7637
+            L_PAREN@7625..7626 "("
+            PARAM@7626..7630
+              DESTRUCT_FULL@7626..7630
+                NAME@7626..7630
+                  IDENT@7626..7630 "arr1"
+            COMMA@7630..7631 ","
+            WHITESPACE@7631..7632 " "
+            PARAM@7632..7636
+              DESTRUCT_FULL@7632..7636
+                NAME@7632..7636
+                  IDENT@7632..7636 "arr2"
+            R_PAREN@7636..7637 ")"
+          COLONCOLON@7637..7639 "::"
+          WHITESPACE@7639..7640 " "
+          EXPR@7640..7676
+            EXPR_BINARY@7640..7676
+              EXPR@7640..7643
+                EXPR_VAR@7640..7643
+                  NAME@7640..7643
+                    IDENT@7640..7643 "std"
+              SUFFIX_INDEX@7643..7659
+                DOT@7643..7644 "."
+                NAME@7644..7659
+                  IDENT@7644..7659 "__compare_array"
+              SUFFIX_APPLY@7659..7671
+                ARGS_DESC@7659..7671
+                  L_PAREN@7659..7660 "("
+                  ARG@7660..7664
+                    EXPR@7660..7664
+                      EXPR_VAR@7660..7664
+                        NAME@7660..7664
+                          IDENT@7660..7664 "arr1"
+                  COMMA@7664..7665 ","
+                  WHITESPACE@7665..7666 " "
+                  ARG@7666..7670
+                    EXPR@7666..7670
+                      EXPR_VAR@7666..7670
+                        NAME@7666..7670
+                          IDENT@7666..7670 "arr2"
+                  R_PAREN@7670..7671 ")"
+              WHITESPACE@7671..7672 " "
+              EQ@7672..7674 "=="
+              WHITESPACE@7674..7675 " "
+              EXPR@7675..7676
+                EXPR_NUMBER@7675..7676
+                  FLOAT@7675..7676 "1"
+        COMMA@7676..7677 ","
+        WHITESPACE@7677..7680 "\n  "
+        MEMBER_FIELD_METHOD@7680..7752
+          FIELD_NAME_FIXED@7680..7701
+            NAME@7680..7701
+              IDENT@7680..7701 "__array_less_or_equal"
+          PARAMS_DESC@7701..7713
+            L_PAREN@7701..7702 "("
+            PARAM@7702..7706
+              DESTRUCT_FULL@7702..7706
+                NAME@7702..7706
+                  IDENT@7702..7706 "arr1"
+            COMMA@7706..7707 ","
+            WHITESPACE@7707..7708 " "
+            PARAM@7708..7712
+              DESTRUCT_FULL@7708..7712
+                NAME@7708..7712
+                  IDENT@7708..7712 "arr2"
+            R_PAREN@7712..7713 ")"
+          COLONCOLON@7713..7715 "::"
+          WHITESPACE@7715..7716 " "
+          EXPR@7716..7752
+            EXPR_BINARY@7716..7752
+              EXPR@7716..7719
+                EXPR_VAR@7716..7719
+                  NAME@7716..7719
+                    IDENT@7716..7719 "std"
+              SUFFIX_INDEX@7719..7735
+                DOT@7719..7720 "."
+                NAME@7720..7735
+                  IDENT@7720..7735 "__compare_array"
+              SUFFIX_APPLY@7735..7747
+                ARGS_DESC@7735..7747
+                  L_PAREN@7735..7736 "("
+                  ARG@7736..7740
+                    EXPR@7736..7740
+                      EXPR_VAR@7736..7740
+                        NAME@7736..7740
+                          IDENT@7736..7740 "arr1"
+                  COMMA@7740..7741 ","
+                  WHITESPACE@7741..7742 " "
+                  ARG@7742..7746
+                    EXPR@7742..7746
+                      EXPR_VAR@7742..7746
+                        NAME@7742..7746
+                          IDENT@7742..7746 "arr2"
+                  R_PAREN@7746..7747 ")"
+              WHITESPACE@7747..7748 " "
+              LE@7748..7750 "<="
+              WHITESPACE@7750..7751 " "
+              EXPR@7751..7752
+                EXPR_NUMBER@7751..7752
+                  FLOAT@7751..7752 "0"
+        COMMA@7752..7753 ","
+        WHITESPACE@7753..7756 "\n  "
+        MEMBER_FIELD_METHOD@7756..7831
+          FIELD_NAME_FIXED@7756..7780
+            NAME@7756..7780
+              IDENT@7756..7780 "__array_greater_or_equal"
+          PARAMS_DESC@7780..7792
+            L_PAREN@7780..7781 "("
+            PARAM@7781..7785
+              DESTRUCT_FULL@7781..7785
+                NAME@7781..7785
+                  IDENT@7781..7785 "arr1"
+            COMMA@7785..7786 ","
+            WHITESPACE@7786..7787 " "
+            PARAM@7787..7791
+              DESTRUCT_FULL@7787..7791
+                NAME@7787..7791
+                  IDENT@7787..7791 "arr2"
+            R_PAREN@7791..7792 ")"
+          COLONCOLON@7792..7794 "::"
+          WHITESPACE@7794..7795 " "
+          EXPR@7795..7831
+            EXPR_BINARY@7795..7831
+              EXPR@7795..7798
+                EXPR_VAR@7795..7798
+                  NAME@7795..7798
+                    IDENT@7795..7798 "std"
+              SUFFIX_INDEX@7798..7814
+                DOT@7798..7799 "."
+                NAME@7799..7814
+                  IDENT@7799..7814 "__compare_array"
+              SUFFIX_APPLY@7814..7826
+                ARGS_DESC@7814..7826
+                  L_PAREN@7814..7815 "("
+                  ARG@7815..7819
+                    EXPR@7815..7819
+                      EXPR_VAR@7815..7819
+                        NAME@7815..7819
+                          IDENT@7815..7819 "arr1"
+                  COMMA@7819..7820 ","
+                  WHITESPACE@7820..7821 " "
+                  ARG@7821..7825
+                    EXPR@7821..7825
+                      EXPR_VAR@7821..7825
+                        NAME@7821..7825
+                          IDENT@7821..7825 "arr2"
+                  R_PAREN@7825..7826 ")"
+              WHITESPACE@7826..7827 " "
+              GE@7827..7829 ">="
+              WHITESPACE@7829..7830 " "
+              EXPR@7830..7831
+                EXPR_NUMBER@7830..7831
+                  FLOAT@7830..7831 "0"
+        COMMA@7831..7832 ","
+        WHITESPACE@7832..7833 "\n"
+        R_BRACE@7833..7834 "}"
+  WHITESPACE@7834..7835 "\n"
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__str_block_missing_indent.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__str_block_missing_indent.snap
@@ -0,0 +1,8 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "|||\n"
+---
+SOURCE_FILE@0..4
+  EXPR@0..4
+    EXPR_STRING@0..4
+      ERROR_STRING_BLOCK_MISSING_INDENT@0..4 "|||\n"
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__str_block_missing_indent_text.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__str_block_missing_indent_text.snap
@@ -0,0 +1,8 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "|||\nhello\n"
+---
+SOURCE_FILE@0..10
+  EXPR@0..10
+    EXPR_STRING@0..10
+      ERROR_STRING_BLOCK_MISSING_INDENT@0..10 "|||\nhello\n"
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__str_block_missing_newline.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__str_block_missing_newline.snap
@@ -0,0 +1,8 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "|||hello\n"
+---
+SOURCE_FILE@0..9
+  EXPR@0..9
+    EXPR_STRING@0..9
+      ERROR_STRING_BLOCK_MISSING_NEW_LINE@0..9 "|||hello\n"
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__str_block_missing_termination.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__str_block_missing_termination.snap
@@ -0,0 +1,8 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "|||\n\thello\n"
+---
+SOURCE_FILE@0..11
+  EXPR@0..11
+    EXPR_STRING@0..11
+      ERROR_STRING_BLOCK_UNEXPECTED_END@0..11 "|||\n\thello\n"
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__unexpected_destruct.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__unexpected_destruct.snap
@@ -0,0 +1,34 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "local * = 1;\na\n"
+---
+SOURCE_FILE@0..15
+  EXPR@0..14
+    STMT_LOCAL@0..12
+      LOCAL_KW@0..5 "local"
+      WHITESPACE@5..6 " "
+      BIND_DESTRUCT@6..11
+        ERROR_UNEXPECTED_TOKEN@6..7
+          MUL@6..7 "*"
+        WHITESPACE@7..8 " "
+        ASSIGN@8..9 "="
+        WHITESPACE@9..10 " "
+        EXPR@10..11
+          EXPR_NUMBER@10..11
+            FLOAT@10..11 "1"
+      SEMI@11..12 ";"
+    WHITESPACE@12..13 "\n"
+    EXPR_VAR@13..14
+      NAME@13..14
+        IDENT@13..14 "a"
+  WHITESPACE@14..15 "\n"
+===
+LocatedSyntaxError { error: Unexpected { expected: Named("destruction specifier"), found: MUL }, range: 6..7 }
+===
+  x syntax error
+   ,-[1:1]
+ 1 | local * = 1;
+   :       |
+   :       `-- expected destruction specifier, found MUL
+ 2 | a
+   `----
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__wrong_field_end.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__wrong_field_end.snap
@@ -0,0 +1,51 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "{\n\ta: 1;\n\tb: 2;\n}\n"
+---
+SOURCE_FILE@0..18
+  EXPR@0..17
+    EXPR_OBJECT@0..17
+      OBJ_BODY_MEMBER_LIST@0..17
+        L_BRACE@0..1 "{"
+        WHITESPACE@1..3 "\n\t"
+        MEMBER_FIELD_NORMAL@3..7
+          FIELD_NAME_FIXED@3..4
+            NAME@3..4
+              IDENT@3..4 "a"
+          COLON@4..5 ":"
+          WHITESPACE@5..6 " "
+          EXPR@6..7
+            EXPR_NUMBER@6..7
+              FLOAT@6..7 "1"
+        ERROR_UNEXPECTED_TOKEN@7..8
+          SEMI@7..8 ";"
+        WHITESPACE@8..10 "\n\t"
+        MEMBER_FIELD_NORMAL@10..14
+          FIELD_NAME_FIXED@10..11
+            NAME@10..11
+              IDENT@10..11 "b"
+          COLON@11..12 ":"
+          WHITESPACE@12..13 " "
+          EXPR@13..14
+            EXPR_NUMBER@13..14
+              FLOAT@13..14 "2"
+        ERROR_UNEXPECTED_TOKEN@14..15
+          SEMI@14..15 ";"
+        WHITESPACE@15..16 "\n"
+        R_BRACE@16..17 "}"
+  WHITESPACE@17..18 "\n"
+===
+LocatedSyntaxError { error: Unexpected { expected: Named("comma"), found: SEMI }, range: 7..8 }
+LocatedSyntaxError { error: Unexpected { expected: Named("comma"), found: SEMI }, range: 14..15 }
+===
+  x syntax error
+   ,-[1:1]
+ 1 | {
+ 2 |     a: 1;
+   :         |
+   :         `-- expected comma, found SEMI
+ 3 |     b: 2;
+   :         |
+   :         `-- expected comma, found SEMI
+ 4 | }
+   `----
addedcrates/jrsonnet-rowan-parser/src/string_block.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/string_block.rs
@@ -0,0 +1,216 @@
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum StringBlockError {
+	UnexpectedEnd,
+	MissingNewLine,
+	MissingTermination,
+	MissingIndent,
+}
+
+use std::ops::Range;
+
+use logos::Lexer;
+use StringBlockError::*;
+
+use crate::SyntaxKind;
+
+pub fn lex_str_block_test(lex: &mut Lexer<SyntaxKind>) {
+	let _ = lex_str_block(lex);
+}
+
+pub fn lex_str_block(lex: &mut Lexer<SyntaxKind>) -> Result<(), StringBlockError> {
+	struct Context<'a> {
+		source: &'a str,
+		index: usize,
+		offset: usize,
+	}
+
+	impl<'a> Context<'a> {
+		fn rest(&self) -> &'a str {
+			&self.source[self.index..]
+		}
+
+		fn next(&mut self) -> Option<char> {
+			if self.index == self.source.len() {
+				return None;
+			}
+
+			match self.rest().chars().next() {
+				None => None,
+				Some(c) => {
+					self.index += c.len_utf8();
+					Some(c)
+				}
+			}
+		}
+
+		fn peek(&self) -> Option<char> {
+			if self.index == self.source.len() {
+				return None;
+			}
+
+			self.rest().chars().next()
+		}
+
+		fn eat_while(&mut self, f: impl Fn(char) -> bool) -> usize {
+			if self.index == self.source.len() {
+				return 0;
+			}
+
+			let next_char = self.rest().char_indices().find(|(_, c)| !f(*c));
+
+			match next_char {
+				None => {
+					let diff = self.source.len() - self.index;
+					self.index = self.source.len();
+					diff
+				}
+				Some((idx, _)) => {
+					self.index += idx;
+					idx
+				}
+			}
+		}
+
+		fn skip(&mut self, len: usize) {
+			self.index = match self.index + len {
+				n if n > self.source.len() => self.source.len(),
+				n => n,
+			};
+		}
+
+		fn pos(&self) -> Range<usize> {
+			if self.index == self.source.len() {
+				self.offset + self.index..self.offset + self.index
+			} else {
+				// TODO: char size
+				self.offset + self.index..self.offset + self.index + 1
+			}
+		}
+	}
+
+	// Check that b has at least the same whitespace prefix as a and returns the
+	// amount of this whitespace, otherwise returns 0.  If a has no whitespace
+	// prefix than return 0.
+	fn check_whitespace(a: &str, b: &str) -> usize {
+		let a = a.as_bytes();
+		let b = b.as_bytes();
+
+		for i in 0..a.len() {
+			if a[i] != b' ' && a[i] != b'\t' {
+				// a has run out of whitespace and b matched up to this point. Return result.
+				return i;
+			}
+
+			if i >= b.len() {
+				// We ran off the edge of b while a still has whitespace. Return 0 as failure.
+				return 0;
+			}
+
+			if a[i] != b[i] {
+				// a has whitespace but b does not. Return 0 as failure.
+				return 0;
+			}
+		}
+
+		// We ran off the end of a and b kept up
+		a.len()
+	}
+
+	fn guess_token_end_and_bump<'a>(lex: &mut Lexer<'a, SyntaxKind>, ctx: &Context<'a>) {
+		let end_index = ctx
+			.rest()
+			.find("|||")
+			.map(|v| v + 3)
+			.unwrap_or_else(|| ctx.rest().len());
+		lex.bump(ctx.index + end_index);
+	}
+
+	debug_assert_eq!(lex.slice(), "|||");
+	let mut ctx = Context {
+		source: lex.remainder(),
+		index: 0,
+		offset: lex.span().end,
+	};
+
+	// Skip whitespaces
+	ctx.eat_while(|r| r == ' ' || r == '\t' || r == '\r');
+
+	// Skip \n
+	match ctx.next() {
+		Some('\n') => (),
+		None => {
+			guess_token_end_and_bump(lex, &ctx);
+			return Err(UnexpectedEnd);
+		}
+		// Text block requires new line after |||.
+		Some(_) => {
+			guess_token_end_and_bump(lex, &ctx);
+			return Err(MissingNewLine);
+		}
+	}
+
+	// Process leading blank lines before calculating string block indent
+	while let Some('\n') = ctx.peek() {
+		ctx.next();
+	}
+
+	let mut num_whitespace = check_whitespace(ctx.rest(), ctx.rest());
+	let str_block_indent = &ctx.rest()[..num_whitespace];
+
+	if num_whitespace == 0 {
+		// Text block's first line must start with whitespace
+		guess_token_end_and_bump(lex, &ctx);
+		return Err(MissingIndent);
+	}
+
+	loop {
+		debug_assert_ne!(num_whitespace, 0, "Unexpected value for num_whitespace");
+		ctx.skip(num_whitespace);
+
+		loop {
+			match ctx.next() {
+				None => {
+					guess_token_end_and_bump(lex, &ctx);
+					return Err(UnexpectedEnd);
+				}
+				Some('\n') => break,
+				Some(_) => (),
+			}
+		}
+
+		// Skip any blank lines
+		while let Some('\n') = ctx.peek() {
+			ctx.next();
+		}
+
+		// Look at the next line
+		num_whitespace = check_whitespace(str_block_indent, ctx.rest());
+		if num_whitespace == 0 {
+			// End of the text block
+			let mut term_indent = String::with_capacity(num_whitespace);
+			while let Some(' ' | '\t') = ctx.peek() {
+				term_indent.push(ctx.next().unwrap());
+			}
+
+			if !ctx.rest().starts_with("|||") {
+				// Text block not terminated with |||
+				let pos = ctx.pos();
+				if pos.is_empty() {
+					// eof
+					lex.bump(ctx.index);
+					return Err(UnexpectedEnd);
+				}
+
+				guess_token_end_and_bump(lex, &ctx);
+				return Err(MissingTermination);
+			}
+
+			// Skip '|||'
+			ctx.skip(3);
+			break;
+		}
+	}
+
+	lex.bump(ctx.index);
+	Ok(())
+}
addedcrates/jrsonnet-rowan-parser/src/tests.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/tests.rs
@@ -0,0 +1,253 @@
+#![cfg(test)]
+
+use miette::{
+	Diagnostic, GraphicalReportHandler, GraphicalTheme, LabeledSpan, ThemeCharacters, ThemeStyles,
+};
+use thiserror::Error;
+
+use crate::{parse, AstNode};
+
+#[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, errors) = parse(text);
+	write!(out, "{:#?}", node.syntax()).unwrap();
+	if !errors.is_empty() && !text.is_empty() {
+		writeln!(out, "===").unwrap();
+		for err in &errors {
+			writeln!(out, "{:?}", err).unwrap();
+		}
+		let mut code = text.to_string();
+
+		// Prettier errors at EOF position
+		if code.ends_with('\n') {
+			code.truncate(code.len() - 1);
+			code += " ";
+		}
+		code += " ";
+
+		let diag = MyDiagnostic {
+			code,
+			spans: errors.into_iter().map(|e| e.into()).collect(),
+		};
+
+		let handler = GraphicalReportHandler::new_themed(GraphicalTheme {
+			characters: ThemeCharacters::ascii(),
+			styles: ThemeStyles::none(),
+		});
+
+		writeln!(out, "===").unwrap();
+		handler
+			.render_report(&mut out, &diag)
+			.expect("fmt error?..");
+	}
+	out.split('\n')
+		.map(|s| s.trim_end().to_string())
+		.collect::<Vec<String>>()
+		.join("\n")
+		.trim_end()
+		.to_string()
+}
+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
+	"#
+
+
+	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;
+		}
+	"
+
+
+	plain_call => "
+		std.substr(a, 0, std.length(b)) == b
+	"
+
+	destruct => "
+		local [a, b, c] = arr;
+		local [a, ...] = arr_rest;
+		local [..., a] = rest_arr;
+		local [...] = rest_in_arr;
+		local [a, ...n] = arr_rest_n;
+		local [...n, a] = rest_arr_n;
+		local [...n] = rest_in_arr_n;
+
+		local {a, b, c} = obj;
+		local {a, b, c, ...} = obj_rest;
+		local {a, b, c, ...n} = obj_rest_n;
+
+		null
+	"
+
+	str_block_missing_indent => "
+		|||
+	"
+	str_block_missing_termination => "
+		|||
+			hello
+	"
+	str_block_missing_newline => "
+		|||hello
+	"
+	str_block_missing_indent_text => "
+		|||
+		hello
+	"
+
+	unexpected_destruct => "
+		local * = 1;
+		a
+	"
+	arr_compspec => r#"
+		[a for a in [1, 2, 3]]
+	"#
+	arr_compspec_comma => "
+		[a, for a in [1, 2, 3]]
+	"
+	arr_compspec_no_elems => "
+		[for a in [1, 2, 3]]
+	"
+	arr_compspec_incompatible_with_multiple_elems => r#"
+		[a for a in [1, 2, 3], b]
+	"#
+	arr_compspec_incompatible_with_multiple_elems_w => r#"
+		[a, b, for a in [1, 2, 3], c]
+	"#
+
+	obj_compspec => r#"
+		{a:1 for a in [1, 2, 3]}
+	"#
+	obj_compspec_comma => "
+		{a:1, for a in [1, 2, 3]}
+	"
+	obj_compspec_no_elems => "
+		{for a in [1, 2, 3]}
+	"
+	obj_compspec_incompatible_with_multiple_elems => r#"
+		{a:1 for a in [1, 2, 3], b:1}
+	"#
+	obj_compspec_incompatible_with_multiple_elems_w => r#"
+		{a:1, b:1, for a in [1, 2, 3], c:1}
+	"#
+
+	obj_compspec_incompatible_with_asserts => r#"
+		{assert 1, a: 1 for a in [1,2,3]}
+	"#
+
+	local_method => r#"
+		local
+			a(x) = x,
+			a = function(x) x,
+		; c
+	"#
+	obj_method => r#"
+		{
+			a(x): x,
+			a: function(x) x,
+		}
+	"#
+
+	continue_after_total_failure => r#"
+		local intr = $intrinsic(test);
+
+		local a = 1, b = 2, c = a + b;
+
+		[c]
+	"#
+);
+
+#[test]
+fn stdlib() {
+	let src = include_str!("../../jrsonnet-stdlib/src/std.jsonnet");
+	let result = process(src);
+	insta::assert_snapshot!("stdlib", result, src);
+}
+#[test]
+fn eval_simple() {
+	let src = "local a = 1, b = 2; a + local c = 1; c";
+	let (node, errors) = parse(src);
+
+	dbg!(node);
+}
addedcrates/jrsonnet-rowan-parser/src/token_set.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/token_set.rs
@@ -0,0 +1,106 @@
+use std::fmt;
+
+use crate::SyntaxKind;
+
+#[derive(Clone, Copy, Default)]
+pub struct SyntaxKindSet(u128);
+
+impl SyntaxKindSet {
+	#[allow(dead_code)]
+	pub const EMPTY: Self = Self(0);
+	pub const ALL: Self = Self(u128::MAX);
+
+	pub const fn new(kinds: &[SyntaxKind]) -> SyntaxKindSet {
+		let mut res = 0u128;
+		let mut i = 0;
+		while i < kinds.len() {
+			res |= mask(kinds[i]);
+			i += 1
+		}
+		SyntaxKindSet(res)
+	}
+
+	pub const fn union(self, other: SyntaxKindSet) -> SyntaxKindSet {
+		SyntaxKindSet(self.0 | other.0)
+	}
+	pub const fn with(self, kind: SyntaxKind) -> SyntaxKindSet {
+		SyntaxKindSet(self.0 | mask(kind))
+	}
+
+	pub fn contains(&self, kind: SyntaxKind) -> bool {
+		if !is_token(kind) {
+			return false;
+		}
+		self.0 & mask(kind) != 0
+	}
+}
+impl fmt::Display for SyntaxKindSet {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		let mut v = self.0;
+		let mut variants = <Vec<SyntaxKind>>::new();
+		for i in 0..128 {
+			if v & 1 == 1 {
+				variants.push(SyntaxKind::from_raw(i))
+			}
+			v >>= 1;
+			if v == 0 {
+				break;
+			}
+		}
+		for (i, v) in variants.iter().enumerate() {
+			if i == 0 {
+			} else if i == variants.len() - 1 {
+				write!(f, " or ")?;
+			} else {
+				write!(f, ", ")?;
+			}
+			write!(f, "{v:?}")?;
+		}
+		Ok(())
+	}
+}
+impl fmt::Debug for SyntaxKindSet {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		let mut v = self.0;
+		let mut variants = <Vec<SyntaxKind>>::new();
+		for i in 0..128 {
+			if v & 1 == 1 {
+				variants.push(SyntaxKind::from_raw(i))
+			}
+			v >>= 1;
+			if v == 0 {
+				break;
+			}
+		}
+		f.debug_tuple("SyntaxKindSet").field(&variants).finish()
+	}
+}
+
+const fn mask(kind: SyntaxKind) -> u128 {
+	if kind as u32 > 128 {
+		panic!("mask for not a token kind")
+	}
+	1u128 << (kind as u128)
+}
+
+#[macro_export]
+macro_rules! TS {
+	($($tt:tt)*) => {
+		$crate::SyntaxKindSet::new(&[
+			$(
+				$crate::T![$tt]
+			),*
+		])
+	};
+}
+
+#[test]
+fn sanity() {
+	assert!(
+		(SyntaxKind::LEXING_ERROR as u32) < 127,
+		"can't keep KindSet as bitset"
+	);
+}
+fn is_token(kind: SyntaxKind) -> bool {
+	(kind as u32) < 127
+}
modifiedcrates/jrsonnet-stdlib/src/strings.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/strings.rs
+++ b/crates/jrsonnet-stdlib/src/strings.rs
@@ -161,7 +161,11 @@
 	use jrsonnet_evaluator::runtime_error;
 	use Either2::*;
 	Ok(match v {
-		A(a) => Val::BigInt(Box::new((a as i64).into())),
+		A(a) => {
+			Val::BigInt(Box::new(a.to_string().parse().map_err(|e| {
+				runtime_error!("number is not convertible to bigint: {e}")
+			})?))
+		}
 		B(b) => Val::BigInt(Box::new(
 			b.as_str()
 				.parse()
modifiedflake.nixdiffbeforeafterboth
--- a/flake.nix
+++ b/flake.nix
@@ -131,6 +131,8 @@
             cargo-edit
             cargo-asm
             cargo-outdated
+            cargo-watch
+            cargo-insta
             lld
             hyperfine
             graphviz
addedxtask/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/xtask/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "xtask"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+anyhow = "1.0.57"
+indexmap = "1.9.0"
+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,399 @@
+use std::collections::{BTreeSet, HashMap};
+
+use proc_macro2::TokenStream;
+use quote::format_ident;
+use ungrammar::{Grammar, Rule};
+
+use super::{
+	util::{pluralize, to_lower_snake_case},
+	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 nodes: Vec<AstNodeSrc>,
+	pub enums: Vec<AstEnumSrc>,
+	pub token_enums: Vec<AstTokenEnumSrc>,
+}
+#[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 {
+	/// This field may not exist in code
+	Optional,
+	/// This field should exist in correctly parsed code
+	Required,
+	/// There may be multiple field values of this kind
+	Many,
+}
+
+#[derive(Debug, Clone)]
+pub struct AstEnumSrc {
+	pub doc: Vec<String>,
+	pub name: String,
+	pub traits: Vec<String>,
+	pub variants: Vec<String>,
+}
+
+#[derive(Debug, Clone)]
+pub struct AstTokenEnumSrc {
+	pub doc: Vec<String>,
+	pub name: String,
+	pub variants: Vec<String>,
+}
+
+impl Field {
+	pub fn is_many(&self) -> bool {
+		matches!(
+			self,
+			Field::Node {
+				cardinality: Cardinality::Many,
+				..
+			}
+		)
+	}
+
+	pub fn token_name(&self) -> Option<String> {
+		match self {
+			Field::Token(token) => Some(token.clone()),
+			_ => None,
+		}
+	}
+	pub fn token_kind(&self, kinds: &KindsSrc) -> Option<TokenStream> {
+		match self {
+			Field::Token(token) => Some(kinds.token(token).expect("token exists").reference()),
+			_ => None,
+		}
+	}
+	pub fn is_token_enum(&self, grammar: &AstSrc) -> bool {
+		match self {
+			Field::Node { ty, .. } => grammar.token_enums.iter().any(|e| &e.name == ty),
+			_ => false,
+		}
+	}
+
+	pub fn method_name(&self, kinds: &KindsSrc) -> proc_macro2::Ident {
+		match self {
+			Field::Token(name) => kinds.token(name).expect("token exists").method_name(),
+			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),
+		}
+	}
+}
+
+pub fn lower(kinds: &KindsSrc, grammar: &Grammar) -> AstSrc {
+	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 => match lower_token_enum(grammar, rule) {
+				Some(variants) => {
+					let tokens_enum_src = AstTokenEnumSrc {
+						doc: Vec::new(),
+						name,
+						variants,
+					};
+					res.token_enums.push(tokens_enum_src);
+				}
+				None => {
+					let mut fields = Vec::new();
+					lower_rule(&mut fields, grammar, None, rule, false);
+					let mut types = HashMap::new();
+					for field in fields.iter().filter(|f| f.token_name().is_none()) {
+						if let Some(old) = types.insert(field.ty(), field.method_name(kinds)) {
+							// panic!("{name}.{} has same type as {name}.{}, resolve conflict by wrapping one field: {}", old, field.method_name(kinds), field.ty());
+						}
+						// TODO: check for assignable field types, i.e you can have
+						// ```
+						// SomeEnum =
+						//     SomeItem
+						// |   SomeOtherItem
+						// ```
+						// And check above will fail to detect conflict in
+						// ```
+						// SomeStruct =
+						//     SomeEnum
+						//     SomeItem
+						// ```
+						// Despite generating getters, which will both return SomeEnum
+					}
+					res.nodes.push(AstNodeSrc {
+						doc: Vec::new(),
+						name,
+						traits: Vec::new(),
+						fields,
+					});
+				}
+			},
+		}
+	}
+
+	deduplicate_fields(&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_token_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::Token(it) => variants.push(grammar[*it].name.clone()),
+			_ => return None,
+		}
+	}
+	Some(variants)
+}
+
+fn lower_rule(
+	acc: &mut Vec<Field>,
+	grammar: &Grammar,
+	label: Option<&String>,
+	rule: &Rule,
+	in_optional: bool,
+) {
+	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: if in_optional {
+					Cardinality::Optional
+				} else {
+					Cardinality::Required
+				},
+			};
+			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, in_optional);
+		}
+		Rule::Seq(rules) | Rule::Alt(rules) => {
+			for rule in rules {
+				lower_rule(acc, grammar, label, rule, in_optional)
+			}
+		}
+		Rule::Opt(rule) => lower_rule(acc, grammar, label, rule, true),
+	}
+}
+
+// (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_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 {
+		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!("could not find struct {var} for enum {}::{var}", enm.name)
+						})
+				})
+		});
+
+		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();
+	}
+}
addedxtask/src/sourcegen/kinds.rsdiffbeforeafterboth
--- /dev/null
+++ b/xtask/src/sourcegen/kinds.rs
@@ -0,0 +1,279 @@
+#[derive(Debug)]
+pub struct KindsSrc {
+	/// Key - how this token appears in ungrammar
+	defined_tokens: IndexMap<String, TokenKind>,
+	defined_node_names: HashSet<String>,
+	pub nodes: Vec<String>,
+}
+
+#[derive(Debug, Clone)]
+pub enum TokenKind {
+	/// May exist in token tree, but never in source code
+	Meta { grammar_name: String, name: String },
+	/// Specific parsing/lexing errors may be emitted as this type of kind
+	Error {
+		grammar_name: String,
+		name: String,
+		/// Is this error returned by lexer directly, or from lex.rs
+		is_lexer_error: bool,
+		regex: Option<String>,
+		priority: Option<u32>,
+	},
+	/// Keyword - literal match of token
+	Keyword {
+		/// How this keyword appears in grammar/code, should be same as Kinds key
+		code: String,
+		name: String,
+	},
+	/// Literal - something defined by user, i.e strings, identifiers, smth
+	Literal {
+		/// How this keyword appears in grammar, should be same as Kinds key
+		grammar_name: String,
+		name: String,
+		/// Regex for Logos lexer
+		regex: String,
+		/// Path to custom lexer
+		lexer: Option<String>,
+	},
+}
+
+impl TokenKind {
+	pub fn grammar_name(&self) -> &str {
+		match self {
+			TokenKind::Keyword { code, .. } => code,
+			TokenKind::Literal { grammar_name, .. } => grammar_name,
+			TokenKind::Meta { grammar_name, .. } => grammar_name,
+			TokenKind::Error { grammar_name, .. } => grammar_name,
+		}
+	}
+	/// How this keyword should appear in kinds enum, screaming snake cased
+	pub fn name(&self) -> &str {
+		match self {
+			TokenKind::Keyword { name, .. } => name,
+			TokenKind::Literal { name, .. } => name,
+			TokenKind::Meta { name, .. } => name,
+			TokenKind::Error { name, .. } => name,
+		}
+	}
+	pub fn expand_kind(&self) -> TokenStream {
+		let name = format_ident!("{}", self.name());
+		let attr = match self {
+			TokenKind::Keyword { code, .. } => quote! {#[token(#code)]},
+			TokenKind::Literal { regex, lexer, .. } => {
+				let lexer = lexer
+					.as_deref()
+					.map(TokenStream::from_str)
+					.map(|r| r.expect("path is correct"));
+				quote! {#[regex(#regex, #lexer)]}
+			}
+			TokenKind::Error {
+				regex, priority, ..
+			} if regex.is_some() => {
+				let priority = priority.map(|p| quote! {, priority = #p});
+				quote! {#[regex(#regex #priority)]}
+			}
+			_ => quote! {},
+		};
+		quote! {
+			#attr
+			#name
+		}
+	}
+	pub fn expand_t_macros(&self) -> Option<TokenStream> {
+		match self {
+			TokenKind::Keyword { code, name } => {
+				let code = escape_token_macro(code);
+				let name = format_ident!("{name}");
+				Some(quote! {
+					[#code] => {$crate::SyntaxKind::#name}
+				})
+			}
+			// Meta items should not appear in T![_]
+			_ => None,
+		}
+	}
+
+	/// How this token should be referenced in code
+	/// Keywords are referenced with `T![_]` macro,
+	/// and literals are referenced directly by name
+	pub fn reference(&self) -> TokenStream {
+		match self {
+			TokenKind::Keyword { code, .. } => {
+				let code = escape_token_macro(code);
+				quote! {T![#code]}
+			}
+			_ => {
+				let name = self.name();
+				let ident = format_ident!("{name}");
+				quote! {#ident}
+			}
+		}
+	}
+
+	pub fn method_name(&self) -> Ident {
+		match self {
+			TokenKind::Keyword { name, .. } => {
+				format_ident!("{}_token", name.to_lowercase())
+			}
+			TokenKind::Literal { name, .. } => {
+				format_ident!("{}_lit", name.to_lowercase())
+			}
+			TokenKind::Meta { name, .. } => format_ident!("{}_meta", name.to_lowercase()),
+			TokenKind::Error { name, .. } => format_ident!("{}_error", name.to_lowercase()),
+		}
+	}
+}
+
+#[macro_export]
+macro_rules! define_kinds {
+	($into:ident = lit($name:literal) => $regex:literal $(, $lexer:literal)? $(; $($rest:tt)*)?) => {{
+		$into.define_token(TokenKind::Literal {
+			grammar_name: format!("LIT_{}!", $name),
+			name: $name.to_owned(),
+			regex: $regex.to_owned(),
+			lexer: None $(.or_else(|| Some($lexer.to_string())))?,
+		});
+		$(define_kinds!($into = $($rest)*))?
+	}};
+	($into:ident = error($name:literal$(, priority = $priority:literal)? $(, lexer = $lexer:literal)?) $(=> $regex:literal)? $(; $($rest:tt)*)?) => {{
+		{
+			let regex = None$(.or(Some($regex.to_owned())))?;
+			let priority = None$(.or(Some($priority)))?;
+			$into.define_token(TokenKind::Error {
+				grammar_name: format!("ERROR_{}!", $name),
+				name: format!("ERROR_{}", $name),
+				is_lexer_error: false $(|| $lexer)? || regex.is_some() || priority.is_some(),
+				regex,
+				priority,
+			});
+		}
+		$(define_kinds!($into = $($rest)*))?
+	}};
+	($into:ident = $tok:literal => $name:literal $(; $($rest:tt)*)?) => {{
+		$into.define_token(TokenKind::Keyword {
+			code: format!("{}", $tok),
+			name: $name.to_owned(),
+		});
+		$(define_kinds!($into = $($rest)*))?
+	}};
+	($into:ident =) => {{}}
+}
+use std::{collections::HashSet, str::FromStr};
+
+pub use define_kinds;
+use indexmap::IndexMap;
+use proc_macro2::{Ident, TokenStream};
+use quote::{format_ident, quote};
+
+use super::escape_token_macro;
+
+impl KindsSrc {
+	pub fn new() -> Self {
+		Self {
+			defined_tokens: IndexMap::new(),
+			defined_node_names: HashSet::new(),
+			nodes: Vec::new(),
+		}
+	}
+	pub fn define_token(&mut self, token: TokenKind) {
+		assert!(
+			self.defined_node_names.insert(token.name().to_owned()),
+			"node name already defined: {}",
+			token.name()
+		);
+		assert!(
+			self.defined_tokens
+				.insert(token.grammar_name().to_owned(), token.clone())
+				.is_none(),
+			"token already defined: {}",
+			token.grammar_name()
+		)
+	}
+	pub fn define_node(&mut self, node: &str) {
+		assert!(
+			self.defined_node_names.insert(node.to_owned()),
+			"node name already defined: {}",
+			node
+		);
+		self.nodes.push(node.to_string())
+	}
+	pub fn token(&self, tok: &str) -> Option<&TokenKind> {
+		self.defined_tokens.get(tok)
+	}
+	pub fn is_token(&self, tok: &str) -> bool {
+		self.defined_tokens.contains_key(tok)
+	}
+	pub fn tokens(&self) -> impl Iterator<Item = &TokenKind> {
+		self.defined_tokens.iter().map(|(_, v)| v)
+	}
+}
+
+pub fn jsonnet_kinds() -> KindsSrc {
+	let mut kinds = KindsSrc::new();
+	define_kinds![kinds =
+		"||" => "OR";
+		"??" => "NULL_COAELSE";
+		"&&" => "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";
+		// Literals
+		lit("FLOAT") => r"(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?";
+		error("FLOAT_JUNK_AFTER_POINT") => r"(?:0|[1-9][0-9]*)\.[^0-9]";
+		error("FLOAT_JUNK_AFTER_EXPONENT") => r"(?:0|[1-9][0-9]*)(?:\.[0-9]+)?[eE][^+\-0-9]";
+		error("FLOAT_JUNK_AFTER_EXPONENT_SIGN") => r"(?:0|[1-9][0-9]*)(?:\.[0-9]+)?[eE][+-][^0-9]";
+		lit("STRING_DOUBLE") => "\"(?s:[^\"\\\\]|\\\\.)*\"";
+		error("STRING_DOUBLE_UNTERMINATED") => "\"(?s:[^\"\\\\]|\\\\.)*";
+		lit("STRING_SINGLE") => "'(?s:[^'\\\\]|\\\\.)*'";
+		error("STRING_SINGLE_UNTERMINATED") => "'(?s:[^'\\\\]|\\\\.)*";
+		lit("STRING_DOUBLE_VERBATIM") => "@\"(?:[^\"]|\"\")*\"";
+		error("STRING_DOUBLE_VERBATIM_UNTERMINATED") => "@\"(?:[^\"]|\"\")*";
+		lit("STRING_SINGLE_VERBATIM") => "@'(?:[^']|'')*'";
+		error("STRING_SINGLE_VERBATIM_UNTERMINATED") => "@'(?:[^']|'')*";
+		error("STRING_VERBATIM_MISSING_QUOTES") => "@[^\"'\\s]\\S+";
+		lit("STRING_BLOCK") => r"\|\|\|", "crate::string_block::lex_str_block_test";
+		error("STRING_BLOCK_UNEXPECTED_END", lexer = true);
+		error("STRING_BLOCK_MISSING_NEW_LINE", lexer = true);
+		error("STRING_BLOCK_MISSING_TERMINATION", lexer = true);
+		error("STRING_BLOCK_MISSING_INDENT", lexer = true);
+		lit("IDENT") => r"[_a-zA-Z][_a-zA-Z0-9]*";
+		lit("WHITESPACE") => r"[ \t\n\r]+";
+		lit("SINGLE_LINE_SLASH_COMMENT") => r"//[^\r\n]*(\r\n|\n)?";
+		lit("SINGLE_LINE_HASH_COMMENT") => r"#[^\r\n]*(\r\n|\n)?";
+		lit("MULTI_LINE_COMMENT") => r"/\*([^*]|\*[^/])*\*/";
+		error("COMMENT_TOO_SHORT") => r"/\*/";
+		error("COMMENT_UNTERMINATED") =>  r"/\*([^*]|\*[^/])+";
+	];
+	kinds
+}
addedxtask/src/sourcegen/mod.rsdiffbeforeafterboth
--- /dev/null
+++ b/xtask/src/sourcegen/mod.rs
@@ -0,0 +1,541 @@
+use std::path::PathBuf;
+
+use anyhow::Result;
+use ast::{lower, AstSrc};
+use itertools::Itertools;
+use kinds::{KindsSrc, TokenKind};
+use proc_macro2::{Punct, Spacing, TokenStream};
+use quote::{format_ident, quote};
+use ungrammar::Grammar;
+use util::{ensure_file_contents, reformat, to_pascal_case, to_upper_snake_case};
+
+mod ast;
+mod kinds;
+mod util;
+
+enum SpecialName {
+	Literal,
+	Meta,
+	Error,
+}
+fn classify_special(name: &str) -> Option<(SpecialName, &str)> {
+	let name = name.strip_suffix('!')?;
+	Some(if let Some(name) = name.strip_prefix("LIT_") {
+		(SpecialName::Literal, name)
+	} else if let Some(name) = name.strip_prefix("META_") {
+		(SpecialName::Meta, name)
+	} else if let Some(name) = name.strip_prefix("ERROR_") {
+		(SpecialName::Error, name)
+	} else {
+		return None;
+	})
+}
+
+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 = kinds::jsonnet_kinds();
+	let ast = lower(&kinds, &grammar);
+
+	for token in grammar.tokens() {
+		let token = &grammar[token];
+		let token = &token.name.clone();
+		if !kinds.is_token(token) {
+			if let Some((special, name)) = classify_special(token) {
+				match special {
+					SpecialName::Literal => panic!("literal is not defined: {name}"),
+					SpecialName::Meta => {
+						eprintln!("implicit meta: {}", name);
+						kinds.define_token(TokenKind::Meta {
+							grammar_name: token.to_owned(),
+							name: format!("META_{}", name),
+						})
+					}
+					SpecialName::Error => {
+						eprintln!("implicit error: {}", name);
+						kinds.define_token(TokenKind::Error {
+							grammar_name: token.to_owned(),
+							name: format!("ERROR_{}", name),
+							regex: None,
+							priority: None,
+							is_lexer_error: true,
+						})
+					}
+				};
+				continue;
+			};
+			let name = to_upper_snake_case(token);
+			eprintln!("implicit kw: {}", token);
+			kinds.define_token(TokenKind::Keyword {
+				code: token.to_owned(),
+				name: format!("{name}_KW"),
+			});
+		}
+	}
+	for node in &ast.nodes {
+		let name = to_upper_snake_case(&node.name);
+		kinds.define_node(&name);
+	}
+	for enum_ in &ast.enums {
+		let name = to_upper_snake_case(&enum_.name);
+		kinds.define_node(&name);
+	}
+	for token_enum in &ast.token_enums {
+		let name = to_upper_snake_case(&token_enum.name);
+		kinds.define_node(&name);
+	}
+
+	let syntax_kinds = generate_syntax_kinds(&kinds, &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/nodes.rs",
+		)),
+		&nodes,
+	)?;
+	Ok(())
+}
+
+fn generate_syntax_kinds(kinds: &KindsSrc, grammar: &AstSrc) -> Result<String> {
+	let t_macros = kinds.tokens().filter_map(TokenKind::expand_t_macros);
+	let token_kinds = kinds.tokens().map(TokenKind::expand_kind);
+
+	let keywords = kinds
+		.tokens()
+		.filter(|k| matches!(k, TokenKind::Keyword { .. }))
+		.map(TokenKind::name)
+		.map(|n| format_ident!("{n}"));
+
+	let nodes = kinds
+		.nodes
+		.iter()
+		.map(|name| format_ident!("{}", name))
+		.collect::<Vec<_>>();
+
+	let enums = grammar
+		.enums
+		.iter()
+		.map(|e| format_ident!("{}", to_upper_snake_case(&e.name)))
+		.chain(
+			grammar
+				.token_enums
+				.iter()
+				.map(|e| format_ident!("{}", to_upper_snake_case(&e.name))),
+		);
+
+	let ast = quote! {
+		#![allow(bad_style, missing_docs, unreachable_pub, clippy::manual_non_exhaustive, clippy::match_like_matches_macro)]
+		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,
+			#(#token_kinds,)*
+			/// Also acts as __LAST_TOKEN
+			#[error]
+			LEXING_ERROR,
+			#(#nodes,)*
+			#[doc(hidden)]
+			__LAST,
+		}
+		use self::SyntaxKind::*;
+
+		impl SyntaxKind {
+			pub fn is_keyword(self) -> bool {
+				match self {
+					#(#keywords)|* => true,
+					_ => false,
+				}
+			}
+			pub fn is_enum(self) -> bool {
+				match self {
+					#(#enums)|* => true,
+					_ => false,
+				}
+			}
+
+			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 {#(#t_macros);*}
+		pub use T;
+	};
+
+	reformat(&ast.to_string())
+}
+
+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(kinds) {
+					quote! {
+						pub fn #method_name(&self) -> Option<#ty> {
+							support::token(&self.syntax, #token_kind)
+						}
+					}
+				} else if field.is_token_enum(grammar) {
+					quote! {
+						pub fn #method_name(&self) -> Option<#ty> {
+							support::token_child(&self.syntax)
+						}
+					}
+				} 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 (token_enum_defs, token_enum_boilerplate_impls): (Vec<_>, Vec<_>) = grammar
+		.token_enums
+		.iter()
+		.map(|en| {
+			let variants: Vec<_> = en
+				.variants
+				.iter()
+				.map(|token| {
+					format_ident!(
+						"{}",
+						to_pascal_case(kinds.token(token).expect("token exists").name())
+					)
+				})
+				.collect();
+			let name = format_ident!("{}", en.name);
+			let kind_name = format_ident!("{}Kind", en.name);
+			let kinds: Vec<_> = variants
+				.iter()
+				.map(|name| format_ident!("{}", to_upper_snake_case(&name.to_string())))
+				.collect();
+
+			let ast_node = quote! {
+				impl AstToken for #name {
+					fn can_cast(kind: SyntaxKind) -> bool {
+						#kind_name::can_cast(kind)
+					}
+					fn cast(syntax: SyntaxToken) -> Option<Self> {
+						let kind = #kind_name::cast(syntax.kind())?;
+						Some(#name { syntax, kind })
+					}
+					fn syntax(&self) -> &SyntaxToken {
+						&self.syntax
+					}
+				}
+
+				impl #kind_name {
+					fn can_cast(kind: SyntaxKind) -> bool {
+						match kind {
+							#(#kinds)|* => true,
+							_ => false,
+						}
+					}
+					pub fn cast(kind: SyntaxKind) -> Option<Self> {
+						let res = match kind {
+							#(#kinds => Self::#variants,)*
+							_ => return None,
+						};
+						Some(res)
+					}
+				}
+			};
+
+			(
+				quote! {
+					#[pretty_doc_comment_placeholder_workaround]
+					#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+					pub struct #name { syntax: SyntaxToken, kind: #kind_name }
+
+					#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+					pub enum #kind_name {
+						#(#variants,)*
+					}
+				},
+				quote! {
+					#ast_node
+
+					impl #name {
+						pub fn kind(&self) -> #kind_name {
+							self.kind
+						}
+					}
+
+					impl std::fmt::Display for #name {
+						fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+							std::fmt::Display::fmt(self.syntax(), f)
+						}
+					}
+				},
+			)
+		})
+		.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 ast = quote! {
+		#![allow(non_snake_case, clippy::match_like_matches_macro)]
+
+		use crate::{
+			SyntaxNode, SyntaxToken, SyntaxKind::{self, *},
+			ast::{AstNode, AstToken, AstChildren, support},
+			T,
+		};
+
+		#(#node_defs)*
+		#(#enum_defs)*
+		#(#token_enum_defs)*
+		#(#any_node_defs)*
+		#(#node_boilerplate_impls)*
+		#(#enum_boilerplate_impls)*
+		#(#token_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();
+	}
+}
+
+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,87 @@
+use std::{fs, path::Path};
+
+use anyhow::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", file.display());
+	if let Some(parent) = file.parent() {
+		let _ = fs::create_dir_all(parent);
+	}
+	fs::write(file, contents).unwrap();
+	Ok(())
+}
+
+// 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").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
+	))
+}