Compare commits
3 Commits
4cda68e635
...
dff87fd5c2
| Author | SHA1 | Date | |
|---|---|---|---|
| dff87fd5c2 | |||
| 792b0c21d0 | |||
| 9d8d8901d7 |
270
Cargo.lock
generated
270
Cargo.lock
generated
@@ -61,6 +61,22 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.102"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ariadne"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8454c8a44ce2cb9cc7e7fae67fc6128465b343b92c6631e94beca3c8d1524ea5"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-width",
|
||||||
|
"yansi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
@@ -85,6 +101,17 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chacha20"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.55"
|
version = "4.5.55"
|
||||||
@@ -143,6 +170,15 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "diff"
|
name = "diff"
|
||||||
version = "0.1.13"
|
version = "0.1.13"
|
||||||
@@ -178,6 +214,12 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.14"
|
version = "0.3.14"
|
||||||
@@ -194,6 +236,12 @@ version = "2.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foldhash"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
@@ -206,18 +254,65 @@ dependencies = [
|
|||||||
"wasip2",
|
"wasip2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"r-efi",
|
||||||
|
"rand_core",
|
||||||
|
"wasip2",
|
||||||
|
"wasip3",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glob"
|
name = "glob"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.15.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||||
|
dependencies = [
|
||||||
|
"foldhash",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.16.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "id-arena"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "2.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
||||||
|
dependencies = [
|
||||||
|
"equivalent",
|
||||||
|
"hashbrown 0.16.1",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "insta"
|
name = "insta"
|
||||||
version = "1.46.1"
|
version = "1.46.1"
|
||||||
@@ -266,6 +361,12 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "leb128fmt"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.180"
|
version = "0.2.180"
|
||||||
@@ -339,6 +440,16 @@ dependencies = [
|
|||||||
"yansi",
|
"yansi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prettyplease"
|
||||||
|
version = "0.2.37"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.106"
|
version = "1.0.106"
|
||||||
@@ -363,6 +474,23 @@ version = "5.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8"
|
||||||
|
dependencies = [
|
||||||
|
"chacha20",
|
||||||
|
"getrandom 0.4.1",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.12.2"
|
version = "1.12.2"
|
||||||
@@ -405,6 +533,12 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "semver"
|
||||||
|
version = "1.0.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.228"
|
version = "1.0.228"
|
||||||
@@ -451,6 +585,7 @@ dependencies = [
|
|||||||
name = "shed"
|
name = "shed"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"ariadne",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"clap",
|
"clap",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
@@ -459,12 +594,14 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"nix",
|
"nix",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
|
"rand",
|
||||||
"regex",
|
"regex",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
"vte",
|
"vte",
|
||||||
|
"yansi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -497,7 +634,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
|
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"getrandom",
|
"getrandom 0.3.4",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
@@ -521,6 +658,12 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8parse"
|
name = "utf8parse"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -546,6 +689,49 @@ dependencies = [
|
|||||||
"wit-bindgen",
|
"wit-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasip3"
|
||||||
|
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-encoder"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
|
||||||
|
dependencies = [
|
||||||
|
"leb128fmt",
|
||||||
|
"wasmparser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-metadata"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"indexmap",
|
||||||
|
"wasm-encoder",
|
||||||
|
"wasmparser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmparser"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"hashbrown 0.15.5",
|
||||||
|
"indexmap",
|
||||||
|
"semver",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@@ -639,6 +825,88 @@ name = "wit-bindgen"
|
|||||||
version = "0.51.0"
|
version = "0.51.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen-rust-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-core"
|
||||||
|
version = "0.51.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"heck",
|
||||||
|
"wit-parser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rust"
|
||||||
|
version = "0.51.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"heck",
|
||||||
|
"indexmap",
|
||||||
|
"prettyplease",
|
||||||
|
"syn",
|
||||||
|
"wasm-metadata",
|
||||||
|
"wit-bindgen-core",
|
||||||
|
"wit-component",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rust-macro"
|
||||||
|
version = "0.51.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"prettyplease",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wit-bindgen-core",
|
||||||
|
"wit-bindgen-rust",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-component"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"bitflags",
|
||||||
|
"indexmap",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"wasm-encoder",
|
||||||
|
"wasm-metadata",
|
||||||
|
"wasmparser",
|
||||||
|
"wit-parser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-parser"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"id-arena",
|
||||||
|
"indexmap",
|
||||||
|
"log",
|
||||||
|
"semver",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"unicode-xid",
|
||||||
|
"wasmparser",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yansi"
|
name = "yansi"
|
||||||
|
|||||||
@@ -10,17 +10,20 @@ edition = "2024"
|
|||||||
debug = true
|
debug = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
ariadne = "0.6.0"
|
||||||
bitflags = "2.8.0"
|
bitflags = "2.8.0"
|
||||||
clap = { version = "4.5.38", features = ["derive"] }
|
clap = { version = "4.5.38", features = ["derive"] }
|
||||||
env_logger = "0.11.9"
|
env_logger = "0.11.9"
|
||||||
glob = "0.3.2"
|
glob = "0.3.2"
|
||||||
log = "0.4.29"
|
log = "0.4.29"
|
||||||
nix = { version = "0.29.0", features = ["uio", "term", "user", "hostname", "fs", "default", "signal", "process", "event", "ioctl", "poll"] }
|
nix = { version = "0.29.0", features = ["uio", "term", "user", "hostname", "fs", "default", "signal", "process", "event", "ioctl", "poll"] }
|
||||||
|
rand = "0.10.0"
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
serde_json = "1.0.149"
|
serde_json = "1.0.149"
|
||||||
unicode-segmentation = "1.12.0"
|
unicode-segmentation = "1.12.0"
|
||||||
unicode-width = "0.2.0"
|
unicode-width = "0.2.0"
|
||||||
vte = "0.15"
|
vte = "0.15"
|
||||||
|
yansi = "1.0.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
insta = "1.42.2"
|
insta = "1.42.2"
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ pub fn alias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
// Display the environment variables
|
// Display the environment variables
|
||||||
@@ -37,19 +38,11 @@ pub fn alias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
} else {
|
} else {
|
||||||
for (arg, span) in argv {
|
for (arg, span) in argv {
|
||||||
if arg == "command" || arg == "builtin" {
|
if arg == "command" || arg == "builtin" {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::ExecFail, span, format!("alias: Cannot assign alias to reserved name '{arg}'")));
|
||||||
ShErrKind::ExecFail,
|
|
||||||
format!("alias: Cannot assign alias to reserved name '{arg}'"),
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some((name, body)) = arg.split_once('=') else {
|
let Some((name, body)) = arg.split_once('=') else {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::SyntaxErr, span, "alias: Expected an assignment in alias args"));
|
||||||
ShErrKind::SyntaxErr,
|
|
||||||
"alias: Expected an assignment in alias args",
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
write_logic(|l| l.insert_alias(name, body));
|
write_logic(|l| l.insert_alias(name, body));
|
||||||
}
|
}
|
||||||
@@ -67,7 +60,8 @@ pub fn unalias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResul
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
// Display the environment variables
|
// Display the environment variables
|
||||||
@@ -86,11 +80,7 @@ pub fn unalias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResul
|
|||||||
} else {
|
} else {
|
||||||
for (arg, span) in argv {
|
for (arg, span) in argv {
|
||||||
if read_logic(|l| l.get_alias(&arg)).is_none() {
|
if read_logic(|l| l.get_alias(&arg)).is_none() {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::SyntaxErr, span, format!("unalias: alias '{arg}' not found")));
|
||||||
ShErrKind::SyntaxErr,
|
|
||||||
format!("unalias: alias '{arg}' not found"),
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
write_logic(|l| l.remove_alias(&arg))
|
write_logic(|l| l.remove_alias(&arg))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::iter::Peekable;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
getopt::{Opt, OptSpec, get_opts_from_tokens}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node}, prelude::*, procio::{IoStack, borrow_fd}, state::{self, VarFlags, VarKind, write_vars}
|
getopt::{Opt, OptSpec, get_opts_from_tokens}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node}, prelude::*, procio::{IoStack, borrow_fd}, state::{self, VarFlags, VarKind, write_vars}
|
||||||
};
|
};
|
||||||
@@ -51,7 +53,8 @@ fn arr_pop_inner(node: Node, io_stack: &mut IoStack, job: &mut JobBldr, end: End
|
|||||||
|
|
||||||
let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?;
|
let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?;
|
||||||
let arr_op_opts = get_arr_op_opts(opts)?;
|
let arr_op_opts = get_arr_op_opts(opts)?;
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
let mut status = 0;
|
let mut status = 0;
|
||||||
|
|
||||||
@@ -81,6 +84,7 @@ fn arr_pop_inner(node: Node, io_stack: &mut IoStack, job: &mut JobBldr, end: End
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn arr_push_inner(node: Node, io_stack: &mut IoStack, job: &mut JobBldr, end: End) -> ShResult<()> {
|
fn arr_push_inner(node: Node, io_stack: &mut IoStack, job: &mut JobBldr, end: End) -> ShResult<()> {
|
||||||
|
let blame = node.get_span().clone();
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
argv,
|
argv,
|
||||||
@@ -91,11 +95,12 @@ fn arr_push_inner(node: Node, io_stack: &mut IoStack, job: &mut JobBldr, end: En
|
|||||||
|
|
||||||
let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?;
|
let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?;
|
||||||
let _arr_op_opts = get_arr_op_opts(opts)?;
|
let _arr_op_opts = get_arr_op_opts(opts)?;
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
let mut argv = argv.into_iter();
|
let mut argv = argv.into_iter();
|
||||||
let Some((name, _)) = argv.next() else {
|
let Some((name, _)) = argv.next() else {
|
||||||
return Err(ShErr::simple(ShErrKind::ExecFail, "push: missing array name".to_string()));
|
return Err(ShErr::at(ShErrKind::ExecFail, blame, "push: missing array name".to_string()));
|
||||||
};
|
};
|
||||||
|
|
||||||
for (val, _) in argv {
|
for (val, _) in argv {
|
||||||
@@ -140,7 +145,8 @@ pub fn arr_rotate(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShRe
|
|||||||
|
|
||||||
let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?;
|
let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?;
|
||||||
let arr_op_opts = get_arr_op_opts(opts)?;
|
let arr_op_opts = get_arr_op_opts(opts)?;
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
for (arg, _) in argv {
|
for (arg, _) in argv {
|
||||||
write_vars(|v| -> ShResult<()> {
|
write_vars(|v| -> ShResult<()> {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
use ariadne::Fmt;
|
||||||
|
use yansi::Color;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
jobs::JobBldr,
|
jobs::JobBldr,
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
libsh::error::{ShErr, ShErrKind, ShResult, next_color},
|
||||||
parse::{NdRule, Node},
|
parse::{NdRule, Node},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
state::{self},
|
state::{self},
|
||||||
@@ -17,44 +20,40 @@ pub fn cd(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
|||||||
else {
|
else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
let cd_span = argv.first().unwrap().span.clone();
|
||||||
|
|
||||||
let (argv, _) = setup_builtin(argv, job, None)?;
|
let (argv, _) = setup_builtin(Some(argv), job, None)?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
let new_dir = if let Some((arg, _)) = argv.into_iter().next() {
|
let (new_dir,arg_span) = if let Some((arg, span)) = argv.into_iter().next() {
|
||||||
PathBuf::from(arg)
|
(PathBuf::from(arg),Some(span))
|
||||||
} else {
|
} else {
|
||||||
PathBuf::from(env::var("HOME").unwrap())
|
(PathBuf::from(env::var("HOME").unwrap()),None)
|
||||||
};
|
};
|
||||||
|
|
||||||
if !new_dir.exists() {
|
if !new_dir.exists() {
|
||||||
return Err(ShErr::full(
|
let mut err = ShErr::new(
|
||||||
ShErrKind::ExecFail,
|
ShErrKind::ExecFail,
|
||||||
format!("cd: No such file or directory '{}'", new_dir.display()),
|
span.clone(),
|
||||||
span,
|
).labeled(cd_span.clone(), "Failed to change directory");
|
||||||
));
|
if let Some(span) = arg_span {
|
||||||
|
err = err.labeled(span, format!("No such file or directory '{}'", new_dir.display().fg(next_color())));
|
||||||
|
}
|
||||||
|
return Err(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !new_dir.is_dir() {
|
if !new_dir.is_dir() {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::new(ShErrKind::ExecFail, span.clone())
|
||||||
ShErrKind::ExecFail,
|
.labeled(cd_span.clone(), format!("cd: Not a directory '{}'", new_dir.display().fg(next_color()))));
|
||||||
format!("cd: Not a directory '{}'", new_dir.display()),
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = env::set_current_dir(new_dir) {
|
if let Err(e) = env::set_current_dir(new_dir) {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::new(ShErrKind::ExecFail, span.clone())
|
||||||
ShErrKind::ExecFail,
|
.labeled(cd_span.clone(), format!("cd: Failed to change directory: '{}'", e.fg(Color::Red))));
|
||||||
format!("cd: Failed to change directory: {}", e),
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
let new_dir = env::current_dir().map_err(|e| {
|
let new_dir = env::current_dir().map_err(|e| {
|
||||||
ShErr::full(
|
ShErr::new(ShErrKind::ExecFail, span.clone())
|
||||||
ShErrKind::ExecFail,
|
.labeled(cd_span.clone(), format!("cd: Failed to get current directory: '{}'", e.fg(Color::Red)))
|
||||||
format!("cd: Failed to get current directory: {}", e),
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
unsafe { env::set_var("PWD", new_dir) };
|
unsafe { env::set_var("PWD", new_dir) };
|
||||||
|
|
||||||
|
|||||||
@@ -168,7 +168,8 @@ pub fn complete_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -
|
|||||||
|
|
||||||
let (argv, opts) = get_opts_from_tokens(argv, &COMP_OPTS)?;
|
let (argv, opts) = get_opts_from_tokens(argv, &COMP_OPTS)?;
|
||||||
let comp_opts = get_comp_opts(opts)?;
|
let comp_opts = get_comp_opts(opts)?;
|
||||||
let (argv, _) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
if comp_opts.flags.contains(CompFlags::PRINT) {
|
if comp_opts.flags.contains(CompFlags::PRINT) {
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
@@ -205,11 +206,7 @@ pub fn complete_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -
|
|||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
state::set_status(1);
|
state::set_status(1);
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::ExecFail, blame, "complete: no command specified"));
|
||||||
ShErrKind::ExecFail,
|
|
||||||
"complete: no command specified",
|
|
||||||
blame,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let comp_spec = BashCompSpec::from_comp_opts(comp_opts).with_source(src);
|
let comp_spec = BashCompSpec::from_comp_opts(comp_opts).with_source(src);
|
||||||
@@ -242,7 +239,7 @@ pub fn compgen_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) ->
|
|||||||
let (argv, opts) = get_opts_from_tokens(argv, &COMPGEN_OPTS)?;
|
let (argv, opts) = get_opts_from_tokens(argv, &COMPGEN_OPTS)?;
|
||||||
let prefix = argv.clone().into_iter().nth(1).unwrap_or_default();
|
let prefix = argv.clone().into_iter().nth(1).unwrap_or_default();
|
||||||
let comp_opts = get_comp_opts(opts)?;
|
let comp_opts = get_comp_opts(opts)?;
|
||||||
let (_, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (_, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
let comp_spec = BashCompSpec::from_comp_opts(comp_opts).with_source(src);
|
let comp_spec = BashCompSpec::from_comp_opts(comp_opts).with_source(src);
|
||||||
|
|
||||||
@@ -284,11 +281,8 @@ pub fn get_comp_opts(opts: Vec<Opt>) -> ShResult<CompOpts> {
|
|||||||
"dirnames" => comp_opts.opt_flags |= CompOptFlags::DIRNAMES,
|
"dirnames" => comp_opts.opt_flags |= CompOptFlags::DIRNAMES,
|
||||||
"space" => comp_opts.opt_flags |= CompOptFlags::SPACE,
|
"space" => comp_opts.opt_flags |= CompOptFlags::SPACE,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShErr::full(
|
let span: crate::parse::lex::Span = Default::default();
|
||||||
ShErrKind::InvalidOpt,
|
return Err(ShErr::at(ShErrKind::InvalidOpt, span, format!("complete: invalid option: {}", opt_flag)));
|
||||||
format!("complete: invalid option: {}", opt_flag),
|
|
||||||
Default::default(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -47,26 +47,18 @@ fn print_dirs() -> ShResult<()> {
|
|||||||
|
|
||||||
fn change_directory(target: &PathBuf, blame: Span) -> ShResult<()> {
|
fn change_directory(target: &PathBuf, blame: Span) -> ShResult<()> {
|
||||||
if !target.is_dir() {
|
if !target.is_dir() {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, format!("not a directory: {}", target.display()))
|
||||||
format!("not a directory: {}", target.display()),
|
);
|
||||||
blame,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = env::set_current_dir(target) {
|
if let Err(e) = env::set_current_dir(target) {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, format!("Failed to change directory: {}", e))
|
||||||
format!("Failed to change directory: {}", e),
|
);
|
||||||
blame,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
let new_dir = env::current_dir().map_err(|e| {
|
let new_dir = env::current_dir().map_err(|e| {
|
||||||
ShErr::full(
|
ShErr::at(ShErrKind::ExecFail, blame, format!("Failed to get current directory: {}", e))
|
||||||
ShErrKind::ExecFail,
|
|
||||||
format!("Failed to get current directory: {}", e),
|
|
||||||
blame,
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
unsafe { env::set_var("PWD", new_dir) };
|
unsafe { env::set_var("PWD", new_dir) };
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -82,32 +74,24 @@ fn parse_stack_idx(arg: &str, blame: Span, cmd: &str) -> ShResult<StackIdx> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if digits.is_empty() {
|
if digits.is_empty() {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, format!(
|
||||||
format!(
|
|
||||||
"{cmd}: missing index after '{}'",
|
"{cmd}: missing index after '{}'",
|
||||||
if from_top { "+" } else { "-" }
|
if from_top { "+" } else { "-" }
|
||||||
),
|
))
|
||||||
blame,
|
);
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for ch in digits.chars() {
|
for ch in digits.chars() {
|
||||||
if !ch.is_ascii_digit() {
|
if !ch.is_ascii_digit() {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, format!("{cmd}: invalid argument: {arg}"))
|
||||||
format!("{cmd}: invalid argument: {arg}"),
|
);
|
||||||
blame,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let n = digits.parse::<usize>().map_err(|e| {
|
let n = digits.parse::<usize>().map_err(|e| {
|
||||||
ShErr::full(
|
ShErr::at(ShErrKind::ExecFail, blame, format!("{cmd}: invalid index: {e}"))
|
||||||
ShErrKind::ExecFail,
|
|
||||||
format!("{cmd}: invalid index: {e}"),
|
|
||||||
blame,
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if from_top {
|
if from_top {
|
||||||
@@ -127,7 +111,8 @@ pub fn pushd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
let mut dir = None;
|
let mut dir = None;
|
||||||
let mut rotate_idx = None;
|
let mut rotate_idx = None;
|
||||||
@@ -141,26 +126,20 @@ pub fn pushd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
} else if arg == "-n" {
|
} else if arg == "-n" {
|
||||||
no_cd = true;
|
no_cd = true;
|
||||||
} else if arg.starts_with('-') {
|
} else if arg.starts_with('-') {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, format!("pushd: invalid option: {arg}"))
|
||||||
format!("pushd: invalid option: {arg}"),
|
);
|
||||||
blame.clone(),
|
|
||||||
));
|
|
||||||
} else {
|
} else {
|
||||||
if dir.is_some() {
|
if dir.is_some() {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, "pushd: too many arguments")
|
||||||
"pushd: too many arguments".to_string(),
|
);
|
||||||
blame.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
let target = PathBuf::from(&arg);
|
let target = PathBuf::from(&arg);
|
||||||
if !target.is_dir() {
|
if !target.is_dir() {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, format!("pushd: not a directory: {arg}"))
|
||||||
format!("pushd: not a directory: {arg}"),
|
);
|
||||||
blame.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
dir = Some(target);
|
dir = Some(target);
|
||||||
}
|
}
|
||||||
@@ -213,7 +192,8 @@ pub fn popd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
let mut remove_idx = None;
|
let mut remove_idx = None;
|
||||||
let mut no_cd = false;
|
let mut no_cd = false;
|
||||||
@@ -226,11 +206,9 @@ pub fn popd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
} else if arg == "-n" {
|
} else if arg == "-n" {
|
||||||
no_cd = true;
|
no_cd = true;
|
||||||
} else if arg.starts_with('-') {
|
} else if arg.starts_with('-') {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, format!("popd: invalid option: {arg}"))
|
||||||
format!("popd: invalid option: {arg}"),
|
);
|
||||||
blame.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,11 +221,9 @@ pub fn popd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
if let Some(dir) = dir {
|
if let Some(dir) = dir {
|
||||||
change_directory(&dir, blame.clone())?;
|
change_directory(&dir, blame.clone())?;
|
||||||
} else {
|
} else {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, "popd: directory stack empty")
|
||||||
"popd: directory stack empty".to_string(),
|
);
|
||||||
blame.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,11 +233,9 @@ pub fn popd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
let dirs = m.dirs_mut();
|
let dirs = m.dirs_mut();
|
||||||
let idx = n - 1;
|
let idx = n - 1;
|
||||||
if idx >= dirs.len() {
|
if idx >= dirs.len() {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame.clone(), format!("popd: directory index out of range: +{n}"))
|
||||||
format!("popd: directory index out of range: +{n}"),
|
);
|
||||||
blame.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
dirs.remove(idx);
|
dirs.remove(idx);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -271,11 +245,7 @@ pub fn popd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
write_meta(|m| -> ShResult<()> {
|
write_meta(|m| -> ShResult<()> {
|
||||||
let dirs = m.dirs_mut();
|
let dirs = m.dirs_mut();
|
||||||
let actual = dirs.len().checked_sub(n + 1).ok_or_else(|| {
|
let actual = dirs.len().checked_sub(n + 1).ok_or_else(|| {
|
||||||
ShErr::full(
|
ShErr::at(ShErrKind::ExecFail, blame.clone(), format!("popd: directory index out of range: -{n}"))
|
||||||
ShErrKind::ExecFail,
|
|
||||||
format!("popd: directory index out of range: -{n}"),
|
|
||||||
blame.clone(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
dirs.remove(actual);
|
dirs.remove(actual);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -295,11 +265,9 @@ pub fn popd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
change_directory(&dir, blame.clone())?;
|
change_directory(&dir, blame.clone())?;
|
||||||
print_dirs()?;
|
print_dirs()?;
|
||||||
} else {
|
} else {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, "popd: directory stack empty")
|
||||||
"popd: directory stack empty".to_string(),
|
);
|
||||||
blame.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,7 +284,8 @@ pub fn dirs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
let mut abbreviate_home = true;
|
let mut abbreviate_home = true;
|
||||||
let mut one_per_line = false;
|
let mut one_per_line = false;
|
||||||
@@ -337,18 +306,14 @@ pub fn dirs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
target_idx = Some(parse_stack_idx(&arg, blame.clone(), "dirs")?);
|
target_idx = Some(parse_stack_idx(&arg, blame.clone(), "dirs")?);
|
||||||
}
|
}
|
||||||
_ if arg.starts_with('-') => {
|
_ if arg.starts_with('-') => {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, format!("dirs: invalid option: {arg}"))
|
||||||
format!("dirs: invalid option: {arg}"),
|
);
|
||||||
blame.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, format!("dirs: unexpected argument: {arg}"))
|
||||||
format!("dirs: unexpected argument: {arg}"),
|
);
|
||||||
blame.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -393,17 +358,15 @@ pub fn dirs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
if let Some(dir) = target {
|
if let Some(dir) = target {
|
||||||
dirs = vec![dir.clone()];
|
dirs = vec![dir.clone()];
|
||||||
} else {
|
} else {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, format!(
|
||||||
format!(
|
|
||||||
"dirs: directory index out of range: {}",
|
"dirs: directory index out of range: {}",
|
||||||
match idx {
|
match idx {
|
||||||
StackIdx::FromTop(n) => format!("+{n}"),
|
StackIdx::FromTop(n) => format!("+{n}"),
|
||||||
StackIdx::FromBottom(n) => format!("-{n}"),
|
StackIdx::FromBottom(n) => format!("-{n}"),
|
||||||
}
|
}
|
||||||
),
|
))
|
||||||
blame.clone(),
|
);
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
assert!(!argv.is_empty());
|
assert!(!argv.is_empty());
|
||||||
let (argv, opts) = get_opts_from_tokens(argv, &ECHO_OPTS)?;
|
let (argv, opts) = get_opts_from_tokens(argv, &ECHO_OPTS)?;
|
||||||
let flags = get_echo_flags(opts).blame(blame)?;
|
let flags = get_echo_flags(opts).blame(blame)?;
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
let output_channel = if flags.contains(EchoFlags::USE_STDERR) {
|
let output_channel = if flags.contains(EchoFlags::USE_STDERR) {
|
||||||
borrow_fd(STDERR_FILENO)
|
borrow_fd(STDERR_FILENO)
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ pub fn eval(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (expanded_argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (expanded_argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let expanded_argv = expanded_argv.unwrap();
|
||||||
|
|
||||||
if expanded_argv.is_empty() {
|
if expanded_argv.is_empty() {
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ pub fn exec_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> Sh
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (expanded_argv, guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (expanded_argv, guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let expanded_argv = expanded_argv.unwrap();
|
||||||
if let Some(g) = guard {
|
if let Some(g) = guard {
|
||||||
// Persist redirections so they affect the entire shell,
|
// Persist redirections so they affect the entire shell,
|
||||||
// not just this command call
|
// not just this command call
|
||||||
@@ -40,7 +41,10 @@ pub fn exec_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> Sh
|
|||||||
// execvpe only returns on error
|
// execvpe only returns on error
|
||||||
let cmd_str = cmd.to_str().unwrap().to_string();
|
let cmd_str = cmd.to_str().unwrap().to_string();
|
||||||
match e {
|
match e {
|
||||||
Errno::ENOENT => Err(ShErr::full(ShErrKind::CmdNotFound(cmd_str), "", span)),
|
Errno::ENOENT => Err(
|
||||||
_ => Err(ShErr::full(ShErrKind::Errno(e), format!("{e}"), span)),
|
ShErr::new(ShErrKind::CmdNotFound, span.clone())
|
||||||
|
.labeled(span, format!("exec: command not found: {}", cmd_str))
|
||||||
|
),
|
||||||
|
_ => Err(ShErr::at(ShErrKind::Errno(e), span, format!("{e}"))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,7 @@ pub fn flowctl(node: Node, kind: ShErrKind) -> ShResult<()> {
|
|||||||
let (arg, span) = argv.into_iter().next().unwrap();
|
let (arg, span) = argv.into_iter().next().unwrap();
|
||||||
|
|
||||||
let Ok(status) = arg.parse::<i32>() else {
|
let Ok(status) = arg.parse::<i32>() else {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::SyntaxErr, span, format!("{cmd}: Expected a number")));
|
||||||
ShErrKind::SyntaxErr,
|
|
||||||
format!("{cmd}: Expected a number"),
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
code = status;
|
code = status;
|
||||||
|
|||||||
@@ -28,21 +28,18 @@ pub fn continue_job(node: Node, job: &mut JobBldr, behavior: JobBehavior) -> ShR
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _) = setup_builtin(argv, job, None)?;
|
let (argv, _) = setup_builtin(Some(argv), job, None)?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
let mut argv = argv.into_iter();
|
let mut argv = argv.into_iter();
|
||||||
|
|
||||||
if read_jobs(|j| j.get_fg().is_some()) {
|
if read_jobs(|j| j.get_fg().is_some()) {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::InternalErr, blame, format!("Somehow called '{}' with an existing foreground job", cmd)));
|
||||||
ShErrKind::InternalErr,
|
|
||||||
format!("Somehow called '{}' with an existing foreground job", cmd),
|
|
||||||
blame,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
|
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
|
||||||
id
|
id
|
||||||
} else {
|
} else {
|
||||||
return Err(ShErr::full(ShErrKind::ExecFail, "No jobs found", blame));
|
return Err(ShErr::at(ShErrKind::ExecFail, blame, "No jobs found"));
|
||||||
};
|
};
|
||||||
|
|
||||||
let tabid = match argv.next() {
|
let tabid = match argv.next() {
|
||||||
@@ -56,11 +53,7 @@ pub fn continue_job(node: Node, job: &mut JobBldr, behavior: JobBehavior) -> ShR
|
|||||||
if query_result.is_some() {
|
if query_result.is_some() {
|
||||||
Ok(j.remove_job(id.clone()).unwrap())
|
Ok(j.remove_job(id.clone()).unwrap())
|
||||||
} else {
|
} else {
|
||||||
Err(ShErr::full(
|
Err(ShErr::at(ShErrKind::ExecFail, blame.clone(), format!("Job id `{}' not found", tabid)))
|
||||||
ShErrKind::ExecFail,
|
|
||||||
format!("Job id `{}' not found", tabid),
|
|
||||||
blame,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@@ -95,11 +88,7 @@ fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
|
|||||||
});
|
});
|
||||||
match result {
|
match result {
|
||||||
Some(id) => Ok(id),
|
Some(id) => Ok(id),
|
||||||
None => Err(ShErr::full(
|
None => Err(ShErr::at(ShErrKind::InternalErr, blame, "Found a job but no table id in parse_job_id()")),
|
||||||
ShErrKind::InternalErr,
|
|
||||||
"Found a job but no table id in parse_job_id()",
|
|
||||||
blame,
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if arg.chars().all(|ch| ch.is_ascii_digit()) {
|
} else if arg.chars().all(|ch| ch.is_ascii_digit()) {
|
||||||
@@ -119,18 +108,10 @@ fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
|
|||||||
|
|
||||||
match result {
|
match result {
|
||||||
Some(id) => Ok(id),
|
Some(id) => Ok(id),
|
||||||
None => Err(ShErr::full(
|
None => Err(ShErr::at(ShErrKind::InternalErr, blame, "Found a job but no table id in parse_job_id()")),
|
||||||
ShErrKind::InternalErr,
|
|
||||||
"Found a job but no table id in parse_job_id()",
|
|
||||||
blame,
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(ShErr::full(
|
Err(ShErr::at(ShErrKind::SyntaxErr, blame, format!("Invalid arg: {}", arg)))
|
||||||
ShErrKind::SyntaxErr,
|
|
||||||
format!("Invalid arg: {}", arg),
|
|
||||||
blame,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,17 +124,14 @@ pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
let mut flags = JobCmdFlags::empty();
|
let mut flags = JobCmdFlags::empty();
|
||||||
for (arg, span) in argv {
|
for (arg, span) in argv {
|
||||||
let mut chars = arg.chars().peekable();
|
let mut chars = arg.chars().peekable();
|
||||||
if chars.peek().is_none_or(|ch| *ch != '-') {
|
if chars.peek().is_none_or(|ch| *ch != '-') {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::SyntaxErr, span, "Invalid flag in jobs call"));
|
||||||
ShErrKind::SyntaxErr,
|
|
||||||
"Invalid flag in jobs call",
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
chars.next();
|
chars.next();
|
||||||
for ch in chars {
|
for ch in chars {
|
||||||
@@ -164,11 +142,7 @@ pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
'r' => JobCmdFlags::RUNNING,
|
'r' => JobCmdFlags::RUNNING,
|
||||||
's' => JobCmdFlags::STOPPED,
|
's' => JobCmdFlags::STOPPED,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::SyntaxErr, span, "Invalid flag in jobs call"));
|
||||||
ShErrKind::SyntaxErr,
|
|
||||||
"Invalid flag in jobs call",
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
flags |= flag
|
flags |= flag
|
||||||
@@ -190,17 +164,14 @@ pub fn disown(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
let mut argv = argv.into_iter();
|
let mut argv = argv.into_iter();
|
||||||
|
|
||||||
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
|
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
|
||||||
id
|
id
|
||||||
} else {
|
} else {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::ExecFail, blame, "disown: No jobs to disown"));
|
||||||
ShErrKind::ExecFail,
|
|
||||||
"disown: No jobs to disown",
|
|
||||||
blame,
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut tabid = curr_job_id;
|
let mut tabid = curr_job_id;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use nix::{libc::STDOUT_FILENO, unistd::write};
|
|||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
expand::expand_cmd_sub, getopt::{Opt, OptSpec, get_opts_from_tokens}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node, lex::{split_all_unescaped, split_at_unescaped}}, procio::{IoStack, borrow_fd}, state::{self, read_vars, write_vars}
|
expand::expand_cmd_sub, getopt::{Opt, OptSpec, get_opts_from_tokens}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node, lex::{split_tk, split_tk_at}}, procio::{IoStack, borrow_fd}, state::{self, read_vars, write_vars}
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -252,17 +252,23 @@ pub fn map(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, opts) = get_opts_from_tokens(argv, &map_opts_spec())?;
|
let (mut argv, opts) = get_opts_from_tokens(argv, &map_opts_spec())?;
|
||||||
let map_opts = get_map_opts(opts);
|
let map_opts = get_map_opts(opts);
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (_, _guard) = setup_builtin(None, job, Some((io_stack, node.redirs)))?;
|
||||||
|
if !argv.is_empty() {
|
||||||
|
argv.remove(0); // remove "map" command from argv
|
||||||
|
}
|
||||||
|
|
||||||
for (arg,_) in argv {
|
for arg in argv {
|
||||||
if let Some((lhs,rhs)) = split_at_unescaped(&arg, "=") {
|
if let Some((lhs,rhs)) = split_tk_at(&arg, "=") {
|
||||||
let path = split_all_unescaped(&lhs, ".");
|
let path = split_tk(&lhs, ".")
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| s.expand().map(|exp| exp.get_words().join(" ")))
|
||||||
|
.collect::<ShResult<Vec<String>>>()?;
|
||||||
let Some(name) = path.first() else {
|
let Some(name) = path.first() else {
|
||||||
return Err(ShErr::simple(
|
return Err(ShErr::simple(
|
||||||
ShErrKind::InternalErr,
|
ShErrKind::InternalErr,
|
||||||
format!("invalid map path: {}", lhs)
|
format!("invalid map path: {}", lhs.as_str())
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -271,39 +277,42 @@ pub fn map(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()
|
|||||||
let make_leaf = |s: String| {
|
let make_leaf = |s: String| {
|
||||||
if is_func { MapNode::DynamicLeaf(s) } else { MapNode::StaticLeaf(s) }
|
if is_func { MapNode::DynamicLeaf(s) } else { MapNode::StaticLeaf(s) }
|
||||||
};
|
};
|
||||||
let found = write_vars(|v| {
|
let expanded = rhs.expand()?.get_words().join(" ");
|
||||||
|
let found = write_vars(|v| -> ShResult<bool> {
|
||||||
if let Some(map) = v.get_map_mut(name) {
|
if let Some(map) = v.get_map_mut(name) {
|
||||||
if is_json {
|
if is_json {
|
||||||
if let Ok(parsed) = serde_json::from_str::<Value>(&rhs) {
|
if let Ok(parsed) = serde_json::from_str::<Value>(expanded.as_str()) {
|
||||||
map.set(&path[1..], parsed.into());
|
map.set(&path[1..], parsed.into());
|
||||||
} else {
|
} else {
|
||||||
map.set(&path[1..], make_leaf(rhs.clone()));
|
map.set(&path[1..], make_leaf(expanded.clone()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
map.set(&path[1..], make_leaf(rhs.clone()));
|
map.set(&path[1..], make_leaf(expanded.clone()));
|
||||||
}
|
}
|
||||||
true
|
Ok(true)
|
||||||
} else {
|
} else {
|
||||||
false
|
Ok(false)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if !found {
|
if !found? {
|
||||||
let mut new = MapNode::default();
|
let mut new = MapNode::default();
|
||||||
if is_json && let Ok(parsed) = serde_json::from_str::<Value>(&rhs) {
|
if is_json /*&& let Ok(parsed) = serde_json::from_str::<Value>(rhs.as_str()) */{
|
||||||
|
let parsed = serde_json::from_str::<Value>(expanded.as_str()).unwrap();
|
||||||
let node: MapNode = parsed.into();
|
let node: MapNode = parsed.into();
|
||||||
new.set(&path[1..], node);
|
new.set(&path[1..], node);
|
||||||
} else {
|
} else {
|
||||||
new.set(&path[1..], make_leaf(rhs));
|
new.set(&path[1..], make_leaf(expanded));
|
||||||
}
|
}
|
||||||
write_vars(|v| v.set_map(name, new, map_opts.flags.contains(MapFlags::LOCAL)));
|
write_vars(|v| v.set_map(name, new, map_opts.flags.contains(MapFlags::LOCAL)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let path = split_all_unescaped(&arg, ".");
|
let expanded = arg.expand()?.get_words().join(" ");
|
||||||
|
let path: Vec<String> = expanded.split('.').map(|s| s.to_string()).collect();
|
||||||
let Some(name) = path.first() else {
|
let Some(name) = path.first() else {
|
||||||
return Err(ShErr::simple(
|
return Err(ShErr::simple(
|
||||||
ShErrKind::InternalErr,
|
ShErrKind::InternalErr,
|
||||||
format!("invalid map path: {}", &arg)
|
format!("invalid map path: {}", expanded)
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -67,13 +67,13 @@ pub const BUILTINS: [&str; 41] = [
|
|||||||
/// * If redirections are given, the second field of the resulting tuple will
|
/// * If redirections are given, the second field of the resulting tuple will
|
||||||
/// *always* be `Some()`
|
/// *always* be `Some()`
|
||||||
/// * If no redirections are given, the second field will *always* be `None`
|
/// * If no redirections are given, the second field will *always* be `None`
|
||||||
type SetupReturns = ShResult<(Vec<(String, Span)>, Option<RedirGuard>)>;
|
type SetupReturns = ShResult<(Option<Vec<(String, Span)>>, Option<RedirGuard>)>;
|
||||||
pub fn setup_builtin(
|
pub fn setup_builtin(
|
||||||
argv: Vec<Tk>,
|
argv: Option<Vec<Tk>>,
|
||||||
job: &mut JobBldr,
|
job: &mut JobBldr,
|
||||||
io_mode: Option<(&mut IoStack, Vec<Redir>)>,
|
io_mode: Option<(&mut IoStack, Vec<Redir>)>,
|
||||||
) -> SetupReturns {
|
) -> SetupReturns {
|
||||||
let mut argv: Vec<(String, Span)> = prepare_argv(argv)?;
|
let mut argv = argv.map(|argv| prepare_argv(argv)).transpose()?;
|
||||||
|
|
||||||
let child_pgid = if let Some(pgid) = job.pgid() {
|
let child_pgid = if let Some(pgid) = job.pgid() {
|
||||||
pgid
|
pgid
|
||||||
@@ -81,18 +81,22 @@ pub fn setup_builtin(
|
|||||||
job.set_pgid(Pid::this());
|
job.set_pgid(Pid::this());
|
||||||
Pid::this()
|
Pid::this()
|
||||||
};
|
};
|
||||||
let cmd_name = argv.remove(0).0;
|
let cmd_name = argv
|
||||||
|
.as_mut()
|
||||||
|
.and_then(|argv| {
|
||||||
|
if argv.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(argv.remove(0).0)
|
||||||
|
}
|
||||||
|
}).unwrap_or_else(|| String::new());
|
||||||
let child = ChildProc::new(Pid::this(), Some(&cmd_name), Some(child_pgid))?;
|
let child = ChildProc::new(Pid::this(), Some(&cmd_name), Some(child_pgid))?;
|
||||||
job.push_child(child);
|
job.push_child(child);
|
||||||
|
|
||||||
let guard = if let Some((io_stack, redirs)) = io_mode {
|
let guard = io_mode.map(|(io,rdrs)| {
|
||||||
io_stack.append_to_frame(redirs);
|
io.append_to_frame(rdrs);
|
||||||
let io_frame = io_stack.pop_frame();
|
io.pop_frame().redirect()
|
||||||
let guard = io_frame.redirect()?;
|
}).transpose()?;
|
||||||
Some(guard)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
// We return the io_frame because the caller needs to also call
|
// We return the io_frame because the caller needs to also call
|
||||||
// io_frame.restore()
|
// io_frame.restore()
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ pub fn pwd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (_, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (_, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,8 @@ pub fn read_builtin(node: Node, _io_stack: &mut IoStack, job: &mut JobBldr) -> S
|
|||||||
|
|
||||||
let (argv, opts) = get_opts_from_tokens(argv, &READ_OPTS)?;
|
let (argv, opts) = get_opts_from_tokens(argv, &READ_OPTS)?;
|
||||||
let read_opts = get_read_flags(opts).blame(blame.clone())?;
|
let read_opts = get_read_flags(opts).blame(blame.clone())?;
|
||||||
let (argv, _) = setup_builtin(argv, job, None).blame(blame.clone())?;
|
let (argv, _) = setup_builtin(Some(argv), job, None).blame(blame.clone())?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
if let Some(prompt) = read_opts.prompt {
|
if let Some(prompt) = read_opts.prompt {
|
||||||
write(borrow_fd(STDOUT_FILENO), prompt.as_bytes())?;
|
write(borrow_fd(STDOUT_FILENO), prompt.as_bytes())?;
|
||||||
|
|||||||
@@ -16,16 +16,13 @@ pub fn shift(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _) = setup_builtin(argv, job, None)?;
|
let (argv, _) = setup_builtin(Some(argv), job, None)?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
let mut argv = argv.into_iter();
|
let mut argv = argv.into_iter();
|
||||||
|
|
||||||
if let Some((arg, span)) = argv.next() {
|
if let Some((arg, span)) = argv.next() {
|
||||||
let Ok(count) = arg.parse::<usize>() else {
|
let Ok(count) = arg.parse::<usize>() else {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::ExecFail, span, "Expected a number in shift args"));
|
||||||
ShErrKind::ExecFail,
|
|
||||||
"Expected a number in shift args",
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
for _ in 0..count {
|
for _ in 0..count {
|
||||||
write_vars(|v| v.cur_scope_mut().fpop_arg());
|
write_vars(|v| v.cur_scope_mut().fpop_arg());
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ pub fn shopt(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
let mut output = write_shopts(|s| s.display_opts())?;
|
let mut output = write_shopts(|s| s.display_opts())?;
|
||||||
|
|||||||
@@ -17,23 +17,16 @@ pub fn source(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _) = setup_builtin(argv, job, None)?;
|
let (argv, _) = setup_builtin(Some(argv), job, None)?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
for (arg, span) in argv {
|
for (arg, span) in argv {
|
||||||
let path = PathBuf::from(arg);
|
let path = PathBuf::from(arg);
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::ExecFail, span, format!("source: File '{}' not found", path.display())));
|
||||||
ShErrKind::ExecFail,
|
|
||||||
format!("source: File '{}' not found", path.display()),
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
if !path.is_file() {
|
if !path.is_file() {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::ExecFail, span, format!("source: Given path '{}' is not a file", path.display())));
|
||||||
ShErrKind::ExecFail,
|
|
||||||
format!("source: Given path '{}' is not a file", path.display()),
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
source_file(path)?;
|
source_file(path)?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,11 +61,10 @@ impl FromStr for UnaryOp {
|
|||||||
"-t" => Ok(Self::Terminal),
|
"-t" => Ok(Self::Terminal),
|
||||||
"-n" => Ok(Self::NonNull),
|
"-n" => Ok(Self::NonNull),
|
||||||
"-z" => Ok(Self::Null),
|
"-z" => Ok(Self::Null),
|
||||||
_ => Err(ShErr::Simple {
|
_ => Err(ShErr::simple(
|
||||||
kind: ShErrKind::SyntaxErr,
|
ShErrKind::SyntaxErr,
|
||||||
msg: "Invalid test operator".into(),
|
"Invalid test operator",
|
||||||
notes: vec![],
|
)),
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,11 +97,10 @@ impl FromStr for TestOp {
|
|||||||
"-ge" => Ok(Self::IntGe),
|
"-ge" => Ok(Self::IntGe),
|
||||||
"-le" => Ok(Self::IntLe),
|
"-le" => Ok(Self::IntLe),
|
||||||
_ if TEST_UNARY_OPS.contains(&s) => Ok(Self::Unary(s.parse::<UnaryOp>()?)),
|
_ if TEST_UNARY_OPS.contains(&s) => Ok(Self::Unary(s.parse::<UnaryOp>()?)),
|
||||||
_ => Err(ShErr::Simple {
|
_ => Err(ShErr::simple(
|
||||||
kind: ShErrKind::SyntaxErr,
|
ShErrKind::SyntaxErr,
|
||||||
msg: "Invalid test operator".into(),
|
"Invalid test operator",
|
||||||
notes: vec![],
|
)),
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,12 +138,7 @@ pub fn double_bracket_test(node: Node) -> ShResult<bool> {
|
|||||||
let operand = operand.expand()?.get_words().join(" ");
|
let operand = operand.expand()?.get_words().join(" ");
|
||||||
conjunct_op = conjunct;
|
conjunct_op = conjunct;
|
||||||
let TestOp::Unary(op) = TestOp::from_str(operator.as_str())? else {
|
let TestOp::Unary(op) = TestOp::from_str(operator.as_str())? else {
|
||||||
return Err(ShErr::Full {
|
return Err(ShErr::at(ShErrKind::SyntaxErr, err_span, "Invalid unary operator"));
|
||||||
kind: ShErrKind::SyntaxErr,
|
|
||||||
msg: "Invalid unary operator".into(),
|
|
||||||
notes: vec![],
|
|
||||||
span: err_span,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
match op {
|
match op {
|
||||||
UnaryOp::Exists => {
|
UnaryOp::Exists => {
|
||||||
@@ -248,12 +241,7 @@ pub fn double_bracket_test(node: Node) -> ShResult<bool> {
|
|||||||
let test_op = operator.as_str().parse::<TestOp>()?;
|
let test_op = operator.as_str().parse::<TestOp>()?;
|
||||||
match test_op {
|
match test_op {
|
||||||
TestOp::Unary(_) => {
|
TestOp::Unary(_) => {
|
||||||
return Err(ShErr::Full {
|
return Err(ShErr::at(ShErrKind::SyntaxErr, err_span, "Expected a binary operator in this test call; found a unary operator"));
|
||||||
kind: ShErrKind::SyntaxErr,
|
|
||||||
msg: "Expected a binary operator in this test call; found a unary operator".into(),
|
|
||||||
notes: vec![],
|
|
||||||
span: err_span,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
TestOp::StringEq => {
|
TestOp::StringEq => {
|
||||||
let pattern = crate::expand::glob_to_regex(rhs.trim(), true);
|
let pattern = crate::expand::glob_to_regex(rhs.trim(), true);
|
||||||
@@ -269,12 +257,7 @@ pub fn double_bracket_test(node: Node) -> ShResult<bool> {
|
|||||||
| TestOp::IntGe
|
| TestOp::IntGe
|
||||||
| TestOp::IntLe
|
| TestOp::IntLe
|
||||||
| TestOp::IntEq => {
|
| TestOp::IntEq => {
|
||||||
let err = ShErr::Full {
|
let err = ShErr::at(ShErrKind::SyntaxErr, err_span.clone(), format!("Expected an integer with '{}' operator", operator));
|
||||||
kind: ShErrKind::SyntaxErr,
|
|
||||||
msg: format!("Expected an integer with '{}' operator", operator.as_str()),
|
|
||||||
notes: vec![],
|
|
||||||
span: err_span.clone(),
|
|
||||||
};
|
|
||||||
let Ok(lhs) = lhs.trim().parse::<i32>() else {
|
let Ok(lhs) = lhs.trim().parse::<i32>() else {
|
||||||
return Err(err);
|
return Err(err);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -123,7 +123,8 @@ pub fn trap(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
jobs::JobBldr,
|
jobs::JobBldr,
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
parse::{NdRule, Node},
|
parse::{NdRule, Node, lex::split_tk_at},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
procio::{IoStack, borrow_fd},
|
procio::{IoStack, borrow_fd},
|
||||||
state::{self, VarFlags, VarKind, read_vars, write_vars},
|
state::{self, VarFlags, VarKind, read_vars, write_vars},
|
||||||
@@ -18,7 +18,10 @@ pub fn readonly(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResu
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (_, _guard) = setup_builtin(None, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
|
// Remove "readonly" from argv
|
||||||
|
let argv = if !argv.is_empty() { &argv[1..] } else { &argv[..] };
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
// Display the local variables
|
// Display the local variables
|
||||||
@@ -38,10 +41,17 @@ pub fn readonly(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResu
|
|||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
write(stdout, vars_output.as_bytes())?; // Write it
|
write(stdout, vars_output.as_bytes())?; // Write it
|
||||||
} else {
|
} else {
|
||||||
for (arg, _) in argv {
|
for tk in argv {
|
||||||
if let Some((var, val)) = arg.split_once('=') {
|
if let Some((var_tk, val_tk)) = split_tk_at(tk, "=") {
|
||||||
write_vars(|v| v.set_var(var, VarKind::Str(val.to_string()), VarFlags::READONLY))?;
|
let var = var_tk.expand()?.get_words().join(" ");
|
||||||
|
let val = if val_tk.as_str().starts_with('(') && val_tk.as_str().ends_with(')') {
|
||||||
|
VarKind::arr_from_tk(val_tk.clone())?
|
||||||
|
} else {
|
||||||
|
VarKind::Str(val_tk.expand()?.get_words().join(" "))
|
||||||
|
};
|
||||||
|
write_vars(|v| v.set_var(&var, val, VarFlags::READONLY))?;
|
||||||
} else {
|
} else {
|
||||||
|
let arg = tk.clone().expand()?.get_words().join(" ");
|
||||||
write_vars(|v| v.set_var(&arg, VarKind::Str(String::new()), VarFlags::READONLY))?;
|
write_vars(|v| v.set_var(&arg, VarKind::Str(String::new()), VarFlags::READONLY))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,23 +71,16 @@ pub fn unset(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::SyntaxErr, blame, "unset: Expected at least one argument"));
|
||||||
ShErrKind::SyntaxErr,
|
|
||||||
"unset: Expected at least one argument",
|
|
||||||
blame,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (arg, span) in argv {
|
for (arg, span) in argv {
|
||||||
if !read_vars(|v| v.var_exists(&arg)) {
|
if !read_vars(|v| v.var_exists(&arg)) {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::ExecFail, span, format!("unset: No such variable '{arg}'")));
|
||||||
ShErrKind::ExecFail,
|
|
||||||
format!("unset: No such variable '{arg}'"),
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
write_vars(|v| v.unset_var(&arg))?;
|
write_vars(|v| v.unset_var(&arg))?;
|
||||||
}
|
}
|
||||||
@@ -95,7 +98,10 @@ pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (_, _guard) = setup_builtin(None, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
|
// Remove "export" from argv
|
||||||
|
let argv = if !argv.is_empty() { &argv[1..] } else { &argv[..] };
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
// Display the environment variables
|
// Display the environment variables
|
||||||
@@ -109,12 +115,18 @@ pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult
|
|||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
write(stdout, env_output.as_bytes())?; // Write it
|
write(stdout, env_output.as_bytes())?; // Write it
|
||||||
} else {
|
} else {
|
||||||
for (arg, _) in argv {
|
for tk in argv {
|
||||||
if let Some((var, val)) = arg.split_once('=') {
|
if let Some((var_tk, val_tk)) = split_tk_at(tk, "=") {
|
||||||
write_vars(|v| v.set_var(var, VarKind::Str(val.to_string()), VarFlags::EXPORT))?;
|
let var = var_tk.expand()?.get_words().join(" ");
|
||||||
|
let val = if val_tk.as_str().starts_with('(') && val_tk.as_str().ends_with(')') {
|
||||||
|
VarKind::arr_from_tk(val_tk.clone())?
|
||||||
|
} else {
|
||||||
|
VarKind::Str(val_tk.expand()?.get_words().join(" "))
|
||||||
|
};
|
||||||
|
write_vars(|v| v.set_var(&var, val, VarFlags::EXPORT))?;
|
||||||
} else {
|
} else {
|
||||||
write_vars(|v| v.export_var(&arg)); // Export an existing variable, if
|
let arg = tk.clone().expand()?.get_words().join(" ");
|
||||||
// any
|
write_vars(|v| v.export_var(&arg)); // Export an existing variable, if any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,7 +143,10 @@ pub fn local(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (_, _guard) = setup_builtin(None, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
|
// Remove "local" from argv
|
||||||
|
let argv = if !argv.is_empty() { &argv[1..] } else { &argv[..] };
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
// Display the local variables
|
// Display the local variables
|
||||||
@@ -150,10 +165,17 @@ pub fn local(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
write(stdout, vars_output.as_bytes())?; // Write it
|
write(stdout, vars_output.as_bytes())?; // Write it
|
||||||
} else {
|
} else {
|
||||||
for (arg, _) in argv {
|
for tk in argv {
|
||||||
if let Some((var, val)) = arg.split_once('=') {
|
if let Some((var_tk, val_tk)) = split_tk_at(tk, "=") {
|
||||||
write_vars(|v| v.set_var(var, VarKind::Str(val.to_string()), VarFlags::LOCAL))?;
|
let var = var_tk.expand()?.get_words().join(" ");
|
||||||
|
let val = if val_tk.as_str().starts_with('(') && val_tk.as_str().ends_with(')') {
|
||||||
|
VarKind::arr_from_tk(val_tk.clone())?
|
||||||
|
} else {
|
||||||
|
VarKind::Str(val_tk.expand()?.get_words().join(" "))
|
||||||
|
};
|
||||||
|
write_vars(|v| v.set_var(&var, val, VarFlags::LOCAL))?;
|
||||||
} else {
|
} else {
|
||||||
|
let arg = tk.clone().expand()?.get_words().join(" ");
|
||||||
write_vars(|v| v.set_var(&arg, VarKind::Str(String::new()), VarFlags::LOCAL))?;
|
write_vars(|v| v.set_var(&arg, VarKind::Str(String::new()), VarFlags::LOCAL))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,8 @@ pub fn zoltraak(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
for (arg, span) in argv {
|
for (arg, span) in argv {
|
||||||
if &arg == "/" && !flags.contains(ZoltFlags::NO_PRESERVE_ROOT) {
|
if &arg == "/" && !flags.contains(ZoltFlags::NO_PRESERVE_ROOT) {
|
||||||
@@ -116,8 +117,7 @@ pub fn zoltraak(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResu
|
|||||||
"zoltraak: Attempted to destroy root directory '/'",
|
"zoltraak: Attempted to destroy root directory '/'",
|
||||||
)
|
)
|
||||||
.with_note(
|
.with_note(
|
||||||
Note::new("If you really want to do this, you can use the --no-preserve-root flag")
|
"If you really want to do this, you can use the --no-preserve-root flag"
|
||||||
.with_sub_notes(vec!["Example: 'zoltraak --no-preserve-root /'"]),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -180,8 +180,7 @@ fn annihilate(path: &str, flags: ZoltFlags) -> ShResult<()> {
|
|||||||
format!("zoltraak: '{path}' is a directory"),
|
format!("zoltraak: '{path}' is a directory"),
|
||||||
)
|
)
|
||||||
.with_note(
|
.with_note(
|
||||||
Note::new("Use the '-r' flag to recursively shred directories")
|
"Use the '-r' flag to recursively shred directories"
|
||||||
.with_sub_notes(vec!["Example: 'zoltraak -r directory'"]),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
198
src/expand.rs
198
src/expand.rs
@@ -7,7 +7,7 @@ use regex::Regex;
|
|||||||
|
|
||||||
use crate::libsh::error::{ShErr, ShErrKind, ShResult};
|
use crate::libsh::error::{ShErr, ShErrKind, ShResult};
|
||||||
use crate::parse::execute::exec_input;
|
use crate::parse::execute::exec_input;
|
||||||
use crate::parse::lex::{LexFlags, LexStream, Tk, TkFlags, TkRule, is_hard_sep};
|
use crate::parse::lex::{LexFlags, LexStream, QuoteState, Tk, TkFlags, TkRule, is_hard_sep};
|
||||||
use crate::parse::{Redir, RedirType};
|
use crate::parse::{Redir, RedirType};
|
||||||
use crate::procio::{IoBuf, IoFrame, IoMode, IoStack};
|
use crate::procio::{IoBuf, IoFrame, IoMode, IoStack};
|
||||||
use crate::readline::markers;
|
use crate::readline::markers;
|
||||||
@@ -130,18 +130,16 @@ fn has_braces(s: &str) -> bool {
|
|||||||
let mut found_open = false;
|
let mut found_open = false;
|
||||||
let mut has_comma = false;
|
let mut has_comma = false;
|
||||||
let mut has_range = false;
|
let mut has_range = false;
|
||||||
let mut cur_quote: Option<char> = None;
|
let mut qt_state = QuoteState::default();
|
||||||
|
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
match ch {
|
match ch {
|
||||||
'\\' => {
|
'\\' => {
|
||||||
chars.next();
|
chars.next();
|
||||||
} // skip escaped char
|
} // skip escaped char
|
||||||
'\'' if cur_quote.is_none() => cur_quote = Some('\''),
|
'\'' => qt_state.toggle_single(),
|
||||||
'\'' if cur_quote == Some('\'') => cur_quote = None,
|
'"' => qt_state.toggle_double(),
|
||||||
'"' if cur_quote.is_none() => cur_quote = Some('"'),
|
'{' if qt_state.in_quote() => {
|
||||||
'"' if cur_quote == Some('"') => cur_quote = None,
|
|
||||||
'{' if cur_quote.is_none() => {
|
|
||||||
if depth == 0 {
|
if depth == 0 {
|
||||||
found_open = true;
|
found_open = true;
|
||||||
has_comma = false;
|
has_comma = false;
|
||||||
@@ -149,16 +147,16 @@ fn has_braces(s: &str) -> bool {
|
|||||||
}
|
}
|
||||||
depth += 1;
|
depth += 1;
|
||||||
}
|
}
|
||||||
'}' if cur_quote.is_none() && depth > 0 => {
|
'}' if qt_state.outside() && depth > 0 => {
|
||||||
depth -= 1;
|
depth -= 1;
|
||||||
if depth == 0 && found_open && (has_comma || has_range) {
|
if depth == 0 && found_open && (has_comma || has_range) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
',' if cur_quote.is_none() && depth == 1 => {
|
',' if qt_state.outside() && depth == 1 => {
|
||||||
has_comma = true;
|
has_comma = true;
|
||||||
}
|
}
|
||||||
'.' if cur_quote.is_none() && depth == 1 => {
|
'.' if qt_state.outside() && depth == 1 => {
|
||||||
if chars.peek() == Some(&'.') {
|
if chars.peek() == Some(&'.') {
|
||||||
chars.next();
|
chars.next();
|
||||||
has_range = true;
|
has_range = true;
|
||||||
@@ -239,7 +237,7 @@ fn expand_one_brace(word: &str) -> ShResult<Vec<String>> {
|
|||||||
fn get_brace_parts(word: &str) -> Option<(String, String, String)> {
|
fn get_brace_parts(word: &str) -> Option<(String, String, String)> {
|
||||||
let mut chars = word.chars().peekable();
|
let mut chars = word.chars().peekable();
|
||||||
let mut prefix = String::new();
|
let mut prefix = String::new();
|
||||||
let mut cur_quote: Option<char> = None;
|
let mut qt_state = QuoteState::default();
|
||||||
|
|
||||||
// Find the opening brace
|
// Find the opening brace
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
@@ -250,23 +248,15 @@ fn get_brace_parts(word: &str) -> Option<(String, String, String)> {
|
|||||||
prefix.push(next);
|
prefix.push(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'\'' if cur_quote.is_none() => {
|
'\'' => {
|
||||||
cur_quote = Some('\'');
|
qt_state.toggle_single();
|
||||||
prefix.push(ch);
|
prefix.push(ch);
|
||||||
}
|
}
|
||||||
'\'' if cur_quote == Some('\'') => {
|
'"' => {
|
||||||
cur_quote = None;
|
qt_state.toggle_double();
|
||||||
prefix.push(ch);
|
prefix.push(ch);
|
||||||
}
|
}
|
||||||
'"' if cur_quote.is_none() => {
|
'{' if qt_state.outside() => {
|
||||||
cur_quote = Some('"');
|
|
||||||
prefix.push(ch);
|
|
||||||
}
|
|
||||||
'"' if cur_quote == Some('"') => {
|
|
||||||
cur_quote = None;
|
|
||||||
prefix.push(ch);
|
|
||||||
}
|
|
||||||
'{' if cur_quote.is_none() => {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_ => prefix.push(ch),
|
_ => prefix.push(ch),
|
||||||
@@ -276,7 +266,7 @@ fn get_brace_parts(word: &str) -> Option<(String, String, String)> {
|
|||||||
// Find matching closing brace
|
// Find matching closing brace
|
||||||
let mut depth = 1;
|
let mut depth = 1;
|
||||||
let mut inner = String::new();
|
let mut inner = String::new();
|
||||||
cur_quote = None;
|
qt_state = QuoteState::default();
|
||||||
|
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
match ch {
|
match ch {
|
||||||
@@ -286,27 +276,19 @@ fn get_brace_parts(word: &str) -> Option<(String, String, String)> {
|
|||||||
inner.push(next);
|
inner.push(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'\'' if cur_quote.is_none() => {
|
'\'' => {
|
||||||
cur_quote = Some('\'');
|
qt_state.toggle_single();
|
||||||
inner.push(ch);
|
inner.push(ch);
|
||||||
}
|
}
|
||||||
'\'' if cur_quote == Some('\'') => {
|
'"' => {
|
||||||
cur_quote = None;
|
qt_state.toggle_double();
|
||||||
inner.push(ch);
|
inner.push(ch);
|
||||||
}
|
}
|
||||||
'"' if cur_quote.is_none() => {
|
'{' if qt_state.outside() => {
|
||||||
cur_quote = Some('"');
|
|
||||||
inner.push(ch);
|
|
||||||
}
|
|
||||||
'"' if cur_quote == Some('"') => {
|
|
||||||
cur_quote = None;
|
|
||||||
inner.push(ch);
|
|
||||||
}
|
|
||||||
'{' if cur_quote.is_none() => {
|
|
||||||
depth += 1;
|
depth += 1;
|
||||||
inner.push(ch);
|
inner.push(ch);
|
||||||
}
|
}
|
||||||
'}' if cur_quote.is_none() => {
|
'}' if qt_state.outside() => {
|
||||||
depth -= 1;
|
depth -= 1;
|
||||||
if depth == 0 {
|
if depth == 0 {
|
||||||
break;
|
break;
|
||||||
@@ -335,7 +317,7 @@ fn split_brace_inner(inner: &str) -> Vec<String> {
|
|||||||
let mut current = String::new();
|
let mut current = String::new();
|
||||||
let mut chars = inner.chars().peekable();
|
let mut chars = inner.chars().peekable();
|
||||||
let mut depth = 0;
|
let mut depth = 0;
|
||||||
let mut cur_quote: Option<char> = None;
|
let mut qt_state = QuoteState::default();
|
||||||
|
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
match ch {
|
match ch {
|
||||||
@@ -345,31 +327,23 @@ fn split_brace_inner(inner: &str) -> Vec<String> {
|
|||||||
current.push(next);
|
current.push(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'\'' if cur_quote.is_none() => {
|
'\'' => {
|
||||||
cur_quote = Some('\'');
|
qt_state.toggle_single();
|
||||||
current.push(ch);
|
current.push(ch);
|
||||||
}
|
}
|
||||||
'\'' if cur_quote == Some('\'') => {
|
'"' => {
|
||||||
cur_quote = None;
|
qt_state.toggle_double();
|
||||||
current.push(ch);
|
current.push(ch);
|
||||||
}
|
}
|
||||||
'"' if cur_quote.is_none() => {
|
'{' if qt_state.outside() => {
|
||||||
cur_quote = Some('"');
|
|
||||||
current.push(ch);
|
|
||||||
}
|
|
||||||
'"' if cur_quote == Some('"') => {
|
|
||||||
cur_quote = None;
|
|
||||||
current.push(ch);
|
|
||||||
}
|
|
||||||
'{' if cur_quote.is_none() => {
|
|
||||||
depth += 1;
|
depth += 1;
|
||||||
current.push(ch);
|
current.push(ch);
|
||||||
}
|
}
|
||||||
'}' if cur_quote.is_none() => {
|
'}' if qt_state.outside() => {
|
||||||
depth -= 1;
|
depth -= 1;
|
||||||
current.push(ch);
|
current.push(ch);
|
||||||
}
|
}
|
||||||
',' if cur_quote.is_none() && depth == 0 => {
|
',' if qt_state.outside() && depth == 0 => {
|
||||||
parts.push(std::mem::take(&mut current));
|
parts.push(std::mem::take(&mut current));
|
||||||
}
|
}
|
||||||
_ => current.push(ch),
|
_ => current.push(ch),
|
||||||
@@ -556,6 +530,11 @@ pub fn expand_var(chars: &mut Peekable<Chars<'_>>) -> ShResult<String> {
|
|||||||
let arg_sep = markers::ARG_SEP.to_string();
|
let arg_sep = markers::ARG_SEP.to_string();
|
||||||
read_vars(|v| v.get_arr_elems(&var_name))?.join(&arg_sep)
|
read_vars(|v| v.get_arr_elems(&var_name))?.join(&arg_sep)
|
||||||
}
|
}
|
||||||
|
ArrIndex::ArgCount => {
|
||||||
|
read_vars(|v| v.get_arr_elems(&var_name))
|
||||||
|
.map(|elems| elems.len().to_string())
|
||||||
|
.unwrap_or_else(|_| "0".to_string())
|
||||||
|
}
|
||||||
ArrIndex::AllJoined => {
|
ArrIndex::AllJoined => {
|
||||||
let ifs = read_vars(|v| v.try_get_var("IFS"))
|
let ifs = read_vars(|v| v.try_get_var("IFS"))
|
||||||
.unwrap_or_else(|| " \t\n".to_string())
|
.unwrap_or_else(|| " \t\n".to_string())
|
||||||
@@ -709,11 +688,10 @@ impl ArithTk {
|
|||||||
chars.next();
|
chars.next();
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShErr::Simple {
|
return Err(ShErr::simple(
|
||||||
kind: ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
msg: "Invalid character in arithmetic substitution".into(),
|
"Invalid character in arithmetic substitution",
|
||||||
notes: vec![],
|
));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -771,16 +749,14 @@ impl ArithTk {
|
|||||||
match token {
|
match token {
|
||||||
ArithTk::Num(n) => stack.push(n),
|
ArithTk::Num(n) => stack.push(n),
|
||||||
ArithTk::Op(op) => {
|
ArithTk::Op(op) => {
|
||||||
let rhs = stack.pop().ok_or(ShErr::Simple {
|
let rhs = stack.pop().ok_or(ShErr::simple(
|
||||||
kind: ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
msg: "Missing right-hand operand".into(),
|
"Missing right-hand operand",
|
||||||
notes: vec![],
|
))?;
|
||||||
})?;
|
let lhs = stack.pop().ok_or(ShErr::simple(
|
||||||
let lhs = stack.pop().ok_or(ShErr::Simple {
|
ShErrKind::ParseErr,
|
||||||
kind: ShErrKind::ParseErr,
|
"Missing left-hand operand",
|
||||||
msg: "Missing left-hand operand".into(),
|
))?;
|
||||||
notes: vec![],
|
|
||||||
})?;
|
|
||||||
let result = match op {
|
let result = match op {
|
||||||
ArithOp::Add => lhs + rhs,
|
ArithOp::Add => lhs + rhs,
|
||||||
ArithOp::Sub => lhs - rhs,
|
ArithOp::Sub => lhs - rhs,
|
||||||
@@ -791,21 +767,19 @@ impl ArithTk {
|
|||||||
stack.push(result);
|
stack.push(result);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShErr::Simple {
|
return Err(ShErr::simple(
|
||||||
kind: ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
msg: "Unexpected token during evaluation".into(),
|
"Unexpected token during evaluation",
|
||||||
notes: vec![],
|
));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if stack.len() != 1 {
|
if stack.len() != 1 {
|
||||||
return Err(ShErr::Simple {
|
return Err(ShErr::simple(
|
||||||
kind: ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
msg: "Invalid arithmetic expression".into(),
|
"Invalid arithmetic expression",
|
||||||
notes: vec![],
|
));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(stack[0])
|
Ok(stack[0])
|
||||||
@@ -830,11 +804,10 @@ impl FromStr for ArithOp {
|
|||||||
'*' => Ok(Self::Mul),
|
'*' => Ok(Self::Mul),
|
||||||
'/' => Ok(Self::Div),
|
'/' => Ok(Self::Div),
|
||||||
'%' => Ok(Self::Mod),
|
'%' => Ok(Self::Mod),
|
||||||
_ => Err(ShErr::Simple {
|
_ => Err(ShErr::simple(
|
||||||
kind: ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
msg: "Invalid arithmetic operator".into(),
|
"Invalid arithmetic operator",
|
||||||
notes: vec![],
|
)),
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -884,7 +857,7 @@ pub fn expand_proc_sub(raw: &str, is_input: bool) -> ShResult<String> {
|
|||||||
io_stack.push_frame(io_frame);
|
io_stack.push_frame(io_frame);
|
||||||
|
|
||||||
if let Err(e) = exec_input(raw.to_string(), Some(io_stack), false) {
|
if let Err(e) = exec_input(raw.to_string(), Some(io_stack), false) {
|
||||||
eprintln!("{e}");
|
e.print_error();
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
exit(0);
|
exit(0);
|
||||||
@@ -915,7 +888,7 @@ pub fn expand_cmd_sub(raw: &str) -> ShResult<String> {
|
|||||||
ForkResult::Child => {
|
ForkResult::Child => {
|
||||||
io_stack.push_frame(cmd_sub_io_frame);
|
io_stack.push_frame(cmd_sub_io_frame);
|
||||||
if let Err(e) = exec_input(raw.to_string(), Some(io_stack), false) {
|
if let Err(e) = exec_input(raw.to_string(), Some(io_stack), false) {
|
||||||
eprintln!("{e}");
|
e.print_error();
|
||||||
unsafe { libc::_exit(1) };
|
unsafe { libc::_exit(1) };
|
||||||
}
|
}
|
||||||
unsafe { libc::_exit(0) };
|
unsafe { libc::_exit(0) };
|
||||||
@@ -1296,11 +1269,10 @@ impl FromStr for ParamExp {
|
|||||||
use ParamExp::*;
|
use ParamExp::*;
|
||||||
|
|
||||||
let parse_err = || {
|
let parse_err = || {
|
||||||
Err(ShErr::Simple {
|
Err(ShErr::simple(
|
||||||
kind: ShErrKind::SyntaxErr,
|
ShErrKind::SyntaxErr,
|
||||||
msg: "Invalid parameter expansion".into(),
|
"Invalid parameter expansion",
|
||||||
notes: vec![],
|
) )
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle indirect var expansion: ${!var}
|
// Handle indirect var expansion: ${!var}
|
||||||
@@ -1458,11 +1430,10 @@ pub fn perform_param_expansion(raw: &str) -> ShResult<String> {
|
|||||||
Some(val) => Ok(val),
|
Some(val) => Ok(val),
|
||||||
None => {
|
None => {
|
||||||
let expanded = expand_raw(&mut err.chars().peekable())?;
|
let expanded = expand_raw(&mut err.chars().peekable())?;
|
||||||
Err(ShErr::Simple {
|
Err(ShErr::simple(
|
||||||
kind: ShErrKind::ExecFail,
|
ShErrKind::ExecFail,
|
||||||
msg: expanded,
|
expanded,
|
||||||
notes: vec![],
|
))
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1470,11 +1441,10 @@ pub fn perform_param_expansion(raw: &str) -> ShResult<String> {
|
|||||||
Some(val) => Ok(val),
|
Some(val) => Ok(val),
|
||||||
None => {
|
None => {
|
||||||
let expanded = expand_raw(&mut err.chars().peekable())?;
|
let expanded = expand_raw(&mut err.chars().peekable())?;
|
||||||
Err(ShErr::Simple {
|
Err(ShErr::simple(
|
||||||
kind: ShErrKind::ExecFail,
|
ShErrKind::ExecFail,
|
||||||
msg: expanded,
|
expanded,
|
||||||
notes: vec![],
|
))
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ParamExp::Substr(pos) => {
|
ParamExp::Substr(pos) => {
|
||||||
|
|||||||
@@ -704,7 +704,11 @@ pub fn term_ctlr() -> Pid {
|
|||||||
/// Calls attach_tty() on the shell's process group to retake control of the
|
/// Calls attach_tty() on the shell's process group to retake control of the
|
||||||
/// terminal
|
/// terminal
|
||||||
pub fn take_term() -> ShResult<()> {
|
pub fn take_term() -> ShResult<()> {
|
||||||
|
// take the terminal back
|
||||||
attach_tty(getpgrp())?;
|
attach_tty(getpgrp())?;
|
||||||
|
|
||||||
|
// send SIGWINCH to tell readline to update its window size in case it changed while we were in the background
|
||||||
|
killpg(getpgrp(), Signal::SIGWINCH)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,65 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::{HashMap, VecDeque};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
use ariadne::Color;
|
||||||
|
use ariadne::{Report, ReportKind};
|
||||||
|
use rand::{RngExt, TryRng};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
libsh::term::{Style, Styled},
|
libsh::term::{Style, Styled},
|
||||||
parse::lex::Span,
|
parse::lex::{Span, SpanSource},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type ShResult<T> = Result<T, ShErr>;
|
pub type ShResult<T> = Result<T, ShErr>;
|
||||||
|
|
||||||
|
pub struct ColorRng {
|
||||||
|
last_color: Option<Color>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorRng {
|
||||||
|
fn get_colors() -> &'static [Color] {
|
||||||
|
&[
|
||||||
|
Color::Red,
|
||||||
|
Color::Cyan,
|
||||||
|
Color::Blue,
|
||||||
|
Color::Green,
|
||||||
|
Color::Yellow,
|
||||||
|
Color::Magenta,
|
||||||
|
Color::Fixed(208), // orange
|
||||||
|
Color::Fixed(39), // deep sky blue
|
||||||
|
Color::Fixed(170), // orchid / magenta-pink
|
||||||
|
Color::Fixed(76), // chartreuse
|
||||||
|
Color::Fixed(51), // aqua
|
||||||
|
Color::Fixed(226), // bright yellow
|
||||||
|
Color::Fixed(99), // slate blue
|
||||||
|
Color::Fixed(214), // light orange
|
||||||
|
Color::Fixed(48), // spring green
|
||||||
|
Color::Fixed(201), // hot pink
|
||||||
|
Color::Fixed(81), // steel blue
|
||||||
|
Color::Fixed(220), // gold
|
||||||
|
Color::Fixed(105), // medium purple
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for ColorRng {
|
||||||
|
type Item = Color;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let colors = Self::get_colors();
|
||||||
|
let idx = rand::rngs::SysRng.try_next_u32().ok()? as usize % colors.len();
|
||||||
|
Some(colors[idx])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static COLOR_RNG: RefCell<ColorRng> = const { RefCell::new(ColorRng { last_color: None }) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_color() -> Color {
|
||||||
|
COLOR_RNG.with(|rng| rng.borrow_mut().next().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
pub trait ShResultExt {
|
pub trait ShResultExt {
|
||||||
fn blame(self, span: Span) -> Self;
|
fn blame(self, span: Span) -> Self;
|
||||||
fn try_blame(self, span: Span) -> Self;
|
fn try_blame(self, span: Span) -> Self;
|
||||||
@@ -16,39 +68,11 @@ pub trait ShResultExt {
|
|||||||
impl<T> ShResultExt for Result<T, ShErr> {
|
impl<T> ShResultExt for Result<T, ShErr> {
|
||||||
/// Blame a span for an error
|
/// Blame a span for an error
|
||||||
fn blame(self, new_span: Span) -> Self {
|
fn blame(self, new_span: Span) -> Self {
|
||||||
let Err(e) = self else { return self };
|
self.map_err(|e| e.blame(new_span))
|
||||||
match e {
|
|
||||||
ShErr::Simple { kind, msg, notes }
|
|
||||||
| ShErr::Full {
|
|
||||||
kind,
|
|
||||||
msg,
|
|
||||||
notes,
|
|
||||||
span: _,
|
|
||||||
} => Err(ShErr::Full {
|
|
||||||
kind: kind.clone(),
|
|
||||||
msg: msg.clone(),
|
|
||||||
notes: notes.clone(),
|
|
||||||
span: new_span,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/// Blame a span if no blame has been assigned yet
|
/// Blame a span if no blame has been assigned yet
|
||||||
fn try_blame(self, new_span: Span) -> Self {
|
fn try_blame(self, new_span: Span) -> Self {
|
||||||
let Err(e) = &self else { return self };
|
self.map_err(|e| e.try_blame(new_span))
|
||||||
match e {
|
|
||||||
ShErr::Simple { kind, msg, notes } => Err(ShErr::Full {
|
|
||||||
kind: kind.clone(),
|
|
||||||
msg: msg.clone(),
|
|
||||||
notes: notes.clone(),
|
|
||||||
span: new_span,
|
|
||||||
}),
|
|
||||||
ShErr::Full {
|
|
||||||
kind: _,
|
|
||||||
msg: _,
|
|
||||||
span: _,
|
|
||||||
notes: _,
|
|
||||||
} => self,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,270 +131,136 @@ impl Display for Note {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ShErr {
|
pub struct ShErr {
|
||||||
Simple {
|
kind: ShErrKind,
|
||||||
kind: ShErrKind,
|
src_span: Option<Span>,
|
||||||
msg: String,
|
labels: Vec<ariadne::Label<Span>>,
|
||||||
notes: Vec<Note>,
|
sources: Vec<SpanSource>,
|
||||||
},
|
notes: Vec<String>
|
||||||
Full {
|
|
||||||
kind: ShErrKind,
|
|
||||||
msg: String,
|
|
||||||
notes: Vec<Note>,
|
|
||||||
span: Span,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShErr {
|
impl ShErr {
|
||||||
pub fn simple(kind: ShErrKind, msg: impl Into<String>) -> Self {
|
pub fn new(kind: ShErrKind, span: Span) -> Self {
|
||||||
let msg = msg.into();
|
Self { kind, src_span: Some(span), labels: vec![], sources: vec![], notes: vec![] }
|
||||||
Self::Simple {
|
}
|
||||||
kind,
|
pub fn simple(kind: ShErrKind, msg: impl Into<String>) -> Self {
|
||||||
msg,
|
Self { kind, src_span: None, labels: vec![], sources: vec![], notes: vec![msg.into()] }
|
||||||
notes: vec![],
|
}
|
||||||
}
|
pub fn at(kind: ShErrKind, span: Span, msg: impl Into<String>) -> Self {
|
||||||
}
|
let color = next_color();
|
||||||
pub fn full(kind: ShErrKind, msg: impl Into<String>, span: Span) -> Self {
|
let src = span.span_source().clone();
|
||||||
let msg = msg.into();
|
let msg: String = msg.into();
|
||||||
Self::Full {
|
Self::new(kind, span.clone())
|
||||||
kind,
|
.with_label(src, ariadne::Label::new(span).with_color(color).with_message(msg))
|
||||||
msg,
|
}
|
||||||
span,
|
pub fn labeled(self, span: Span, msg: impl Into<String>) -> Self {
|
||||||
notes: vec![],
|
let color = next_color();
|
||||||
}
|
let src = span.span_source().clone();
|
||||||
}
|
let msg: String = msg.into();
|
||||||
pub fn unpack(self) -> (ShErrKind, String, Vec<Note>, Option<Span>) {
|
self.with_label(src, ariadne::Label::new(span).with_color(color).with_message(msg))
|
||||||
match self {
|
}
|
||||||
ShErr::Simple { kind, msg, notes } => (kind, msg, notes, None),
|
pub fn blame(self, span: Span) -> Self {
|
||||||
ShErr::Full {
|
let ShErr { kind, src_span: _, labels, sources, notes } = self;
|
||||||
kind,
|
Self { kind, src_span: Some(span), labels, sources, notes }
|
||||||
msg,
|
}
|
||||||
notes,
|
pub fn try_blame(self, span: Span) -> Self {
|
||||||
span,
|
match self {
|
||||||
} => (kind, msg, notes, Some(span)),
|
ShErr { kind, src_span: None, labels, sources, notes } => Self { kind, src_span: Some(span), labels, sources, notes },
|
||||||
}
|
_ => self
|
||||||
}
|
}
|
||||||
pub fn with_note(self, note: Note) -> Self {
|
}
|
||||||
let (kind, msg, mut notes, span) = self.unpack();
|
pub fn kind(&self) -> &ShErrKind {
|
||||||
notes.push(note);
|
&self.kind
|
||||||
if let Some(span) = span {
|
}
|
||||||
Self::Full {
|
pub fn rename(mut self, name: impl Into<String>) -> Self {
|
||||||
kind,
|
if let Some(span) = self.src_span.as_mut() {
|
||||||
msg,
|
span.rename(name.into());
|
||||||
notes,
|
}
|
||||||
span,
|
self
|
||||||
}
|
}
|
||||||
} else {
|
pub fn with_label(self, source: SpanSource, label: ariadne::Label<Span>) -> Self {
|
||||||
Self::Simple { kind, msg, notes }
|
let ShErr { kind, src_span, mut labels, mut sources, notes } = self;
|
||||||
}
|
sources.push(source);
|
||||||
}
|
labels.push(label);
|
||||||
pub fn with_span(sherr: ShErr, span: Span) -> Self {
|
Self { kind, src_span, labels, sources, notes }
|
||||||
let (kind, msg, notes, _) = sherr.unpack();
|
}
|
||||||
Self::Full {
|
pub fn with_context(self, ctx: VecDeque<(SpanSource, ariadne::Label<Span>)>) -> Self {
|
||||||
kind,
|
let ShErr { kind, src_span, mut labels, mut sources, notes } = self;
|
||||||
msg,
|
for (src, label) in ctx {
|
||||||
notes,
|
sources.push(src);
|
||||||
span,
|
labels.push(label);
|
||||||
}
|
}
|
||||||
}
|
Self { kind, src_span, labels, sources, notes }
|
||||||
pub fn kind(&self) -> &ShErrKind {
|
}
|
||||||
match self {
|
pub fn with_note(self, note: impl Into<String>) -> Self {
|
||||||
ShErr::Simple {
|
let ShErr { kind, src_span, labels, sources, mut notes } = self;
|
||||||
kind,
|
notes.push(note.into());
|
||||||
msg: _,
|
Self { kind, src_span, labels, sources, notes }
|
||||||
notes: _,
|
}
|
||||||
}
|
pub fn build_report(&self) -> Option<Report<'_, Span>> {
|
||||||
| ShErr::Full {
|
let span = self.src_span.as_ref()?;
|
||||||
kind,
|
let mut report = Report::build(ReportKind::Error, span.clone())
|
||||||
msg: _,
|
.with_config(ariadne::Config::default().with_color(true));
|
||||||
notes: _,
|
let msg = if self.notes.is_empty() {
|
||||||
span: _,
|
self.kind.to_string()
|
||||||
} => kind,
|
} else {
|
||||||
}
|
format!("{} - {}", self.kind, self.notes.first().unwrap())
|
||||||
}
|
};
|
||||||
pub fn get_window(&self) -> Vec<(usize, String)> {
|
report = report.with_message(msg);
|
||||||
let ShErr::Full {
|
|
||||||
kind: _,
|
|
||||||
msg: _,
|
|
||||||
notes: _,
|
|
||||||
span,
|
|
||||||
} = self
|
|
||||||
else {
|
|
||||||
unreachable!()
|
|
||||||
};
|
|
||||||
let mut total_len: usize = 0;
|
|
||||||
let mut total_lines: usize = 1;
|
|
||||||
let mut lines = vec![];
|
|
||||||
let mut cur_line = String::new();
|
|
||||||
|
|
||||||
let src = span.get_source();
|
for label in self.labels.clone() {
|
||||||
let mut chars = src.chars();
|
report = report.with_label(label);
|
||||||
|
}
|
||||||
|
for note in &self.notes {
|
||||||
|
report = report.with_note(note);
|
||||||
|
}
|
||||||
|
|
||||||
while let Some(ch) = chars.next() {
|
Some(report.finish())
|
||||||
total_len += ch.len_utf8();
|
}
|
||||||
cur_line.push(ch);
|
fn collect_sources(&self) -> HashMap<SpanSource, String> {
|
||||||
if ch == '\n' {
|
let mut source_map = HashMap::new();
|
||||||
if total_len > span.start {
|
if let Some(span) = &self.src_span {
|
||||||
let line = (total_lines, mem::take(&mut cur_line));
|
let src = span.span_source().clone();
|
||||||
lines.push(line);
|
source_map.entry(src.clone())
|
||||||
}
|
.or_insert_with(|| src.content().to_string());
|
||||||
if total_len >= span.end {
|
}
|
||||||
break;
|
for src in &self.sources {
|
||||||
}
|
source_map.entry(src.clone())
|
||||||
total_lines += 1;
|
.or_insert_with(|| src.content().to_string());
|
||||||
|
}
|
||||||
|
source_map
|
||||||
|
}
|
||||||
|
pub fn print_error(&self) {
|
||||||
|
let default = || {
|
||||||
|
eprintln!("{}", self.kind);
|
||||||
|
for note in &self.notes {
|
||||||
|
eprintln!("note: {note}");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let Some(report) = self.build_report() else {
|
||||||
|
return default();
|
||||||
|
};
|
||||||
|
|
||||||
cur_line.clear();
|
let sources = self.collect_sources();
|
||||||
}
|
let cache = ariadne::FnCache::new(move |src: &SpanSource| {
|
||||||
}
|
sources.get(src)
|
||||||
|
.cloned()
|
||||||
if !cur_line.is_empty() {
|
.ok_or_else(|| format!("Failed to fetch source '{}'", src.name()))
|
||||||
let line = (total_lines, mem::take(&mut cur_line));
|
});
|
||||||
lines.push(line);
|
if report.eprint(cache).is_err() {
|
||||||
}
|
default();
|
||||||
|
}
|
||||||
lines
|
}
|
||||||
}
|
|
||||||
pub fn get_line_col(&self) -> (usize, usize) {
|
|
||||||
let ShErr::Full {
|
|
||||||
kind: _,
|
|
||||||
msg: _,
|
|
||||||
notes: _,
|
|
||||||
span,
|
|
||||||
} = self
|
|
||||||
else {
|
|
||||||
unreachable!()
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut lineno = 1;
|
|
||||||
let mut colno = 1;
|
|
||||||
let src = span.get_source();
|
|
||||||
let mut chars = src.chars().enumerate();
|
|
||||||
while let Some((pos, ch)) = chars.next() {
|
|
||||||
if pos >= span.start {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if ch == '\n' {
|
|
||||||
lineno += 1;
|
|
||||||
colno = 1;
|
|
||||||
} else {
|
|
||||||
colno += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(lineno, colno)
|
|
||||||
}
|
|
||||||
pub fn get_indicator_lines(&self) -> Option<Vec<String>> {
|
|
||||||
match self {
|
|
||||||
ShErr::Simple {
|
|
||||||
kind: _,
|
|
||||||
msg: _,
|
|
||||||
notes: _,
|
|
||||||
} => None,
|
|
||||||
ShErr::Full {
|
|
||||||
kind: _,
|
|
||||||
msg: _,
|
|
||||||
notes: _,
|
|
||||||
span,
|
|
||||||
} => {
|
|
||||||
let text = span.as_str();
|
|
||||||
let lines = text.lines();
|
|
||||||
let mut indicator_lines = vec![];
|
|
||||||
|
|
||||||
for line in lines {
|
|
||||||
let indicator_line = "^"
|
|
||||||
.repeat(line.trim().len())
|
|
||||||
.styled(Style::Red | Style::Bold);
|
|
||||||
indicator_lines.push(indicator_line);
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(indicator_lines)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ShErr {
|
impl Display for ShErr {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
if self.notes.is_empty() {
|
||||||
Self::Simple {
|
write!(f, "{}", self.kind)
|
||||||
msg,
|
} else {
|
||||||
kind: _,
|
write!(f, "{} - {}", self.kind, self.notes.first().unwrap())
|
||||||
notes,
|
}
|
||||||
} => {
|
}
|
||||||
let mut all_strings = vec![msg.to_string()];
|
|
||||||
let mut notes_fmt = vec![];
|
|
||||||
for note in notes {
|
|
||||||
let fmt = format!("{note}");
|
|
||||||
notes_fmt.push(fmt);
|
|
||||||
}
|
|
||||||
all_strings.append(&mut notes_fmt);
|
|
||||||
let mut output = all_strings.join("\n");
|
|
||||||
output.push('\n');
|
|
||||||
|
|
||||||
writeln!(f, "{}", output)
|
|
||||||
}
|
|
||||||
|
|
||||||
Self::Full {
|
|
||||||
msg,
|
|
||||||
kind,
|
|
||||||
notes,
|
|
||||||
span: _,
|
|
||||||
} => {
|
|
||||||
let window = self.get_window();
|
|
||||||
let mut indicator_lines = self.get_indicator_lines().unwrap().into_iter();
|
|
||||||
let mut lineno_pad_count = 0;
|
|
||||||
for (lineno, _) in window.clone() {
|
|
||||||
if lineno.to_string().len() > lineno_pad_count {
|
|
||||||
lineno_pad_count = lineno.to_string().len() + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let padding = " ".repeat(lineno_pad_count);
|
|
||||||
writeln!(f)?;
|
|
||||||
|
|
||||||
let (line, col) = self.get_line_col();
|
|
||||||
let line_fmt = line.styled(Style::Cyan | Style::Bold);
|
|
||||||
let col_fmt = col.styled(Style::Cyan | Style::Bold);
|
|
||||||
let kind = kind.styled(Style::Red | Style::Bold);
|
|
||||||
let arrow = "->".styled(Style::Cyan | Style::Bold);
|
|
||||||
writeln!(f, "{kind} - {msg}",)?;
|
|
||||||
writeln!(f, "{padding}{arrow} [{line_fmt};{col_fmt}]",)?;
|
|
||||||
|
|
||||||
let bar = format!("{padding}|").styled(Style::Cyan | Style::Bold);
|
|
||||||
writeln!(f, "{bar}")?;
|
|
||||||
|
|
||||||
let mut first_ind_ln = true;
|
|
||||||
for (lineno, line) in window {
|
|
||||||
let lineno = lineno.to_string();
|
|
||||||
let line = line.trim();
|
|
||||||
let mut prefix = format!("{padding}|");
|
|
||||||
prefix.replace_range(0..lineno.len(), &lineno);
|
|
||||||
prefix = prefix.styled(Style::Cyan | Style::Bold);
|
|
||||||
writeln!(f, "{prefix} {line}")?;
|
|
||||||
|
|
||||||
if let Some(ind_ln) = indicator_lines.next() {
|
|
||||||
if first_ind_ln {
|
|
||||||
let ind_ln_padding = " ".repeat(col);
|
|
||||||
let ind_ln = format!("{ind_ln_padding}{ind_ln}");
|
|
||||||
writeln!(f, "{bar}{ind_ln}")?;
|
|
||||||
first_ind_ln = false;
|
|
||||||
} else {
|
|
||||||
writeln!(f, "{bar} {ind_ln}")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(f, "{bar}")?;
|
|
||||||
|
|
||||||
let bar_break = "-".styled(Style::Cyan | Style::Bold);
|
|
||||||
if !notes.is_empty() {
|
|
||||||
writeln!(f)?;
|
|
||||||
}
|
|
||||||
for note in notes {
|
|
||||||
write!(f, "{padding}{bar_break} {note}")?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::io::Error> for ShErr {
|
impl From<std::io::Error> for ShErr {
|
||||||
@@ -404,9 +294,8 @@ pub enum ShErrKind {
|
|||||||
ResourceLimitExceeded,
|
ResourceLimitExceeded,
|
||||||
BadPermission,
|
BadPermission,
|
||||||
Errno(Errno),
|
Errno(Errno),
|
||||||
FileNotFound(String),
|
FileNotFound,
|
||||||
CmdNotFound(String),
|
CmdNotFound,
|
||||||
ReadlineIntr(String),
|
|
||||||
ReadlineErr,
|
ReadlineErr,
|
||||||
|
|
||||||
// Not really errors, more like internal signals
|
// Not really errors, more like internal signals
|
||||||
@@ -431,13 +320,12 @@ impl Display for ShErrKind {
|
|||||||
Self::ResourceLimitExceeded => "Resource Limit Exceeded",
|
Self::ResourceLimitExceeded => "Resource Limit Exceeded",
|
||||||
Self::BadPermission => "Bad Permissions",
|
Self::BadPermission => "Bad Permissions",
|
||||||
Self::Errno(e) => &format!("Errno: {}", e.desc()),
|
Self::Errno(e) => &format!("Errno: {}", e.desc()),
|
||||||
Self::FileNotFound(file) => &format!("File not found: {file}"),
|
Self::FileNotFound => "File not found",
|
||||||
Self::CmdNotFound(cmd) => &format!("Command not found: {cmd}"),
|
Self::CmdNotFound => "Command not found",
|
||||||
Self::CleanExit(_) => "",
|
Self::CleanExit(_) => "",
|
||||||
Self::FuncReturn(_) => "Syntax Error",
|
Self::FuncReturn(_) => "Syntax Error",
|
||||||
Self::LoopContinue(_) => "Syntax Error",
|
Self::LoopContinue(_) => "Syntax Error",
|
||||||
Self::LoopBreak(_) => "Syntax Error",
|
Self::LoopBreak(_) => "Syntax Error",
|
||||||
Self::ReadlineIntr(_) => "",
|
|
||||||
Self::ReadlineErr => "Readline Error",
|
Self::ReadlineErr => "Readline Error",
|
||||||
Self::ClearReadline => "",
|
Self::ClearReadline => "",
|
||||||
Self::Null => "",
|
Self::Null => "",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::{fmt::Display, ops::BitOr};
|
use std::{cell::RefCell, fmt::Display, ops::BitOr};
|
||||||
|
|
||||||
pub trait Styled: Sized + Display {
|
pub trait Styled: Sized + Display {
|
||||||
fn styled<S: Into<StyleSet>>(self, style: S) -> String {
|
fn styled<S: Into<StyleSet>>(self, style: S) -> String {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
use ariadne::Span as AriadneSpan;
|
||||||
|
|
||||||
use crate::parse::lex::{Span, Tk, TkRule};
|
use crate::parse::lex::{Span, Tk, TkRule};
|
||||||
use crate::parse::{Redir, RedirType};
|
use crate::parse::{Node, Redir, RedirType};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub trait VecDequeExt<T> {
|
pub trait VecDequeExt<T> {
|
||||||
@@ -27,6 +29,10 @@ pub trait RedirVecUtils<Redir> {
|
|||||||
fn split_by_channel(self) -> (Vec<Redir>, Vec<Redir>);
|
fn split_by_channel(self) -> (Vec<Redir>, Vec<Redir>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait NodeVecUtils<Node> {
|
||||||
|
fn get_span(&self) -> Option<Span>;
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> VecDequeExt<T> for VecDeque<T> {
|
impl<T> VecDequeExt<T> for VecDeque<T> {
|
||||||
fn to_vec(self) -> Vec<T> {
|
fn to_vec(self) -> Vec<T> {
|
||||||
self.into_iter().collect::<Vec<T>>()
|
self.into_iter().collect::<Vec<T>>()
|
||||||
@@ -78,7 +84,7 @@ impl TkVecUtils<Tk> for Vec<Tk> {
|
|||||||
if let Some(first_tk) = self.first() {
|
if let Some(first_tk) = self.first() {
|
||||||
self
|
self
|
||||||
.last()
|
.last()
|
||||||
.map(|last_tk| Span::new(first_tk.span.start..last_tk.span.end, first_tk.source()))
|
.map(|last_tk| Span::new(first_tk.span.range().start..last_tk.span.range().end, first_tk.source()))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -124,3 +130,17 @@ impl RedirVecUtils<Redir> for Vec<Redir> {
|
|||||||
(input, output)
|
(input, output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl NodeVecUtils<Node> for Vec<Node> {
|
||||||
|
fn get_span(&self) -> Option<Span> {
|
||||||
|
if let Some(first_nd) = self.first()
|
||||||
|
&& let Some(last_nd) = self.last() {
|
||||||
|
let first_start = first_nd.get_span().range().start;
|
||||||
|
let last_end = last_nd.get_span().range().end;
|
||||||
|
if first_start <= last_end {
|
||||||
|
return Some(Span::new(first_start..last_end, first_nd.get_span().source().content()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
12
src/main.rs
12
src/main.rs
@@ -1,7 +1,8 @@
|
|||||||
#![allow(
|
#![allow(
|
||||||
clippy::derivable_impls,
|
clippy::derivable_impls,
|
||||||
clippy::tabs_in_doc_comments,
|
clippy::tabs_in_doc_comments,
|
||||||
clippy::while_let_on_iterator
|
clippy::while_let_on_iterator,
|
||||||
|
clippy::result_large_err
|
||||||
)]
|
)]
|
||||||
pub mod builtin;
|
pub mod builtin;
|
||||||
pub mod expand;
|
pub mod expand;
|
||||||
@@ -87,6 +88,7 @@ fn setup_panic_handler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> ExitCode {
|
fn main() -> ExitCode {
|
||||||
|
yansi::enable();
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
kickstart_lazy_evals();
|
kickstart_lazy_evals();
|
||||||
setup_panic_handler();
|
setup_panic_handler();
|
||||||
@@ -162,7 +164,7 @@ fn shed_interactive() -> ShResult<()> {
|
|||||||
sig_setup();
|
sig_setup();
|
||||||
|
|
||||||
if let Err(e) = source_rc() {
|
if let Err(e) = source_rc() {
|
||||||
eprintln!("{e}");
|
e.print_error();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create readline instance with initial prompt
|
// Create readline instance with initial prompt
|
||||||
@@ -197,7 +199,7 @@ fn shed_interactive() -> ShResult<()> {
|
|||||||
QUIT_CODE.store(*code, Ordering::SeqCst);
|
QUIT_CODE.store(*code, Ordering::SeqCst);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
_ => eprintln!("{e}"),
|
_ => e.print_error(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -269,7 +271,7 @@ fn shed_interactive() -> ShResult<()> {
|
|||||||
QUIT_CODE.store(*code, Ordering::SeqCst);
|
QUIT_CODE.store(*code, Ordering::SeqCst);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
_ => eprintln!("{e}"),
|
_ => e.print_error(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let command_run_time = start.elapsed();
|
let command_run_time = start.elapsed();
|
||||||
@@ -295,7 +297,7 @@ fn shed_interactive() -> ShResult<()> {
|
|||||||
QUIT_CODE.store(*code, Ordering::SeqCst);
|
QUIT_CODE.store(*code, Ordering::SeqCst);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
_ => eprintln!("{e}"),
|
_ => e.print_error(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::{HashSet, VecDeque},
|
cell::Cell, collections::{HashSet, VecDeque}, os::unix::fs::PermissionsExt
|
||||||
os::unix::fs::PermissionsExt,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
use ariadne::{Fmt, Label, Span as AriadneSpan};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
builtin::{
|
builtin::{
|
||||||
alias::{alias, unalias}, arrops::{arr_pop, arr_fpop, arr_push, arr_fpush, arr_rotate}, cd::cd, complete::{compgen_builtin, complete_builtin}, dirstack::{dirs, popd, pushd}, echo::echo, eval, exec, flowctl::flowctl, jobctl::{JobBehavior, continue_job, disown, jobs}, map, pwd::pwd, read::read_builtin, shift::shift, shopt::shopt, source::source, test::double_bracket_test, trap::{TrapTarget, trap}, varcmds::{export, local, readonly, unset}, zoltraak::zoltraak
|
alias::{alias, unalias}, arrops::{arr_fpop, arr_fpush, arr_pop, arr_push, arr_rotate}, cd::cd, complete::{compgen_builtin, complete_builtin}, dirstack::{dirs, popd, pushd}, echo::echo, eval, exec, flowctl::flowctl, jobctl::{JobBehavior, continue_job, disown, jobs}, map, pwd::pwd, read::read_builtin, shift::shift, shopt::shopt, source::source, test::double_bracket_test, trap::{TrapTarget, trap}, varcmds::{export, local, readonly, unset}, zoltraak::zoltraak
|
||||||
},
|
},
|
||||||
expand::{expand_aliases, glob_to_regex},
|
expand::{expand_aliases, glob_to_regex},
|
||||||
jobs::{ChildProc, JobStack, dispatch_job},
|
jobs::{ChildProc, JobStack, dispatch_job},
|
||||||
libsh::{
|
libsh::{
|
||||||
error::{ShErr, ShErrKind, ShResult, ShResultExt},
|
error::{ShErr, ShErrKind, ShResult, ShResultExt, next_color},
|
||||||
utils::RedirVecUtils,
|
utils::RedirVecUtils,
|
||||||
},
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
@@ -27,7 +29,7 @@ use super::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static RECURSE_DEPTH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
|
static RECURSE_DEPTH: Cell<usize> = const { Cell::new(0) };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_in_path(name: &str) -> bool {
|
pub fn is_in_path(name: &str) -> bool {
|
||||||
@@ -160,7 +162,7 @@ pub fn exec_input(input: String, io_stack: Option<IoStack>, interactive: bool) -
|
|||||||
let mut parser = ParsedSrc::new(Arc::new(input)).with_lex_flags(lex_flags);
|
let mut parser = ParsedSrc::new(Arc::new(input)).with_lex_flags(lex_flags);
|
||||||
if let Err(errors) = parser.parse_src() {
|
if let Err(errors) = parser.parse_src() {
|
||||||
for error in errors {
|
for error in errors {
|
||||||
eprintln!("{error}");
|
error.print_error();
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -284,6 +286,7 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
pub fn exec_func_def(&mut self, func_def: Node) -> ShResult<()> {
|
pub fn exec_func_def(&mut self, func_def: Node) -> ShResult<()> {
|
||||||
let blame = func_def.get_span();
|
let blame = func_def.get_span();
|
||||||
|
let ctx = func_def.context.clone();
|
||||||
let NdRule::FuncDef { name, body } = func_def.class else {
|
let NdRule::FuncDef { name, body } = func_def.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
@@ -292,17 +295,17 @@ impl Dispatcher {
|
|||||||
let name = name.span.as_str().strip_suffix("()").unwrap();
|
let name = name.span.as_str().strip_suffix("()").unwrap();
|
||||||
|
|
||||||
if KEYWORDS.contains(&name) {
|
if KEYWORDS.contains(&name) {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(
|
||||||
ShErrKind::SyntaxErr,
|
ShErrKind::SyntaxErr,
|
||||||
format!("function: Forbidden function name `{name}`"),
|
|
||||||
blame,
|
blame,
|
||||||
|
format!("function: Forbidden function name `{name}`"),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut func_parser = ParsedSrc::new(Arc::new(body));
|
let mut func_parser = ParsedSrc::new(Arc::new(body)).with_context(ctx);
|
||||||
if let Err(errors) = func_parser.parse_src() {
|
if let Err(errors) = func_parser.parse_src() {
|
||||||
for error in errors {
|
for error in errors {
|
||||||
eprintln!("{error}");
|
error.print_error();
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -318,14 +321,14 @@ impl Dispatcher {
|
|||||||
|
|
||||||
self.run_fork("anonymous_subshell", |s| {
|
self.run_fork("anonymous_subshell", |s| {
|
||||||
if let Err(e) = s.set_assignments(assignments, AssignBehavior::Export) {
|
if let Err(e) = s.set_assignments(assignments, AssignBehavior::Export) {
|
||||||
eprintln!("{e}");
|
e.print_error();
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
s.io_stack.append_to_frame(subsh.redirs);
|
s.io_stack.append_to_frame(subsh.redirs);
|
||||||
let mut argv = match prepare_argv(argv) {
|
let mut argv = match prepare_argv(argv) {
|
||||||
Ok(argv) => argv,
|
Ok(argv) => argv,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("{e}");
|
e.print_error();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -334,12 +337,14 @@ impl Dispatcher {
|
|||||||
let subsh_body = subsh.0.to_string();
|
let subsh_body = subsh.0.to_string();
|
||||||
|
|
||||||
if let Err(e) = exec_input(subsh_body, None, s.interactive) {
|
if let Err(e) = exec_input(subsh_body, None, s.interactive) {
|
||||||
eprintln!("{e}");
|
e.print_error();
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn exec_func(&mut self, func: Node) -> ShResult<()> {
|
fn exec_func(&mut self, func: Node) -> ShResult<()> {
|
||||||
let blame = func.get_span().clone();
|
let mut blame = func.get_span().clone();
|
||||||
|
let func_name = func.get_command().unwrap().to_string();
|
||||||
|
let func_ctx = func.get_context(format!("in call to function '{}'",func_name.fg(next_color())));
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments,
|
assignments,
|
||||||
mut argv,
|
mut argv,
|
||||||
@@ -356,23 +361,25 @@ impl Dispatcher {
|
|||||||
});
|
});
|
||||||
if depth > max_depth {
|
if depth > max_depth {
|
||||||
RECURSE_DEPTH.with(|d| d.set(d.get() - 1));
|
RECURSE_DEPTH.with(|d| d.set(d.get() - 1));
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(
|
||||||
ShErrKind::InternalErr,
|
ShErrKind::InternalErr,
|
||||||
format!("maximum recursion depth ({max_depth}) exceeded"),
|
|
||||||
blame,
|
blame,
|
||||||
|
format!("maximum recursion depth ({max_depth}) exceeded"),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let env_vars = self.set_assignments(assignments, AssignBehavior::Export)?;
|
let env_vars = self.set_assignments(assignments, AssignBehavior::Export)?;
|
||||||
|
let func_name = argv.remove(0).to_string();
|
||||||
let _var_guard = VarCtxGuard::new(env_vars.into_iter().collect());
|
let _var_guard = VarCtxGuard::new(env_vars.into_iter().collect());
|
||||||
|
|
||||||
self.io_stack.append_to_frame(func.redirs);
|
self.io_stack.append_to_frame(func.redirs);
|
||||||
|
|
||||||
let func_name = argv.remove(0).span.as_str().to_string();
|
blame.rename(func_name.clone());
|
||||||
|
|
||||||
let argv = prepare_argv(argv)?;
|
let argv = prepare_argv(argv)?;
|
||||||
let result = if let Some(ref mut func_body) = read_logic(|l| l.get_func(&func_name)) {
|
let result = if let Some(ref mut func_body) = read_logic(|l| l.get_func(&func_name)) {
|
||||||
let _guard = ScopeGuard::exclusive_scope(Some(argv));
|
let _guard = ScopeGuard::exclusive_scope(Some(argv));
|
||||||
|
func_body.body_mut().propagate_context(func_ctx);
|
||||||
func_body.body_mut().flags = func.flags;
|
func_body.body_mut().flags = func.flags;
|
||||||
|
|
||||||
if let Err(e) = self.exec_brc_grp(func_body.body().clone()) {
|
if let Err(e) = self.exec_brc_grp(func_body.body().clone()) {
|
||||||
@@ -381,16 +388,16 @@ impl Dispatcher {
|
|||||||
state::set_status(*code);
|
state::set_status(*code);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
_ => Err(e).blame(blame),
|
_ => Err(e)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(ShErr::full(
|
Err(ShErr::at(
|
||||||
ShErrKind::InternalErr,
|
ShErrKind::InternalErr,
|
||||||
format!("Failed to find function '{}'", func_name),
|
|
||||||
blame,
|
blame,
|
||||||
|
format!("Failed to find function '{}'", func_name),
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -418,7 +425,7 @@ impl Dispatcher {
|
|||||||
log::trace!("Forking brace group");
|
log::trace!("Forking brace group");
|
||||||
self.run_fork("brace group", |s| {
|
self.run_fork("brace group", |s| {
|
||||||
if let Err(e) = brc_grp_logic(s) {
|
if let Err(e) = brc_grp_logic(s) {
|
||||||
eprintln!("{e}");
|
e.print_error();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -472,7 +479,7 @@ impl Dispatcher {
|
|||||||
log::trace!("Forking builtin: case");
|
log::trace!("Forking builtin: case");
|
||||||
self.run_fork("case", |s| {
|
self.run_fork("case", |s| {
|
||||||
if let Err(e) = case_logic(s) {
|
if let Err(e) = case_logic(s) {
|
||||||
eprintln!("{e}");
|
e.print_error();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -535,7 +542,7 @@ impl Dispatcher {
|
|||||||
log::trace!("Forking builtin: loop");
|
log::trace!("Forking builtin: loop");
|
||||||
self.run_fork("loop", |s| {
|
self.run_fork("loop", |s| {
|
||||||
if let Err(e) = loop_logic(s) {
|
if let Err(e) = loop_logic(s) {
|
||||||
eprintln!("{e}");
|
e.print_error();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -613,7 +620,7 @@ impl Dispatcher {
|
|||||||
log::trace!("Forking builtin: for");
|
log::trace!("Forking builtin: for");
|
||||||
self.run_fork("for", |s| {
|
self.run_fork("for", |s| {
|
||||||
if let Err(e) = for_logic(s) {
|
if let Err(e) = for_logic(s) {
|
||||||
eprintln!("{e}");
|
e.print_error();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -669,7 +676,7 @@ impl Dispatcher {
|
|||||||
log::trace!("Forking builtin: if");
|
log::trace!("Forking builtin: if");
|
||||||
self.run_fork("if", |s| {
|
self.run_fork("if", |s| {
|
||||||
if let Err(e) = if_logic(s) {
|
if let Err(e) = if_logic(s) {
|
||||||
eprintln!("{e}");
|
e.print_error();
|
||||||
state::set_status(1);
|
state::set_status(1);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -726,7 +733,7 @@ impl Dispatcher {
|
|||||||
let _guard = self.io_stack.pop_frame().redirect()?;
|
let _guard = self.io_stack.pop_frame().redirect()?;
|
||||||
self.run_fork(&cmd_raw, |s| {
|
self.run_fork(&cmd_raw, |s| {
|
||||||
if let Err(e) = s.dispatch_builtin(cmd) {
|
if let Err(e) = s.dispatch_builtin(cmd) {
|
||||||
eprintln!("{e}");
|
e.print_error();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -744,6 +751,7 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
fn dispatch_builtin(&mut self, mut cmd: Node) -> ShResult<()> {
|
fn dispatch_builtin(&mut self, mut cmd: Node) -> ShResult<()> {
|
||||||
let cmd_raw = cmd.get_command().unwrap().to_string();
|
let cmd_raw = cmd.get_command().unwrap().to_string();
|
||||||
|
let context = cmd.context.clone();
|
||||||
let NdRule::Command { assignments, argv } = &mut cmd.class else {
|
let NdRule::Command { assignments, argv } = &mut cmd.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
@@ -770,7 +778,7 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
return self.exec_cmd(cmd);
|
return self.exec_cmd(cmd);
|
||||||
}
|
}
|
||||||
match cmd_raw.as_str() {
|
let result = match cmd_raw.as_str() {
|
||||||
"echo" => echo(cmd, io_stack_mut, curr_job_mut),
|
"echo" => echo(cmd, io_stack_mut, curr_job_mut),
|
||||||
"cd" => cd(cmd, curr_job_mut),
|
"cd" => cd(cmd, curr_job_mut),
|
||||||
"export" => export(cmd, io_stack_mut, curr_job_mut),
|
"export" => export(cmd, io_stack_mut, curr_job_mut),
|
||||||
@@ -816,9 +824,16 @@ impl Dispatcher {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
_ => unimplemented!("Have not yet added support for builtin '{}'", cmd_raw),
|
_ => unimplemented!("Have not yet added support for builtin '{}'", cmd_raw),
|
||||||
}
|
};
|
||||||
|
|
||||||
|
if let Err(e) = result {
|
||||||
|
Err(e.with_context(context))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn exec_cmd(&mut self, cmd: Node) -> ShResult<()> {
|
fn exec_cmd(&mut self, cmd: Node) -> ShResult<()> {
|
||||||
|
let context = cmd.context.clone();
|
||||||
let NdRule::Command { assignments, argv } = cmd.class else {
|
let NdRule::Command { assignments, argv } = cmd.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
@@ -855,12 +870,15 @@ impl Dispatcher {
|
|||||||
let cmd_str = cmd.to_str().unwrap().to_string();
|
let cmd_str = cmd.to_str().unwrap().to_string();
|
||||||
match e {
|
match e {
|
||||||
Errno::ENOENT => {
|
Errno::ENOENT => {
|
||||||
let err = ShErr::full(ShErrKind::CmdNotFound(cmd_str), "", span);
|
ShErr::new(ShErrKind::CmdNotFound, span.clone())
|
||||||
eprintln!("{err}");
|
.labeled(span, format!("{cmd_str}: command not found"))
|
||||||
|
.with_context(context)
|
||||||
|
.print_error();
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let err = ShErr::full(ShErrKind::Errno(e), format!("{e}"), span);
|
ShErr::at(ShErrKind::Errno(e), span, format!("{e}"))
|
||||||
eprintln!("{err}");
|
.with_context(context)
|
||||||
|
.print_error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exit(e as i32)
|
exit(e as i32)
|
||||||
|
|||||||
371
src/parse/lex.rs
371
src/parse/lex.rs
@@ -24,24 +24,99 @@ pub const KEYWORDS: [&str; 16] = [
|
|||||||
|
|
||||||
pub const OPENERS: [&str; 6] = ["if", "while", "until", "for", "select", "case"];
|
pub const OPENERS: [&str; 6] = ["if", "while", "until", "for", "select", "case"];
|
||||||
|
|
||||||
|
/// Used to track whether the lexer is currently inside a quote, and if so, which type
|
||||||
|
#[derive(Default,Debug)]
|
||||||
|
pub enum QuoteState {
|
||||||
|
#[default]
|
||||||
|
Outside,
|
||||||
|
Single,
|
||||||
|
Double
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QuoteState {
|
||||||
|
pub fn outside(&self) -> bool {
|
||||||
|
matches!(self, QuoteState::Outside)
|
||||||
|
}
|
||||||
|
pub fn in_single(&self) -> bool {
|
||||||
|
matches!(self, QuoteState::Single)
|
||||||
|
}
|
||||||
|
pub fn in_double(&self) -> bool {
|
||||||
|
matches!(self, QuoteState::Double)
|
||||||
|
}
|
||||||
|
pub fn in_quote(&self) -> bool {
|
||||||
|
!self.outside()
|
||||||
|
}
|
||||||
|
/// Toggles whether we are in a double quote. If self = QuoteState::Single, this does nothing, since double quotes inside single quotes are just literal characters
|
||||||
|
pub fn toggle_double(&mut self) {
|
||||||
|
match self {
|
||||||
|
QuoteState::Outside => *self = QuoteState::Double,
|
||||||
|
QuoteState::Double => *self = QuoteState::Outside,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Toggles whether we are in a single quote. If self == QuoteState::Double, this does nothing, since single quotes are not interpreted inside double quotes
|
||||||
|
pub fn toggle_single(&mut self) {
|
||||||
|
match self {
|
||||||
|
QuoteState::Outside => *self = QuoteState::Single,
|
||||||
|
QuoteState::Single => *self = QuoteState::Outside,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Default, Debug, Eq, Hash)]
|
||||||
|
pub struct SpanSource {
|
||||||
|
name: String,
|
||||||
|
content: Arc<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpanSource {
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
pub fn content(&self) -> Arc<String> {
|
||||||
|
self.content.clone()
|
||||||
|
}
|
||||||
|
pub fn rename(&mut self, name: String) {
|
||||||
|
self.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for SpanSource {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Span::new(10..20)
|
/// Span::new(10..20)
|
||||||
#[derive(Clone, PartialEq, Default, Debug)]
|
#[derive(Clone, PartialEq, Default, Debug)]
|
||||||
pub struct Span {
|
pub struct Span {
|
||||||
range: Range<usize>,
|
range: Range<usize>,
|
||||||
source: Arc<String>,
|
source: SpanSource
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Span {
|
impl Span {
|
||||||
/// New `Span`. Wraps a range and a string slice that it refers to.
|
/// New `Span`. Wraps a range and a string slice that it refers to.
|
||||||
pub fn new(range: Range<usize>, source: Arc<String>) -> Self {
|
pub fn new(range: Range<usize>, source: Arc<String>) -> Self {
|
||||||
|
let source = SpanSource { name: "<stdin>".into(), content: source };
|
||||||
Span { range, source }
|
Span { range, source }
|
||||||
}
|
}
|
||||||
|
pub fn rename(&mut self, name: String) {
|
||||||
|
self.source.name = name;
|
||||||
|
}
|
||||||
|
pub fn with_name(mut self, name: String) -> Self {
|
||||||
|
self.source.name = name;
|
||||||
|
self
|
||||||
|
}
|
||||||
/// Slice the source string at the wrapped range
|
/// Slice the source string at the wrapped range
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
&self.source[self.start..self.end]
|
&self.source.content[self.range().start..self.range().end]
|
||||||
}
|
}
|
||||||
pub fn get_source(&self) -> Arc<String> {
|
pub fn get_source(&self) -> Arc<String> {
|
||||||
self.source.clone()
|
self.source.content.clone()
|
||||||
|
}
|
||||||
|
pub fn span_source(&self) -> &SpanSource {
|
||||||
|
&self.source
|
||||||
}
|
}
|
||||||
pub fn range(&self) -> Range<usize> {
|
pub fn range(&self) -> Range<usize> {
|
||||||
self.range.clone()
|
self.range.clone()
|
||||||
@@ -53,14 +128,23 @@ impl Span {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allows simple access to the underlying range wrapped by the span
|
impl ariadne::Span for Span {
|
||||||
impl Deref for Span {
|
type SourceId = SpanSource;
|
||||||
type Target = Range<usize>;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn source(&self) -> &Self::SourceId {
|
||||||
&self.range
|
&self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn start(&self) -> usize {
|
||||||
|
self.range.start
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end(&self) -> usize {
|
||||||
|
self.range.end
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Allows simple access to the underlying range wrapped by the span
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub enum TkRule {
|
pub enum TkRule {
|
||||||
Null,
|
Null,
|
||||||
@@ -108,7 +192,7 @@ impl Tk {
|
|||||||
self.span.as_str()
|
self.span.as_str()
|
||||||
}
|
}
|
||||||
pub fn source(&self) -> Arc<String> {
|
pub fn source(&self) -> Arc<String> {
|
||||||
self.span.source.clone()
|
self.span.source.content.clone()
|
||||||
}
|
}
|
||||||
pub fn mark(&mut self, flag: TkFlags) {
|
pub fn mark(&mut self, flag: TkFlags) {
|
||||||
self.flags |= flag;
|
self.flags |= flag;
|
||||||
@@ -150,7 +234,7 @@ bitflags! {
|
|||||||
pub struct LexStream {
|
pub struct LexStream {
|
||||||
source: Arc<String>,
|
source: Arc<String>,
|
||||||
pub cursor: usize,
|
pub cursor: usize,
|
||||||
in_quote: bool,
|
quote_state: QuoteState,
|
||||||
brc_grp_start: Option<usize>,
|
brc_grp_start: Option<usize>,
|
||||||
flags: LexFlags,
|
flags: LexFlags,
|
||||||
}
|
}
|
||||||
@@ -183,11 +267,11 @@ impl LexStream {
|
|||||||
pub fn new(source: Arc<String>, flags: LexFlags) -> Self {
|
pub fn new(source: Arc<String>, flags: LexFlags) -> Self {
|
||||||
let flags = flags | LexFlags::FRESH | LexFlags::NEXT_IS_CMD;
|
let flags = flags | LexFlags::FRESH | LexFlags::NEXT_IS_CMD;
|
||||||
Self {
|
Self {
|
||||||
|
flags,
|
||||||
source,
|
source,
|
||||||
cursor: 0,
|
cursor: 0,
|
||||||
in_quote: false,
|
quote_state: QuoteState::default(),
|
||||||
brc_grp_start: None,
|
brc_grp_start: None,
|
||||||
flags,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Returns a slice of the source input using the given range
|
/// Returns a slice of the source input using the given range
|
||||||
@@ -270,10 +354,10 @@ impl LexStream {
|
|||||||
if !found_fd && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
if !found_fd && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||||
let span_start = self.cursor;
|
let span_start = self.cursor;
|
||||||
self.cursor = pos;
|
self.cursor = pos;
|
||||||
return Some(Err(ShErr::full(
|
return Some(Err(ShErr::at(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Invalid redirection",
|
|
||||||
Span::new(span_start..pos, self.source.clone()),
|
Span::new(span_start..pos, self.source.clone()),
|
||||||
|
"Invalid redirection",
|
||||||
)));
|
)));
|
||||||
} else {
|
} else {
|
||||||
tk = self.get_token(self.cursor..pos, TkRule::Redir);
|
tk = self.get_token(self.cursor..pos, TkRule::Redir);
|
||||||
@@ -352,6 +436,47 @@ impl LexStream {
|
|||||||
if let Some(ch) = chars.next() {
|
if let Some(ch) = chars.next() {
|
||||||
pos += ch.len_utf8();
|
pos += ch.len_utf8();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
'\'' => {
|
||||||
|
pos += 1;
|
||||||
|
self.quote_state.toggle_single();
|
||||||
|
}
|
||||||
|
_ if self.quote_state.in_single() => pos += ch.len_utf8(),
|
||||||
|
'$' if chars.peek() == Some(&'(') => {
|
||||||
|
pos += 2;
|
||||||
|
chars.next();
|
||||||
|
let mut paren_count = 1;
|
||||||
|
let paren_pos = pos;
|
||||||
|
while let Some(ch) = chars.next() {
|
||||||
|
match ch {
|
||||||
|
'\\' => {
|
||||||
|
pos += 1;
|
||||||
|
if let Some(next_ch) = chars.next() {
|
||||||
|
pos += next_ch.len_utf8();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'(' => {
|
||||||
|
pos += 1;
|
||||||
|
paren_count += 1;
|
||||||
|
}
|
||||||
|
')' => {
|
||||||
|
pos += 1;
|
||||||
|
paren_count -= 1;
|
||||||
|
if paren_count <= 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => pos += ch.len_utf8(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !paren_count == 0 && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||||
|
self.cursor = pos;
|
||||||
|
return Err(ShErr::at(
|
||||||
|
ShErrKind::ParseErr,
|
||||||
|
Span::new(paren_pos..paren_pos + 1, self.source.clone()),
|
||||||
|
"Unclosed subshell",
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
'$' if chars.peek() == Some(&'{') => {
|
'$' if chars.peek() == Some(&'{') => {
|
||||||
pos += 2;
|
pos += 2;
|
||||||
@@ -380,6 +505,11 @@ impl LexStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
'"' => {
|
||||||
|
pos += 1;
|
||||||
|
self.quote_state.toggle_double();
|
||||||
|
}
|
||||||
|
_ if self.quote_state.in_double() => pos += ch.len_utf8(),
|
||||||
'<' if chars.peek() == Some(&'(') => {
|
'<' if chars.peek() == Some(&'(') => {
|
||||||
pos += 2;
|
pos += 2;
|
||||||
chars.next();
|
chars.next();
|
||||||
@@ -409,10 +539,10 @@ impl LexStream {
|
|||||||
}
|
}
|
||||||
if !paren_count == 0 && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
if !paren_count == 0 && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||||
self.cursor = pos;
|
self.cursor = pos;
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Unclosed subshell",
|
|
||||||
Span::new(paren_pos..paren_pos + 1, self.source.clone()),
|
Span::new(paren_pos..paren_pos + 1, self.source.clone()),
|
||||||
|
"Unclosed subshell",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -445,46 +575,10 @@ impl LexStream {
|
|||||||
}
|
}
|
||||||
if !paren_count == 0 && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
if !paren_count == 0 && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||||
self.cursor = pos;
|
self.cursor = pos;
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Unclosed subshell",
|
|
||||||
Span::new(paren_pos..paren_pos + 1, self.source.clone()),
|
Span::new(paren_pos..paren_pos + 1, self.source.clone()),
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'$' if chars.peek() == Some(&'(') => {
|
|
||||||
pos += 2;
|
|
||||||
chars.next();
|
|
||||||
let mut paren_count = 1;
|
|
||||||
let paren_pos = pos;
|
|
||||||
while let Some(ch) = chars.next() {
|
|
||||||
match ch {
|
|
||||||
'\\' => {
|
|
||||||
pos += 1;
|
|
||||||
if let Some(next_ch) = chars.next() {
|
|
||||||
pos += next_ch.len_utf8();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'(' => {
|
|
||||||
pos += 1;
|
|
||||||
paren_count += 1;
|
|
||||||
}
|
|
||||||
')' => {
|
|
||||||
pos += 1;
|
|
||||||
paren_count -= 1;
|
|
||||||
if paren_count <= 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => pos += ch.len_utf8(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !paren_count == 0 && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
|
||||||
self.cursor = pos;
|
|
||||||
return Err(ShErr::full(
|
|
||||||
ShErrKind::ParseErr,
|
|
||||||
"Unclosed subshell",
|
"Unclosed subshell",
|
||||||
Span::new(paren_pos..paren_pos + 1, self.source.clone()),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -516,10 +610,10 @@ impl LexStream {
|
|||||||
}
|
}
|
||||||
if paren_count != 0 && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
if paren_count != 0 && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||||
self.cursor = pos;
|
self.cursor = pos;
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Unclosed subshell",
|
|
||||||
Span::new(paren_pos..paren_pos + 1, self.source.clone()),
|
Span::new(paren_pos..paren_pos + 1, self.source.clone()),
|
||||||
|
"Unclosed subshell",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let mut subsh_tk = self.get_token(self.cursor..pos, TkRule::Str);
|
let mut subsh_tk = self.get_token(self.cursor..pos, TkRule::Str);
|
||||||
@@ -547,82 +641,6 @@ impl LexStream {
|
|||||||
self.cursor = pos;
|
self.cursor = pos;
|
||||||
return Ok(tk);
|
return Ok(tk);
|
||||||
}
|
}
|
||||||
'\'' => {
|
|
||||||
self.in_quote = true;
|
|
||||||
pos += 1;
|
|
||||||
while let Some(q_ch) = chars.next() {
|
|
||||||
match q_ch {
|
|
||||||
'\\' => {
|
|
||||||
pos += 1;
|
|
||||||
if chars.next().is_some() {
|
|
||||||
pos += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ if q_ch == '\'' => {
|
|
||||||
pos += 1;
|
|
||||||
self.in_quote = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Any time an ambiguous character is found
|
|
||||||
// we must push the cursor by the length of the character
|
|
||||||
// instead of just assuming a length of 1.
|
|
||||||
// Allows spans to work for wide characters
|
|
||||||
_ => pos += q_ch.len_utf8(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'"' => {
|
|
||||||
self.in_quote = true;
|
|
||||||
pos += 1;
|
|
||||||
while let Some(q_ch) = chars.next() {
|
|
||||||
match q_ch {
|
|
||||||
'\\' => {
|
|
||||||
pos += 1;
|
|
||||||
if chars.next().is_some() {
|
|
||||||
pos += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'$' if chars.peek() == Some(&'(') => {
|
|
||||||
pos += 2;
|
|
||||||
chars.next();
|
|
||||||
let mut cmdsub_count = 1;
|
|
||||||
while let Some(cmdsub_ch) = chars.next() {
|
|
||||||
match cmdsub_ch {
|
|
||||||
'\\' => {
|
|
||||||
pos += 1;
|
|
||||||
if chars.next().is_some() {
|
|
||||||
pos += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'$' if chars.peek() == Some(&'(') => {
|
|
||||||
cmdsub_count += 1;
|
|
||||||
pos += 2;
|
|
||||||
chars.next();
|
|
||||||
}
|
|
||||||
')' => {
|
|
||||||
cmdsub_count -= 1;
|
|
||||||
pos += 1;
|
|
||||||
if cmdsub_count <= 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => pos += cmdsub_ch.len_utf8(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ if q_ch == '"' => {
|
|
||||||
pos += 1;
|
|
||||||
self.in_quote = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Any time an ambiguous character is found
|
|
||||||
// we must push the cursor by the length of the character
|
|
||||||
// instead of just assuming a length of 1.
|
|
||||||
// Allows spans to work for wide characters
|
|
||||||
_ => pos += q_ch.len_utf8(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'=' if chars.peek() == Some(&'(') => {
|
'=' if chars.peek() == Some(&'(') => {
|
||||||
pos += 1; // '='
|
pos += 1; // '='
|
||||||
let mut depth = 1;
|
let mut depth = 1;
|
||||||
@@ -652,18 +670,17 @@ impl LexStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ if !self.in_quote && is_op(ch) => break,
|
|
||||||
_ if is_hard_sep(ch) => break,
|
_ if is_hard_sep(ch) => break,
|
||||||
_ => pos += ch.len_utf8(),
|
_ => pos += ch.len_utf8(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut new_tk = self.get_token(self.cursor..pos, TkRule::Str);
|
let mut new_tk = self.get_token(self.cursor..pos, TkRule::Str);
|
||||||
if self.in_quote && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
if self.quote_state.in_quote() && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||||
self.cursor = pos;
|
self.cursor = pos;
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Unterminated quote",
|
|
||||||
new_tk.span,
|
new_tk.span,
|
||||||
|
"Unterminated quote",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -733,10 +750,10 @@ impl Iterator for LexStream {
|
|||||||
if self.in_brc_grp() && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
if self.in_brc_grp() && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||||
let start = self.brc_grp_start.unwrap_or(self.cursor.saturating_sub(1));
|
let start = self.brc_grp_start.unwrap_or(self.cursor.saturating_sub(1));
|
||||||
self.flags |= LexFlags::STALE;
|
self.flags |= LexFlags::STALE;
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Unclosed brace group",
|
|
||||||
Span::new(start..self.cursor, self.source.clone()),
|
Span::new(start..self.cursor, self.source.clone()),
|
||||||
|
"Unclosed brace group",
|
||||||
))
|
))
|
||||||
.into();
|
.into();
|
||||||
}
|
}
|
||||||
@@ -772,10 +789,10 @@ impl Iterator for LexStream {
|
|||||||
if self.cursor == self.source.len() {
|
if self.cursor == self.source.len() {
|
||||||
if self.in_brc_grp() && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
if self.in_brc_grp() && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||||
let start = self.brc_grp_start.unwrap_or(self.cursor.saturating_sub(1));
|
let start = self.brc_grp_start.unwrap_or(self.cursor.saturating_sub(1));
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Unclosed brace group",
|
|
||||||
Span::new(start..self.cursor, self.source.clone()),
|
Span::new(start..self.cursor, self.source.clone()),
|
||||||
|
"Unclosed brace group",
|
||||||
))
|
))
|
||||||
.into();
|
.into();
|
||||||
}
|
}
|
||||||
@@ -912,6 +929,8 @@ pub fn ends_with_unescaped(slice: &str, pat: &str) -> bool {
|
|||||||
slice.ends_with(pat) && !pos_is_escaped(slice, slice.len() - pat.len())
|
slice.ends_with(pat) && !pos_is_escaped(slice, slice.len() - pat.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Splits a string by a pattern, but only if the pattern is not escaped by a backslash
|
||||||
|
/// and not in quotes.
|
||||||
pub fn split_all_unescaped(slice: &str, pat: &str) -> Vec<String> {
|
pub fn split_all_unescaped(slice: &str, pat: &str) -> Vec<String> {
|
||||||
let mut cursor = 0;
|
let mut cursor = 0;
|
||||||
let mut splits = vec![];
|
let mut splits = vec![];
|
||||||
@@ -925,19 +944,71 @@ pub fn split_all_unescaped(slice: &str, pat: &str) -> Vec<String> {
|
|||||||
splits
|
splits
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Splits a string at the first occurrence of a pattern, but only if the pattern is not escaped by a backslash
|
||||||
|
/// and not in quotes. Returns None if the pattern is not found or only found escaped.
|
||||||
pub fn split_at_unescaped(slice: &str, pat: &str) -> Option<(String,String)> {
|
pub fn split_at_unescaped(slice: &str, pat: &str) -> Option<(String,String)> {
|
||||||
let mut window_start = 0;
|
let mut chars = slice.char_indices().peekable();
|
||||||
let mut window_end = pat.len();
|
let mut qt_state = QuoteState::default();
|
||||||
if window_end > slice.len() {
|
|
||||||
return None;
|
while let Some((i, ch)) = chars.next() {
|
||||||
}
|
match ch {
|
||||||
while window_end <= slice.len() {
|
'\\' => { chars.next(); continue; }
|
||||||
if &slice[window_start..window_end] == pat && !pos_is_escaped(slice, window_start) {
|
'\'' => qt_state.toggle_single(),
|
||||||
return Some((slice[..window_start].to_string(), slice[window_end..].to_string()));
|
'"' => qt_state.toggle_double(),
|
||||||
|
_ if qt_state.in_quote() => continue,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if slice[i..].starts_with(pat) {
|
||||||
|
let before = slice[..i].to_string();
|
||||||
|
let after = slice[i + pat.len()..].to_string();
|
||||||
|
return Some((before, after));
|
||||||
}
|
}
|
||||||
window_start += 1;
|
|
||||||
window_end += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split_tk(tk: &Tk, pat: &str) -> Vec<Tk> {
|
||||||
|
let slice = tk.as_str();
|
||||||
|
let mut cursor = 0;
|
||||||
|
let mut splits = vec![];
|
||||||
|
while let Some(split) = split_at_unescaped(&slice[cursor..], pat) {
|
||||||
|
let before_span = Span::new(tk.span.range().start + cursor..tk.span.range().start + cursor + split.0.len(), tk.source().clone());
|
||||||
|
splits.push(Tk::new(tk.class.clone(), before_span));
|
||||||
|
cursor += split.0.len() + pat.len();
|
||||||
|
}
|
||||||
|
if slice.get(cursor..).is_some_and(|s| !s.is_empty()) {
|
||||||
|
let remaining_span = Span::new(tk.span.range().start + cursor..tk.span.range().end, tk.source().clone());
|
||||||
|
splits.push(Tk::new(tk.class.clone(), remaining_span));
|
||||||
|
}
|
||||||
|
splits
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split_tk_at(tk: &Tk, pat: &str) -> Option<(Tk, Tk)> {
|
||||||
|
let slice = tk.as_str();
|
||||||
|
let mut chars = slice.char_indices().peekable();
|
||||||
|
let mut qt_state = QuoteState::default();
|
||||||
|
|
||||||
|
while let Some((i, ch)) = chars.next() {
|
||||||
|
match ch {
|
||||||
|
'\\' => { chars.next(); continue; }
|
||||||
|
'\'' => qt_state.toggle_single(),
|
||||||
|
'"' => qt_state.toggle_double(),
|
||||||
|
_ if qt_state.in_quote() => continue,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if slice[i..].starts_with(pat) {
|
||||||
|
let before_span = Span::new(tk.span.range().start..tk.span.range().start + i, tk.source().clone());
|
||||||
|
let after_span = Span::new(tk.span.range().start + i + pat.len()..tk.span.range().end, tk.source().clone());
|
||||||
|
let before_tk = Tk::new(tk.class.clone(), before_span);
|
||||||
|
let after_tk = Tk::new(tk.class.clone(), after_span);
|
||||||
|
return Some((before_tk, after_tk));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
251
src/parse/mod.rs
251
src/parse/mod.rs
@@ -1,13 +1,15 @@
|
|||||||
use std::str::FromStr;
|
use std::{collections::VecDeque, fmt::Debug, str::FromStr, sync::Arc};
|
||||||
|
|
||||||
|
use ariadne::{Fmt, Label, Span as AriadneSpan};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use fmt::Display;
|
use fmt::Display;
|
||||||
use lex::{LexFlags, LexStream, Span, Tk, TkFlags, TkRule};
|
use lex::{LexFlags, LexStream, Span, SpanSource, Tk, TkFlags, TkRule};
|
||||||
|
use yansi::Color;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
libsh::{
|
libsh::{
|
||||||
error::{Note, ShErr, ShErrKind, ShResult},
|
error::{Note, ShErr, ShErrKind, ShResult, next_color},
|
||||||
utils::TkVecUtils,
|
utils::{NodeVecUtils, TkVecUtils},
|
||||||
},
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
procio::IoMode,
|
procio::IoMode,
|
||||||
@@ -45,6 +47,7 @@ pub struct ParsedSrc {
|
|||||||
pub src: Arc<String>,
|
pub src: Arc<String>,
|
||||||
pub ast: Ast,
|
pub ast: Ast,
|
||||||
pub lex_flags: LexFlags,
|
pub lex_flags: LexFlags,
|
||||||
|
pub context: LabelCtx,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParsedSrc {
|
impl ParsedSrc {
|
||||||
@@ -53,12 +56,17 @@ impl ParsedSrc {
|
|||||||
src,
|
src,
|
||||||
ast: Ast::new(vec![]),
|
ast: Ast::new(vec![]),
|
||||||
lex_flags: LexFlags::empty(),
|
lex_flags: LexFlags::empty(),
|
||||||
|
context: VecDeque::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn with_lex_flags(mut self, flags: LexFlags) -> Self {
|
pub fn with_lex_flags(mut self, flags: LexFlags) -> Self {
|
||||||
self.lex_flags = flags;
|
self.lex_flags = flags;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
pub fn with_context(mut self, ctx: LabelCtx) -> Self {
|
||||||
|
self.context = ctx;
|
||||||
|
self
|
||||||
|
}
|
||||||
pub fn parse_src(&mut self) -> Result<(), Vec<ShErr>> {
|
pub fn parse_src(&mut self) -> Result<(), Vec<ShErr>> {
|
||||||
let mut tokens = vec![];
|
let mut tokens = vec![];
|
||||||
for lex_result in LexStream::new(self.src.clone(), self.lex_flags) {
|
for lex_result in LexStream::new(self.src.clone(), self.lex_flags) {
|
||||||
@@ -70,7 +78,7 @@ impl ParsedSrc {
|
|||||||
|
|
||||||
let mut errors = vec![];
|
let mut errors = vec![];
|
||||||
let mut nodes = vec![];
|
let mut nodes = vec![];
|
||||||
for parse_result in ParseStream::new(tokens) {
|
for parse_result in ParseStream::with_context(tokens, self.context.clone()) {
|
||||||
match parse_result {
|
match parse_result {
|
||||||
Ok(node) => nodes.push(node),
|
Ok(node) => nodes.push(node),
|
||||||
Err(error) => errors.push(error),
|
Err(error) => errors.push(error),
|
||||||
@@ -89,7 +97,7 @@ impl ParsedSrc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
pub struct Ast(Vec<Node>);
|
pub struct Ast(Vec<Node>);
|
||||||
|
|
||||||
impl Ast {
|
impl Ast {
|
||||||
@@ -104,12 +112,15 @@ impl Ast {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type LabelCtx = VecDeque<(SpanSource, Label<Span>)>;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Node {
|
pub struct Node {
|
||||||
pub class: NdRule,
|
pub class: NdRule,
|
||||||
pub flags: NdFlags,
|
pub flags: NdFlags,
|
||||||
pub redirs: Vec<Redir>,
|
pub redirs: Vec<Redir>,
|
||||||
pub tokens: Vec<Tk>,
|
pub tokens: Vec<Tk>,
|
||||||
|
pub context: LabelCtx,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node {
|
impl Node {
|
||||||
@@ -124,6 +135,108 @@ impl Node {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn get_context(&self, msg: String) -> (SpanSource, Label<Span>) {
|
||||||
|
let color = next_color();
|
||||||
|
let span = self.get_span().clone();
|
||||||
|
(
|
||||||
|
span.clone().source().clone(),
|
||||||
|
Label::new(span).with_color(color).with_message(msg)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn walk_tree<F: Fn(&mut Node)>(&mut self, f: &F) {
|
||||||
|
f(self);
|
||||||
|
|
||||||
|
match self.class {
|
||||||
|
NdRule::IfNode {
|
||||||
|
ref mut cond_nodes,
|
||||||
|
ref mut else_block,
|
||||||
|
} => {
|
||||||
|
for node in cond_nodes {
|
||||||
|
let CondNode { cond, body } = node;
|
||||||
|
cond.walk_tree(f);
|
||||||
|
for body_node in body {
|
||||||
|
body_node.walk_tree(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for else_node in else_block {
|
||||||
|
else_node.walk_tree(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NdRule::LoopNode {
|
||||||
|
kind: _,
|
||||||
|
ref mut cond_node,
|
||||||
|
} => {
|
||||||
|
let CondNode { cond, body } = cond_node;
|
||||||
|
cond.walk_tree(f);
|
||||||
|
for body_node in body {
|
||||||
|
body_node.walk_tree(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NdRule::ForNode {
|
||||||
|
vars: _,
|
||||||
|
arr: _,
|
||||||
|
ref mut body,
|
||||||
|
} => {
|
||||||
|
for body_node in body {
|
||||||
|
body_node.walk_tree(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NdRule::CaseNode {
|
||||||
|
pattern: _,
|
||||||
|
ref mut case_blocks,
|
||||||
|
} => {
|
||||||
|
for block in case_blocks {
|
||||||
|
let CaseNode { pattern: _, body } = block;
|
||||||
|
for body_node in body {
|
||||||
|
body_node.walk_tree(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NdRule::Command {
|
||||||
|
ref mut assignments,
|
||||||
|
argv: _,
|
||||||
|
} => {
|
||||||
|
for assign_node in assignments {
|
||||||
|
assign_node.walk_tree(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NdRule::Pipeline {
|
||||||
|
ref mut cmds,
|
||||||
|
pipe_err: _,
|
||||||
|
} => {
|
||||||
|
for cmd_node in cmds {
|
||||||
|
cmd_node.walk_tree(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NdRule::Conjunction { ref mut elements } => {
|
||||||
|
for node in elements.iter_mut() {
|
||||||
|
let ConjunctNode { cmd, operator: _ } = node;
|
||||||
|
cmd.walk_tree(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NdRule::Assignment {
|
||||||
|
kind: _,
|
||||||
|
var: _,
|
||||||
|
val: _,
|
||||||
|
} => (), // No nodes to check
|
||||||
|
NdRule::BraceGrp { ref mut body } => {
|
||||||
|
for body_node in body {
|
||||||
|
body_node.walk_tree(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NdRule::FuncDef {
|
||||||
|
name: _,
|
||||||
|
ref mut body,
|
||||||
|
} => {
|
||||||
|
body.walk_tree(f);
|
||||||
|
}
|
||||||
|
NdRule::Test { cases: _ } => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn propagate_context(&mut self, ctx: (SpanSource, Label<Span>)) {
|
||||||
|
self.walk_tree(&|nd| nd.context.push_back(ctx.clone()));
|
||||||
|
}
|
||||||
pub fn get_span(&self) -> Span {
|
pub fn get_span(&self) -> Span {
|
||||||
let Some(first_tk) = self.tokens.first() else {
|
let Some(first_tk) = self.tokens.first() else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
@@ -133,7 +246,7 @@ impl Node {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Span::new(
|
Span::new(
|
||||||
first_tk.span.start..last_tk.span.end,
|
first_tk.span.range().start..last_tk.span.range().end,
|
||||||
first_tk.span.get_source(),
|
first_tk.span.get_source(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -539,14 +652,25 @@ pub enum NdRule {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ParseStream {
|
pub struct ParseStream {
|
||||||
pub tokens: Vec<Tk>,
|
pub tokens: Vec<Tk>,
|
||||||
|
pub context: LabelCtx
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for ParseStream {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("ParseStream")
|
||||||
|
.field("tokens", &self.tokens)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParseStream {
|
impl ParseStream {
|
||||||
pub fn new(tokens: Vec<Tk>) -> Self {
|
pub fn new(tokens: Vec<Tk>) -> Self {
|
||||||
Self { tokens }
|
Self { tokens, context: VecDeque::new() }
|
||||||
|
}
|
||||||
|
pub fn with_context(tokens: Vec<Tk>, context: LabelCtx) -> Self {
|
||||||
|
Self { tokens, context }
|
||||||
}
|
}
|
||||||
fn next_tk_class(&self) -> &TkRule {
|
fn next_tk_class(&self) -> &TkRule {
|
||||||
if let Some(tk) = self.tokens.first() {
|
if let Some(tk) = self.tokens.first() {
|
||||||
@@ -684,6 +808,7 @@ impl ParseStream {
|
|||||||
class: NdRule::Conjunction { elements },
|
class: NdRule::Conjunction { elements },
|
||||||
flags: NdFlags::empty(),
|
flags: NdFlags::empty(),
|
||||||
redirs: vec![],
|
redirs: vec![],
|
||||||
|
context: self.context.clone(),
|
||||||
tokens: node_tks,
|
tokens: node_tks,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@@ -697,23 +822,40 @@ impl ParseStream {
|
|||||||
}
|
}
|
||||||
let name_tk = self.next_tk().unwrap();
|
let name_tk = self.next_tk().unwrap();
|
||||||
node_tks.push(name_tk.clone());
|
node_tks.push(name_tk.clone());
|
||||||
let name = name_tk;
|
let name = name_tk.clone();
|
||||||
|
let name_raw = name.to_string();
|
||||||
|
let mut src = name_tk.span.span_source().clone();
|
||||||
|
src.rename(name_raw.clone());
|
||||||
|
let color = next_color();
|
||||||
|
// Push a placeholder context so child nodes inherit it
|
||||||
|
self.context.push_back((
|
||||||
|
src.clone(),
|
||||||
|
Label::new(name_tk.span.clone().with_name(name_raw.clone()))
|
||||||
|
.with_message(format!("in function '{}' defined here", name_raw.clone().fg(color)))
|
||||||
|
.with_color(color),
|
||||||
|
));
|
||||||
|
|
||||||
let Some(brc_grp) = self.parse_brc_grp(true /* from_func_def */)? else {
|
let Some(brc_grp) = self.parse_brc_grp(true /* from_func_def */)? else {
|
||||||
|
self.context.pop_back();
|
||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Expected a brace group after function name",
|
"Expected a brace group after function name",
|
||||||
&node_tks.get_span().unwrap(),
|
&node_tks.get_span().unwrap(),
|
||||||
|
self.context.clone()
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
body = Box::new(brc_grp);
|
body = Box::new(brc_grp);
|
||||||
|
// Replace placeholder with full-span label
|
||||||
|
self.context.pop_back();
|
||||||
|
|
||||||
let node = Node {
|
let node = Node {
|
||||||
class: NdRule::FuncDef { name, body },
|
class: NdRule::FuncDef { name, body },
|
||||||
flags: NdFlags::empty(),
|
flags: NdFlags::empty(),
|
||||||
redirs: vec![],
|
redirs: vec![],
|
||||||
tokens: node_tks,
|
tokens: node_tks,
|
||||||
|
context: self.context.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.context.pop_back();
|
||||||
Ok(Some(node))
|
Ok(Some(node))
|
||||||
}
|
}
|
||||||
fn panic_mode(&mut self, node_tks: &mut Vec<Tk>) {
|
fn panic_mode(&mut self, node_tks: &mut Vec<Tk>) {
|
||||||
@@ -743,6 +885,7 @@ impl ParseStream {
|
|||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Malformed test call",
|
"Malformed test call",
|
||||||
&node_tks.get_span().unwrap(),
|
&node_tks.get_span().unwrap(),
|
||||||
|
self.context.clone()
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
@@ -769,6 +912,7 @@ impl ParseStream {
|
|||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Invalid placement for logical operator in test",
|
"Invalid placement for logical operator in test",
|
||||||
&node_tks.get_span().unwrap(),
|
&node_tks.get_span().unwrap(),
|
||||||
|
self.context.clone()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let op = match tk.class {
|
let op = match tk.class {
|
||||||
@@ -784,6 +928,7 @@ impl ParseStream {
|
|||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Invalid placement for logical operator in test",
|
"Invalid placement for logical operator in test",
|
||||||
&node_tks.get_span().unwrap(),
|
&node_tks.get_span().unwrap(),
|
||||||
|
self.context.clone()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -797,6 +942,7 @@ impl ParseStream {
|
|||||||
class: NdRule::Test { cases },
|
class: NdRule::Test { cases },
|
||||||
flags: NdFlags::empty(),
|
flags: NdFlags::empty(),
|
||||||
redirs: vec![],
|
redirs: vec![],
|
||||||
|
context: self.context.clone(),
|
||||||
tokens: node_tks,
|
tokens: node_tks,
|
||||||
};
|
};
|
||||||
Ok(Some(node))
|
Ok(Some(node))
|
||||||
@@ -828,6 +974,7 @@ impl ParseStream {
|
|||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Expected a closing brace for this brace group",
|
"Expected a closing brace for this brace group",
|
||||||
&node_tks.get_span().unwrap(),
|
&node_tks.get_span().unwrap(),
|
||||||
|
self.context.clone()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -840,6 +987,7 @@ impl ParseStream {
|
|||||||
class: NdRule::BraceGrp { body },
|
class: NdRule::BraceGrp { body },
|
||||||
flags: NdFlags::empty(),
|
flags: NdFlags::empty(),
|
||||||
redirs,
|
redirs,
|
||||||
|
context: self.context.clone(),
|
||||||
tokens: node_tks,
|
tokens: node_tks,
|
||||||
};
|
};
|
||||||
Ok(Some(node))
|
Ok(Some(node))
|
||||||
@@ -853,10 +1001,10 @@ impl ParseStream {
|
|||||||
let path_tk = self.next_tk();
|
let path_tk = self.next_tk();
|
||||||
|
|
||||||
if path_tk.clone().is_none_or(|tk| tk.class == TkRule::EOI) {
|
if path_tk.clone().is_none_or(|tk| tk.class == TkRule::EOI) {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Expected a filename after this redirection",
|
|
||||||
tk.span.clone(),
|
tk.span.clone(),
|
||||||
|
"Expected a filename after this redirection",
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -893,12 +1041,10 @@ impl ParseStream {
|
|||||||
let pat_err = parse_err_full(
|
let pat_err = parse_err_full(
|
||||||
"Expected a pattern after 'case' keyword",
|
"Expected a pattern after 'case' keyword",
|
||||||
&node_tks.get_span().unwrap(),
|
&node_tks.get_span().unwrap(),
|
||||||
|
self.context.clone()
|
||||||
)
|
)
|
||||||
.with_note(
|
.with_note(
|
||||||
Note::new("Patterns can be raw text, or anything that gets substituted with raw text")
|
"Patterns can be raw text, or anything that gets substituted with raw text"
|
||||||
.with_sub_notes(vec![
|
|
||||||
"This includes variables like '$foo' or command substitutions like '$(echo foo)'",
|
|
||||||
]),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let Some(pat_tk) = self.next_tk() else {
|
let Some(pat_tk) = self.next_tk() else {
|
||||||
@@ -919,6 +1065,7 @@ impl ParseStream {
|
|||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Expected 'in' after case variable name",
|
"Expected 'in' after case variable name",
|
||||||
&node_tks.get_span().unwrap(),
|
&node_tks.get_span().unwrap(),
|
||||||
|
self.context.clone()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
node_tks.push(self.next_tk().unwrap());
|
node_tks.push(self.next_tk().unwrap());
|
||||||
@@ -931,6 +1078,7 @@ impl ParseStream {
|
|||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Expected a case pattern here",
|
"Expected a case pattern here",
|
||||||
&node_tks.get_span().unwrap(),
|
&node_tks.get_span().unwrap(),
|
||||||
|
self.context.clone()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let case_pat_tk = self.next_tk().unwrap();
|
let case_pat_tk = self.next_tk().unwrap();
|
||||||
@@ -967,6 +1115,7 @@ impl ParseStream {
|
|||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Expected 'esac' after case block",
|
"Expected 'esac' after case block",
|
||||||
&node_tks.get_span().unwrap(),
|
&node_tks.get_span().unwrap(),
|
||||||
|
self.context.clone()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -978,10 +1127,17 @@ impl ParseStream {
|
|||||||
},
|
},
|
||||||
flags: NdFlags::empty(),
|
flags: NdFlags::empty(),
|
||||||
redirs,
|
redirs,
|
||||||
|
context: self.context.clone(),
|
||||||
tokens: node_tks,
|
tokens: node_tks,
|
||||||
};
|
};
|
||||||
Ok(Some(node))
|
Ok(Some(node))
|
||||||
}
|
}
|
||||||
|
fn make_err(&self, span: lex::Span, label: Label<lex::Span>) -> ShErr {
|
||||||
|
let src = span.span_source().clone();
|
||||||
|
ShErr::new(ShErrKind::ParseErr, span)
|
||||||
|
.with_label(src, label)
|
||||||
|
.with_context(self.context.clone())
|
||||||
|
}
|
||||||
fn parse_if(&mut self) -> ShResult<Option<Node>> {
|
fn parse_if(&mut self) -> ShResult<Option<Node>> {
|
||||||
// Needs at last one 'if-then',
|
// Needs at last one 'if-then',
|
||||||
// Any number of 'elif-then',
|
// Any number of 'elif-then',
|
||||||
@@ -1000,10 +1156,14 @@ impl ParseStream {
|
|||||||
let prefix_keywrd = if cond_nodes.is_empty() { "if" } else { "elif" };
|
let prefix_keywrd = if cond_nodes.is_empty() { "if" } else { "elif" };
|
||||||
let Some(cond) = self.parse_cmd_list()? else {
|
let Some(cond) = self.parse_cmd_list()? else {
|
||||||
self.panic_mode(&mut node_tks);
|
self.panic_mode(&mut node_tks);
|
||||||
return Err(parse_err_full(
|
let span = node_tks.get_span().unwrap();
|
||||||
&format!("Expected an expression after '{prefix_keywrd}'"),
|
let color = next_color();
|
||||||
&node_tks.get_span().unwrap(),
|
return Err(self.make_err(span.clone(),
|
||||||
));
|
Label::new(span)
|
||||||
|
.with_message(format!("Expected an expression after '{}'", prefix_keywrd.fg(color)))
|
||||||
|
.with_color(color)
|
||||||
|
));
|
||||||
|
|
||||||
};
|
};
|
||||||
node_tks.extend(cond.tokens.clone());
|
node_tks.extend(cond.tokens.clone());
|
||||||
|
|
||||||
@@ -1012,6 +1172,7 @@ impl ParseStream {
|
|||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
&format!("Expected 'then' after '{prefix_keywrd}' condition"),
|
&format!("Expected 'then' after '{prefix_keywrd}' condition"),
|
||||||
&node_tks.get_span().unwrap(),
|
&node_tks.get_span().unwrap(),
|
||||||
|
self.context.clone()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
node_tks.push(self.next_tk().unwrap());
|
node_tks.push(self.next_tk().unwrap());
|
||||||
@@ -1027,6 +1188,7 @@ impl ParseStream {
|
|||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Expected an expression after 'then'",
|
"Expected an expression after 'then'",
|
||||||
&node_tks.get_span().unwrap(),
|
&node_tks.get_span().unwrap(),
|
||||||
|
self.context.clone()
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
let cond_node = CondNode {
|
let cond_node = CondNode {
|
||||||
@@ -1035,6 +1197,7 @@ impl ParseStream {
|
|||||||
};
|
};
|
||||||
cond_nodes.push(cond_node);
|
cond_nodes.push(cond_node);
|
||||||
|
|
||||||
|
self.catch_separator(&mut node_tks);
|
||||||
if !self.check_keyword("elif") || !self.next_tk_is_some() {
|
if !self.check_keyword("elif") || !self.next_tk_is_some() {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
@@ -1043,6 +1206,7 @@ impl ParseStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.catch_separator(&mut node_tks);
|
||||||
if self.check_keyword("else") {
|
if self.check_keyword("else") {
|
||||||
node_tks.push(self.next_tk().unwrap());
|
node_tks.push(self.next_tk().unwrap());
|
||||||
self.catch_separator(&mut node_tks);
|
self.catch_separator(&mut node_tks);
|
||||||
@@ -1054,6 +1218,7 @@ impl ParseStream {
|
|||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Expected an expression after 'else'",
|
"Expected an expression after 'else'",
|
||||||
&node_tks.get_span().unwrap(),
|
&node_tks.get_span().unwrap(),
|
||||||
|
self.context.clone()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1064,6 +1229,7 @@ impl ParseStream {
|
|||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Expected 'fi' after if statement",
|
"Expected 'fi' after if statement",
|
||||||
&node_tks.get_span().unwrap(),
|
&node_tks.get_span().unwrap(),
|
||||||
|
self.context.clone()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
node_tks.push(self.next_tk().unwrap());
|
node_tks.push(self.next_tk().unwrap());
|
||||||
@@ -1079,6 +1245,7 @@ impl ParseStream {
|
|||||||
},
|
},
|
||||||
flags: NdFlags::empty(),
|
flags: NdFlags::empty(),
|
||||||
redirs,
|
redirs,
|
||||||
|
context: self.context.clone(),
|
||||||
tokens: node_tks,
|
tokens: node_tks,
|
||||||
};
|
};
|
||||||
Ok(Some(node))
|
Ok(Some(node))
|
||||||
@@ -1118,6 +1285,7 @@ impl ParseStream {
|
|||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"This for loop is missing a variable",
|
"This for loop is missing a variable",
|
||||||
&node_tks.get_span().unwrap(),
|
&node_tks.get_span().unwrap(),
|
||||||
|
self.context.clone()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if arr.is_empty() {
|
if arr.is_empty() {
|
||||||
@@ -1125,6 +1293,7 @@ impl ParseStream {
|
|||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"This for loop is missing an array",
|
"This for loop is missing an array",
|
||||||
&node_tks.get_span().unwrap(),
|
&node_tks.get_span().unwrap(),
|
||||||
|
self.context.clone()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if !self.check_keyword("do") || !self.next_tk_is_some() {
|
if !self.check_keyword("do") || !self.next_tk_is_some() {
|
||||||
@@ -1132,6 +1301,7 @@ impl ParseStream {
|
|||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Missing a 'do' for this for loop",
|
"Missing a 'do' for this for loop",
|
||||||
&node_tks.get_span().unwrap(),
|
&node_tks.get_span().unwrap(),
|
||||||
|
self.context.clone()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
node_tks.push(self.next_tk().unwrap());
|
node_tks.push(self.next_tk().unwrap());
|
||||||
@@ -1147,6 +1317,7 @@ impl ParseStream {
|
|||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Missing a 'done' after this for loop",
|
"Missing a 'done' after this for loop",
|
||||||
&node_tks.get_span().unwrap(),
|
&node_tks.get_span().unwrap(),
|
||||||
|
self.context.clone()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
node_tks.push(self.next_tk().unwrap());
|
node_tks.push(self.next_tk().unwrap());
|
||||||
@@ -1157,6 +1328,7 @@ impl ParseStream {
|
|||||||
class: NdRule::ForNode { vars, arr, body },
|
class: NdRule::ForNode { vars, arr, body },
|
||||||
flags: NdFlags::empty(),
|
flags: NdFlags::empty(),
|
||||||
redirs,
|
redirs,
|
||||||
|
context: self.context.clone(),
|
||||||
tokens: node_tks,
|
tokens: node_tks,
|
||||||
};
|
};
|
||||||
Ok(Some(node))
|
Ok(Some(node))
|
||||||
@@ -1186,6 +1358,7 @@ impl ParseStream {
|
|||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
&format!("Expected an expression after '{loop_kind}'"), // It also implements Display
|
&format!("Expected an expression after '{loop_kind}'"), // It also implements Display
|
||||||
&node_tks.get_span().unwrap(),
|
&node_tks.get_span().unwrap(),
|
||||||
|
self.context.clone()
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
node_tks.extend(cond.tokens.clone());
|
node_tks.extend(cond.tokens.clone());
|
||||||
@@ -1195,6 +1368,7 @@ impl ParseStream {
|
|||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Expected 'do' after loop condition",
|
"Expected 'do' after loop condition",
|
||||||
&node_tks.get_span().unwrap(),
|
&node_tks.get_span().unwrap(),
|
||||||
|
self.context.clone()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
node_tks.push(self.next_tk().unwrap());
|
node_tks.push(self.next_tk().unwrap());
|
||||||
@@ -1210,6 +1384,7 @@ impl ParseStream {
|
|||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Expected an expression after 'do'",
|
"Expected an expression after 'do'",
|
||||||
&node_tks.get_span().unwrap(),
|
&node_tks.get_span().unwrap(),
|
||||||
|
self.context.clone()
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1219,6 +1394,7 @@ impl ParseStream {
|
|||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Expected 'done' after loop body",
|
"Expected 'done' after loop body",
|
||||||
&node_tks.get_span().unwrap(),
|
&node_tks.get_span().unwrap(),
|
||||||
|
self.context.clone()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
node_tks.push(self.next_tk().unwrap());
|
node_tks.push(self.next_tk().unwrap());
|
||||||
@@ -1238,6 +1414,7 @@ impl ParseStream {
|
|||||||
},
|
},
|
||||||
flags: NdFlags::empty(),
|
flags: NdFlags::empty(),
|
||||||
redirs,
|
redirs,
|
||||||
|
context: self.context.clone(),
|
||||||
tokens: node_tks,
|
tokens: node_tks,
|
||||||
};
|
};
|
||||||
Ok(Some(loop_node))
|
Ok(Some(loop_node))
|
||||||
@@ -1275,6 +1452,7 @@ impl ParseStream {
|
|||||||
},
|
},
|
||||||
flags,
|
flags,
|
||||||
redirs: vec![],
|
redirs: vec![],
|
||||||
|
context: self.context.clone(),
|
||||||
tokens: node_tks,
|
tokens: node_tks,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@@ -1293,6 +1471,7 @@ impl ParseStream {
|
|||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Found case pattern in command",
|
"Found case pattern in command",
|
||||||
&prefix_tk.span,
|
&prefix_tk.span,
|
||||||
|
self.context.clone()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let is_cmd = prefix_tk.flags.contains(TkFlags::IS_CMD);
|
let is_cmd = prefix_tk.flags.contains(TkFlags::IS_CMD);
|
||||||
@@ -1328,11 +1507,20 @@ impl ParseStream {
|
|||||||
// If we have assignments but no command word,
|
// If we have assignments but no command word,
|
||||||
// return the assignment-only command without parsing more tokens
|
// return the assignment-only command without parsing more tokens
|
||||||
self.commit(node_tks.len());
|
self.commit(node_tks.len());
|
||||||
|
let mut context = self.context.clone();
|
||||||
|
let assignments_span = assignments.get_span().unwrap();
|
||||||
|
context.push_back((
|
||||||
|
assignments_span.source().clone(),
|
||||||
|
Label::new(assignments_span)
|
||||||
|
.with_message("in variable assignment defined here".to_string())
|
||||||
|
.with_color(next_color())
|
||||||
|
));
|
||||||
return Ok(Some(Node {
|
return Ok(Some(Node {
|
||||||
class: NdRule::Command { assignments, argv },
|
class: NdRule::Command { assignments, argv },
|
||||||
tokens: node_tks,
|
tokens: node_tks,
|
||||||
flags,
|
flags,
|
||||||
redirs,
|
redirs,
|
||||||
|
context: self.context.clone(),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1363,10 +1551,11 @@ impl ParseStream {
|
|||||||
let path_tk = tk_iter.next();
|
let path_tk = tk_iter.next();
|
||||||
|
|
||||||
if path_tk.is_none_or(|tk| tk.class == TkRule::EOI) {
|
if path_tk.is_none_or(|tk| tk.class == TkRule::EOI) {
|
||||||
return Err(ShErr::full(
|
self.panic_mode(&mut node_tks);
|
||||||
|
return Err(ShErr::at(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Expected a filename after this redirection",
|
|
||||||
tk.span.clone(),
|
tk.span.clone(),
|
||||||
|
"Expected a filename after this redirection",
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1396,16 +1585,17 @@ impl ParseStream {
|
|||||||
tokens: node_tks,
|
tokens: node_tks,
|
||||||
flags,
|
flags,
|
||||||
redirs,
|
redirs,
|
||||||
|
context: self.context.clone(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
fn parse_assignment(&self, token: &Tk) -> Option<Node> {
|
fn parse_assignment(&self, token: &Tk) -> Option<Node> {
|
||||||
let mut chars = token.span.as_str().chars();
|
let mut chars = token.span.as_str().chars();
|
||||||
let mut var_name = String::new();
|
let mut var_name = String::new();
|
||||||
let mut name_range = token.span.start..token.span.start;
|
let mut name_range = token.span.range().start..token.span.range().start;
|
||||||
let mut var_val = String::new();
|
let mut var_val = String::new();
|
||||||
let mut val_range = token.span.end..token.span.end;
|
let mut val_range = token.span.range().end..token.span.range().end;
|
||||||
let mut assign_kind = None;
|
let mut assign_kind = None;
|
||||||
let mut pos = token.span.start;
|
let mut pos = token.span.range().start;
|
||||||
|
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
if assign_kind.is_some() {
|
if assign_kind.is_some() {
|
||||||
@@ -1498,6 +1688,7 @@ impl ParseStream {
|
|||||||
tokens: vec![token.clone()],
|
tokens: vec![token.clone()],
|
||||||
flags,
|
flags,
|
||||||
redirs: vec![],
|
redirs: vec![],
|
||||||
|
context: self.context.clone(),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -1554,8 +1745,14 @@ pub fn get_redir_file(class: RedirType, path: PathBuf) -> ShResult<File> {
|
|||||||
Ok(result?)
|
Ok(result?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_err_full(reason: &str, blame: &Span) -> ShErr {
|
fn parse_err_full(reason: &str, blame: &Span, context: LabelCtx) -> ShErr {
|
||||||
ShErr::full(ShErrKind::ParseErr, reason, blame.clone())
|
let color = next_color();
|
||||||
|
ShErr::new(ShErrKind::ParseErr, blame.clone())
|
||||||
|
.with_label(
|
||||||
|
blame.span_source().clone(),
|
||||||
|
Label::new(blame.clone()).with_message(reason).with_color(color)
|
||||||
|
)
|
||||||
|
.with_context(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_func_name(tk: Option<&Tk>) -> bool {
|
fn is_func_name(tk: Option<&Tk>) -> bool {
|
||||||
|
|||||||
@@ -696,7 +696,7 @@ impl Completer {
|
|||||||
tks
|
tks
|
||||||
.iter()
|
.iter()
|
||||||
.next()
|
.next()
|
||||||
.is_some_and(|tk| tk.span.start > cursor_pos)
|
.is_some_and(|tk| tk.span.range().start > cursor_pos)
|
||||||
})
|
})
|
||||||
.map(|i| i.saturating_sub(1))
|
.map(|i| i.saturating_sub(1))
|
||||||
.unwrap_or(segments.len().saturating_sub(1));
|
.unwrap_or(segments.len().saturating_sub(1));
|
||||||
@@ -705,13 +705,13 @@ impl Completer {
|
|||||||
|
|
||||||
let cword = if let Some(pos) = relevant
|
let cword = if let Some(pos) = relevant
|
||||||
.iter()
|
.iter()
|
||||||
.position(|tk| cursor_pos >= tk.span.start && cursor_pos <= tk.span.end)
|
.position(|tk| cursor_pos >= tk.span.range().start && cursor_pos <= tk.span.range().end)
|
||||||
{
|
{
|
||||||
pos
|
pos
|
||||||
} else {
|
} else {
|
||||||
let insert_pos = relevant
|
let insert_pos = relevant
|
||||||
.iter()
|
.iter()
|
||||||
.position(|tk| tk.span.start > cursor_pos)
|
.position(|tk| tk.span.range().start > cursor_pos)
|
||||||
.unwrap_or(relevant.len());
|
.unwrap_or(relevant.len());
|
||||||
|
|
||||||
let mut new_tk = Tk::default();
|
let mut new_tk = Tk::default();
|
||||||
@@ -761,7 +761,7 @@ impl Completer {
|
|||||||
|
|
||||||
// Set token_span from CompContext's current word
|
// Set token_span from CompContext's current word
|
||||||
if let Some(cur) = ctx.words.get(ctx.cword) {
|
if let Some(cur) = ctx.words.get(ctx.cword) {
|
||||||
self.token_span = (cur.span.start, cur.span.end);
|
self.token_span = (cur.span.range().start, cur.span.range().end);
|
||||||
} else {
|
} else {
|
||||||
self.token_span = (cursor_pos, cursor_pos);
|
self.token_span = (cursor_pos, cursor_pos);
|
||||||
}
|
}
|
||||||
@@ -799,7 +799,7 @@ impl Completer {
|
|||||||
return Ok(CompResult::from_candidates(candidates));
|
return Ok(CompResult::from_candidates(candidates));
|
||||||
};
|
};
|
||||||
|
|
||||||
self.token_span = (cur_token.span.start, cur_token.span.end);
|
self.token_span = (cur_token.span.range().start, cur_token.span.range().end);
|
||||||
|
|
||||||
// Use marker-based context detection for sub-token awareness (e.g. VAR_SUB
|
// Use marker-based context detection for sub-token awareness (e.g. VAR_SUB
|
||||||
// inside a token)
|
// inside a token)
|
||||||
@@ -812,7 +812,7 @@ impl Completer {
|
|||||||
// If token contains '=', only complete after the '='
|
// If token contains '=', only complete after the '='
|
||||||
let token_str = cur_token.span.as_str();
|
let token_str = cur_token.span.as_str();
|
||||||
if let Some(eq_pos) = token_str.rfind('=') {
|
if let Some(eq_pos) = token_str.rfind('=') {
|
||||||
self.token_span.0 = cur_token.span.start + eq_pos + 1;
|
self.token_span.0 = cur_token.span.range().start + eq_pos + 1;
|
||||||
cur_token
|
cur_token
|
||||||
.span
|
.span
|
||||||
.set_range(self.token_span.0..self.token_span.1);
|
.set_range(self.token_span.0..self.token_span.1);
|
||||||
|
|||||||
@@ -70,11 +70,10 @@ impl HistEntry {
|
|||||||
impl FromStr for HistEntry {
|
impl FromStr for HistEntry {
|
||||||
type Err = ShErr;
|
type Err = ShErr;
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let err = Err(ShErr::Simple {
|
let err = Err(ShErr::simple(
|
||||||
kind: ShErrKind::HistoryReadErr,
|
ShErrKind::HistoryReadErr,
|
||||||
msg: format!("Bad formatting on history entry '{s}'"),
|
format!("Bad formatting on history entry '{s}'"),
|
||||||
notes: vec![],
|
));
|
||||||
});
|
|
||||||
|
|
||||||
//: 248972349;148;echo foo; echo bar
|
//: 248972349;148;echo foo; echo bar
|
||||||
let Some(cleaned) = s.strip_prefix(": ") else {
|
let Some(cleaned) = s.strip_prefix(": ") else {
|
||||||
@@ -133,11 +132,10 @@ impl FromStr for HistEntries {
|
|||||||
|
|
||||||
while let Some((i, line)) = lines.next() {
|
while let Some((i, line)) = lines.next() {
|
||||||
if !line.starts_with(": ") {
|
if !line.starts_with(": ") {
|
||||||
return Err(ShErr::Simple {
|
return Err(ShErr::simple(
|
||||||
kind: ShErrKind::HistoryReadErr,
|
ShErrKind::HistoryReadErr,
|
||||||
msg: format!("Bad formatting on line {i}"),
|
format!("Bad formatting on line {i}"),
|
||||||
notes: vec![],
|
));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
let mut chars = line.chars().peekable();
|
let mut chars = line.chars().peekable();
|
||||||
let mut feeding_lines = true;
|
let mut feeding_lines = true;
|
||||||
@@ -163,11 +161,10 @@ impl FromStr for HistEntries {
|
|||||||
}
|
}
|
||||||
if feeding_lines {
|
if feeding_lines {
|
||||||
let Some((_, line)) = lines.next() else {
|
let Some((_, line)) = lines.next() else {
|
||||||
return Err(ShErr::Simple {
|
return Err(ShErr::simple(
|
||||||
kind: ShErrKind::HistoryReadErr,
|
ShErrKind::HistoryReadErr,
|
||||||
msg: format!("Bad formatting on line {i}"),
|
format!("Bad formatting on line {i}"),
|
||||||
notes: vec![],
|
));
|
||||||
});
|
|
||||||
};
|
};
|
||||||
chars = line.chars().peekable();
|
chars = line.chars().peekable();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use vimode::{CmdReplay, ModeReport, ViInsert, ViMode, ViNormal, ViReplace, ViVis
|
|||||||
|
|
||||||
use crate::expand::expand_prompt;
|
use crate::expand::expand_prompt;
|
||||||
use crate::libsh::sys::TTY_FILENO;
|
use crate::libsh::sys::TTY_FILENO;
|
||||||
use crate::parse::lex::LexStream;
|
use crate::parse::lex::{LexStream, QuoteState};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::readline::term::{Pos, calc_str_width};
|
use crate::readline::term::{Pos, calc_str_width};
|
||||||
use crate::state::read_shopts;
|
use crate::state::read_shopts;
|
||||||
@@ -339,8 +339,14 @@ impl ShedVi {
|
|||||||
let line = self.editor.as_str().to_string();
|
let line = self.editor.as_str().to_string();
|
||||||
let cursor_pos = self.editor.cursor_byte_pos();
|
let cursor_pos = self.editor.cursor_byte_pos();
|
||||||
|
|
||||||
match self.completer.complete(line, cursor_pos, direction)? {
|
match self.completer.complete(line, cursor_pos, direction) {
|
||||||
Some(line) => {
|
Err(e) => {
|
||||||
|
self.writer.flush_write(&format!("\n{e}\n\n"))?;
|
||||||
|
|
||||||
|
// Printing the error invalidates the layout
|
||||||
|
self.old_layout = None;
|
||||||
|
}
|
||||||
|
Ok(Some(line)) => {
|
||||||
let span_start = self.completer.token_span.0;
|
let span_start = self.completer.token_span.0;
|
||||||
let new_cursor = span_start
|
let new_cursor = span_start
|
||||||
+ self
|
+ self
|
||||||
@@ -361,7 +367,7 @@ impl ShedVi {
|
|||||||
let hint = self.history.get_hint();
|
let hint = self.history.get_hint();
|
||||||
self.editor.set_hint(hint);
|
self.editor.set_hint(hint);
|
||||||
}
|
}
|
||||||
None => {
|
Ok(None) => {
|
||||||
self.writer.send_bell().ok();
|
self.writer.send_bell().ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -969,7 +975,7 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
|||||||
other => other,
|
other => other,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
stack.retain(|(i, m)| *i <= token.span.start && !markers::END_MARKERS.contains(m));
|
stack.retain(|(i, m)| *i <= token.span.range().start && !markers::END_MARKERS.contains(m));
|
||||||
|
|
||||||
let Some(ctx) = stack.last() else {
|
let Some(ctx) = stack.last() else {
|
||||||
return false;
|
return false;
|
||||||
@@ -983,30 +989,29 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
|||||||
if token.class != TkRule::Str
|
if token.class != TkRule::Str
|
||||||
&& let Some(marker) = marker_for(&token.class)
|
&& let Some(marker) = marker_for(&token.class)
|
||||||
{
|
{
|
||||||
insertions.push((token.span.end, markers::RESET));
|
insertions.push((token.span.range().end, markers::RESET));
|
||||||
insertions.push((token.span.start, marker));
|
insertions.push((token.span.range().start, marker));
|
||||||
return insertions;
|
return insertions;
|
||||||
} else if token.flags.contains(TkFlags::IS_SUBSH) {
|
} else if token.flags.contains(TkFlags::IS_SUBSH) {
|
||||||
let token_raw = token.span.as_str();
|
let token_raw = token.span.as_str();
|
||||||
if token_raw.ends_with(')') {
|
if token_raw.ends_with(')') {
|
||||||
insertions.push((token.span.end, markers::SUBSH_END));
|
insertions.push((token.span.range().end, markers::SUBSH_END));
|
||||||
}
|
}
|
||||||
insertions.push((token.span.start, markers::SUBSH));
|
insertions.push((token.span.range().start, markers::SUBSH));
|
||||||
return insertions;
|
return insertions;
|
||||||
} else if token.class == TkRule::CasePattern {
|
} else if token.class == TkRule::CasePattern {
|
||||||
insertions.push((token.span.end, markers::RESET));
|
insertions.push((token.span.range().end, markers::RESET));
|
||||||
insertions.push((token.span.end - 1, markers::CASE_PAT));
|
insertions.push((token.span.range().end - 1, markers::CASE_PAT));
|
||||||
insertions.push((token.span.start, markers::OPERATOR));
|
insertions.push((token.span.range().start, markers::OPERATOR));
|
||||||
return insertions;
|
return insertions;
|
||||||
}
|
}
|
||||||
|
|
||||||
let token_raw = token.span.as_str();
|
let token_raw = token.span.as_str();
|
||||||
let mut token_chars = token_raw.char_indices().peekable();
|
let mut token_chars = token_raw.char_indices().peekable();
|
||||||
|
|
||||||
let span_start = token.span.start;
|
let span_start = token.span.range().start;
|
||||||
|
|
||||||
let mut in_dub_qt = false;
|
let mut qt_state = QuoteState::default();
|
||||||
let mut in_sng_qt = false;
|
|
||||||
let mut cmd_sub_depth = 0;
|
let mut cmd_sub_depth = 0;
|
||||||
let mut proc_sub_depth = 0;
|
let mut proc_sub_depth = 0;
|
||||||
|
|
||||||
@@ -1026,7 +1031,7 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
|||||||
insertions.insert(0, (span_start, markers::ASSIGNMENT));
|
insertions.insert(0, (span_start, markers::ASSIGNMENT));
|
||||||
}
|
}
|
||||||
|
|
||||||
insertions.insert(0, (token.span.end, markers::RESET)); // reset at the end of the token
|
insertions.insert(0, (token.span.range().end, markers::RESET)); // reset at the end of the token
|
||||||
|
|
||||||
while let Some((i, ch)) = token_chars.peek() {
|
while let Some((i, ch)) = token_chars.peek() {
|
||||||
let index = *i; // we have to dereference this here because rustc is a very pedantic program
|
let index = *i; // we have to dereference this here because rustc is a very pedantic program
|
||||||
@@ -1045,7 +1050,7 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'$' if !in_sng_qt => {
|
'$' if !qt_state.in_single() => {
|
||||||
let dollar_pos = index;
|
let dollar_pos = index;
|
||||||
token_chars.next(); // consume the dollar
|
token_chars.next(); // consume the dollar
|
||||||
if let Some((_, dollar_ch)) = token_chars.peek() {
|
if let Some((_, dollar_ch)) = token_chars.peek() {
|
||||||
@@ -1115,13 +1120,13 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
|||||||
token_chars.next(); // consume the char with no special handling
|
token_chars.next(); // consume the char with no special handling
|
||||||
}
|
}
|
||||||
|
|
||||||
'\\' if !in_sng_qt => {
|
'\\' if !qt_state.in_single() => {
|
||||||
token_chars.next(); // consume the backslash
|
token_chars.next(); // consume the backslash
|
||||||
if token_chars.peek().is_some() {
|
if token_chars.peek().is_some() {
|
||||||
token_chars.next(); // consume the escaped char
|
token_chars.next(); // consume the escaped char
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'<' | '>' if !in_dub_qt && !in_sng_qt && cmd_sub_depth == 0 && proc_sub_depth == 0 => {
|
'<' | '>' if !qt_state.in_quote() && cmd_sub_depth == 0 && proc_sub_depth == 0 => {
|
||||||
token_chars.next();
|
token_chars.next();
|
||||||
if let Some((_, proc_sub_ch)) = token_chars.peek()
|
if let Some((_, proc_sub_ch)) = token_chars.peek()
|
||||||
&& *proc_sub_ch == '('
|
&& *proc_sub_ch == '('
|
||||||
@@ -1133,25 +1138,25 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'"' if !in_sng_qt => {
|
'"' if !qt_state.in_single() => {
|
||||||
if in_dub_qt {
|
if qt_state.in_double() {
|
||||||
insertions.push((span_start + *i + 1, markers::STRING_DQ_END));
|
insertions.push((span_start + *i + 1, markers::STRING_DQ_END));
|
||||||
} else {
|
} else {
|
||||||
insertions.push((span_start + *i, markers::STRING_DQ));
|
insertions.push((span_start + *i, markers::STRING_DQ));
|
||||||
}
|
}
|
||||||
in_dub_qt = !in_dub_qt;
|
qt_state.toggle_double();
|
||||||
token_chars.next(); // consume the quote
|
token_chars.next(); // consume the quote
|
||||||
}
|
}
|
||||||
'\'' if !in_dub_qt => {
|
'\'' if !qt_state.in_double() => {
|
||||||
if in_sng_qt {
|
if qt_state.in_single() {
|
||||||
insertions.push((span_start + *i + 1, markers::STRING_SQ_END));
|
insertions.push((span_start + *i + 1, markers::STRING_SQ_END));
|
||||||
} else {
|
} else {
|
||||||
insertions.push((span_start + *i, markers::STRING_SQ));
|
insertions.push((span_start + *i, markers::STRING_SQ));
|
||||||
}
|
}
|
||||||
in_sng_qt = !in_sng_qt;
|
qt_state.toggle_single();
|
||||||
token_chars.next(); // consume the quote
|
token_chars.next(); // consume the quote
|
||||||
}
|
}
|
||||||
'[' if !in_dub_qt && !in_sng_qt && !token.flags.contains(TkFlags::ASSIGN) => {
|
'[' if !qt_state.in_quote() && !token.flags.contains(TkFlags::ASSIGN) => {
|
||||||
token_chars.next(); // consume the opening bracket
|
token_chars.next(); // consume the opening bracket
|
||||||
let start_pos = span_start + index;
|
let start_pos = span_start + index;
|
||||||
let mut is_glob_pat = false;
|
let mut is_glob_pat = false;
|
||||||
@@ -1177,7 +1182,7 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
|||||||
insertions.push((start_pos, markers::GLOB));
|
insertions.push((start_pos, markers::GLOB));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'*' | '?' if (!in_dub_qt && !in_sng_qt) => {
|
'*' | '?' if !qt_state.in_quote() => {
|
||||||
let glob_ch = *ch;
|
let glob_ch = *ch;
|
||||||
token_chars.next(); // consume the first glob char
|
token_chars.next(); // consume the first glob char
|
||||||
if !in_context(markers::COMMAND, &insertions) {
|
if !in_context(markers::COMMAND, &insertions) {
|
||||||
|
|||||||
61
src/shopt.rs
61
src/shopt.rs
@@ -109,10 +109,6 @@ impl ShOpts {
|
|||||||
ShErrKind::SyntaxErr,
|
ShErrKind::SyntaxErr,
|
||||||
"shopt: expected 'core' or 'prompt' in shopt key",
|
"shopt: expected 'core' or 'prompt' in shopt key",
|
||||||
)
|
)
|
||||||
.with_note(
|
|
||||||
Note::new("'shopt' takes arguments separated by periods to denote namespaces")
|
|
||||||
.with_sub_notes(vec!["Example: 'shopt core.autocd=true'"]),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,10 +134,6 @@ impl ShOpts {
|
|||||||
ShErrKind::SyntaxErr,
|
ShErrKind::SyntaxErr,
|
||||||
"shopt: Expected 'core' or 'prompt' in shopt key",
|
"shopt: Expected 'core' or 'prompt' in shopt key",
|
||||||
)
|
)
|
||||||
.with_note(
|
|
||||||
Note::new("'shopt' takes arguments separated by periods to denote namespaces")
|
|
||||||
.with_sub_notes(vec!["Example: 'shopt core.autocd=true'"]),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,19 +232,6 @@ impl ShOptCore {
|
|||||||
ShErrKind::SyntaxErr,
|
ShErrKind::SyntaxErr,
|
||||||
format!("shopt: Unexpected 'core' option '{opt}'"),
|
format!("shopt: Unexpected 'core' option '{opt}'"),
|
||||||
)
|
)
|
||||||
.with_note(Note::new("options can be accessed like 'core.option_name'"))
|
|
||||||
.with_note(
|
|
||||||
Note::new("'core' contains the following options").with_sub_notes(vec![
|
|
||||||
"dotglob",
|
|
||||||
"autocd",
|
|
||||||
"hist_ignore_dupes",
|
|
||||||
"max_hist",
|
|
||||||
"interactive_comments",
|
|
||||||
"auto_hist",
|
|
||||||
"bell_enabled",
|
|
||||||
"max_recurse_depth",
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -315,19 +294,6 @@ impl ShOptCore {
|
|||||||
ShErrKind::SyntaxErr,
|
ShErrKind::SyntaxErr,
|
||||||
format!("shopt: Unexpected 'core' option '{query}'"),
|
format!("shopt: Unexpected 'core' option '{query}'"),
|
||||||
)
|
)
|
||||||
.with_note(Note::new("options can be accessed like 'core.option_name'"))
|
|
||||||
.with_note(
|
|
||||||
Note::new("'core' contains the following options").with_sub_notes(vec![
|
|
||||||
"dotglob",
|
|
||||||
"autocd",
|
|
||||||
"hist_ignore_dupes",
|
|
||||||
"max_hist",
|
|
||||||
"interactive_comments",
|
|
||||||
"auto_hist",
|
|
||||||
"bell_enabled",
|
|
||||||
"max_recurse_depth",
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -445,20 +411,6 @@ impl ShOptPrompt {
|
|||||||
ShErrKind::SyntaxErr,
|
ShErrKind::SyntaxErr,
|
||||||
format!("shopt: Unexpected 'prompt' option '{opt}'"),
|
format!("shopt: Unexpected 'prompt' option '{opt}'"),
|
||||||
)
|
)
|
||||||
.with_note(Note::new(
|
|
||||||
"options can be accessed like 'prompt.option_name'",
|
|
||||||
))
|
|
||||||
.with_note(
|
|
||||||
Note::new("'prompt' contains the following options").with_sub_notes(vec![
|
|
||||||
"trunc_prompt_path",
|
|
||||||
"edit_mode",
|
|
||||||
"comp_limit",
|
|
||||||
"highlight",
|
|
||||||
"auto_indent",
|
|
||||||
"linebreak_on_incomplete",
|
|
||||||
"custom",
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -512,19 +464,6 @@ impl ShOptPrompt {
|
|||||||
ShErrKind::SyntaxErr,
|
ShErrKind::SyntaxErr,
|
||||||
format!("shopt: Unexpected 'prompt' option '{query}'"),
|
format!("shopt: Unexpected 'prompt' option '{query}'"),
|
||||||
)
|
)
|
||||||
.with_note(Note::new(
|
|
||||||
"options can be accessed like 'prompt.option_name'",
|
|
||||||
))
|
|
||||||
.with_note(
|
|
||||||
Note::new("'prompt' contains the following options").with_sub_notes(vec![
|
|
||||||
"trunc_prompt_path",
|
|
||||||
"edit_mode",
|
|
||||||
"comp_limit",
|
|
||||||
"highlight",
|
|
||||||
"auto_indent",
|
|
||||||
"linebreak_on_incomplete",
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -620,6 +620,7 @@ impl VarFlags {
|
|||||||
pub enum ArrIndex {
|
pub enum ArrIndex {
|
||||||
Literal(usize),
|
Literal(usize),
|
||||||
FromBack(usize),
|
FromBack(usize),
|
||||||
|
ArgCount,
|
||||||
AllJoined,
|
AllJoined,
|
||||||
AllSplit,
|
AllSplit,
|
||||||
}
|
}
|
||||||
@@ -630,6 +631,7 @@ impl FromStr for ArrIndex {
|
|||||||
match s {
|
match s {
|
||||||
"@" => Ok(Self::AllSplit),
|
"@" => Ok(Self::AllSplit),
|
||||||
"*" => Ok(Self::AllJoined),
|
"*" => Ok(Self::AllJoined),
|
||||||
|
"#" => Ok(Self::ArgCount),
|
||||||
_ if s.starts_with('-') && s[1..].chars().all(|c| c.is_digit(1)) => {
|
_ if s.starts_with('-') && s[1..].chars().all(|c| c.is_digit(1)) => {
|
||||||
let idx = s[1..].parse::<usize>().unwrap();
|
let idx = s[1..].parse::<usize>().unwrap();
|
||||||
Ok(Self::FromBack(idx))
|
Ok(Self::FromBack(idx))
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ fn cmd_not_found() {
|
|||||||
.next()
|
.next()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let err = ShErr::full(ShErrKind::CmdNotFound("foo".into()), "", token.span);
|
let err = ShErr::at(ShErrKind::CmdNotFound, token.span, "");
|
||||||
|
|
||||||
let err_fmt = format!("{err}");
|
let err_fmt = format!("{err}");
|
||||||
insta::assert_snapshot!(err_fmt)
|
insta::assert_snapshot!(err_fmt)
|
||||||
|
|||||||
Reference in New Issue
Block a user