git.delta.rocks / jrsonnet / refs/commits / 5382799864e9

difftreelog

feat lsp prototype

Yaroslav Bolyukin2022-03-29parent: #609e5ef.patch.diff
in: master

29 files changed

deletedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ /dev/null
@@ -1,984 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 3
-
-[[package]]
-name = "ahash"
-version = "0.8.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
-dependencies = [
- "cfg-if",
- "once_cell",
- "version_check",
-]
-
-[[package]]
-name = "annotate-snippets"
-version = "0.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3b9d411ecbaf79885c6df4d75fff75858d5995ff25385657a28af47e82f9c36"
-dependencies = [
- "unicode-width",
- "yansi-term",
-]
-
-[[package]]
-name = "anstream"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
-dependencies = [
- "anstyle",
- "anstyle-parse",
- "anstyle-query",
- "anstyle-wincon",
- "colorchoice",
- "is-terminal",
- "utf8parse",
-]
-
-[[package]]
-name = "anstyle"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd"
-
-[[package]]
-name = "anstyle-parse"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333"
-dependencies = [
- "utf8parse",
-]
-
-[[package]]
-name = "anstyle-query"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
-dependencies = [
- "windows-sys",
-]
-
-[[package]]
-name = "anstyle-wincon"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
-dependencies = [
- "anstyle",
- "windows-sys",
-]
-
-[[package]]
-name = "anyhow"
-version = "1.0.72"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
-
-[[package]]
-name = "async-trait"
-version = "0.1.71"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.28",
-]
-
-[[package]]
-name = "autocfg"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
-
-[[package]]
-name = "base64"
-version = "0.21.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
-
-[[package]]
-name = "bincode"
-version = "1.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "bitflags"
-version = "1.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
-
-[[package]]
-name = "bitflags"
-version = "2.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
-
-[[package]]
-name = "block-buffer"
-version = "0.10.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
-dependencies = [
- "generic-array",
-]
-
-[[package]]
-name = "cc"
-version = "1.0.79"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
-
-[[package]]
-name = "cfg-if"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
-
-[[package]]
-name = "clap"
-version = "4.3.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3eab9e8ceb9afdade1ab3f0fd8dbce5b1b2f468ad653baf10e771781b2b67b73"
-dependencies = [
- "clap_builder",
- "clap_derive",
- "once_cell",
-]
-
-[[package]]
-name = "clap_builder"
-version = "4.3.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f2763db829349bf00cfc06251268865ed4363b93a943174f638daf3ecdba2cd"
-dependencies = [
- "anstream",
- "anstyle",
- "clap_lex",
- "strsim",
-]
-
-[[package]]
-name = "clap_complete"
-version = "4.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fc443334c81a804575546c5a8a79b4913b50e28d69232903604cada1de817ce"
-dependencies = [
- "clap",
-]
-
-[[package]]
-name = "clap_derive"
-version = "4.3.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050"
-dependencies = [
- "heck",
- "proc-macro2",
- "quote",
- "syn 2.0.28",
-]
-
-[[package]]
-name = "clap_lex"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
-
-[[package]]
-name = "colorchoice"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
-
-[[package]]
-name = "cpufeatures"
-version = "0.2.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "crypto-common"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
-dependencies = [
- "generic-array",
- "typenum",
-]
-
-[[package]]
-name = "derivative"
-version = "2.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
-[[package]]
-name = "digest"
-version = "0.10.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
-dependencies = [
- "block-buffer",
- "crypto-common",
-]
-
-[[package]]
-name = "errno"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
-dependencies = [
- "errno-dragonfly",
- "libc",
- "windows-sys",
-]
-
-[[package]]
-name = "errno-dragonfly"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
-dependencies = [
- "cc",
- "libc",
-]
-
-[[package]]
-name = "generic-array"
-version = "0.14.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
-dependencies = [
- "typenum",
- "version_check",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.12.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
-
-[[package]]
-name = "hashbrown"
-version = "0.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
-dependencies = [
- "ahash",
-]
-
-[[package]]
-name = "heck"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
-
-[[package]]
-name = "hermit-abi"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
-
-[[package]]
-name = "indexmap"
-version = "1.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
-dependencies = [
- "autocfg",
- "hashbrown 0.12.3",
-]
-
-[[package]]
-name = "is-terminal"
-version = "0.4.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
-dependencies = [
- "hermit-abi",
- "rustix",
- "windows-sys",
-]
-
-[[package]]
-name = "itoa"
-version = "1.0.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
-
-[[package]]
-name = "jrsonnet"
-version = "0.5.0-pre95"
-dependencies = [
- "clap",
- "clap_complete",
- "jrsonnet-cli",
- "jrsonnet-evaluator",
- "jrsonnet-gcmodule",
- "jrsonnet-parser",
- "mimallocator",
- "serde",
- "serde_json",
- "thiserror",
-]
-
-[[package]]
-name = "jrsonnet-cli"
-version = "0.5.0-pre95"
-dependencies = [
- "clap",
- "jrsonnet-evaluator",
- "jrsonnet-gcmodule",
- "jrsonnet-parser",
- "jrsonnet-stdlib",
-]
-
-[[package]]
-name = "jrsonnet-evaluator"
-version = "0.5.0-pre95"
-dependencies = [
- "annotate-snippets",
- "anyhow",
- "async-trait",
- "bincode",
- "derivative",
- "hashbrown 0.13.2",
- "jrsonnet-gcmodule",
- "jrsonnet-interner",
- "jrsonnet-macros",
- "jrsonnet-parser",
- "jrsonnet-types",
- "num-bigint",
- "pathdiff",
- "rustc-hash",
- "serde",
- "static_assertions",
- "strsim",
- "thiserror",
-]
-
-[[package]]
-name = "jrsonnet-gcmodule"
-version = "0.3.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c11fb98940a7f8b419619e98ccbf2e094671a5fdd0e277f05acd373071186d57"
-dependencies = [
- "jrsonnet-gcmodule-derive",
- "parking_lot",
-]
-
-[[package]]
-name = "jrsonnet-gcmodule-derive"
-version = "0.3.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6bee774b7ba86fc86ee84482cd6732aa860ae3559f9827c65efd75c51e66ac76"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
-[[package]]
-name = "jrsonnet-interner"
-version = "0.5.0-pre95"
-dependencies = [
- "hashbrown 0.13.2",
- "jrsonnet-gcmodule",
- "rustc-hash",
- "serde",
- "structdump",
-]
-
-[[package]]
-name = "jrsonnet-macros"
-version = "0.5.0-pre95"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
-[[package]]
-name = "jrsonnet-parser"
-version = "0.5.0-pre95"
-dependencies = [
- "jrsonnet-gcmodule",
- "jrsonnet-interner",
- "peg",
- "serde",
- "static_assertions",
- "structdump",
-]
-
-[[package]]
-name = "jrsonnet-stdlib"
-version = "0.5.0-pre95"
-dependencies = [
- "base64",
- "bincode",
- "jrsonnet-evaluator",
- "jrsonnet-gcmodule",
- "jrsonnet-macros",
- "jrsonnet-parser",
- "md5",
- "num-bigint",
- "serde",
- "serde_json",
- "serde_yaml_with_quirks",
- "sha1",
- "sha2",
- "sha3",
- "structdump",
-]
-
-[[package]]
-name = "jrsonnet-types"
-version = "0.5.0-pre95"
-dependencies = [
- "jrsonnet-gcmodule",
- "peg",
-]
-
-[[package]]
-name = "keccak"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940"
-dependencies = [
- "cpufeatures",
-]
-
-[[package]]
-name = "libc"
-version = "0.2.147"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
-
-[[package]]
-name = "libjsonnet"
-version = "0.5.0-pre95"
-dependencies = [
- "jrsonnet-evaluator",
- "jrsonnet-gcmodule",
- "jrsonnet-parser",
- "jrsonnet-stdlib",
-]
-
-[[package]]
-name = "linked-hash-map"
-version = "0.5.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
-
-[[package]]
-name = "linux-raw-sys"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0"
-
-[[package]]
-name = "lock_api"
-version = "0.4.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
-dependencies = [
- "autocfg",
- "scopeguard",
-]
-
-[[package]]
-name = "md5"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
-
-[[package]]
-name = "mimalloc-sys"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4aa3cefb626f6ae3d0b2f71c5378c89d2b1d4d7bc246b0ca9a7ee61a4daad291"
-dependencies = [
- "cc",
- "libc",
-]
-
-[[package]]
-name = "mimallocator"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d44fe4ebf6b538fcf39d9975c2b90bb3232d1ba8e8bffeacd004f27b20c577a"
-dependencies = [
- "mimalloc-sys",
-]
-
-[[package]]
-name = "num-bigint"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
-dependencies = [
- "autocfg",
- "num-integer",
- "num-traits",
- "serde",
-]
-
-[[package]]
-name = "num-integer"
-version = "0.1.45"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
-dependencies = [
- "autocfg",
- "num-traits",
-]
-
-[[package]]
-name = "num-traits"
-version = "0.2.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
-dependencies = [
- "autocfg",
-]
-
-[[package]]
-name = "once_cell"
-version = "1.18.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
-
-[[package]]
-name = "parking_lot"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
-dependencies = [
- "lock_api",
- "parking_lot_core",
-]
-
-[[package]]
-name = "parking_lot_core"
-version = "0.9.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
-dependencies = [
- "cfg-if",
- "libc",
- "redox_syscall",
- "smallvec",
- "windows-targets",
-]
-
-[[package]]
-name = "pathdiff"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
-
-[[package]]
-name = "peg"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a07f2cafdc3babeebc087e499118343442b742cc7c31b4d054682cc598508554"
-dependencies = [
- "peg-macros",
- "peg-runtime",
-]
-
-[[package]]
-name = "peg-macros"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a90084dc05cf0428428e3d12399f39faad19b0909f64fb9170c9fdd6d9cd49b"
-dependencies = [
- "peg-runtime",
- "proc-macro2",
- "quote",
-]
-
-[[package]]
-name = "peg-runtime"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fa00462b37ead6d11a82c9d568b26682d78e0477dc02d1966c013af80969739"
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.65"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92de25114670a878b1261c79c9f8f729fb97e95bac93f6312f583c60dd6a1dfe"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5907a1b7c277254a8b15170f6e7c97cfa60ee7872a3217663bb81151e48184bb"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "redox_syscall"
-version = "0.3.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
-dependencies = [
- "bitflags 1.3.2",
-]
-
-[[package]]
-name = "rustc-hash"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
-
-[[package]]
-name = "rustix"
-version = "0.38.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5"
-dependencies = [
- "bitflags 2.3.3",
- "errno",
- "libc",
- "linux-raw-sys",
- "windows-sys",
-]
-
-[[package]]
-name = "ryu"
-version = "1.0.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
-
-[[package]]
-name = "scopeguard"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
-
-[[package]]
-name = "serde"
-version = "1.0.171"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9"
-dependencies = [
- "serde_derive",
-]
-
-[[package]]
-name = "serde_derive"
-version = "1.0.171"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.28",
-]
-
-[[package]]
-name = "serde_json"
-version = "1.0.104"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c"
-dependencies = [
- "itoa",
- "ryu",
- "serde",
-]
-
-[[package]]
-name = "serde_yaml_with_quirks"
-version = "0.8.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47c5983eba86eae2d0058c35fb1065ccffb23af7f8965871069269088098321a"
-dependencies = [
- "indexmap",
- "ryu",
- "serde",
- "yaml-rust",
-]
-
-[[package]]
-name = "sha1"
-version = "0.10.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
-dependencies = [
- "cfg-if",
- "cpufeatures",
- "digest",
-]
-
-[[package]]
-name = "sha2"
-version = "0.10.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8"
-dependencies = [
- "cfg-if",
- "cpufeatures",
- "digest",
-]
-
-[[package]]
-name = "sha3"
-version = "0.10.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
-dependencies = [
- "digest",
- "keccak",
-]
-
-[[package]]
-name = "smallvec"
-version = "1.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
-
-[[package]]
-name = "static_assertions"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
-
-[[package]]
-name = "strsim"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
-
-[[package]]
-name = "structdump"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0570327507bf281d8a6e6b0d4c082b12cb6bcee27efce755aa5efacd44076c1"
-dependencies = [
- "proc-macro2",
- "quote",
- "structdump-derive",
-]
-
-[[package]]
-name = "structdump-derive"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29cc0b59cfa11f1bceda09a9a7e37e6a6c3138575fd24ade8aa9af6d09aedf28"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
-[[package]]
-name = "syn"
-version = "1.0.109"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "syn"
-version = "2.0.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "tests"
-version = "0.1.0"
-dependencies = [
- "jrsonnet-evaluator",
- "jrsonnet-gcmodule",
- "jrsonnet-stdlib",
- "serde",
-]
-
-[[package]]
-name = "thiserror"
-version = "1.0.43"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42"
-dependencies = [
- "thiserror-impl",
-]
-
-[[package]]
-name = "thiserror-impl"
-version = "1.0.43"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.28",
-]
-
-[[package]]
-name = "typenum"
-version = "1.16.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
-
-[[package]]
-name = "unicode-ident"
-version = "1.0.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
-
-[[package]]
-name = "unicode-width"
-version = "0.1.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
-
-[[package]]
-name = "utf8parse"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
-
-[[package]]
-name = "version_check"
-version = "0.9.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
-
-[[package]]
-name = "winapi"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
-dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
-]
-
-[[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-
-[[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
-
-[[package]]
-name = "windows-sys"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
-dependencies = [
- "windows-targets",
-]
-
-[[package]]
-name = "windows-targets"
-version = "0.48.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
-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",
-]
-
-[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
-
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
-
-[[package]]
-name = "windows_i686_gnu"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
-
-[[package]]
-name = "windows_i686_msvc"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
-
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
-
-[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
-
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
-
-[[package]]
-name = "yaml-rust"
-version = "0.4.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
-dependencies = [
- "linked-hash-map",
-]
-
-[[package]]
-name = "yansi-term"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1"
-dependencies = [
- "winapi",
-]
modifiedCargo.tomldiffbeforeafterboth
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,7 @@
 [workspace]
 package.version = "0.5.0-pre95"
 package.repository = "https://github.com/CertainLach/jrsonnet"
-members = ["crates/*", "bindings/jsonnet", "cmds/jrsonnet", "tests"]
+members = ["crates/*", "bindings/jsonnet", "cmds/*", "tests"]
 default-members = ["cmds/jrsonnet"]
 resolver = "2"
 
addedcrates/jrsonnet-rowan-parser/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "jrsonnet-rowan-parser"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+anyhow = "1.0.51"
+backtrace = "0.3.63"
+drop_bomb = "0.1.5"
+indoc = "1.0.3"
+logos = "0.12.0"
+miette = { version = "4.2.1", features = ["fancy"] }
+rowan = "0.15.0"
+text-size = "1.1.0"
+thiserror = "1.0.30"
+
+[dev-dependencies]
+backtrace = "0.3.63"
+indoc = "1.0.3"
+insta = "1.10.0"
addedcrates/jrsonnet-rowan-parser/src/binary.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/binary.rs
@@ -0,0 +1,47 @@
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum BinaryOperator {
+	Mul,
+	Div,
+	Mod,
+	Plus,
+	Minus,
+	ShiftLeft,
+	ShiftRight,
+	LessThan,
+	GreaterThan,
+	LessThanOrEqual,
+	GreaterThanOrEqual,
+	Equal,
+	NotEqual,
+	BitAnd,
+	BitXor,
+	BitOr,
+	And,
+	Or,
+	In,
+	ObjectApply,
+	Invalid,
+}
+
+impl BinaryOperator {
+	pub fn binding_power(&self) -> (u8, u8) {
+		match self {
+			Self::ObjectApply => (22, 23),
+			Self::Mul | Self::Div | Self::Mod => (20, 21),
+			Self::Plus | Self::Minus => (18, 19),
+			Self::ShiftLeft | Self::ShiftRight => (16, 17),
+			Self::LessThan
+			| Self::GreaterThan
+			| Self::LessThanOrEqual
+			| Self::GreaterThanOrEqual
+			| Self::In => (14, 15),
+			Self::Equal | Self::NotEqual => (12, 13),
+			Self::BitAnd => (10, 11),
+			Self::BitXor => (8, 9),
+			Self::BitOr => (6, 7),
+			Self::And => (4, 5),
+			Self::Or => (2, 3),
+			Self::Invalid => (0, 1),
+		}
+	}
+}
addedcrates/jrsonnet-rowan-parser/src/event.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/event.rs
@@ -0,0 +1,107 @@
+use std::mem;
+
+use rowan::{GreenNode, GreenNodeBuilder, Language};
+
+use crate::{
+	lex::{Lang, Lexeme, SyntaxKind},
+	parser::{Parse, SyntaxError},
+};
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum Event {
+	Start {
+		kind: SyntaxKind,
+		forward_parent: Option<usize>,
+	},
+	Token,
+	Finish,
+	Placeholder,
+	Error(SyntaxError),
+}
+
+pub(super) struct Sink<'i> {
+	pub builder: GreenNodeBuilder<'static>,
+	lexemes: &'i [Lexeme<'i>],
+	offset: usize,
+	events: Vec<Event>,
+	pub errors: Vec<SyntaxError>,
+}
+
+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![],
+		}
+	}
+
+	pub(super) fn finish(mut self) -> Parse {
+		for idx in 0..self.events.len() {
+			match mem::replace(&mut self.events[idx], Event::Placeholder) {
+				Event::Start {
+					kind,
+					forward_parent,
+				} => {
+					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;
+
+						forward_parent = if let Event::Start {
+							kind,
+							forward_parent,
+						} = mem::replace(&mut self.events[idx], Event::Placeholder)
+						{
+							kinds.push(kind);
+							forward_parent
+						} else {
+							unreachable!()
+						};
+					}
+
+					for kind in kinds.into_iter().rev() {
+						self.builder.start_node(Lang::kind_to_raw(kind));
+					}
+				}
+				Event::Token => self.token(),
+				Event::Finish => {
+					self.builder.finish_node();
+				}
+				Event::Placeholder => {}
+				Event::Error(e) => {
+					self.errors.push(e);
+				}
+			}
+			self.skip_whitespace();
+		}
+
+		Parse {
+			green_node: self.builder.finish(),
+			errors: self.errors,
+		}
+	}
+	fn token(&mut self) {
+		let lexeme = self.lexemes[self.offset];
+		self.builder
+			.token(Lang::kind_to_raw(lexeme.kind), lexeme.text);
+		self.offset += 1;
+	}
+	fn skip_whitespace(&mut self) {
+		while let Some(lexeme) = self.lexemes.get(self.offset) {
+			if !lexeme.kind.is_trivia() {
+				break;
+			}
+
+			self.token();
+		}
+	}
+}
addedcrates/jrsonnet-rowan-parser/src/lex.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/lex.rs
@@ -0,0 +1,315 @@
+use crate::string_block::lex_str_block_test;
+use core::ops::Range;
+use logos::Logos;
+use rowan::{Checkpoint, TextRange, TextSize};
+use std::{convert::TryFrom, iter::Peekable};
+
+#[derive(Logos, Debug, PartialEq, Hash, Eq, PartialOrd, Ord, Clone, Copy)]
+#[repr(u16)]
+pub enum SyntaxKind {
+	#[token("assert")]
+	KeywordAssert = 0,
+
+	#[token("else")]
+	KeywordElse,
+
+	#[token("error")]
+	KeywordError,
+
+	#[token("false")]
+	KeywordFalse,
+
+	#[token("for")]
+	KeywordFor,
+
+	#[token("function")]
+	KeywordFunction,
+
+	#[token("if")]
+	KeywordIf,
+
+	#[token("import")]
+	KeywordImport,
+
+	#[token("importstr")]
+	KeywordImportStr,
+
+	#[token("local")]
+	KeywordLocal,
+
+	#[token("null")]
+	KeywordNull,
+
+	#[token("tailstrict")]
+	KeywordTailStrict,
+
+	#[token("then")]
+	KeywordThen,
+
+	#[token("self")]
+	KeywordSelf,
+
+	#[token("super")]
+	KeywordSuper,
+
+	#[token("true")]
+	KeywordTrue,
+
+	#[regex(r"[_a-zA-Z][_a-zA-Z0-9]*")]
+	Ident,
+
+	#[regex(r"(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?")]
+	Number,
+
+	#[regex(r"(?:0|[1-9][0-9]*)\.[^0-9]")]
+	ErrorNumJunkAfterDecimalPoint,
+
+	#[regex(r"(?:0|[1-9][0-9]*)(?:\.[0-9]+)?[eE][^+\-0-9]")]
+	ErrorNumJunkAfterExponent,
+
+	#[regex(r"(?:0|[1-9][0-9]*)(?:\.[0-9]+)?[eE][+-][^0-9]")]
+	ErrorNumJunkAfterExponentSign,
+
+	#[token("{")]
+	SymbolLeftBrace,
+
+	#[token("}")]
+	SymbolRightBrace,
+
+	#[token("[")]
+	SymbolLeftBracket,
+
+	#[token("]")]
+	SymbolRightBracket,
+
+	#[token(",")]
+	SymbolComma,
+
+	#[token(".")]
+	SymbolDot,
+
+	#[token("(")]
+	LParen,
+
+	#[token(")")]
+	RParen,
+
+	#[token(";")]
+	SymbolSemi,
+	#[token(":")]
+	SymbolColon,
+
+	#[token("$")]
+	SymbolDollar,
+
+	#[token("*")]
+	OpMul,
+	#[token("/")]
+	OpDiv,
+	#[token("%")]
+	OpMod,
+	#[token("+")]
+	OpPlus,
+	#[token("-")]
+	OpMinus,
+	#[token("<<")]
+	OpShiftLeft,
+	#[token(">>")]
+	OpShiftRight,
+	#[token("<")]
+	OpLessThan,
+	#[token(">")]
+	OpGreaterThan,
+	#[token("<=")]
+	OpLessThanOrEqual,
+	#[token(">=")]
+	OpGreaterThanOrEqual,
+	#[token("==")]
+	OpEqual,
+	#[token("!=")]
+	OpNotEqual,
+	#[token("&")]
+	OpBitAnd,
+	#[token("^")]
+	OpBitXor,
+	#[token("|")]
+	OpBitOr,
+	#[token("&&")]
+	OpAnd,
+	#[token("||")]
+	OpOr,
+	#[token("in")]
+	OpIn,
+	#[token("!")]
+	OpNot,
+	#[token("~")]
+	OpBitNegate,
+	#[token("=")]
+	SymbolAssign,
+
+	#[regex("\"(?s:[^\"\\\\]|\\\\.)*\"")]
+	StringDoubleQuoted,
+
+	#[regex("'(?s:[^'\\\\]|\\\\.)*'")]
+	StringSingleQuoted,
+
+	#[regex("@\"(?:[^\"]|\"\")*\"")]
+	StringDoubleVerbatim,
+
+	#[regex("@'(?:[^']|'')*'")]
+	StringSingleVerbatim,
+
+	#[regex(r"\|\|\|", lex_str_block_test)]
+	StringBlock, //(StringBlockToken),
+
+	#[regex("\"(?s:[^\"\\\\]|\\\\.)*")]
+	ErrorStringDoubleQuotedUnterminated,
+
+	#[regex("'(?s:[^'\\\\]|\\\\.)*")]
+	ErrorStringSingleQuotedUnterminated,
+
+	#[regex("@\"(?:[^\"]|\"\")*")]
+	ErrorStringDoubleVerbatimUnterminated,
+
+	#[regex("@'(?:[^']|'')*")]
+	ErrorStringSingleVerbatimUnterminated,
+
+	#[regex("@[^\"'\\s]\\S+")]
+	ErrorStringMissingQuotes,
+
+	#[token("/*/")]
+	ErrorCommentTooShort,
+
+	#[regex(r"/\*([^*]|\*[^/])+")]
+	ErrorCommentUnterminated,
+
+	#[regex(r"[ \t\n\r]+")]
+	Whitespace,
+
+	#[regex(r"//[^\r\n]*(\r\n|\n)?")]
+	SingelLineSlashComment,
+
+	#[regex(r"#[^\r\n]*(\r\n|\n)?")]
+	SingleLineHashComment,
+
+	#[regex(r"/\*([^*]|\*[^/])*\*/")]
+	MultiLineComment,
+
+	#[error]
+	Error,
+
+	ErrorPositionalAfterNamed,
+
+	Literal,
+	Expr,
+	Array,
+	ArrayElem,
+	Object,
+	Field,
+
+	CompspecFor,
+	CompspecIf,
+
+	Slice,
+	FieldAccess,
+	ObjectApply,
+	FunctionCall,
+	FunctionDef,
+	BodyDef,
+
+	BinOp,
+	UnaryOp,
+	Local,
+	ExprError,
+	ExprAssert,
+	ExprImport,
+
+	DefParam,
+	DefParams,
+
+	DefArgs,
+	DefNamedArg,
+	DefPositionalArg,
+
+	Parened,
+
+	Root,
+}
+
+impl SyntaxKind {
+	pub fn is_trivia(self) -> bool {
+		matches!(
+			self,
+			Self::Whitespace
+				| Self::MultiLineComment
+				| Self::SingelLineSlashComment
+				| Self::SingleLineHashComment
+		)
+	}
+}
+
+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> {
+		let kind = self.inner.next()?;
+		let text = self.inner.slice();
+
+		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)]
+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()
+}
+
+impl From<SyntaxKind> for rowan::SyntaxKind {
+	fn from(kind: SyntaxKind) -> Self {
+		Self(kind as u16)
+	}
+}
+
+use SyntaxKind::*;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum Lang {}
+impl rowan::Language for Lang {
+	type Kind = SyntaxKind;
+	fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
+		assert!(raw.0 <= Root as u16);
+		unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
+	}
+	fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
+		kind.into()
+	}
+}
addedcrates/jrsonnet-rowan-parser/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/lib.rs
@@ -0,0 +1,139 @@
+#![deny(unused_must_use)]
+
+mod binary;
+mod event;
+mod lex;
+mod marker;
+mod parser;
+mod string_block;
+mod token_set;
+mod unary;
+
+#[cfg(test)]
+mod tests {
+	use miette::{Diagnostic, GraphicalReportHandler, LabeledSpan};
+	use thiserror::Error;
+
+	use crate::parser::parse;
+
+	#[derive(Debug, Error)]
+	#[error("syntax error")]
+	struct MyDiagnostic {
+		code: String,
+		spans: Vec<LabeledSpan>,
+	}
+	impl Diagnostic for MyDiagnostic {
+		fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
+			None
+		}
+
+		fn severity(&self) -> Option<miette::Severity> {
+			None
+		}
+
+		fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
+			None
+		}
+
+		fn url<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
+			None
+		}
+
+		fn source_code(&self) -> Option<&dyn miette::SourceCode> {
+			Some(&self.code)
+		}
+
+		fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
+			Some(Box::new(self.spans.clone().into_iter()))
+		}
+
+		fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
+			None
+		}
+	}
+
+	fn process(text: &str) -> String {
+		use std::fmt::Write;
+		let mut out = String::new();
+		let node = parse(text);
+		write!(out, "{:#?}", node.syntax()).unwrap();
+		if !node.errors.is_empty() && !text.is_empty() {
+			writeln!(out, "===").unwrap();
+			for err in &node.errors {
+				writeln!(out, "{:?}", err).unwrap();
+			}
+			let diag = MyDiagnostic {
+				code: text.to_string(),
+				spans: node.errors.into_iter().map(|e| e.into()).collect(),
+			};
+
+			let handler = GraphicalReportHandler::new();
+
+			write!(out, "===").unwrap();
+			handler.render_report(&mut out, &diag).unwrap();
+		}
+		out
+	}
+	macro_rules! mk_test {
+		($($name:ident => $test:expr)+) => {$(
+			#[test]
+			fn $name() {
+				let src = indoc::indoc!($test);
+				let result = process(&src);
+				insta::assert_snapshot!(stringify!($name), result, src);
+
+			}
+		)+};
+	}
+	mk_test!(
+		empty => r#" "#
+		function => r#"
+			function(a, b = 1) a + b
+		"#
+		function_error_no_value => r#"
+			function(a, b = ) a + b
+		"#
+		function_error_rparen => r#"
+			function(a, b
+		"#
+		function_error_body => r#"
+			function(a, b)
+		"#
+		local_novalue => r#"
+			local a =
+		"#
+		local_no_value_recovery => r#"
+			local a =
+			local b = 3;
+			1
+		"#
+
+		array_comp => r#"
+			[a for a in [1, 2, 3]]
+		"#
+		array_comp_incompatible_with_multiple_elems => r#"
+			[a for a in [1, 2, 3], b]
+		"#
+
+		no_rhs => r#"
+			a +
+		"#
+		no_lhs => r#"
+			+ 2
+		"#
+		no_operator => "
+			2 2
+		"
+
+		named_before_positional => "
+			a(1, 2, b=4, 3, 5, k = 12, 6)
+		"
+
+		wrong_field_end => "
+			{
+				a: 1;
+				b: 2;
+			}
+		"
+	);
+}
addedcrates/jrsonnet-rowan-parser/src/marker.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/marker.rs
@@ -0,0 +1,114 @@
+use drop_bomb::DropBomb;
+use rowan::TextRange;
+
+use crate::{event::Event, lex::SyntaxKind, parser::Parser};
+
+pub struct Ranger {
+	pub pos: usize,
+}
+impl Ranger {
+	pub fn finish(mut 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 {
+	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,
+	pub token: usize,
+	bomb: DropBomb,
+}
+impl Marker {
+	pub fn new(pos: usize, token: usize) -> Self {
+		Self {
+			start_event_idx: pos,
+			token,
+			bomb: DropBomb::new("marked dropped while not completed"),
+		}
+	}
+	pub fn complete(mut self, p: &mut Parser, kind: SyntaxKind) -> CompletedMarker {
+		self.bomb.defuse();
+		let event_at_pos = &mut p.events[self.start_event_idx];
+		assert_eq!(*event_at_pos, Event::Placeholder);
+
+		*event_at_pos = Event::Start {
+			kind,
+			forward_parent: None,
+		};
+
+		p.events.push(Event::Finish);
+		p.entered -= 1;
+		p.clear_outdated_hints();
+		CompletedMarker {
+			start_event_idx: self.start_event_idx,
+			start_token: self.token,
+			end_token: self.token.max(p.offset.saturating_sub(1)),
+		}
+	}
+}
+pub struct CompletedMarker {
+	start_event_idx: usize,
+	pub start_token: usize,
+	pub end_token: usize,
+}
+impl CompletedMarker {
+	pub(super) fn precede(self, p: &mut Parser) -> Marker {
+		let mut new_m = p.start();
+		new_m.token = self.start_token;
+
+		if let Event::Start {
+			ref mut forward_parent,
+			..
+		} = p.events[self.start_event_idx]
+		{
+			*forward_parent = Some(new_m.start_event_idx - self.start_event_idx);
+		} else {
+			unreachable!();
+		}
+
+		new_m
+	}
+}
+
+pub trait AsRange {
+	fn as_range(&self, p: &Parser) -> TextRange;
+	fn end_token(&self) -> usize;
+}
+
+impl AsRange for CompletedMarker {
+	fn as_range(&self, p: &Parser) -> TextRange {
+		TextRange::new(
+			p.start_of_token(self.start_token),
+			p.end_of_token(self.end_token),
+		)
+	}
+	fn end_token(&self) -> usize {
+		self.end_token
+	}
+}
+
+impl AsRange for FinishedRanger {
+	fn as_range(&self, p: &Parser) -> TextRange {
+		TextRange::new(
+			p.start_of_token(self.start_token),
+			p.end_of_token(self.end_token),
+		)
+	}
+
+	fn end_token(&self) -> usize {
+		self.end_token
+	}
+}
addedcrates/jrsonnet-rowan-parser/src/parser.rsdiffbeforeafterboth
after Β· crates/jrsonnet-rowan-parser/src/parser.rs
1use std::cell::Cell;2use std::fmt::Display;3use std::rc::Rc;45use miette::Diagnostic;6use miette::LabeledSpan;7use miette::SourceOffset;8use miette::SourceSpan;9use rowan::GreenNode;1011use rowan::TextRange;12use rowan::TextSize;13use thiserror::Error;1415use crate::binary::BinaryOperator;16use crate::event::Event;17use crate::event::Sink;18use crate::lex::lex;19use crate::lex::Lang;20use crate::lex::Lexeme;21use crate::lex::SyntaxKind;22use crate::lex::SyntaxKind::*;23use crate::marker::AsRange;24use crate::marker::CompletedMarker;25use crate::marker::FinishedRanger;26use crate::marker::Marker;27use crate::marker::Ranger;28use crate::token_set::TokenSet;29use crate::unary::UnaryOperator;3031pub struct Parse {32	pub green_node: GreenNode,33	pub errors: Vec<SyntaxError>,34}3536#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]37pub enum ExpectedSyntax {38	Named(&'static str),39	Unnamed(SyntaxKind),40}41impl Display for ExpectedSyntax {42	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {43		match self {44			ExpectedSyntax::Named(n) => write!(f, "{}", n),45			ExpectedSyntax::Unnamed(u) => write!(f, "{:?}", u),46		}47	}48}4950pub struct Parser<'i> {51	lexemes: &'i [Lexeme<'i>],52	pub offset: usize,53	pub events: Vec<Event>,54	pub entered: u32,55	pub hints: Vec<(u32, TextRange, String)>,56	pub last_error_token: usize,57	expected_syntax: Option<ExpectedSyntax>,58	expected_syntax_tracking_state: Rc<Cell<ExpectedSyntaxTrackingState>>,59}6061const DEFAULT_RECOVERY_SET: TokenSet = TokenSet::new(&[62	SymbolSemi,63	RParen,64	SymbolRightBracket,65	SymbolRightBrace,66	KeywordLocal,67]);6869#[derive(Clone, Debug, PartialEq, Eq)]70pub enum SyntaxError {71	Unexpected {72		expected: ExpectedSyntax,73		found: SyntaxKind,74		range: TextRange,75	},76	Missing {77		expected: ExpectedSyntax,78		offset: TextSize,79	},80	Custom {81		error: String,82		range: TextRange,83	},84	Hint {85		error: String,86		range: TextRange,87	},88}8990impl Into<LabeledSpan> for SyntaxError {91	fn into(self) -> LabeledSpan {92		match self {93			SyntaxError::Unexpected {94				expected,95				found,96				range,97			} => LabeledSpan::new_with_span(98				Some(format!("expected {}, found {:?}", expected, found)),99				SourceSpan::new(100					SourceOffset::from(usize::from(range.start())),101					SourceOffset::from(usize::from(range.end() - range.start())),102				),103			),104			SyntaxError::Missing { expected, offset } => LabeledSpan::new_with_span(105				Some(format!("missing {}", expected)),106				SourceSpan::new(107					SourceOffset::from(usize::from(offset)),108					SourceOffset::from(0),109				),110			),111			SyntaxError::Custom { error, range } | SyntaxError::Hint { error, range } => {112				LabeledSpan::new_with_span(113					Some(format!("{}", error)),114					SourceSpan::new(115						SourceOffset::from(usize::from(range.start())),116						SourceOffset::from(usize::from(range.end() - range.start())),117					),118				)119			}120		}121	}122}123124impl<'i> Parser<'i> {125	fn new(lexemes: &'i [Lexeme<'i>]) -> Self {126		Self {127			lexemes,128			offset: 0,129			events: vec![],130			entered: 0,131			last_error_token: 0,132			hints: vec![],133			expected_syntax: None,134			expected_syntax_tracking_state: Rc::new(Cell::new(135				ExpectedSyntaxTrackingState::Unnamed,136			)),137		}138	}139	pub fn clear_outdated_hints(&mut self) {140		let amount = self141			.hints142			.iter()143			.rev()144			.take_while(|h| h.0 > self.entered)145			.count();146		self.hints.truncate(self.hints.len() - amount)147	}148	fn clear_expected_syntaxes(&mut self) {149		self.expected_syntax = None;150		self.expected_syntax_tracking_state151			.set(ExpectedSyntaxTrackingState::Unnamed);152	}153	pub fn start(&mut self) -> Marker {154		let start_event_idx = self.events.len();155		self.events.push(Event::Placeholder);156		self.entered += 1;157		Marker::new(start_event_idx, self.offset)158	}159	pub fn start_ranger(&mut self) -> Ranger {160		let pos = self.offset;161		Ranger { pos }162	}163	fn parse(mut self) -> Vec<Event> {164		let m = self.start();165		expr(&mut self);166		if !self.at_end() {167			let ranger = self.start_ranger();168169			while self.peek().is_some() {170				self.bump()171			}172			let end = ranger.finish(&self);173			self.custom_error(end, "unexpected input after expression");174		}175		m.complete(&mut self, Root);176177		self.events178	}179180	pub(crate) fn expect(&mut self, kind: SyntaxKind) {181		self.expect_with_recovery_set(kind, TokenSet::default())182	}183184	pub(crate) fn expect_with_recovery_set(&mut self, kind: SyntaxKind, recovery_set: TokenSet) {185		if self.at(kind) {186			self.bump();187		} else {188			self.error_with_recovery_set(recovery_set);189		}190	}191192	pub(crate) fn expect_with_no_skip(&mut self, kind: SyntaxKind) {193		if self.at(kind) {194			self.bump();195		} else {196			self.error_with_no_skip();197		}198	}199	pub(crate) fn last_token_range(&self) -> Option<TextRange> {200		self.lexemes.last().map(|Lexeme { range, .. }| *range)201	}202	fn current_token(&self) -> Lexeme<'i> {203		self.lexemes[self.offset]204	}205	fn previous_token(&mut self) -> Option<Lexeme<'i>> {206		if self.offset == 0 {207			return None;208		}209		let mut previous_token_idx = self.offset - 1;210		while self211			.lexemes212			.get(previous_token_idx)213			.map_or(false, |l| l.kind.is_trivia())214			&& previous_token_idx != 0215		{216			previous_token_idx -= 1;217		}218219		Some(self.lexemes[previous_token_idx])220	}221	pub fn start_of_token(&self, mut idx: usize) -> TextSize {222		while self.lexemes[idx].kind.is_trivia() {223			idx += 1;224		}225		self.lexemes[idx].range.start()226	}227	pub fn end_of_token(&self, mut idx: usize) -> TextSize {228		while self.lexemes[idx].kind.is_trivia() {229			idx -= 1;230		}231		self.lexemes[idx].range.end()232	}233	pub(crate) fn custom_error(&mut self, marker: impl AsRange, error: impl AsRef<str>) {234		self.last_error_token = marker.end_token();235		self.events.push(Event::Error(SyntaxError::Custom {236			error: error.as_ref().to_string(),237			range: marker.as_range(self),238		}));239	}240	pub(crate) fn error_with_recovery_set(241		&mut self,242		recovery_set: TokenSet,243	) -> Option<CompletedMarker> {244		self.error_with_recovery_set_no_default(recovery_set.union(DEFAULT_RECOVERY_SET))245	}246	pub fn error_with_no_skip(&mut self) -> Option<CompletedMarker> {247		self.error_with_recovery_set_no_default(TokenSet::ALL)248	}249250	pub fn error_with_recovery_set_no_default(251		&mut self,252		recovery_set: TokenSet,253	) -> Option<CompletedMarker> {254		let expected_syntax = self.expected_syntax.take().unwrap();255		self.expected_syntax_tracking_state256			.set(ExpectedSyntaxTrackingState::Unnamed);257258		if self.at_end() || self.at_set(recovery_set) {259			let range = self260				.previous_token()261				.map(|t| t.range)262				.unwrap_or(TextRange::at(TextSize::from(0), TextSize::from(0)));263264			self.events.push(Event::Error(SyntaxError::Missing {265				expected: expected_syntax,266				offset: range.end(),267			}));268			return None;269		}270271		let current_token = self.current_token();272273		self.events.push(Event::Error(SyntaxError::Unexpected {274			expected: expected_syntax.clone(),275			found: current_token.kind,276			range: current_token.range,277		}));278		self.clear_expected_syntaxes();279		self.last_error_token = self.offset;280281		let m = self.start();282		self.bump();283		Some(m.complete(self, SyntaxKind::Error))284	}285286	fn bump(&mut self) {287		self.skip_trivia();288		assert_ne!(self.offset, self.lexemes.len(), "already at end");289		self.events.push(Event::Token);290		self.offset += 1;291		self.clear_expected_syntaxes();292	}293	fn peek(&mut self) -> Option<SyntaxKind> {294		self.skip_trivia();295		self.peek_raw()296	}297	pub fn peek_token(&mut self) -> Option<&Lexeme<'i>> {298		self.skip_trivia();299		self.peek_token_raw()300	}301	fn skip_trivia(&mut self) {302		while self.peek_raw().map(|c| c.is_trivia()).unwrap_or(false) {303			self.offset += 1;304		}305	}306	fn peek_raw(&mut self) -> Option<SyntaxKind> {307		self.lexemes.get(self.offset).map(|l| l.kind)308	}309	fn peek_token_raw(&mut self) -> Option<&Lexeme<'i>> {310		self.lexemes.get(self.offset)311	}312	#[must_use]313	pub(crate) fn expected_syntax_name(&mut self, name: &'static str) -> ExpectedSyntaxGuard {314		self.expected_syntax_tracking_state315			.set(ExpectedSyntaxTrackingState::Named);316		self.expected_syntax = Some(ExpectedSyntax::Named(name));317318		ExpectedSyntaxGuard::new(Rc::clone(&self.expected_syntax_tracking_state))319	}320	pub fn at(&mut self, kind: SyntaxKind) -> bool {321		if let ExpectedSyntaxTrackingState::Unnamed = self.expected_syntax_tracking_state.get() {322			self.expected_syntax = Some(ExpectedSyntax::Unnamed(kind));323		}324		self.peek() == Some(kind)325	}326	pub fn at_set(&mut self, set: TokenSet) -> bool {327		self.peek().map_or(false, |k| set.contains(k))328	}329	pub fn at_end(&mut self) -> bool {330		self.peek().is_none()331	}332}333pub(crate) struct ExpectedSyntaxGuard {334	expected_syntax_tracking_state: Rc<Cell<ExpectedSyntaxTrackingState>>,335}336337impl ExpectedSyntaxGuard {338	fn new(expected_syntax_tracking_state: Rc<Cell<ExpectedSyntaxTrackingState>>) -> Self {339		Self {340			expected_syntax_tracking_state,341		}342	}343}344345impl Drop for ExpectedSyntaxGuard {346	fn drop(&mut self) {347		self.expected_syntax_tracking_state348			.set(ExpectedSyntaxTrackingState::Unnamed);349	}350}351352#[derive(Debug, Clone, Copy)]353enum ExpectedSyntaxTrackingState {354	Named,355	Unnamed,356}357macro_rules! at_match {358	($p:ident {359		$($r:ident => $e:expr,)*360		_ => $else:expr $(,)?361	}) => {{362		$(363			if $p.at($r) {$e} else364		)* {365			$else366		}367	}}368}369370fn expr(p: &mut Parser) {371	expr_binding_power(p, 0);372}373fn expr_binding_power(p: &mut Parser, minimum_binding_power: u8) -> Option<CompletedMarker> {374	let mut lhs = lhs(p)?;375376	loop {377		let op = at_match!(p {378			OpMul => BinaryOperator::Mul,379			OpDiv => BinaryOperator::Div,380			OpMod => BinaryOperator::Mod,381			OpPlus => BinaryOperator::Plus,382			OpMinus => BinaryOperator::Minus,383			OpShiftLeft => BinaryOperator::ShiftLeft,384			OpShiftRight => BinaryOperator::ShiftRight,385			OpLessThan => BinaryOperator::LessThan,386			OpGreaterThan => BinaryOperator::GreaterThan,387			OpLessThanOrEqual => BinaryOperator::LessThanOrEqual,388			OpGreaterThanOrEqual => BinaryOperator::GreaterThanOrEqual,389			OpEqual => BinaryOperator::Equal,390			OpNotEqual => BinaryOperator::NotEqual,391			OpBitAnd => BinaryOperator::BitAnd,392			OpBitXor => BinaryOperator::BitXor,393			OpBitOr => BinaryOperator::BitOr,394			OpAnd => BinaryOperator::And,395			OpOr => BinaryOperator::Or,396			OpIn => BinaryOperator::In,397			SymbolLeftBrace => BinaryOperator::ObjectApply,398			_ => break,399		});400		let (left_binding_power, right_binding_power) = op.binding_power();401		if left_binding_power < minimum_binding_power {402			break;403		}404405		// Object apply is not a real operator, we dont have something to bump406		if op != BinaryOperator::ObjectApply {407			p.bump();408		}409410		let m = lhs.precede(p);411		let parsed_rhs = expr_binding_power(p, right_binding_power).is_some();412		lhs = m.complete(413			p,414			if op == BinaryOperator::ObjectApply {415				ObjectApply416			} else {417				BinOp418			},419		);420421		if !parsed_rhs {422			break;423		}424	}425	Some(lhs)426}427fn compspec(p: &mut Parser) {428	assert!(p.at(KeywordFor) || p.at(KeywordIf));429	if p.at(KeywordFor) {430		let m = p.start();431		p.bump();432		p.expect(Ident);433		p.expect(OpIn);434		expr(p);435		m.complete(p, CompspecFor);436	} else if p.at(KeywordIf) {437		let m = p.start();438		p.bump();439		expr(p);440		m.complete(p, CompspecIf);441	} else {442		unreachable!()443	}444}445fn comma(p: &mut Parser) -> bool {446	if p.at(SymbolComma) {447		p.bump();448		true449	} else {450		false451	}452}453fn comma_with_alternatives(p: &mut Parser, set: TokenSet) -> bool {454	if p.at(SymbolComma) {455		p.bump();456		true457	} else if p.at_set(set) {458		p.expect_with_no_skip(SymbolComma);459		p.bump();460		true461	} else {462		false463	}464}465fn field_name(p: &mut Parser) {466	let _e = p.expected_syntax_name("field name");467	if p.at(SymbolLeftBracket) {468		p.bump();469		expr(p);470		p.expect(SymbolRightBracket);471	} else if p.at(Ident) {472		p.bump()473	} else {474		p.error_with_recovery_set(TokenSet::new(&[SymbolSemi]));475	}476}477fn object(p: &mut Parser) -> CompletedMarker {478	assert!(p.at(SymbolLeftBrace));479	let m = p.start();480	p.bump();481482	loop {483		if p.at(SymbolRightBrace) {484			p.bump();485			break;486		}487		let m = p.start();488		field_name(p);489		p.expect(SymbolColon);490		expr(p);491		while p.at(KeywordFor) || p.at(KeywordIf) {492			compspec(p)493		}494		m.complete(p, Field);495		if comma_with_alternatives(p, TokenSet::new(&[SymbolAssign])) {496			continue;497		}498		p.expect(SymbolRightBrace);499		break;500	}501502	m.complete(p, Object)503}504505fn params(p: &mut Parser) -> CompletedMarker {506	assert!(p.at(LParen));507	let m = p.start();508	p.bump();509510	loop {511		if p.at(RParen) {512			p.bump();513			break;514		}515		let m = p.start();516		p.expect(Ident);517		if p.at(SymbolAssign) {518			p.bump();519			expr(p);520		}521		m.complete(p, DefParam);522		if comma(p) {523			continue;524		}525		p.expect(RParen);526		break;527	}528529	m.complete(p, DefParams)530}531fn args(p: &mut Parser) {532	assert!(p.at(LParen));533	p.bump();534535	let mut error_positional_start = None::<Marker>;536	let mut started_named = Cell::new(false);537	let mut on_positional = |p: &mut Parser, m: Marker| {538		let c = m.complete(p, DefPositionalArg);539		if started_named.get() && error_positional_start.is_none() {540			error_positional_start = Some(c.precede(p));541		}542	};543	loop {544		if p.at(RParen) {545			break;546		}547548		let m = p.start();549		if p.at(Ident) {550			p.bump();551			if p.at(SymbolAssign) {552				p.bump();553				expr(p);554				m.complete(p, DefNamedArg);555				started_named.set(true);556			} else {557				on_positional(p, m);558			}559		} else {560			expr(p);561			on_positional(p, m);562		}563		if comma(p) {564			continue;565		}566		break;567	}568	if let Some(error_positional_start) = error_positional_start {569		let c = error_positional_start.complete(p, ErrorPositionalAfterNamed);570		p.custom_error(c, "positional arguments can't be placed after named")571	}572	p.expect(RParen);573}574575fn array(p: &mut Parser) -> CompletedMarker {576	assert!(p.at(SymbolLeftBracket));577	// Start the list node578	let m = p.start();579	p.bump(); // '['580581	// This vec will have at most one element in case of correct input582	let mut compspecs = Vec::with_capacity(1);583	let mut elems = 0;584585	loop {586		if p.at(SymbolRightBracket) {587			p.bump();588			break;589		}590		elems += 1;591		let m = p.start();592		{593			let m = p.start();594			expr(p);595			m.complete(p, BodyDef);596		}597		let c = p.start_ranger();598		let mut had_spec = false;599		while p.at(KeywordFor) || p.at(KeywordIf) {600			had_spec = true;601			compspec(p)602		}603		if had_spec {604			compspecs.push(c.finish(p));605		}606		m.complete(p, ArrayElem);607		if comma(p) {608			continue;609		}610		p.expect(SymbolRightBracket);611		break;612	}613614	if elems > 1 && !compspecs.is_empty() {615		for spec in compspecs {616			p.custom_error(617				spec,618				"compspec may only be used if there is only one array element",619			)620		}621	}622623	m.complete(p, Array)624}625626fn lhs(p: &mut Parser) -> Option<CompletedMarker> {627	let mut lhs = lhs_basic(p)?;628629	loop {630		if p.at(SymbolDot) {631			let m = lhs.precede(p);632			p.bump();633			p.expect(Ident);634			lhs = m.complete(p, FieldAccess);635		} else if p.at(SymbolLeftBracket) {636			let m = lhs.precede(p);637			p.bump();638			// Start639			if !p.at(SymbolColon) {640				expr(p);641			}642			if p.at(SymbolColon) {643				p.bump();644				// End645				if !p.at(SymbolRightBracket) && !p.at(SymbolColon) {646					expr(p);647				}648				if p.at(SymbolColon) {649					p.bump();650					// Step651					if !p.at(SymbolRightBracket) {652						expr(p);653					}654				}655			}656			p.expect(SymbolRightBracket);657			lhs = m.complete(p, Slice);658		} else if p.at(LParen) {659			let m = lhs.precede(p);660			args(p);661			lhs = m.complete(p, FunctionCall);662		} else {663			break;664		}665	}666667	Some(lhs)668}669670fn lhs_basic(p: &mut Parser) -> Option<CompletedMarker> {671	let _e = p.expected_syntax_name("value");672	Some(673		if p.at(Number)674			|| p.at(StringSingleQuoted)675			|| p.at(StringDoubleQuoted)676			|| p.at(StringSingleVerbatim)677			|| p.at(StringDoubleVerbatim)678			|| p.at(StringBlock)679			|| p.at(KeywordNull)680			|| p.at(SymbolDollar)681			|| p.at(KeywordSuper)682			|| p.at(KeywordSelf)683		{684			let m = p.start();685			p.bump();686			m.complete(p, Literal)687		} else if p.at(Ident) {688			let m = p.start();689			p.bump();690			m.complete(p, Ident)691		} else if p.at(SymbolLeftBracket) {692			array(p)693		} else if p.at(SymbolLeftBrace) {694			object(p)695		} else if p.at(KeywordLocal) {696			let m = p.start();697			p.bump();698			let mut sus_local = None;699			loop {700				p.expect_with_recovery_set(701					Ident,702					TokenSet::new(&[SymbolAssign, SymbolSemi, KeywordLocal]),703				);704				if p.at(LParen) {705					params(p);706				}707708				let sus_local_candidate = p.start_ranger();709				p.expect_with_recovery_set(710					SymbolAssign,711					TokenSet::new(&[SymbolSemi, KeywordLocal]),712				);713714				sus_local = p.at(KeywordLocal).then(|| sus_local_candidate.finish(p));715				expr(p);716717				if !comma(p) {718					break;719				}720			}721			p.expect(SymbolSemi);722			if let Some(sus_local) = sus_local {723				if sus_local.had_error_since(p) {724					p.custom_error(sus_local, "unusal local placement, missing ';' ?")725				}726			}727			{728				let m = p.start();729				expr(p);730				m.complete(p, BodyDef);731			}732			m.complete(p, Local)733		} else if p.at(KeywordFunction) {734			let m = p.start();735			p.bump();736			args(p);737			{738				let m = p.start();739				expr(p);740				m.complete(p, BodyDef);741			}742			m.complete(p, FunctionDef)743		} else if p.at(KeywordError) {744			let m = p.start();745			p.bump();746			expr(p);747			m.complete(p, ExprError)748		} else if p.at(KeywordAssert) {749			let m = p.start();750			p.bump();751			expr(p);752			if p.at(SymbolColon) {753				p.bump();754				expr(p);755			}756			m.complete(p, ExprAssert)757		} else if p.at(KeywordImport) || p.at(KeywordImportStr) {758			let m = p.start();759			p.bump();760			expr(p);761			m.complete(p, ExprImport)762		} else if p.at(OpMinus) || p.at(OpNot) || p.at(OpBitNegate) {763			let op = match p.peek().unwrap() {764				OpMinus => UnaryOperator::Minus,765				OpNot => UnaryOperator::Not,766				OpBitNegate => UnaryOperator::BitNegate,767				_ => unreachable!(),768			};769			let ((), right_binding_power) = op.binding_power();770771			let m = p.start();772			p.bump();773			expr_binding_power(p, right_binding_power);774			m.complete(p, UnaryOp)775		} else if p.at(LParen) {776			let m = p.start();777			p.bump();778			expr(p);779			assert!(p.at(RParen));780			p.bump();781			m.complete(p, Parened)782		} else {783			p.error_with_no_skip();784			return None;785		},786	)787}788789type SyntaxNode = rowan::SyntaxNode<Lang>;790#[allow(unused)]791type SyntaxToken = rowan::SyntaxToken<Lang>;792#[allow(unused)]793type SyntaxElement = rowan::NodeOrToken<SyntaxNode, SyntaxToken>;794795impl Parse {796	pub fn syntax(&self) -> SyntaxNode {797		SyntaxNode::new_root(self.green_node.clone())798	}799}800801pub fn parse(input: &str) -> Parse {802	let lexemes = lex(input);803	let parser = Parser::new(&lexemes);804	let events = parser.parse();805	dbg!(&events);806	let sink = Sink::new(events, &lexemes);807808	sink.finish()809}
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__array_comp.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__array_comp.snap
@@ -0,0 +1,43 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "[a for a in [1, 2, 3]]\n"
+
+---
+Root@0..23
+  Array@0..23
+    SymbolLeftBracket@0..1 "["
+    ArrayElem@1..21
+      BodyDef@1..3
+        Ident@1..3
+          Ident@1..2 "a"
+          Whitespace@2..3 " "
+      CompspecFor@3..21
+        KeywordFor@3..6 "for"
+        Whitespace@6..7 " "
+        Ident@7..8 "a"
+        Whitespace@8..9 " "
+        OpIn@9..11 "in"
+        Whitespace@11..12 " "
+        Array@12..21
+          SymbolLeftBracket@12..13 "["
+          ArrayElem@13..14
+            BodyDef@13..14
+              Literal@13..14
+                Number@13..14 "1"
+          SymbolComma@14..15 ","
+          Whitespace@15..16 " "
+          ArrayElem@16..17
+            BodyDef@16..17
+              Literal@16..17
+                Number@16..17 "2"
+          SymbolComma@17..18 ","
+          Whitespace@18..19 " "
+          ArrayElem@19..20
+            BodyDef@19..20
+              Literal@19..20
+                Number@19..20 "3"
+          SymbolRightBracket@20..21 "]"
+    SymbolRightBracket@21..22 "]"
+    Whitespace@22..23 "\n"
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__array_comp_incompatible_with_multiple_elems.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__array_comp_incompatible_with_multiple_elems.snap
@@ -0,0 +1,58 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "[a for a in [1, 2, 3], b]\n"
+
+---
+Root@0..26
+  Array@0..26
+    SymbolLeftBracket@0..1 "["
+    ArrayElem@1..21
+      BodyDef@1..3
+        Ident@1..3
+          Ident@1..2 "a"
+          Whitespace@2..3 " "
+      CompspecFor@3..21
+        KeywordFor@3..6 "for"
+        Whitespace@6..7 " "
+        Ident@7..8 "a"
+        Whitespace@8..9 " "
+        OpIn@9..11 "in"
+        Whitespace@11..12 " "
+        Array@12..21
+          SymbolLeftBracket@12..13 "["
+          ArrayElem@13..14
+            BodyDef@13..14
+              Literal@13..14
+                Number@13..14 "1"
+          SymbolComma@14..15 ","
+          Whitespace@15..16 " "
+          ArrayElem@16..17
+            BodyDef@16..17
+              Literal@16..17
+                Number@16..17 "2"
+          SymbolComma@17..18 ","
+          Whitespace@18..19 " "
+          ArrayElem@19..20
+            BodyDef@19..20
+              Literal@19..20
+                Number@19..20 "3"
+          SymbolRightBracket@20..21 "]"
+    SymbolComma@21..22 ","
+    Whitespace@22..23 " "
+    ArrayElem@23..24
+      BodyDef@23..24
+        Ident@23..24
+          Ident@23..24 "b"
+    SymbolRightBracket@24..25 "]"
+    Whitespace@25..26 "\n"
+===
+Custom { error: "compspec may only be used if there is only one array element", range: 3..21 }
+===
+  Γ— syntax error
+   ╭────
+ 1 β”‚ [a for a in [1, 2, 3], b]
+   Β·    ─────────┬────────
+   Β·             ╰── compspec may only be used if there is only one array element
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__empty.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__empty.snap
@@ -0,0 +1,18 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: " "
+
+---
+Root@0..1
+  Whitespace@0..1 " "
+===
+Missing { expected: Named("value"), offset: 1 }
+===
+  Γ— syntax error
+   ╭────
+ 1 β”‚  
+   Β·  β–²
+   Β·  ╰── missing value
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__function.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__function.snap
@@ -0,0 +1,34 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "function(a, b = 1) a + b\n"
+
+---
+Root@0..25
+  FunctionDef@0..25
+    KeywordFunction@0..8 "function"
+    LParen@8..9 "("
+    DefPositionalArg@9..10
+      Ident@9..10 "a"
+    SymbolComma@10..11 ","
+    Whitespace@11..12 " "
+    DefNamedArg@12..17
+      Ident@12..13 "b"
+      Whitespace@13..14 " "
+      SymbolAssign@14..15 "="
+      Whitespace@15..16 " "
+      Literal@16..17
+        Number@16..17 "1"
+    RParen@17..18 ")"
+    Whitespace@18..19 " "
+    BodyDef@19..25
+      BinOp@19..25
+        Ident@19..21
+          Ident@19..20 "a"
+          Whitespace@20..21 " "
+        OpPlus@21..22 "+"
+        Whitespace@22..23 " "
+        Ident@23..25
+          Ident@23..24 "b"
+          Whitespace@24..25 "\n"
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__function_error_body.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__function_error_body.snap
@@ -0,0 +1,29 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "function(a, b)\n"
+
+---
+Root@0..15
+  FunctionDef@0..15
+    KeywordFunction@0..8 "function"
+    LParen@8..9 "("
+    DefPositionalArg@9..10
+      Ident@9..10 "a"
+    SymbolComma@10..11 ","
+    Whitespace@11..12 " "
+    DefPositionalArg@12..13
+      Ident@12..13 "b"
+    RParen@13..14 ")"
+    Whitespace@14..15 "\n"
+    BodyDef@15..15
+===
+Missing { expected: Named("value"), offset: 14 }
+===
+  Γ— syntax error
+   ╭────
+ 1 β”‚ function(a, b)
+   Β·               β–²
+   Β·               ╰── missing value
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__function_error_no_value.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__function_error_no_value.snap
@@ -0,0 +1,41 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "function(a, b = ) a + b\n"
+
+---
+Root@0..24
+  FunctionDef@0..24
+    KeywordFunction@0..8 "function"
+    LParen@8..9 "("
+    DefPositionalArg@9..10
+      Ident@9..10 "a"
+    SymbolComma@10..11 ","
+    Whitespace@11..12 " "
+    DefNamedArg@12..16
+      Ident@12..13 "b"
+      Whitespace@13..14 " "
+      SymbolAssign@14..15 "="
+      Whitespace@15..16 " "
+    RParen@16..17 ")"
+    Whitespace@17..18 " "
+    BodyDef@18..24
+      BinOp@18..24
+        Ident@18..20
+          Ident@18..19 "a"
+          Whitespace@19..20 " "
+        OpPlus@20..21 "+"
+        Whitespace@21..22 " "
+        Ident@22..24
+          Ident@22..23 "b"
+          Whitespace@23..24 "\n"
+===
+Missing { expected: Named("value"), offset: 15 }
+===
+  Γ— syntax error
+   ╭────
+ 1 β”‚ function(a, b = ) a + b
+   Β·                β–²
+   Β·                ╰── missing value
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__function_error_rparen.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__function_error_rparen.snap
@@ -0,0 +1,30 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "function(a, b\n"
+
+---
+Root@0..14
+  FunctionDef@0..14
+    KeywordFunction@0..8 "function"
+    LParen@8..9 "("
+    DefPositionalArg@9..10
+      Ident@9..10 "a"
+    SymbolComma@10..11 ","
+    Whitespace@11..12 " "
+    DefPositionalArg@12..14
+      Ident@12..13 "b"
+      Whitespace@13..14 "\n"
+    BodyDef@14..14
+===
+Missing { expected: Unnamed(RParen), offset: 13 }
+Missing { expected: Named("value"), offset: 13 }
+===
+  Γ— syntax error
+   ╭────
+ 1 β”‚ function(a, b
+   Β·              β–²
+   Β·              β”‚╰── missing value
+   Β·              ╰── missing RParen
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__local_no_value_recovery.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__local_no_value_recovery.snap
@@ -0,0 +1,47 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "local a =\nlocal b = 3;\n1\n"
+
+---
+Root@0..25
+  Local@0..25
+    KeywordLocal@0..5 "local"
+    Whitespace@5..6 " "
+    Ident@6..7 "a"
+    Whitespace@7..8 " "
+    SymbolAssign@8..9 "="
+    Whitespace@9..10 "\n"
+    Local@10..25
+      KeywordLocal@10..15 "local"
+      Whitespace@15..16 " "
+      Ident@16..17 "b"
+      Whitespace@17..18 " "
+      SymbolAssign@18..19 "="
+      Whitespace@19..20 " "
+      Literal@20..21
+        Number@20..21 "3"
+      SymbolSemi@21..22 ";"
+      Whitespace@22..23 "\n"
+      BodyDef@23..25
+        Literal@23..25
+          Number@23..24 "1"
+          Whitespace@24..25 "\n"
+    BodyDef@25..25
+===
+Missing { expected: Unnamed(SymbolSemi), offset: 24 }
+Custom { error: "unusal local placement, missing ';' ?", range: 8..9 }
+Missing { expected: Named("value"), offset: 24 }
+===
+  Γ— syntax error
+   ╭─[1:1]
+ 1 β”‚ local a =
+   Β·         ┬
+   Β·         ╰── unusal local placement, missing ';' ?
+ 2 β”‚ local b = 3;
+ 3 β”‚ 1
+   Β·  β–²
+   Β·  β”‚╰── missing value
+   Β·  ╰── missing SymbolSemi
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__local_no_value_recovery.snap.newdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__local_no_value_recovery.snap.new
@@ -0,0 +1,43 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "local a =\nlocal b = 3;\n1\n"
+
+---
+Root@0..25
+  Local@0..25
+    KeywordLocal@0..5 "local"
+    Whitespace@5..6 " "
+    Ident@6..7 "a"
+    Whitespace@7..8 " "
+    SymbolAssign@8..9 "="
+    Whitespace@9..10 "\n"
+    Local@10..25
+      KeywordLocal@10..15 "local"
+      Whitespace@15..16 " "
+      Ident@16..17 "b"
+      Whitespace@17..18 " "
+      SymbolAssign@18..19 "="
+      Whitespace@19..20 " "
+      Literal@20..21
+        Number@20..21 "3"
+      SymbolSemi@21..22 ";"
+      Whitespace@22..23 "\n"
+      BodyDef@23..25
+        Literal@23..25
+          Number@23..24 "1"
+          Whitespace@24..25 "\n"
+    BodyDef@25..25
+===
+Missing { expected: Unnamed(SymbolSemi), offset: 24 }
+Missing { expected: Named("value"), offset: 24 }
+===
+  Γ— syntax error
+   ╭─[2:1]
+ 2 β”‚ local b = 3;
+ 3 β”‚ 1
+   Β·  β–²
+   Β·  β”‚╰── missing value
+   Β·  ╰── missing SymbolSemi
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__local_novalue.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__local_novalue.snap
@@ -0,0 +1,29 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "local a =\n"
+
+---
+Root@0..10
+  Local@0..10
+    KeywordLocal@0..5 "local"
+    Whitespace@5..6 " "
+    Ident@6..7 "a"
+    Whitespace@7..8 " "
+    SymbolAssign@8..9 "="
+    Whitespace@9..10 "\n"
+    BodyDef@10..10
+===
+Missing { expected: Named("value"), offset: 9 }
+Missing { expected: Unnamed(SymbolSemi), offset: 9 }
+Missing { expected: Named("value"), offset: 9 }
+===
+  Γ— syntax error
+   ╭────
+ 1 β”‚ local a =
+   Β·          β–²
+   Β·          ╰── missing value
+   Β·          β”‚╰── missing SymbolSemi
+   Β·          ╰── missing value
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__named_before_positional.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__named_before_positional.snap
@@ -0,0 +1,63 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "a(1, 2, b=4, 3, 5, k = 12, 6)\n"
+
+---
+Root@0..30
+  FunctionCall@0..30
+    Ident@0..1
+      Ident@0..1 "a"
+    LParen@1..2 "("
+    DefPositionalArg@2..3
+      Literal@2..3
+        Number@2..3 "1"
+    SymbolComma@3..4 ","
+    Whitespace@4..5 " "
+    DefPositionalArg@5..6
+      Literal@5..6
+        Number@5..6 "2"
+    SymbolComma@6..7 ","
+    Whitespace@7..8 " "
+    DefNamedArg@8..11
+      Ident@8..9 "b"
+      SymbolAssign@9..10 "="
+      Literal@10..11
+        Number@10..11 "4"
+    SymbolComma@11..12 ","
+    Whitespace@12..13 " "
+    ErrorPositionalAfterNamed@13..28
+      DefPositionalArg@13..14
+        Literal@13..14
+          Number@13..14 "3"
+      SymbolComma@14..15 ","
+      Whitespace@15..16 " "
+      DefPositionalArg@16..17
+        Literal@16..17
+          Number@16..17 "5"
+      SymbolComma@17..18 ","
+      Whitespace@18..19 " "
+      DefNamedArg@19..25
+        Ident@19..20 "k"
+        Whitespace@20..21 " "
+        SymbolAssign@21..22 "="
+        Whitespace@22..23 " "
+        Literal@23..25
+          Number@23..25 "12"
+      SymbolComma@25..26 ","
+      Whitespace@26..27 " "
+      DefPositionalArg@27..28
+        Literal@27..28
+          Number@27..28 "6"
+    RParen@28..29 ")"
+    Whitespace@29..30 "\n"
+===
+Custom { error: "positional arguments can't be placed after named", range: 13..28 }
+===
+  Γ— syntax error
+   ╭────
+ 1 β”‚ a(1, 2, b=4, 3, 5, k = 12, 6)
+   Β·              ───────┬───────
+   Β·                     ╰── positional arguments can't be placed after named
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__no_lhs.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__no_lhs.snap
@@ -0,0 +1,23 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "+ 2\n"
+
+---
+Root@0..4
+  OpPlus@0..1 "+"
+  Whitespace@1..2 " "
+  Number@2..3 "2"
+  Whitespace@3..4 "\n"
+===
+Missing { expected: Named("value"), offset: 0 }
+Custom { error: "unexpected input after expression", range: 0..3 }
+===
+  Γ— syntax error
+   ╭────
+ 1 β”‚ + 2
+   Β· β–²─┬─
+   Β· β”‚╰── unexpected input after expression
+   Β· ╰── missing value
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__no_operator.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__no_operator.snap
@@ -0,0 +1,22 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "2 2\n"
+
+---
+Root@0..4
+  Literal@0..2
+    Number@0..1 "2"
+    Whitespace@1..2 " "
+  Number@2..3 "2"
+  Whitespace@3..4 "\n"
+===
+Custom { error: "unexpected input after expression", range: 2..3 }
+===
+  Γ— syntax error
+   ╭────
+ 1 β”‚ 2 2
+   Β·   ┬
+   Β·   ╰── unexpected input after expression
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__no_rhs.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__no_rhs.snap
@@ -0,0 +1,23 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "a +\n"
+
+---
+Root@0..4
+  BinOp@0..4
+    Ident@0..2
+      Ident@0..1 "a"
+      Whitespace@1..2 " "
+    OpPlus@2..3 "+"
+    Whitespace@3..4 "\n"
+===
+Missing { expected: Named("value"), offset: 3 }
+===
+  Γ— syntax error
+   ╭────
+ 1 β”‚ a +
+   Β·    β–²
+   Β·    ╰── missing value
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__wrong_field_end.snap.newdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__wrong_field_end.snap.new
@@ -0,0 +1,41 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "{\n\ta: 1;\n\tb: 2;\n}\n"
+
+---
+Root@0..18
+  Object@0..7
+    SymbolLeftBrace@0..1 "{"
+    Whitespace@1..3 "\n\t"
+    Field@3..7
+      Ident@3..4 "a"
+      SymbolColon@4..5 ":"
+      Whitespace@5..6 " "
+      Literal@6..7
+        Number@6..7 "1"
+  SymbolSemi@7..8 ";"
+  Whitespace@8..10 "\n\t"
+  Ident@10..11 "b"
+  SymbolColon@11..12 ":"
+  Whitespace@12..13 " "
+  Number@13..14 "2"
+  SymbolSemi@14..15 ";"
+  Whitespace@15..16 "\n"
+  SymbolRightBrace@16..17 "}"
+  Whitespace@17..18 "\n"
+===
+Missing { expected: Unnamed(SymbolRightBrace), offset: 7 }
+Custom { error: "unexpected input after expression", range: 7..17 }
+===
+  Γ— syntax error
+   ╭─[1:1]
+ 1 β”‚     {
+ 2 β”‚ β•­─β–Ά 	a: 1;
+   Β· β”‚β”‚     β–²
+   Β· β”‚β”‚     ╰── missing SymbolRightBrace
+ 3 β”‚ β”‚   	b: 2;
+ 4 β”‚ β”œ─β–Ά }
+   Β· β•°──── unexpected input after expression
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/string_block.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/string_block.rs
@@ -0,0 +1,221 @@
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum StringBlockToken {
+	Valid,
+	UnexpectedEndOfString,
+	MissingTextBlockNewLine,
+	MissingTextBlockTermination,
+	MissingTextBlockIndent,
+}
+
+use std::ops::Range;
+
+use StringBlockToken::*;
+
+use crate::lex::SyntaxKind;
+
+pub fn lex_str_block_test<'a>(lex: &mut logos::Lexer<'a, SyntaxKind>) {
+	lex_str_block(lex);
+}
+
+fn lex_str_block<'a>(lex: &mut logos::Lexer<'a, SyntaxKind>) -> StringBlockToken {
+	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 logos::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 UnexpectedEndOfString;
+		}
+		// Text block requires new line after |||.
+		Some(_) => {
+			guess_token_end_and_bump(lex, &ctx);
+			return MissingTextBlockNewLine;
+		}
+	}
+
+	// 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 MissingTextBlockIndent;
+	}
+
+	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 UnexpectedEndOfString;
+				}
+				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);
+			loop {
+				match ctx.peek() {
+					Some(' ') | Some('\t') => {
+						term_indent.push(ctx.next().unwrap());
+					}
+					_ => break,
+				}
+			}
+
+			if !ctx.rest().starts_with("|||") {
+				// Text block not terminated with |||
+				let pos = ctx.pos();
+				if pos.len() == 0 {
+					// eof
+					lex.bump(ctx.index);
+					return UnexpectedEndOfString;
+				}
+
+				guess_token_end_and_bump(lex, &ctx);
+				return MissingTextBlockTermination;
+			}
+
+			// Skip '|||'
+			ctx.skip(3);
+			break;
+		}
+	}
+
+	lex.bump(ctx.index);
+	Valid
+}
addedcrates/jrsonnet-rowan-parser/src/token_set.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/token_set.rs
@@ -0,0 +1,31 @@
+use crate::lex::SyntaxKind;
+
+#[derive(Clone, Copy, Default)]
+pub struct TokenSet(u64);
+
+impl TokenSet {
+	pub const EMPTY: Self = Self(0);
+	pub const ALL: Self = Self(u64::MAX);
+
+	pub const fn new(kinds: &[SyntaxKind]) -> TokenSet {
+		let mut res = 0u64;
+		let mut i = 0;
+		while i < kinds.len() {
+			res |= mask(kinds[i]);
+			i += 1
+		}
+		TokenSet(res)
+	}
+
+	pub const fn union(self, other: TokenSet) -> TokenSet {
+		TokenSet(self.0 | other.0)
+	}
+
+	pub const fn contains(&self, kind: SyntaxKind) -> bool {
+		self.0 & mask(kind) != 0
+	}
+}
+
+const fn mask(kind: SyntaxKind) -> u64 {
+	1u64 << (kind as usize)
+}
addedcrates/jrsonnet-rowan-parser/src/unary.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/unary.rs
@@ -0,0 +1,15 @@
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum UnaryOperator {
+	Minus,
+	Not,
+	BitNegate,
+}
+impl UnaryOperator {
+	pub fn binding_power(&self) -> ((), u8) {
+		match self {
+			UnaryOperator::Minus => ((), 20),
+			UnaryOperator::Not => ((), 20),
+			UnaryOperator::BitNegate => ((), 20),
+		}
+	}
+}
addedjrsonnet-lsp/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/jrsonnet-lsp/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "jrsonnet-lsp"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1.0.48"
+jrsonnet-evaluator = { path = "../jrsonnet-evaluator" }
+jrsonnet-parser = { path = "../jrsonnet-parser" }
+lsp-server = "0.5.2"
+lsp-types = "0.92.0"
+serde = "1.0.130"
+serde_json = "1.0.71"
addedjrsonnet-lsp/src/main.rsdiffbeforeafterboth
--- /dev/null
+++ b/jrsonnet-lsp/src/main.rs
@@ -0,0 +1,211 @@
+use std::{
+	collections::HashMap,
+	fs::File,
+	path::{Path, PathBuf},
+	str::FromStr,
+};
+
+use jrsonnet_evaluator::{EvaluationState, FileImportResolver, Val};
+use jrsonnet_parser::{ExprLocation, ParserSettings};
+use lsp_server::{Connection, ErrorCode, Message, Request, RequestId, Response};
+use lsp_types::{
+	notification::{DidChangeTextDocument, DidOpenTextDocument, Notification},
+	request::{DocumentLinkRequest, HoverRequest},
+	CompletionOptions, DidChangeTextDocumentParams, DidOpenTextDocumentParams, DocumentLink,
+	DocumentLinkOptions, Hover, HoverContents, MarkupContent, MarkupKind, ServerCapabilities,
+	TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, Url,
+	WorkDoneProgressOptions,
+};
+
+use std::io::Write;
+
+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
+				/*
+				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
+				*/
+				{
+					reply(Response::new_err(
+						req.id,
+						ErrorCode::MethodNotFound as i32,
+						format!("unrecognized request {}", req.method),
+					))
+				}
+			}
+			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 parsed = match jrsonnet_parser::parse(
+						&text,
+						&ParserSettings {
+							file_name: path.clone().into(),
+						},
+					) {
+						Ok(v) => v,
+						Err(e) => {
+							writeln!(log, "fuck D: {:?}", e).unwrap();
+							return;
+							// connection.sender.send(Message::Notification(Notification::new_err(req.id, ErrorCode::ParseError as i32, format!("Fuck D: {:?}", e))))
+						}
+					};
+					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()
+}