difftreelog
Merge pull request #132 from CertainLach/lsp
in: master
Introduce experimental rowan parser and pretty-printer
74 files changed
.cargo/config.tomldiffbeforeafterboth--- /dev/null
+++ b/.cargo/config.toml
@@ -0,0 +1,2 @@
+[alias]
+xtask = "run --package xtask --bin xtask --"
Cargo.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"
Cargo.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" }
cmds/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"
cmds/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)
+ }
+}
cmds/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
+}
cmds/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);
+ }
+}
cmds/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: '',
+ },
+}
cmds/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: '',
+ },
+ }"
+ )))
+}
cmds/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"
cmds/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()
+}
cmds/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" }
cmds/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>,
crates/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
crates/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"
crates/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!'
crates/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)
+ }
+}
crates/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);
+ }
+ }
+}
crates/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;
crates/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)
+ }
+}
crates/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;
crates/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>;
crates/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()
+}
crates/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()
+ }
+}
crates/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(),
+ }),
+ )
+ }
+}
crates/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())
+ }
+}
crates/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),
+ }
+ }
+}
crates/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"
crates/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"
crates/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
+ `----
crates/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
+ `----
crates/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
+ `----
crates/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"
+
crates/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
+ `----
+
crates/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
+ `----
crates/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"
crates/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
+ `----
crates/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"
crates/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
+ `----
crates/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
+ `----
crates/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
+ `----
crates/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"
crates/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
+ `----
crates/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
+ `----
crates/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
+ `----
crates/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
+ `----
crates/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
+ `----
crates/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
+ `----
crates/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"
crates/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"
crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__obj_compspec_incompatible_with_asserts.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__obj_compspec_incompatible_with_asserts.snap
@@ -0,0 +1,64 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "{assert 1, a: 1 for a in [1,2,3]}\n"
+---
+SOURCE_FILE@0..34
+ EXPR@0..33
+ EXPR_OBJECT@0..33
+ OBJ_BODY_COMP@0..33
+ L_BRACE@0..1 "{"
+ ERROR_CUSTOM@1..9
+ MEMBER_ASSERT_STMT@1..9
+ ASSERTION@1..9
+ ASSERT_KW@1..7 "assert"
+ WHITESPACE@7..8 " "
+ EXPR@8..9
+ EXPR_NUMBER@8..9
+ FLOAT@8..9 "1"
+ COMMA@9..10 ","
+ WHITESPACE@10..11 " "
+ MEMBER_FIELD_NORMAL@11..15
+ FIELD_NAME_FIXED@11..12
+ NAME@11..12
+ IDENT@11..12 "a"
+ COLON@12..13 ":"
+ WHITESPACE@13..14 " "
+ EXPR@14..15
+ EXPR_NUMBER@14..15
+ FLOAT@14..15 "1"
+ WHITESPACE@15..16 " "
+ FOR_SPEC@16..32
+ FOR_KW@16..19 "for"
+ WHITESPACE@19..20 " "
+ DESTRUCT_FULL@20..21
+ NAME@20..21
+ IDENT@20..21 "a"
+ WHITESPACE@21..22 " "
+ IN_KW@22..24 "in"
+ WHITESPACE@24..25 " "
+ EXPR@25..32
+ EXPR_ARRAY@25..32
+ L_BRACK@25..26 "["
+ EXPR@26..27
+ EXPR_NUMBER@26..27
+ FLOAT@26..27 "1"
+ COMMA@27..28 ","
+ EXPR@28..29
+ EXPR_NUMBER@28..29
+ FLOAT@28..29 "2"
+ COMMA@29..30 ","
+ EXPR@30..31
+ EXPR_NUMBER@30..31
+ FLOAT@30..31 "3"
+ R_BRACK@31..32 "]"
+ R_BRACE@32..33 "}"
+ WHITESPACE@33..34 "\n"
+===
+LocatedSyntaxError { error: Custom { error: "asserts can't be used in object comprehensions" }, range: 1..9 }
+===
+ x syntax error
+ ,----
+ 1 | {assert 1, a: 1 for a in [1,2,3]}
+ : ^^^^|^^^
+ : `-- asserts can't be used in object comprehensions
+ `----
crates/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
+ `----
crates/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
+ `----
crates/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
+ `----
crates/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"
crates/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"
crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__stdlib.snapdiffbeforeafterbothno changes
crates/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"
crates/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"
crates/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"
crates/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"
crates/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
+ `----
crates/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 | }
+ `----
crates/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(())
+}
crates/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);
+}
crates/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
+}
crates/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()
flake.nixdiffbeforeafterboth--- a/flake.nix
+++ b/flake.nix
@@ -131,6 +131,8 @@
cargo-edit
cargo-asm
cargo-outdated
+ cargo-watch
+ cargo-insta
lld
hyperfine
graphviz
xtask/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"
xtask/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()
+}
xtask/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();
+ }
+}
xtask/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
+}
xtask/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)* }
+ }
+}
xtask/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
+ ))
+}