├── .github └── workflows │ ├── audit.yaml │ └── test.yaml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── crates ├── fennel-language-server │ ├── Cargo.toml │ └── src │ │ ├── cli.rs │ │ ├── config.rs │ │ ├── helper.rs │ │ ├── main.rs │ │ └── view.rs └── fennel-parser │ ├── Cargo.toml │ ├── examples │ └── parse_file.rs │ ├── src │ ├── ast.rs │ ├── ast │ │ ├── bind.rs │ │ ├── error.rs │ │ ├── eval.rs │ │ ├── func.rs │ │ ├── macros.rs │ │ ├── models.rs │ │ └── nodes.rs │ ├── errors.rs │ ├── lexer.rs │ ├── lib.rs │ ├── parser.rs │ ├── parser │ │ ├── bnf.rs │ │ ├── helper.rs │ │ └── sets.rs │ ├── static │ │ ├── compiler-macro │ │ ├── globals │ │ ├── globals-func │ │ ├── globals-module │ │ ├── globals-var │ │ ├── keywords │ │ ├── literals │ │ ├── modules │ │ │ ├── coroutine │ │ │ ├── debug │ │ │ ├── file │ │ │ ├── io │ │ │ ├── math │ │ │ ├── os │ │ │ ├── package │ │ │ ├── string │ │ │ └── table │ │ ├── operator │ │ └── reserved │ └── syntax.rs │ └── testdata │ └── parser │ ├── cst │ └── raw.fnl └── rustfmt.toml /.github/workflows/audit.yaml: -------------------------------------------------------------------------------- 1 | name: Security Audit 2 | on: 3 | schedule: 4 | - cron: '0 0 * * *' 5 | push: 6 | paths: 7 | - '**/Cargo.toml' 8 | - '**/Cargo.lock' 9 | - '**/audit.toml' 10 | jobs: 11 | audit: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | - uses: actions-rs/audit-check@v1 17 | with: 18 | token: ${{ secrets.GITHUB_TOKEN }} 19 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | RUSTFLAGS: "-D warnings" 7 | CARGO_TERM_COLOR: always 8 | 9 | jobs: 10 | build_and_test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | - name: Build 16 | run: cargo build --verbose 17 | - name: Run tests 18 | run: cargo test --verbose 19 | 20 | clippy: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v2 25 | - name: Install rust toolchain 26 | uses: actions-rs/toolchain@v1 27 | with: 28 | toolchain: stable 29 | override: true 30 | components: clippy 31 | - name: Run clippy 32 | uses: actions-rs/clippy-check@v1 33 | with: 34 | token: ${{ secrets.GITHUB_TOKEN }} 35 | args: --all-features 36 | 37 | rustfmt: 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Checkout 41 | uses: actions/checkout@v2 42 | - name: Install rust toolchain 43 | uses: actions-rs/toolchain@v1 44 | with: 45 | toolchain: nightly 46 | override: true 47 | components: rustfmt 48 | - name: Run rustfmt 49 | uses: actions-rs/cargo@v1 50 | with: 51 | command: fmt 52 | args: --all -- --check 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "async-trait" 7 | version = "0.1.64" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" 10 | dependencies = [ 11 | "proc-macro2", 12 | "quote", 13 | "syn", 14 | ] 15 | 16 | [[package]] 17 | name = "auto_impl" 18 | version = "1.0.1" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "8a8c1df849285fbacd587de7818cc7d13be6cd2cbcd47a04fb1801b0e2706e33" 21 | dependencies = [ 22 | "proc-macro-error", 23 | "proc-macro2", 24 | "quote", 25 | "syn", 26 | ] 27 | 28 | [[package]] 29 | name = "autocfg" 30 | version = "1.1.0" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 33 | 34 | [[package]] 35 | name = "bitflags" 36 | version = "1.3.2" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 39 | 40 | [[package]] 41 | name = "bytes" 42 | version = "1.4.0" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" 45 | 46 | [[package]] 47 | name = "cc" 48 | version = "1.0.79" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 51 | 52 | [[package]] 53 | name = "cfg-if" 54 | version = "1.0.0" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 57 | 58 | [[package]] 59 | name = "clap" 60 | version = "4.1.4" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76" 63 | dependencies = [ 64 | "bitflags", 65 | "clap_derive", 66 | "clap_lex", 67 | "is-terminal", 68 | "once_cell", 69 | "strsim", 70 | "termcolor", 71 | ] 72 | 73 | [[package]] 74 | name = "clap_derive" 75 | version = "4.1.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" 78 | dependencies = [ 79 | "heck", 80 | "proc-macro-error", 81 | "proc-macro2", 82 | "quote", 83 | "syn", 84 | ] 85 | 86 | [[package]] 87 | name = "clap_lex" 88 | version = "0.3.1" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" 91 | dependencies = [ 92 | "os_str_bytes", 93 | ] 94 | 95 | [[package]] 96 | name = "countme" 97 | version = "3.0.1" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" 100 | 101 | [[package]] 102 | name = "dashmap" 103 | version = "5.4.0" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" 106 | dependencies = [ 107 | "cfg-if", 108 | "hashbrown", 109 | "lock_api", 110 | "once_cell", 111 | "parking_lot_core", 112 | ] 113 | 114 | [[package]] 115 | name = "errno" 116 | version = "0.2.8" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" 119 | dependencies = [ 120 | "errno-dragonfly", 121 | "libc", 122 | "winapi", 123 | ] 124 | 125 | [[package]] 126 | name = "errno-dragonfly" 127 | version = "0.1.2" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 130 | dependencies = [ 131 | "cc", 132 | "libc", 133 | ] 134 | 135 | [[package]] 136 | name = "fennel-language-server" 137 | version = "0.1.0" 138 | dependencies = [ 139 | "clap", 140 | "dashmap", 141 | "fennel-parser", 142 | "ropey", 143 | "serde", 144 | "serde_json", 145 | "tokio", 146 | "tower-lsp", 147 | ] 148 | 149 | [[package]] 150 | name = "fennel-parser" 151 | version = "0.1.0" 152 | dependencies = [ 153 | "once_cell", 154 | "regex", 155 | "rowan", 156 | ] 157 | 158 | [[package]] 159 | name = "form_urlencoded" 160 | version = "1.1.0" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" 163 | dependencies = [ 164 | "percent-encoding", 165 | ] 166 | 167 | [[package]] 168 | name = "futures" 169 | version = "0.3.26" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" 172 | dependencies = [ 173 | "futures-channel", 174 | "futures-core", 175 | "futures-io", 176 | "futures-sink", 177 | "futures-task", 178 | "futures-util", 179 | ] 180 | 181 | [[package]] 182 | name = "futures-channel" 183 | version = "0.3.26" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" 186 | dependencies = [ 187 | "futures-core", 188 | "futures-sink", 189 | ] 190 | 191 | [[package]] 192 | name = "futures-core" 193 | version = "0.3.26" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" 196 | 197 | [[package]] 198 | name = "futures-io" 199 | version = "0.3.26" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" 202 | 203 | [[package]] 204 | name = "futures-macro" 205 | version = "0.3.26" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" 208 | dependencies = [ 209 | "proc-macro2", 210 | "quote", 211 | "syn", 212 | ] 213 | 214 | [[package]] 215 | name = "futures-sink" 216 | version = "0.3.26" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" 219 | 220 | [[package]] 221 | name = "futures-task" 222 | version = "0.3.26" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" 225 | 226 | [[package]] 227 | name = "futures-util" 228 | version = "0.3.26" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" 231 | dependencies = [ 232 | "futures-channel", 233 | "futures-core", 234 | "futures-io", 235 | "futures-macro", 236 | "futures-sink", 237 | "futures-task", 238 | "memchr", 239 | "pin-project-lite", 240 | "pin-utils", 241 | "slab", 242 | ] 243 | 244 | [[package]] 245 | name = "hashbrown" 246 | version = "0.12.3" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 249 | 250 | [[package]] 251 | name = "heck" 252 | version = "0.4.1" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 255 | 256 | [[package]] 257 | name = "hermit-abi" 258 | version = "0.2.6" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" 261 | dependencies = [ 262 | "libc", 263 | ] 264 | 265 | [[package]] 266 | name = "hermit-abi" 267 | version = "0.3.1" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" 270 | 271 | [[package]] 272 | name = "httparse" 273 | version = "1.8.0" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 276 | 277 | [[package]] 278 | name = "idna" 279 | version = "0.3.0" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" 282 | dependencies = [ 283 | "unicode-bidi", 284 | "unicode-normalization", 285 | ] 286 | 287 | [[package]] 288 | name = "io-lifetimes" 289 | version = "1.0.5" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" 292 | dependencies = [ 293 | "libc", 294 | "windows-sys 0.45.0", 295 | ] 296 | 297 | [[package]] 298 | name = "is-terminal" 299 | version = "0.4.3" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef" 302 | dependencies = [ 303 | "hermit-abi 0.3.1", 304 | "io-lifetimes", 305 | "rustix", 306 | "windows-sys 0.45.0", 307 | ] 308 | 309 | [[package]] 310 | name = "itoa" 311 | version = "1.0.5" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" 314 | 315 | [[package]] 316 | name = "libc" 317 | version = "0.2.139" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 320 | 321 | [[package]] 322 | name = "linux-raw-sys" 323 | version = "0.1.4" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" 326 | 327 | [[package]] 328 | name = "lock_api" 329 | version = "0.4.9" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 332 | dependencies = [ 333 | "autocfg", 334 | "scopeguard", 335 | ] 336 | 337 | [[package]] 338 | name = "log" 339 | version = "0.4.17" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 342 | dependencies = [ 343 | "cfg-if", 344 | ] 345 | 346 | [[package]] 347 | name = "lsp-types" 348 | version = "0.93.2" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "9be6e9c7e2d18f651974370d7aff703f9513e0df6e464fd795660edc77e6ca51" 351 | dependencies = [ 352 | "bitflags", 353 | "serde", 354 | "serde_json", 355 | "serde_repr", 356 | "url", 357 | ] 358 | 359 | [[package]] 360 | name = "memchr" 361 | version = "2.5.0" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 364 | 365 | [[package]] 366 | name = "memoffset" 367 | version = "0.6.5" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 370 | dependencies = [ 371 | "autocfg", 372 | ] 373 | 374 | [[package]] 375 | name = "mio" 376 | version = "0.8.6" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" 379 | dependencies = [ 380 | "libc", 381 | "log", 382 | "wasi", 383 | "windows-sys 0.45.0", 384 | ] 385 | 386 | [[package]] 387 | name = "num_cpus" 388 | version = "1.15.0" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" 391 | dependencies = [ 392 | "hermit-abi 0.2.6", 393 | "libc", 394 | ] 395 | 396 | [[package]] 397 | name = "once_cell" 398 | version = "1.17.0" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" 401 | 402 | [[package]] 403 | name = "os_str_bytes" 404 | version = "6.4.1" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" 407 | 408 | [[package]] 409 | name = "parking_lot_core" 410 | version = "0.9.7" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" 413 | dependencies = [ 414 | "cfg-if", 415 | "libc", 416 | "redox_syscall", 417 | "smallvec", 418 | "windows-sys 0.45.0", 419 | ] 420 | 421 | [[package]] 422 | name = "percent-encoding" 423 | version = "2.2.0" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" 426 | 427 | [[package]] 428 | name = "pin-project" 429 | version = "1.0.12" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" 432 | dependencies = [ 433 | "pin-project-internal", 434 | ] 435 | 436 | [[package]] 437 | name = "pin-project-internal" 438 | version = "1.0.12" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" 441 | dependencies = [ 442 | "proc-macro2", 443 | "quote", 444 | "syn", 445 | ] 446 | 447 | [[package]] 448 | name = "pin-project-lite" 449 | version = "0.2.9" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 452 | 453 | [[package]] 454 | name = "pin-utils" 455 | version = "0.1.0" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 458 | 459 | [[package]] 460 | name = "proc-macro-error" 461 | version = "1.0.4" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 464 | dependencies = [ 465 | "proc-macro-error-attr", 466 | "proc-macro2", 467 | "quote", 468 | "syn", 469 | "version_check", 470 | ] 471 | 472 | [[package]] 473 | name = "proc-macro-error-attr" 474 | version = "1.0.4" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 477 | dependencies = [ 478 | "proc-macro2", 479 | "quote", 480 | "version_check", 481 | ] 482 | 483 | [[package]] 484 | name = "proc-macro2" 485 | version = "1.0.51" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" 488 | dependencies = [ 489 | "unicode-ident", 490 | ] 491 | 492 | [[package]] 493 | name = "quote" 494 | version = "1.0.23" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" 497 | dependencies = [ 498 | "proc-macro2", 499 | ] 500 | 501 | [[package]] 502 | name = "redox_syscall" 503 | version = "0.2.16" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 506 | dependencies = [ 507 | "bitflags", 508 | ] 509 | 510 | [[package]] 511 | name = "regex" 512 | version = "1.7.1" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" 515 | dependencies = [ 516 | "regex-syntax", 517 | ] 518 | 519 | [[package]] 520 | name = "regex-syntax" 521 | version = "0.6.28" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" 524 | 525 | [[package]] 526 | name = "ropey" 527 | version = "1.6.0" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "53ce7a2c43a32e50d666e33c5a80251b31147bb4b49024bcab11fb6f20c671ed" 530 | dependencies = [ 531 | "smallvec", 532 | "str_indices", 533 | ] 534 | 535 | [[package]] 536 | name = "rowan" 537 | version = "0.15.10" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "5811547e7ba31e903fe48c8ceab10d40d70a101f3d15523c847cce91aa71f332" 540 | dependencies = [ 541 | "countme", 542 | "hashbrown", 543 | "memoffset", 544 | "rustc-hash", 545 | "text-size", 546 | ] 547 | 548 | [[package]] 549 | name = "rustc-hash" 550 | version = "1.1.0" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 553 | 554 | [[package]] 555 | name = "rustix" 556 | version = "0.36.8" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" 559 | dependencies = [ 560 | "bitflags", 561 | "errno", 562 | "io-lifetimes", 563 | "libc", 564 | "linux-raw-sys", 565 | "windows-sys 0.45.0", 566 | ] 567 | 568 | [[package]] 569 | name = "ryu" 570 | version = "1.0.12" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" 573 | 574 | [[package]] 575 | name = "scopeguard" 576 | version = "1.1.0" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 579 | 580 | [[package]] 581 | name = "serde" 582 | version = "1.0.152" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" 585 | dependencies = [ 586 | "serde_derive", 587 | ] 588 | 589 | [[package]] 590 | name = "serde_derive" 591 | version = "1.0.152" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" 594 | dependencies = [ 595 | "proc-macro2", 596 | "quote", 597 | "syn", 598 | ] 599 | 600 | [[package]] 601 | name = "serde_json" 602 | version = "1.0.93" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" 605 | dependencies = [ 606 | "itoa", 607 | "ryu", 608 | "serde", 609 | ] 610 | 611 | [[package]] 612 | name = "serde_repr" 613 | version = "0.1.10" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e" 616 | dependencies = [ 617 | "proc-macro2", 618 | "quote", 619 | "syn", 620 | ] 621 | 622 | [[package]] 623 | name = "slab" 624 | version = "0.4.7" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" 627 | dependencies = [ 628 | "autocfg", 629 | ] 630 | 631 | [[package]] 632 | name = "smallvec" 633 | version = "1.10.0" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 636 | 637 | [[package]] 638 | name = "socket2" 639 | version = "0.4.7" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" 642 | dependencies = [ 643 | "libc", 644 | "winapi", 645 | ] 646 | 647 | [[package]] 648 | name = "str_indices" 649 | version = "0.4.1" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "5f026164926842ec52deb1938fae44f83dfdb82d0a5b0270c5bd5935ab74d6dd" 652 | 653 | [[package]] 654 | name = "strsim" 655 | version = "0.10.0" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 658 | 659 | [[package]] 660 | name = "syn" 661 | version = "1.0.107" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" 664 | dependencies = [ 665 | "proc-macro2", 666 | "quote", 667 | "unicode-ident", 668 | ] 669 | 670 | [[package]] 671 | name = "termcolor" 672 | version = "1.2.0" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" 675 | dependencies = [ 676 | "winapi-util", 677 | ] 678 | 679 | [[package]] 680 | name = "text-size" 681 | version = "1.1.0" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "288cb548dbe72b652243ea797201f3d481a0609a967980fcc5b2315ea811560a" 684 | 685 | [[package]] 686 | name = "tinyvec" 687 | version = "1.6.0" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 690 | dependencies = [ 691 | "tinyvec_macros", 692 | ] 693 | 694 | [[package]] 695 | name = "tinyvec_macros" 696 | version = "0.1.1" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 699 | 700 | [[package]] 701 | name = "tokio" 702 | version = "1.25.0" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" 705 | dependencies = [ 706 | "autocfg", 707 | "bytes", 708 | "libc", 709 | "memchr", 710 | "mio", 711 | "num_cpus", 712 | "pin-project-lite", 713 | "socket2", 714 | "windows-sys 0.42.0", 715 | ] 716 | 717 | [[package]] 718 | name = "tokio-util" 719 | version = "0.7.7" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" 722 | dependencies = [ 723 | "bytes", 724 | "futures-core", 725 | "futures-sink", 726 | "pin-project-lite", 727 | "tokio", 728 | "tracing", 729 | ] 730 | 731 | [[package]] 732 | name = "tower" 733 | version = "0.4.13" 734 | source = "registry+https://github.com/rust-lang/crates.io-index" 735 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 736 | dependencies = [ 737 | "futures-core", 738 | "futures-util", 739 | "pin-project", 740 | "pin-project-lite", 741 | "tower-layer", 742 | "tower-service", 743 | ] 744 | 745 | [[package]] 746 | name = "tower-layer" 747 | version = "0.3.2" 748 | source = "registry+https://github.com/rust-lang/crates.io-index" 749 | checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" 750 | 751 | [[package]] 752 | name = "tower-lsp" 753 | version = "0.18.0" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "7d0ad391e4e58fccec398abd3da22d5e59fbcbae8d036df0d00dfd9703c7ee96" 756 | dependencies = [ 757 | "async-trait", 758 | "auto_impl", 759 | "bytes", 760 | "dashmap", 761 | "futures", 762 | "httparse", 763 | "lsp-types", 764 | "memchr", 765 | "serde", 766 | "serde_json", 767 | "tokio", 768 | "tokio-util", 769 | "tower", 770 | "tower-lsp-macros", 771 | "tracing", 772 | ] 773 | 774 | [[package]] 775 | name = "tower-lsp-macros" 776 | version = "0.7.0" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "74697a0324a76eedfc784ffef1cc4de2300af19720de3c3fd99cd7ec484479da" 779 | dependencies = [ 780 | "proc-macro2", 781 | "quote", 782 | "syn", 783 | ] 784 | 785 | [[package]] 786 | name = "tower-service" 787 | version = "0.3.2" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 790 | 791 | [[package]] 792 | name = "tracing" 793 | version = "0.1.37" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 796 | dependencies = [ 797 | "cfg-if", 798 | "pin-project-lite", 799 | "tracing-attributes", 800 | "tracing-core", 801 | ] 802 | 803 | [[package]] 804 | name = "tracing-attributes" 805 | version = "0.1.23" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" 808 | dependencies = [ 809 | "proc-macro2", 810 | "quote", 811 | "syn", 812 | ] 813 | 814 | [[package]] 815 | name = "tracing-core" 816 | version = "0.1.30" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" 819 | dependencies = [ 820 | "once_cell", 821 | ] 822 | 823 | [[package]] 824 | name = "unicode-bidi" 825 | version = "0.3.10" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" 828 | 829 | [[package]] 830 | name = "unicode-ident" 831 | version = "1.0.6" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" 834 | 835 | [[package]] 836 | name = "unicode-normalization" 837 | version = "0.1.22" 838 | source = "registry+https://github.com/rust-lang/crates.io-index" 839 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 840 | dependencies = [ 841 | "tinyvec", 842 | ] 843 | 844 | [[package]] 845 | name = "url" 846 | version = "2.3.1" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" 849 | dependencies = [ 850 | "form_urlencoded", 851 | "idna", 852 | "percent-encoding", 853 | "serde", 854 | ] 855 | 856 | [[package]] 857 | name = "version_check" 858 | version = "0.9.4" 859 | source = "registry+https://github.com/rust-lang/crates.io-index" 860 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 861 | 862 | [[package]] 863 | name = "wasi" 864 | version = "0.11.0+wasi-snapshot-preview1" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 867 | 868 | [[package]] 869 | name = "winapi" 870 | version = "0.3.9" 871 | source = "registry+https://github.com/rust-lang/crates.io-index" 872 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 873 | dependencies = [ 874 | "winapi-i686-pc-windows-gnu", 875 | "winapi-x86_64-pc-windows-gnu", 876 | ] 877 | 878 | [[package]] 879 | name = "winapi-i686-pc-windows-gnu" 880 | version = "0.4.0" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 883 | 884 | [[package]] 885 | name = "winapi-util" 886 | version = "0.1.5" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 889 | dependencies = [ 890 | "winapi", 891 | ] 892 | 893 | [[package]] 894 | name = "winapi-x86_64-pc-windows-gnu" 895 | version = "0.4.0" 896 | source = "registry+https://github.com/rust-lang/crates.io-index" 897 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 898 | 899 | [[package]] 900 | name = "windows-sys" 901 | version = "0.42.0" 902 | source = "registry+https://github.com/rust-lang/crates.io-index" 903 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 904 | dependencies = [ 905 | "windows_aarch64_gnullvm", 906 | "windows_aarch64_msvc", 907 | "windows_i686_gnu", 908 | "windows_i686_msvc", 909 | "windows_x86_64_gnu", 910 | "windows_x86_64_gnullvm", 911 | "windows_x86_64_msvc", 912 | ] 913 | 914 | [[package]] 915 | name = "windows-sys" 916 | version = "0.45.0" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 919 | dependencies = [ 920 | "windows-targets", 921 | ] 922 | 923 | [[package]] 924 | name = "windows-targets" 925 | version = "0.42.1" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" 928 | dependencies = [ 929 | "windows_aarch64_gnullvm", 930 | "windows_aarch64_msvc", 931 | "windows_i686_gnu", 932 | "windows_i686_msvc", 933 | "windows_x86_64_gnu", 934 | "windows_x86_64_gnullvm", 935 | "windows_x86_64_msvc", 936 | ] 937 | 938 | [[package]] 939 | name = "windows_aarch64_gnullvm" 940 | version = "0.42.1" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" 943 | 944 | [[package]] 945 | name = "windows_aarch64_msvc" 946 | version = "0.42.1" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" 949 | 950 | [[package]] 951 | name = "windows_i686_gnu" 952 | version = "0.42.1" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" 955 | 956 | [[package]] 957 | name = "windows_i686_msvc" 958 | version = "0.42.1" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" 961 | 962 | [[package]] 963 | name = "windows_x86_64_gnu" 964 | version = "0.42.1" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" 967 | 968 | [[package]] 969 | name = "windows_x86_64_gnullvm" 970 | version = "0.42.1" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" 973 | 974 | [[package]] 975 | name = "windows_x86_64_msvc" 976 | version = "0.42.1" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" 979 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["crates/*"] 3 | 4 | [profile.release] 5 | lto = true 6 | strip = true 7 | codegen-units = 1 8 | opt-level = 3 9 | panic = "abort" 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 rydesun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fennel-language-server 2 | 3 | [![Test](https://github.com/rydesun/fennel-language-server/actions/workflows/test.yaml/badge.svg)](https://github.com/rydesun/fennel-language-server/actions/workflows/test.yaml) 4 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/rydesun/fennel-language-server/blob/master/LICENSE) 5 | 6 | Fennel language server protocol (LSP) support. 7 | 8 | `fennel-language-server` is currently in a very early stage and unreliable. 9 | Use it just for an encouraging try. 10 | 11 | ## Installation 12 | 13 | Because it is written in pure Rust language, 14 | the server should be installed via `cargo`. 15 | 16 | ```sh 17 | cargo install --git https://github.com/rydesun/fennel-language-server 18 | ``` 19 | 20 | No demand for the Fennel environment. You don't even need Fennel runtime! 21 | (It sounds a little weird but that's the truth) 22 | 23 | ## Integration 24 | 25 | **NOTE**: The executable file is now named `fennel-language-server`. 26 | The former name `fennel-ls` has been abandoned. 27 | 28 | ### Neovim 29 | 30 | For Nvim user to setup `fennel-language-server` with `nvim-lspconfig`, 31 | add the following code to your configuration. 32 | 33 | ```lua 34 | local lspconfig = require 'lspconfig' 35 | require 'lspconfig.configs'.fennel_language_server = { 36 | default_config = { 37 | -- replace it with true path 38 | cmd = {'/PATH/TO/BINFILE'}, 39 | filetypes = {'fennel'}, 40 | single_file_support = true, 41 | -- source code resides in directory `fnl/` 42 | root_dir = lspconfig.util.root_pattern("fnl"), 43 | settings = { 44 | fennel = { 45 | workspace = { 46 | -- If you are using hotpot.nvim or aniseed, 47 | -- make the server aware of neovim runtime files. 48 | library = vim.api.nvim_list_runtime_paths(), 49 | }, 50 | diagnostics = { 51 | globals = {'vim'}, 52 | }, 53 | }, 54 | }, 55 | }, 56 | } 57 | 58 | lspconfig.fennel_language_server.setup{} 59 | ``` 60 | 61 | ## Status 62 | 63 | There is a long way to go. 64 | Features are partially completed: 65 | 66 | - [x] `Diagnostics`: Be careful these are not fully provided! 67 | - [x] `Goto Definition` 68 | - [x] `Code Completion` 69 | - [x] `References` 70 | - [x] `Hover` 71 | - [x] `Rename` 72 | - [ ] `Formatter` 73 | 74 | **All features don't work properly on multi-symbols.** 75 | It means that you cannot hover on the part after the dot, for example. 76 | 77 | The following are also known issues: 78 | 79 | - Macro grammar support is very limited. 80 | You may suffer from wrong diagnostics. 81 | - Type checking is very weak. 82 | - Lack of cross-file operation. 83 | Such as `require-macros` still does not analyzed. 84 | You should use `import-macros` for a clear namespace. 85 | 86 | ## Also See 87 | 88 | XeroOl `fennel-ls` written in pure fennel you may love 89 | 90 | [https://git.sr.ht/~xerool/fennel-ls](https://git.sr.ht/~xerool/fennel-ls) 91 | -------------------------------------------------------------------------------- /crates/fennel-language-server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fennel-language-server" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | clap = { version = "4.1.4", features = ["derive"] } 8 | dashmap = "5.4.0" 9 | fennel-parser = { path = "../fennel-parser" } 10 | ropey = "1.6.0" 11 | serde = "1.0.152" 12 | serde_json = "1.0.93" 13 | tokio = { version = "1.25.0", features = [ 14 | "rt-multi-thread", 15 | "io-std", 16 | "io-util", 17 | "net", 18 | ] } 19 | tower-lsp = "0.18.0" 20 | -------------------------------------------------------------------------------- /crates/fennel-language-server/src/cli.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | 3 | #[derive(Parser, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 4 | #[command(name = env!("CARGO_PKG_NAME"))] 5 | #[command(bin_name = env!("CARGO_PKG_NAME"))] 6 | #[command(author, version, about, long_about = None)] 7 | pub(crate) struct Cli { 8 | #[command(subcommand)] 9 | pub(crate) cmd: Option, 10 | } 11 | 12 | #[derive(Subcommand, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 13 | pub(crate) enum Command { 14 | /// Run the language server. 15 | Lsp { 16 | #[clap(subcommand)] 17 | cmd: LspCommand, 18 | }, 19 | } 20 | 21 | #[derive(Subcommand, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 22 | pub(crate) enum LspCommand { 23 | /// Listen on a TCP address. 24 | Tcp { 25 | /// The address and port to listen on. 26 | #[clap(long)] 27 | address: String, 28 | }, 29 | /// Attach to standard input and output. 30 | Stdio, 31 | } 32 | -------------------------------------------------------------------------------- /crates/fennel-language-server/src/config.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use serde::{de::Error, Deserialize, Deserializer}; 4 | 5 | #[derive( 6 | Deserialize, Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, 7 | )] 8 | pub(crate) struct Configuration { 9 | #[serde(default)] 10 | pub(crate) fennel: Fennel, 11 | } 12 | 13 | #[derive( 14 | Deserialize, Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, 15 | )] 16 | pub(crate) struct Fennel { 17 | #[serde(default)] 18 | pub(crate) workspace: Workspace, 19 | #[serde(default)] 20 | pub(crate) diagnostics: Diagnostics, 21 | } 22 | 23 | #[derive( 24 | Deserialize, Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, 25 | )] 26 | pub(crate) struct Workspace { 27 | #[serde(default)] 28 | pub(crate) library: Vec, 29 | } 30 | 31 | #[derive( 32 | Deserialize, Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, 33 | )] 34 | pub(crate) struct Diagnostics { 35 | #[serde(default)] 36 | pub(crate) globals: Vec, 37 | } 38 | 39 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 40 | pub(crate) struct Url(pub(crate) tower_lsp::lsp_types::Url); 41 | 42 | impl<'de> Deserialize<'de> for Url { 43 | fn deserialize(deserializer: D) -> Result 44 | where 45 | D: Deserializer<'de>, 46 | { 47 | let s: String = Deserialize::deserialize(deserializer)?; 48 | tower_lsp::lsp_types::Url::from_directory_path(PathBuf::from(s)) 49 | .map(Self) 50 | .map_err(|_| D::Error::custom("invalid path")) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /crates/fennel-language-server/src/helper.rs: -------------------------------------------------------------------------------- 1 | use fennel_parser::TextRange; 2 | use ropey::Rope; 3 | use tower_lsp::{ 4 | jsonrpc::{Error, Result}, 5 | lsp_types::{Position, Range}, 6 | }; 7 | 8 | pub(crate) fn lsp_range(rope: &Rope, range: TextRange) -> Result { 9 | let (range_start, range_end) = (range.start(), range.end()); 10 | let pos_start = byte_offset_to_position(rope, range_start.into())?; 11 | let pos_end = byte_offset_to_position(rope, range_end.into())?; 12 | Ok(Range::new(pos_start, pos_end)) 13 | } 14 | 15 | pub(crate) fn rope_range( 16 | rope: &Rope, 17 | lsp_range: Range, 18 | ) -> Result> { 19 | let (pos_start, pos_end) = (lsp_range.start, lsp_range.end); 20 | let range_start = position_to_char_idx(rope, pos_start)?; 21 | let range_end = position_to_char_idx(rope, pos_end)?; 22 | Ok(range_start..range_end) 23 | } 24 | 25 | pub(crate) fn position_to_char_idx( 26 | rope: &Rope, 27 | position: Position, 28 | ) -> Result { 29 | let start_char = rope 30 | .try_line_to_char(position.line as usize) 31 | .map_err(|_| Error::invalid_request())?; 32 | let utf16_cu = 33 | rope.char_to_utf16_cu(start_char) + position.character as usize; 34 | Ok(rope.utf16_cu_to_char(utf16_cu)) 35 | } 36 | 37 | pub(crate) fn position_to_byte_offset( 38 | rope: &Rope, 39 | position: Position, 40 | ) -> Result { 41 | let start_char = rope 42 | .try_line_to_char(position.line as usize) 43 | .map_err(|_| Error::invalid_request())?; 44 | let utf16_cu = 45 | rope.char_to_utf16_cu(start_char) + position.character as usize; 46 | let char = rope.utf16_cu_to_char(utf16_cu); 47 | Ok(rope.char_to_byte(char) as u32) 48 | } 49 | 50 | pub(crate) fn byte_offset_to_position( 51 | rope: &Rope, 52 | offset: usize, 53 | ) -> Result { 54 | let line = 55 | rope.try_byte_to_line(offset).map_err(|_| Error::invalid_request())?; 56 | let start_char = rope.line_to_char(line); 57 | let utf16_cu = rope.char_to_utf16_cu(start_char); 58 | let column = rope.char_to_utf16_cu(rope.byte_to_char(offset)) - utf16_cu; 59 | Ok(Position::new(line as u32, column as u32)) 60 | } 61 | 62 | pub(crate) fn lsp_range_head() -> Range { 63 | Range::new(Position::new(0, 0), Position::new(0, 0)) 64 | } 65 | -------------------------------------------------------------------------------- /crates/fennel-language-server/src/main.rs: -------------------------------------------------------------------------------- 1 | mod cli; 2 | mod config; 3 | mod helper; 4 | mod view; 5 | 6 | use std::{ 7 | collections::{HashMap, HashSet}, 8 | path::PathBuf, 9 | sync::{Arc, RwLock}, 10 | }; 11 | 12 | use clap::Parser; 13 | use dashmap::DashMap; 14 | use fennel_parser::{models, Ast}; 15 | use helper::*; 16 | use ropey::Rope; 17 | use tokio::{ 18 | io::{AsyncRead, AsyncWrite}, 19 | net::TcpListener, 20 | }; 21 | use tower_lsp::{ 22 | jsonrpc::{Error, Result}, 23 | lsp_types::*, 24 | }; 25 | 26 | #[derive(Debug)] 27 | struct Backend { 28 | client: tower_lsp::Client, 29 | config: Arc>, 30 | doc_map: DashMap, 31 | ast_map: DashMap, 32 | workspace_map: DashMap, 33 | // publish those after saving 34 | on_save_or_open_errors: DashMap>, 35 | } 36 | 37 | #[tower_lsp::async_trait] 38 | impl tower_lsp::LanguageServer for Backend { 39 | async fn initialize( 40 | &self, 41 | params: InitializeParams, 42 | ) -> Result { 43 | if let Some(folders) = params.workspace_folders { 44 | folders.into_iter().for_each(|folder| { 45 | self.workspace_map.insert(folder.uri, folder.name); 46 | }); 47 | } 48 | 49 | Ok(InitializeResult { 50 | server_info: Some(ServerInfo { 51 | name: env!("CARGO_PKG_NAME").into(), 52 | version: Some(env!("CARGO_PKG_VERSION").into()), 53 | }), 54 | capabilities: ServerCapabilities { 55 | text_document_sync: Some(TextDocumentSyncCapability::Kind( 56 | TextDocumentSyncKind::INCREMENTAL, 57 | )), 58 | workspace: Some(WorkspaceServerCapabilities { 59 | workspace_folders: Some( 60 | WorkspaceFoldersServerCapabilities { 61 | supported: Some(true), 62 | change_notifications: None, 63 | }, 64 | ), 65 | file_operations: None, 66 | }), 67 | definition_provider: Some(OneOf::Left(true)), 68 | references_provider: Some(OneOf::Left(true)), 69 | rename_provider: Some(OneOf::Left(true)), 70 | hover_provider: Some(HoverProviderCapability::Simple(true)), 71 | completion_provider: Some(CompletionOptions { 72 | resolve_provider: Some(false), 73 | trigger_characters: Some(vec![".".into(), ":".into()]), 74 | ..Default::default() 75 | }), 76 | code_action_provider: Some( 77 | CodeActionProviderCapability::Simple(true), 78 | ), 79 | ..Default::default() 80 | }, 81 | }) 82 | } 83 | 84 | async fn goto_definition( 85 | &self, 86 | params: GotoDefinitionParams, 87 | ) -> Result> { 88 | let uri = params.text_document_position_params.text_document.uri; 89 | let position = params.text_document_position_params.position; 90 | let ast = self.ast_map.get(&uri).ok_or_else(Error::invalid_request)?; 91 | let doc = self.doc_map.get(&uri).ok_or_else(Error::invalid_request)?; 92 | let offset = position_to_byte_offset(&doc, position)?; 93 | 94 | match ast.definition(offset) { 95 | Some(fennel_parser::Definition::Symbol(symbol, _)) => { 96 | let range = lsp_range(&doc, symbol.token.range)?; 97 | match symbol.value.kind { 98 | models::ValueKind::Require(Some(file)) => { 99 | self.find_file(&uri, file).map_or_else( 100 | || { 101 | Ok(Some(GotoDefinitionResponse::Scalar( 102 | Location::new(uri.clone(), range), 103 | ))) 104 | }, 105 | |new_uri| { 106 | Ok(Some(GotoDefinitionResponse::Array(vec![ 107 | Location::new(uri.clone(), range), 108 | Location::new(new_uri, lsp_range_head()), 109 | ]))) 110 | }, 111 | ) 112 | } 113 | _ => Ok(Some(GotoDefinitionResponse::Scalar( 114 | Location::new(uri, range), 115 | ))), 116 | } 117 | } 118 | Some(fennel_parser::Definition::FileSymbol(path, symbol)) => { 119 | let range = lsp_range(&doc, symbol.token.range)?; 120 | let res = self.find_file(&uri, path).map(|uri| { 121 | GotoDefinitionResponse::Scalar(Location::new(uri, range)) 122 | }); 123 | Ok(res) 124 | } 125 | Some(fennel_parser::Definition::File(path)) => { 126 | let res = self.find_file(&uri, path).map(|uri| { 127 | GotoDefinitionResponse::Scalar(Location::new( 128 | uri, 129 | lsp_range_head(), 130 | )) 131 | }); 132 | Ok(res) 133 | } 134 | None => Ok(None), 135 | } 136 | } 137 | 138 | async fn references( 139 | &self, 140 | params: ReferenceParams, 141 | ) -> Result>> { 142 | let uri = params.text_document_position.text_document.uri; 143 | let position = params.text_document_position.position; 144 | let ast = self.ast_map.get(&uri).ok_or_else(Error::invalid_request)?; 145 | let doc = self.doc_map.get(&uri).ok_or_else(Error::invalid_request)?; 146 | let offset = position_to_byte_offset(&doc, position)?; 147 | 148 | let references = ast.reference(offset); 149 | if references.is_none() { 150 | return Ok(None); 151 | } 152 | let references = references.unwrap(); 153 | if references.is_empty() { 154 | return Err(Error::request_cancelled()); 155 | } 156 | let mut locations = Vec::with_capacity(references.len()); 157 | for reference in references { 158 | let range = lsp_range(&doc, reference)?; 159 | locations.push(Location::new(uri.clone(), range)); 160 | } 161 | Ok(Some(locations)) 162 | } 163 | 164 | async fn rename( 165 | &self, 166 | params: RenameParams, 167 | ) -> Result> { 168 | let uri = params.text_document_position.text_document.uri; 169 | let position = params.text_document_position.position; 170 | let ast = self.ast_map.get(&uri).ok_or_else(Error::invalid_request)?; 171 | let doc = self.doc_map.get(&uri).ok_or_else(Error::invalid_request)?; 172 | let offset = position_to_byte_offset(&doc, position)?; 173 | 174 | if !ast.validate_name(¶ms.new_name) { 175 | return Err(Error::invalid_params("Illegal identifier name")); 176 | } 177 | let ranges = ast.reference(offset).ok_or_else(|| { 178 | Error::invalid_params("No references found at position") 179 | })?; 180 | if ranges.is_empty() { 181 | return Ok(None); 182 | } 183 | 184 | let mut changes = Vec::with_capacity(ranges.len()); 185 | for range in ranges { 186 | let range = lsp_range(&doc, range)?; 187 | changes.push(TextEdit::new(range, params.new_name.clone())) 188 | } 189 | let mut map = HashMap::new(); 190 | map.insert(uri, changes); 191 | Ok(Some(WorkspaceEdit::new(map))) 192 | } 193 | 194 | async fn hover(&self, params: HoverParams) -> Result> { 195 | let uri = params.text_document_position_params.text_document.uri; 196 | let position = params.text_document_position_params.position; 197 | let ast = self.ast_map.get(&uri).ok_or_else(Error::invalid_request)?; 198 | let doc = self.doc_map.get(&uri).ok_or_else(Error::invalid_request)?; 199 | let offset = position_to_byte_offset(&doc, position)?; 200 | 201 | let symbol = match ast.definition(offset) { 202 | Some(fennel_parser::Definition::Symbol(symbol, _)) => symbol, 203 | _ => return Ok(None), 204 | }; 205 | let range = lsp_range(&doc, symbol.token.range)?; 206 | let text = symbol.token.text; 207 | let scope_kind = view::scope_kind(symbol.scope.kind); 208 | let value_kind = view::value_kind(&symbol.value.kind); 209 | 210 | let header_text = format!( 211 | "{} {}{}{}", 212 | scope_kind, 213 | text, 214 | if value_kind.is_empty() { 215 | "".to_owned() 216 | } else { 217 | " : ".to_owned() + value_kind 218 | }, 219 | if let Some(literal) = ast.literal_value(symbol.value) { 220 | let prefix = 221 | if literal.contains('\n') { " =\n" } else { " = " }; 222 | prefix.to_owned() + &literal 223 | } else { 224 | "".to_owned() 225 | }, 226 | ); 227 | let body_text = if symbol.scope.kind == models::ScopeKind::Func { 228 | ast.docstring(symbol.token.range) 229 | } else { 230 | None 231 | }; 232 | 233 | let header = MarkedString::LanguageString(LanguageString { 234 | language: "fennel".into(), 235 | value: header_text, 236 | }); 237 | let contents = if let Some(body_text) = body_text { 238 | HoverContents::Array(vec![ 239 | header, 240 | MarkedString::LanguageString(LanguageString { 241 | language: "markdown".into(), 242 | value: body_text, 243 | }), 244 | ]) 245 | } else { 246 | HoverContents::Scalar(header) 247 | }; 248 | Ok(Some(Hover { contents, range: Some(range) })) 249 | } 250 | 251 | async fn completion( 252 | &self, 253 | params: CompletionParams, 254 | ) -> Result> { 255 | let uri = params.text_document_position.text_document.uri; 256 | let position = params.text_document_position.position; 257 | let ast = self.ast_map.get(&uri).ok_or_else(Error::invalid_request)?; 258 | let doc = self.doc_map.get(&uri).ok_or_else(Error::invalid_request)?; 259 | let offset = position_to_byte_offset(&doc, position)?; 260 | 261 | let trigger = params.context.and_then(|ctx| ctx.trigger_character); 262 | let (symbols, globals) = ast.completion(offset, trigger); 263 | let symbols = symbols.map(|symbol| CompletionItem { 264 | label: symbol.token.text.clone(), 265 | insert_text: Some(symbol.token.text.clone()), 266 | kind: Some(view::completion_scope_kind(symbol.scope.kind)), 267 | detail: Some(symbol.token.text.clone()), 268 | ..Default::default() 269 | }); 270 | let globals = globals.into_iter().flat_map(|(kind, vec)| { 271 | vec.into_iter().map(move |word| CompletionItem { 272 | label: word.to_owned(), 273 | insert_text: Some(word.to_owned()), 274 | kind: Some(view::completion_value_kind(kind)), 275 | detail: Some(word.to_owned()), 276 | ..Default::default() 277 | }) 278 | }); 279 | let completions = symbols.chain(globals).collect(); 280 | Ok(Some(CompletionResponse::Array(completions))) 281 | } 282 | 283 | async fn code_action( 284 | &self, 285 | params: CodeActionParams, 286 | ) -> Result> { 287 | let uri = params.text_document.uri; 288 | let ast = self.ast_map.get(&uri).ok_or_else(Error::invalid_request)?; 289 | let doc = self.doc_map.get(&uri).ok_or_else(Error::invalid_request)?; 290 | let offset = position_to_byte_offset(&doc, params.range.start)?; 291 | 292 | let actions = ast.hint_action(offset); 293 | let res = actions.iter().filter_map(|(range, action)| { 294 | let range = lsp_range(&doc, *range).ok()?; 295 | let action = match action { 296 | fennel_parser::Action::ConvertToColonString(s) => { 297 | let mut map = HashMap::new(); 298 | map.insert(uri.clone(), vec![TextEdit::new( 299 | range, 300 | s.to_owned(), 301 | )]); 302 | CodeActionOrCommand::CodeAction(CodeAction { 303 | title: "Convert string to start with a colon" 304 | .to_string(), 305 | kind: Some(CodeActionKind::REFACTOR), 306 | edit: Some(WorkspaceEdit::new(map)), 307 | ..Default::default() 308 | }) 309 | } 310 | fennel_parser::Action::ConvertToQuoteString(s) => { 311 | let mut map = HashMap::new(); 312 | map.insert(uri.clone(), vec![TextEdit::new( 313 | range, 314 | s.to_owned(), 315 | )]); 316 | CodeActionOrCommand::CodeAction(CodeAction { 317 | title: "Convert string to double-quotes form" 318 | .to_string(), 319 | kind: Some(CodeActionKind::REFACTOR), 320 | edit: Some(WorkspaceEdit::new(map)), 321 | ..Default::default() 322 | }) 323 | } 324 | }; 325 | Some(action) 326 | }); 327 | Ok(Some(res.collect())) 328 | } 329 | 330 | async fn initialized(&self, _: InitializedParams) { 331 | self.client.log_message(MessageType::INFO, "initialized!").await; 332 | } 333 | 334 | async fn shutdown(&self) -> Result<()> { 335 | Ok(()) 336 | } 337 | 338 | async fn did_open(&self, params: DidOpenTextDocumentParams) { 339 | self.client.log_message(MessageType::INFO, "file opened!").await; 340 | let uri = params.text_document.uri; 341 | let text = params.text_document.text; 342 | let version = params.text_document.version; 343 | 344 | let doc = ropey::Rope::from_str(&text); 345 | self.doc_map.insert(uri.clone(), doc.clone()); 346 | 347 | let mut globals = HashSet::new(); 348 | for global in &self.config.read().unwrap().fennel.diagnostics.globals { 349 | globals.insert(global.clone()); 350 | } 351 | 352 | let ast = fennel_parser::parse(text.chars(), globals); 353 | self.publish_diagnostics(&doc, uri.clone(), &ast, Some(version), true) 354 | .await; 355 | 356 | self.on_save_or_open_errors 357 | .insert(uri.clone(), ast.on_save_errors().cloned().collect()); 358 | 359 | self.ast_map.insert(uri, ast); 360 | } 361 | 362 | async fn did_change(&self, params: DidChangeTextDocumentParams) { 363 | let uri = params.text_document.uri; 364 | let version = params.text_document.version; 365 | let mut doc = if let Some(doc) = self.doc_map.get_mut(&uri) { 366 | doc 367 | } else { 368 | return; 369 | }; 370 | 371 | params.content_changes.iter().for_each(|change| { 372 | if let Some(lsp_range) = change.range { 373 | let range = rope_range(&doc, lsp_range).unwrap(); 374 | doc.remove(range.clone()); 375 | if !change.text.is_empty() { 376 | doc.insert(range.start, &change.text); 377 | } 378 | } else { 379 | *doc = Rope::from_str(&change.text); 380 | } 381 | }); 382 | 383 | let mut globals = HashSet::new(); 384 | for global in &self.config.read().unwrap().fennel.diagnostics.globals { 385 | globals.insert(global.clone()); 386 | } 387 | 388 | let ast = fennel_parser::parse(doc.chars(), globals); 389 | self.publish_diagnostics( 390 | &doc, 391 | uri.clone(), 392 | &ast, 393 | Some(version), 394 | false, 395 | ) 396 | .await; 397 | 398 | self.ast_map.insert(uri, ast); 399 | } 400 | 401 | async fn did_save(&self, params: DidSaveTextDocumentParams) { 402 | let uri = params.text_document.uri; 403 | let ast = self.ast_map.get(&uri).ok_or_else(Error::invalid_request); 404 | if ast.is_err() { 405 | return; 406 | } 407 | let doc = self.doc_map.get(&uri).ok_or_else(Error::invalid_request); 408 | if doc.is_err() { 409 | return; 410 | } 411 | self.publish_diagnostics( 412 | &doc.unwrap(), 413 | uri, 414 | &ast.unwrap(), 415 | None, 416 | true, 417 | ) 418 | .await; 419 | } 420 | 421 | async fn did_close(&self, params: DidCloseTextDocumentParams) { 422 | let uri = params.text_document.uri; 423 | self.free_doc(&uri); 424 | } 425 | 426 | async fn did_change_workspace_folders( 427 | &self, 428 | params: DidChangeWorkspaceFoldersParams, 429 | ) { 430 | params.event.added.iter().for_each(|r| { 431 | self.workspace_map.insert(r.uri.clone(), r.name.clone()); 432 | }); 433 | params.event.removed.iter().for_each(|r| { 434 | self.workspace_map.remove(&r.uri); 435 | }); 436 | } 437 | 438 | async fn did_change_configuration( 439 | &self, 440 | params: DidChangeConfigurationParams, 441 | ) { 442 | match ::deserialize( 443 | params.settings, 444 | ) { 445 | Ok(config) => { 446 | *self.config.write().unwrap() = config.clone(); 447 | for mut r in self.ast_map.iter_mut() { 448 | let ast = r.value_mut(); 449 | ast.update_globals( 450 | config.fennel.diagnostics.globals.clone(), 451 | ); 452 | let uri = r.key(); 453 | let doc = self.doc_map.get(uri).unwrap(); 454 | self.publish_diagnostics( 455 | &doc, 456 | uri.clone(), 457 | r.value(), 458 | None, 459 | false, 460 | ) 461 | .await; 462 | } 463 | } 464 | Err(e) => { 465 | self.client 466 | .log_message( 467 | MessageType::ERROR, 468 | format!("Invalid config: {}", e), 469 | ) 470 | .await; 471 | } 472 | } 473 | } 474 | } 475 | 476 | impl Backend { 477 | async fn publish_diagnostics( 478 | &self, 479 | doc: &Rope, 480 | uri: Url, 481 | ast: &Ast, 482 | version: Option, 483 | on_save_or_open: bool, 484 | ) { 485 | if on_save_or_open { 486 | self.on_save_or_open_errors 487 | .insert(uri.clone(), ast.on_save_errors().cloned().collect()); 488 | } else if let Some(mut errs) = 489 | self.on_save_or_open_errors.get_mut(&uri) 490 | { 491 | let new_errors: Vec<&fennel_parser::Error> = 492 | ast.on_save_errors().collect(); 493 | errs.retain(|e| new_errors.contains(&e)) 494 | }; 495 | 496 | let errors: Vec = if let Some(on_save_errors) = 497 | self.on_save_or_open_errors.get(&uri) 498 | { 499 | ast.errors().chain(on_save_errors.iter()).cloned().collect() 500 | } else { 501 | ast.errors().cloned().collect() 502 | }; 503 | 504 | let diagnostics = errors.into_iter().flat_map(|error| { 505 | lsp_range(doc, error.range).map(|range| { 506 | let (message, severity) = view::error(error.kind); 507 | Diagnostic::new( 508 | range, 509 | Some(severity), 510 | None, 511 | Some("Fennel Diagnostics".into()), 512 | message, 513 | None, 514 | None, 515 | ) 516 | }) 517 | }); 518 | self.client 519 | .publish_diagnostics(uri, diagnostics.collect(), version) 520 | .await; 521 | } 522 | 523 | fn free_doc(&self, uri: &Url) { 524 | self.doc_map.remove(uri); 525 | self.ast_map.remove(uri); 526 | self.on_save_or_open_errors.remove(uri); 527 | } 528 | 529 | fn find_file(&self, rel: &Url, path: PathBuf) -> Option { 530 | path.to_str()?; 531 | 532 | let check_exist = |rel: &Url, ext: &str, init: bool| -> Option { 533 | let path = if init { path.join("init") } else { path.clone() }; 534 | if let Ok(url) = 535 | rel.join(path.with_extension(ext).to_str().unwrap()) 536 | { 537 | if std::fs::metadata(url.path()) 538 | .map(|m| m.is_file()) 539 | .unwrap_or(false) 540 | { 541 | return Some(url); 542 | } 543 | } 544 | None 545 | }; 546 | 547 | let library = &self.config.read().unwrap().fennel.workspace.library; 548 | let library_file = library.iter().find_map(|uri| { 549 | let uri_fnl = uri.0.join("fnl/").unwrap(); 550 | let uri_lua = uri.0.join("lua/").unwrap(); 551 | check_exist(&uri_lua, "lua", false) 552 | .or_else(|| check_exist(&uri_lua, "lua", true)) 553 | .or_else(|| check_exist(&uri_fnl, "fnl", false)) 554 | .or_else(|| check_exist(&uri_fnl, "fnl", true)) 555 | }); 556 | 557 | let workspace_file = self.workspace_map.iter().find_map(|ref r| { 558 | let mut uri = r.key().clone(); 559 | uri.path_segments_mut().ok()?.push(""); 560 | if !rel.path().starts_with(uri.path()) { 561 | return None; 562 | }; 563 | 564 | let uri_fnl = uri.join("fnl/").unwrap(); 565 | check_exist(&uri_fnl, "fnl", false) 566 | .or_else(|| check_exist(&uri_fnl, "fnl", true)) 567 | }); 568 | 569 | workspace_file.or(library_file).or_else(|| { 570 | check_exist(rel, "lua", false) 571 | .or_else(|| check_exist(rel, "lua", true)) 572 | .or_else(|| check_exist(rel, "so", false)) 573 | .or_else(|| check_exist(rel, "fnl", false)) 574 | .or_else(|| check_exist(rel, "fnl", true)) 575 | }) 576 | } 577 | } 578 | 579 | fn main() { 580 | let cli = cli::Cli::parse(); 581 | let (read, write) = match &cli.cmd { 582 | Some(cli::Command::Lsp { cmd: cli::LspCommand::Stdio }) | None => { 583 | stdio() 584 | } 585 | Some(cli::Command::Lsp { cmd: cli::LspCommand::Tcp { address } }) => { 586 | // Only one connection accepted 587 | tokio::runtime::Builder::new_current_thread() 588 | .enable_all() 589 | .build() 590 | .unwrap() 591 | .block_on(tcp_listen(address)) 592 | } 593 | }; 594 | 595 | let (service, socket) = tower_lsp::LspService::build(|client| Backend { 596 | client, 597 | doc_map: DashMap::new(), 598 | ast_map: DashMap::new(), 599 | workspace_map: DashMap::new(), 600 | on_save_or_open_errors: DashMap::new(), 601 | config: Arc::new(RwLock::new(config::Configuration::default())), 602 | }) 603 | .finish(); 604 | tokio::runtime::Builder::new_multi_thread() 605 | .enable_all() 606 | .build() 607 | .unwrap() 608 | .block_on(async { 609 | tower_lsp::Server::new(read, write, socket).serve(service).await; 610 | }) 611 | } 612 | 613 | fn stdio() -> (Box, Box) { 614 | let (read, write) = (tokio::io::stdin(), tokio::io::stdout()); 615 | (Box::new(read), Box::new(write)) 616 | } 617 | 618 | async fn tcp_listen( 619 | address: &str, 620 | ) -> (Box, Box) { 621 | let listener = TcpListener::bind(address).await.unwrap(); 622 | let (stream, _) = listener.accept().await.unwrap(); 623 | let (read, write) = tokio::io::split(stream); 624 | (Box::new(read), Box::new(write)) 625 | } 626 | -------------------------------------------------------------------------------- /crates/fennel-language-server/src/view.rs: -------------------------------------------------------------------------------- 1 | use fennel_parser::{models, ErrorKind}; 2 | use tower_lsp::lsp_types::{CompletionItemKind, DiagnosticSeverity}; 3 | 4 | pub(crate) fn scope_kind(kind: models::ScopeKind) -> &'static str { 5 | match kind { 6 | models::ScopeKind::Param => "[function parameter]", 7 | models::ScopeKind::MacroParam => "[macro parameter]", 8 | models::ScopeKind::Global => "global", 9 | models::ScopeKind::Let => "let", 10 | models::ScopeKind::WithOpen => "with-open", 11 | models::ScopeKind::Local => "local", 12 | models::ScopeKind::Macro => "macro", 13 | models::ScopeKind::Func => "function", 14 | models::ScopeKind::Lambda => "lambda", 15 | models::ScopeKind::Var => "var", 16 | models::ScopeKind::Match => "", 17 | models::ScopeKind::MatchTry => "", 18 | models::ScopeKind::Catch => "", 19 | models::ScopeKind::IterValue => "", 20 | models::ScopeKind::AccuValue => "", 21 | } 22 | } 23 | 24 | pub(crate) fn value_kind(kind: &models::ValueKind) -> &'static str { 25 | match kind { 26 | models::ValueKind::Nil => "nil", 27 | models::ValueKind::Number => "number", 28 | models::ValueKind::Module => "module", 29 | models::ValueKind::String => "string", 30 | models::ValueKind::Bool => "bool", 31 | models::ValueKind::SeqTable => "[...]", 32 | models::ValueKind::KvTable => "{...}", 33 | models::ValueKind::FileHandle => "", 34 | models::ValueKind::Func 35 | | models::ValueKind::Macro 36 | | models::ValueKind::Param 37 | | models::ValueKind::MacroParam 38 | | models::ValueKind::Match 39 | | models::ValueKind::Require(_) 40 | | models::ValueKind::Unknown => "", 41 | models::ValueKind::Symbol => "unknown", 42 | } 43 | } 44 | 45 | pub(crate) fn completion_scope_kind( 46 | kind: models::ScopeKind, 47 | ) -> CompletionItemKind { 48 | match kind { 49 | models::ScopeKind::Local 50 | | models::ScopeKind::Let 51 | | models::ScopeKind::IterValue 52 | | models::ScopeKind::AccuValue 53 | | models::ScopeKind::Param 54 | | models::ScopeKind::Global 55 | | models::ScopeKind::WithOpen 56 | | models::ScopeKind::Macro 57 | | models::ScopeKind::MacroParam 58 | | models::ScopeKind::Match 59 | | models::ScopeKind::MatchTry 60 | | models::ScopeKind::Catch 61 | | models::ScopeKind::Var => CompletionItemKind::VARIABLE, 62 | models::ScopeKind::Func | models::ScopeKind::Lambda => { 63 | CompletionItemKind::FUNCTION 64 | } 65 | } 66 | } 67 | 68 | pub(crate) fn completion_value_kind( 69 | kind: models::CompletionKind, 70 | ) -> CompletionItemKind { 71 | match kind { 72 | models::CompletionKind::Func => CompletionItemKind::FUNCTION, 73 | models::CompletionKind::Module => CompletionItemKind::MODULE, 74 | models::CompletionKind::Field => CompletionItemKind::FIELD, 75 | models::CompletionKind::Keyword => CompletionItemKind::KEYWORD, 76 | models::CompletionKind::Operator => CompletionItemKind::OPERATOR, 77 | models::CompletionKind::Var => CompletionItemKind::VARIABLE, 78 | } 79 | } 80 | 81 | pub(crate) fn error(e: ErrorKind) -> (String, DiagnosticSeverity) { 82 | match e { 83 | ErrorKind::Unterminated(kind) => { 84 | (format!("Incomplete {}", kind), DiagnosticSeverity::ERROR) 85 | } 86 | ErrorKind::Unexpected(kind) => ( 87 | format!("Unexpected {} in input", kind), 88 | DiagnosticSeverity::ERROR, 89 | ), 90 | ErrorKind::UnexpectedEof => ( 91 | "Unexpected end of file. Expected closing delimiter".into(), 92 | DiagnosticSeverity::ERROR, 93 | ), 94 | ErrorKind::EmptyList => { 95 | ("Found empty list".into(), DiagnosticSeverity::ERROR) 96 | } 97 | ErrorKind::DirectCall(kind) => ( 98 | format!("Cannot directly call value {}", kind), 99 | DiagnosticSeverity::ERROR, 100 | ), 101 | ErrorKind::LiteralCall(kind) => ( 102 | format!("Cannot call literal value {}", kind), 103 | DiagnosticSeverity::ERROR, 104 | ), 105 | ErrorKind::GlobalConflict => { 106 | ("Global conflicts with local".into(), DiagnosticSeverity::ERROR) 107 | } 108 | ErrorKind::Dismatched => { 109 | ("Closing delimiter is missing".into(), DiagnosticSeverity::ERROR) 110 | } 111 | ErrorKind::Undefined => { 112 | ("Undefined identifier".into(), DiagnosticSeverity::ERROR) 113 | } 114 | ErrorKind::Unused => { 115 | ("Unused identifier".into(), DiagnosticSeverity::HINT) 116 | } 117 | ErrorKind::MissingWhitespace => ( 118 | "Expected whitespace before opening delimiter".into(), 119 | DiagnosticSeverity::ERROR, 120 | ), 121 | ErrorKind::MacroWhitespace => { 122 | ("Invalid macro".into(), DiagnosticSeverity::ERROR) 123 | } 124 | 125 | ErrorKind::InvalidSymbol => { 126 | ("Invalid symbol".into(), DiagnosticSeverity::ERROR) 127 | } 128 | 129 | ErrorKind::MethodNotAllowed => { 130 | ("Unexpected symbol method".into(), DiagnosticSeverity::ERROR) 131 | } 132 | 133 | ErrorKind::FieldAndMethodNotAllowed => { 134 | ("Unexpected multi symbol".into(), DiagnosticSeverity::ERROR) 135 | } 136 | ErrorKind::MultiVarargs => ( 137 | "Multiple varargs are not allowed".into(), 138 | DiagnosticSeverity::WARNING, 139 | ), 140 | ErrorKind::UnexpectedVarargs => ( 141 | "Varargs not found in parameter table".into(), 142 | DiagnosticSeverity::ERROR, 143 | ), 144 | ErrorKind::MultiCatch => ( 145 | "Only one catch clause permitted".into(), 146 | DiagnosticSeverity::ERROR, 147 | ), 148 | ErrorKind::CatchNotLast => ( 149 | "Catch clause must be at the end".into(), 150 | DiagnosticSeverity::ERROR, 151 | ), 152 | ErrorKind::Deprecated(version, recommendation) => ( 153 | format!( 154 | "Deprecated in version `{}`. Use `{}` instead", 155 | version, recommendation 156 | ), 157 | DiagnosticSeverity::HINT, 158 | ), 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /crates/fennel-parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fennel-parser" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | once_cell = "1.17.0" 8 | regex = { version = "1.7.1", default-features = false, features = ["std"] } 9 | rowan = "0.15.10" 10 | -------------------------------------------------------------------------------- /crates/fennel-parser/examples/parse_file.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | fn main() { 4 | let file = std::env::args().nth(1).unwrap(); 5 | let contents = std::fs::read_to_string(file).unwrap(); 6 | let ast = fennel_parser::parse(contents.chars(), HashSet::new()); 7 | ast.errors().for_each(|e| eprintln!("diagnostic: {:#?}", e)) 8 | } 9 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/ast/bind.rs: -------------------------------------------------------------------------------- 1 | use rowan::{ast::AstNode, TextRange}; 2 | 3 | use crate::{ 4 | ast::{eval, func::FuncAst, macros::ast_assoc, models, nodes::*}, 5 | SyntaxKind, SyntaxNode, 6 | }; 7 | 8 | #[rustfmt::skip] 9 | ast_assoc!( 10 | BindingListAst, [ 11 | // Bindings live in the local scope. 12 | Let, WithOpen, Each, For, Fcollect, Icollect, Collect, Accumulate, 13 | // Parameters live in the local scope. 14 | // Function name is extended to the outer scope. 15 | FuncAst, Macros, Macro, 16 | // Bindings are extended to the outer scope. 17 | Var, Local, Global, ImportMacros, 18 | ] 19 | ); 20 | 21 | pub(crate) trait Binding: 22 | rowan::ast::AstNode 23 | { 24 | fn bindings(&self) -> Option>; 25 | 26 | fn target_node(&self, target: SyntaxKind) -> Option { 27 | self.syntax().children().find(|n| n.kind() == target) 28 | } 29 | 30 | fn target_nodes( 31 | &self, 32 | target: SyntaxKind, 33 | ) -> Box> { 34 | Box::new(self.syntax().children().filter(move |n| n.kind() == target)) 35 | } 36 | 37 | fn target_child_node( 38 | &self, 39 | parent: SyntaxKind, 40 | target: SyntaxKind, 41 | ) -> Option { 42 | self.syntax() 43 | .children() 44 | .find(|n| n.kind() == parent)? 45 | .children() 46 | .find(|n| n.kind() == target) 47 | } 48 | 49 | fn target_child_nodes( 50 | &self, 51 | parent: SyntaxKind, 52 | target: SyntaxKind, 53 | ) -> Option> { 54 | let nodes = self 55 | .syntax() 56 | .children() 57 | .find(|n| n.kind() == parent)? 58 | .children() 59 | .filter(|n| n.kind() == target) 60 | .collect(); 61 | Some(nodes) 62 | } 63 | 64 | fn _bindings( 65 | &self, 66 | node: SyntaxNode, 67 | scope_kind: models::ScopeKind, 68 | scope_extend: ScopeExtend, 69 | // override value eval 70 | override_value_kind: Option, 71 | // fallback if destructuring occurs 72 | // TOOD: support destructuring eval 73 | fallback_value_kind: models::ValueKind, 74 | ) -> Option> { 75 | let scope_range = scope_extend.range(&node); 76 | 77 | // Guess first child is a symbol 78 | if let Some(symbol) = Symbol::cast(node.first_token()?.parent()?) { 79 | let (id, ..) = symbol.id()?; 80 | // NOTE: should not pretend symbol pairs with value 81 | let value_node = node.last_child(); 82 | let value = if let Some(override_value_kind) = override_value_kind 83 | { 84 | models::Value { 85 | kind: override_value_kind, 86 | range: value_node.map(|n| n.text_range()), 87 | } 88 | } else { 89 | let value_node = value_node?; 90 | let range = value_node.text_range(); 91 | models::Value { 92 | kind: eval::EvalAst::cast(value_node)?.eval_kind(), 93 | range: Some(range), 94 | } 95 | }; 96 | return Some(vec![models::LSymbol { 97 | token: id, 98 | scope: models::Scope { kind: scope_kind, range: scope_range }, 99 | value, 100 | }]); 101 | } 102 | 103 | // else do destructuring 104 | // TODO: support destructuring eval 105 | let value_kind = fallback_value_kind; 106 | 107 | let tokens = node 108 | .first_child()? 109 | .descendants() 110 | .filter_map(Symbol::cast) 111 | .filter_map(|n| n.id()) 112 | .map(|(id, ..)| id); 113 | 114 | let symbols = tokens 115 | .map(|t| models::LSymbol { 116 | token: t, 117 | scope: models::Scope { kind: scope_kind, range: scope_range }, 118 | value: models::Value { kind: value_kind.clone(), range: None }, 119 | }) 120 | .collect(); 121 | Some(symbols) 122 | } 123 | } 124 | 125 | impl Binding for Var { 126 | fn bindings(&self) -> Option> { 127 | self._bindings( 128 | self.target_node(SyntaxKind::N_ASSIGN_PAIR)?, 129 | models::ScopeKind::Var, 130 | ScopeExtend::Current, 131 | // TODO: support set 132 | Some(models::ValueKind::Unknown), 133 | models::ValueKind::Unknown, 134 | ) 135 | } 136 | } 137 | 138 | impl Binding for Local { 139 | fn bindings(&self) -> Option> { 140 | self._bindings( 141 | self.target_node(SyntaxKind::N_ASSIGN_PAIR)?, 142 | models::ScopeKind::Local, 143 | ScopeExtend::Current, 144 | None, 145 | models::ValueKind::Unknown, 146 | ) 147 | } 148 | } 149 | 150 | impl Binding for Global { 151 | fn bindings(&self) -> Option> { 152 | self._bindings( 153 | self.target_node(SyntaxKind::N_ASSIGN_PAIR)?, 154 | models::ScopeKind::Global, 155 | ScopeExtend::Outer, 156 | None, 157 | models::ValueKind::Unknown, 158 | ) 159 | } 160 | } 161 | 162 | impl Binding for Let { 163 | fn bindings(&self) -> Option> { 164 | let nodes = self.target_child_nodes( 165 | SyntaxKind::N_ASSIGN_TABLE, 166 | SyntaxKind::N_ASSIGN_PAIR, 167 | )?; 168 | let symbols = nodes 169 | .into_iter() 170 | .filter_map(|n| { 171 | self._bindings( 172 | n, 173 | models::ScopeKind::Let, 174 | ScopeExtend::Current, 175 | None, 176 | models::ValueKind::Unknown, 177 | ) 178 | }) 179 | .flatten() 180 | .collect(); 181 | Some(symbols) 182 | } 183 | } 184 | 185 | impl Binding for ImportMacros { 186 | fn bindings(&self) -> Option> { 187 | let nodes = self.target_nodes(SyntaxKind::N_IMPORT_MACROS_PAIR); 188 | let symbols = nodes 189 | .filter_map(|n| { 190 | self._bindings( 191 | n, 192 | models::ScopeKind::Macro, 193 | ScopeExtend::Outer, 194 | // TODO: more clear type 195 | Some(models::ValueKind::Module), 196 | models::ValueKind::Macro, 197 | ) 198 | }) 199 | .flatten() 200 | .collect(); 201 | Some(symbols) 202 | } 203 | } 204 | 205 | impl Binding for WithOpen { 206 | fn bindings(&self) -> Option> { 207 | let nodes = self.target_child_nodes( 208 | SyntaxKind::N_NV_PAIR_TABLE, 209 | SyntaxKind::N_NV_PAIR, 210 | )?; 211 | let symbols = nodes 212 | .into_iter() 213 | .filter_map(|n| { 214 | self._bindings( 215 | n, 216 | models::ScopeKind::WithOpen, 217 | ScopeExtend::Current, 218 | Some(models::ValueKind::FileHandle), 219 | models::ValueKind::Unknown, 220 | ) 221 | }) 222 | .flatten() 223 | .collect(); 224 | Some(symbols) 225 | } 226 | } 227 | 228 | impl Binding for Each { 229 | fn bindings(&self) -> Option> { 230 | let nodes = self.target_child_nodes( 231 | SyntaxKind::N_EACH_TABLE, 232 | SyntaxKind::N_ITERATION, 233 | )?; 234 | let symbols = nodes 235 | .into_iter() 236 | .filter_map(|n| { 237 | self._bindings( 238 | n, 239 | models::ScopeKind::IterValue, 240 | ScopeExtend::Current, 241 | None, 242 | models::ValueKind::Unknown, 243 | ) 244 | }) 245 | .flatten() 246 | .collect(); 247 | Some(symbols) 248 | } 249 | } 250 | 251 | impl Binding for For { 252 | fn bindings(&self) -> Option> { 253 | self._bindings( 254 | self.target_child_node( 255 | SyntaxKind::N_FOR_TABLE, 256 | SyntaxKind::N_ITERATION_VALUE, 257 | )?, 258 | models::ScopeKind::IterValue, 259 | ScopeExtend::Current, 260 | Some(models::ValueKind::Number), 261 | models::ValueKind::Unknown, 262 | ) 263 | } 264 | } 265 | 266 | impl Binding for Icollect { 267 | fn bindings(&self) -> Option> { 268 | let nodes = self.target_child_nodes( 269 | SyntaxKind::N_ICOLLECT_TABLE, 270 | SyntaxKind::N_ITERATION, 271 | )?; 272 | let symbols = nodes 273 | .into_iter() 274 | .filter_map(|n| { 275 | self._bindings( 276 | n, 277 | models::ScopeKind::IterValue, 278 | ScopeExtend::Current, 279 | None, 280 | models::ValueKind::Unknown, 281 | ) 282 | }) 283 | .flatten() 284 | .collect(); 285 | Some(symbols) 286 | } 287 | } 288 | 289 | impl Binding for Fcollect { 290 | fn bindings(&self) -> Option> { 291 | self._bindings( 292 | self.target_child_node( 293 | SyntaxKind::N_FCOLLECT_TABLE, 294 | SyntaxKind::N_ITERATION_VALUE, 295 | )?, 296 | models::ScopeKind::IterValue, 297 | ScopeExtend::Current, 298 | Some(models::ValueKind::Number), 299 | models::ValueKind::Unknown, 300 | ) 301 | } 302 | } 303 | 304 | impl Binding for Collect { 305 | fn bindings(&self) -> Option> { 306 | let nodes = self.target_child_nodes( 307 | SyntaxKind::N_COLLECT_TABLE, 308 | SyntaxKind::N_ITERATION, 309 | )?; 310 | let symbols = nodes 311 | .into_iter() 312 | .filter_map(|n| { 313 | self._bindings( 314 | n, 315 | models::ScopeKind::IterValue, 316 | ScopeExtend::Current, 317 | None, 318 | models::ValueKind::Unknown, 319 | ) 320 | }) 321 | .flatten() 322 | .collect(); 323 | Some(symbols) 324 | } 325 | } 326 | 327 | impl Binding for Accumulate { 328 | fn bindings(&self) -> Option> { 329 | let mut symbols = self._bindings( 330 | self.target_child_node( 331 | SyntaxKind::N_ACCUMULATE_TABLE, 332 | SyntaxKind::N_ACCUMULATOR, 333 | )?, 334 | models::ScopeKind::AccuValue, 335 | ScopeExtend::Current, 336 | Some(models::ValueKind::Unknown), 337 | models::ValueKind::Unknown, 338 | )?; 339 | 340 | let iter_nodes = self.target_child_nodes( 341 | SyntaxKind::N_ACCUMULATE_TABLE, 342 | SyntaxKind::N_ITERATION, 343 | )?; 344 | let iters = iter_nodes.into_iter().filter_map(|n| { 345 | self._bindings( 346 | n, 347 | models::ScopeKind::IterValue, 348 | ScopeExtend::Current, 349 | None, 350 | models::ValueKind::Unknown, 351 | ) 352 | }); 353 | 354 | symbols.extend(iters.flatten()); 355 | Some(symbols) 356 | } 357 | } 358 | 359 | impl Binding for FuncAst { 360 | fn bindings(&self) -> Option> { 361 | let name = self.name().and_then(|(name_node, is_pure)| { 362 | if !is_pure { 363 | return None; 364 | } 365 | Some(models::LSymbol { 366 | token: models::Token { 367 | text: name_node.first_token().unwrap().to_string(), 368 | range: name_node.text_range(), 369 | }, 370 | scope: models::Scope { 371 | kind: match self { 372 | Self::Func(_) => models::ScopeKind::Func, 373 | Self::Lambda(_) => models::ScopeKind::Lambda, 374 | }, 375 | range: ScopeExtend::Outer.range(&name_node), 376 | }, 377 | value: models::Value { 378 | kind: models::ValueKind::Func, 379 | range: Some(self.syntax().text_range()), 380 | }, 381 | }) 382 | }); 383 | 384 | let param_nodes = self 385 | .target_child_nodes( 386 | SyntaxKind::N_PARAM_TABLE, 387 | SyntaxKind::N_ASSIGN_PATTERN, 388 | ) 389 | .unwrap_or_default(); 390 | let params = param_nodes 391 | .into_iter() 392 | .filter_map(|n| { 393 | self._bindings( 394 | n, 395 | models::ScopeKind::Param, 396 | ScopeExtend::Current, 397 | Some(models::ValueKind::Param), 398 | models::ValueKind::Param, 399 | ) 400 | }) 401 | .flatten(); 402 | 403 | let varargs = self.varargs().map(|n| models::LSymbol { 404 | token: models::Token { 405 | text: "...".to_owned(), 406 | range: n.text_range(), 407 | }, 408 | scope: models::Scope { 409 | kind: models::ScopeKind::Param, 410 | range: self.syntax().text_range(), 411 | }, 412 | value: models::Value { 413 | kind: models::ValueKind::Param, 414 | range: None, 415 | }, 416 | }); 417 | 418 | let mut symbols: Vec = params.collect(); 419 | if let Some(s) = name { 420 | symbols.push(s) 421 | } 422 | if let Some(s) = varargs { 423 | symbols.push(s) 424 | } 425 | Some(symbols) 426 | } 427 | } 428 | 429 | impl Binding for Macro { 430 | fn bindings(&self) -> Option> { 431 | let name_node = self 432 | .syntax() 433 | .children() 434 | .find(|n| n.kind() == SyntaxKind::N_MACRO_NAME); 435 | 436 | let name = name_node.map(|n| models::LSymbol { 437 | token: models::Token { 438 | text: n.text().to_string(), 439 | range: n.text_range(), 440 | }, 441 | scope: models::Scope { 442 | kind: models::ScopeKind::Macro, 443 | range: ScopeExtend::Outer.range(self.syntax()), 444 | }, 445 | value: models::Value { 446 | kind: models::ValueKind::Macro, 447 | range: None, 448 | }, 449 | }); 450 | 451 | let param_nodes = self 452 | .target_child_nodes( 453 | SyntaxKind::N_PARAM_TABLE, 454 | SyntaxKind::N_ASSIGN_PATTERN, 455 | ) 456 | .unwrap_or_default(); 457 | let params = param_nodes 458 | .into_iter() 459 | .filter_map(|n| { 460 | self._bindings( 461 | n, 462 | models::ScopeKind::MacroParam, 463 | ScopeExtend::Current, 464 | Some(models::ValueKind::MacroParam), 465 | models::ValueKind::MacroParam, 466 | ) 467 | }) 468 | .flatten(); 469 | 470 | let mut symbols: Vec = params.collect(); 471 | if let Some(name) = name { 472 | symbols.push(name) 473 | } 474 | Some(symbols) 475 | } 476 | } 477 | 478 | impl Binding for Macros { 479 | fn bindings(&self) -> Option> { 480 | let table = self.syntax().first_child().and_then(KvTable::cast)?; 481 | 482 | let symbols = table 483 | .iter() 484 | .filter_map(|(k, v)| { 485 | let key = k?; 486 | let text = key.cast_string()?.0; 487 | Some(models::LSymbol { 488 | token: models::Token { 489 | text, 490 | range: key.syntax().text_range(), 491 | }, 492 | scope: models::Scope { 493 | kind: models::ScopeKind::Macro, 494 | range: ScopeExtend::Outer.range(self.syntax()), 495 | }, 496 | value: models::Value { 497 | kind: models::ValueKind::Macro, 498 | range: v.map(|v| v.syntax().text_range()), 499 | }, 500 | }) 501 | }) 502 | .collect(); 503 | 504 | Some(symbols) 505 | } 506 | } 507 | 508 | impl Binding for BindingListAst { 509 | fn bindings(&self) -> Option> { 510 | match self { 511 | Self::FuncAst(n) => n.bindings(), 512 | Self::Let(n) => n.bindings(), 513 | Self::Var(n) => n.bindings(), 514 | Self::Local(n) => n.bindings(), 515 | Self::Global(n) => n.bindings(), 516 | Self::ImportMacros(n) => n.bindings(), 517 | Self::WithOpen(n) => n.bindings(), 518 | Self::Each(n) => n.bindings(), 519 | Self::For(n) => n.bindings(), 520 | Self::Icollect(n) => n.bindings(), 521 | Self::Fcollect(n) => n.bindings(), 522 | Self::Collect(n) => n.bindings(), 523 | Self::Accumulate(n) => n.bindings(), 524 | Self::Macro(n) => n.bindings(), 525 | Self::Macros(n) => n.bindings(), 526 | } 527 | } 528 | } 529 | 530 | ast_assoc!(MatchAst, [Match, Catch, MatchTry]); 531 | 532 | impl Match { 533 | fn l_or_r_symbols( 534 | &self, 535 | l_symbols: &mut models::LSymbols, 536 | ) -> Vec { 537 | let mut r_symbols = vec![]; 538 | for (p, ns) in self 539 | .syntax() 540 | .children() 541 | .filter(|n| n.kind() == SyntaxKind::N_MATCH_CLAUSE) 542 | .filter_map(|n| { 543 | let c = n.first_child(); // N_MATCH_PATTERN_TOP 544 | c.map(|c| (n, c)) 545 | }) 546 | .map(|(p, n)| (p, n.descendants())) 547 | .map(|(p, ns)| (p, ns.filter(|n| n.kind() != SyntaxKind::N_COND))) 548 | .map(|(p, ns)| (p, ns.filter_map(LeftOrRightSymbol::cast))) 549 | { 550 | for n in ns { 551 | let node = n.syntax(); 552 | let token = Symbol::cast( 553 | node.first_token().unwrap().parent().unwrap(), 554 | ) 555 | .and_then(|n| n.id()); 556 | if token.is_none() { 557 | continue; 558 | } 559 | let token = token.unwrap().0; 560 | if l_symbols.nearest(&token.clone()).is_none() { 561 | let scope_range = ScopeExtend::This(p.clone()).range(node); 562 | l_symbols.0.insert( 563 | token.range.start().into(), 564 | models::LSymbol { 565 | token, 566 | scope: models::Scope { 567 | kind: models::ScopeKind::Match, 568 | range: scope_range, 569 | }, 570 | value: models::Value { 571 | kind: models::ValueKind::Match, 572 | range: None, 573 | }, 574 | }, 575 | ); 576 | } else { 577 | r_symbols.push(models::RSymbol { 578 | special: models::SpecialKind::Normal, 579 | token, 580 | }); 581 | } 582 | } 583 | } 584 | r_symbols 585 | } 586 | } 587 | 588 | impl Catch { 589 | fn l_or_r_symbols( 590 | &self, 591 | l_symbols: &mut models::LSymbols, 592 | ) -> Vec { 593 | let mut r_symbols = vec![]; 594 | for (p, ns) in self 595 | .syntax() 596 | .children() 597 | .filter(|n| n.kind() == SyntaxKind::N_MATCH_CLAUSE) 598 | .filter_map(|n| { 599 | let c = n.first_child(); // N_MATCH_PATTERN_TOP 600 | c.map(|c| (n, c)) 601 | }) 602 | .map(|(p, n)| (p, n.descendants())) 603 | .map(|(p, ns)| (p, ns.filter(|n| n.kind() != SyntaxKind::N_COND))) 604 | .map(|(p, ns)| (p, ns.filter_map(LeftOrRightSymbol::cast))) 605 | { 606 | for n in ns { 607 | let node = n.syntax(); 608 | let token = Symbol::cast( 609 | node.first_token().unwrap().parent().unwrap(), 610 | ) 611 | .and_then(|n| n.id()); 612 | if token.is_none() { 613 | continue; 614 | } 615 | let token = token.unwrap().0; 616 | if l_symbols.nearest(&token.clone()).is_none() { 617 | let scope_range = ScopeExtend::This(p.clone()).range(node); 618 | l_symbols.0.insert( 619 | token.range.start().into(), 620 | models::LSymbol { 621 | token, 622 | scope: models::Scope { 623 | kind: models::ScopeKind::Catch, 624 | range: scope_range, 625 | }, 626 | value: models::Value { 627 | kind: models::ValueKind::Match, 628 | range: None, 629 | }, 630 | }, 631 | ); 632 | } else { 633 | r_symbols.push(models::RSymbol { 634 | special: models::SpecialKind::Normal, 635 | token, 636 | }); 637 | } 638 | } 639 | } 640 | r_symbols 641 | } 642 | } 643 | 644 | impl MatchTry { 645 | fn l_or_r_symbols( 646 | &self, 647 | l_symbols: &mut models::LSymbols, 648 | ) -> Vec { 649 | let mut r_symbols = vec![]; 650 | for (_p, ns) in self 651 | .syntax() 652 | .children() 653 | .filter(|n| n.kind() == SyntaxKind::N_MATCH_TRY_CLAUSE) 654 | .filter_map(|n| { 655 | let c = n.first_child(); 656 | if let Some(c) = c { 657 | if c.kind() == SyntaxKind::N_CATCH_LIST { 658 | None 659 | } else { 660 | Some((n, c)) 661 | } 662 | } else { 663 | None 664 | } 665 | }) 666 | .map(|(p, n)| (p, n.descendants())) 667 | .map(|(p, ns)| (p, ns.filter(|n| n.kind() != SyntaxKind::N_COND))) 668 | .map(|(p, ns)| (p, ns.filter_map(LeftOrRightSymbol::cast))) 669 | { 670 | for n in ns { 671 | let node = n.syntax(); 672 | let token = Symbol::cast( 673 | node.first_token().unwrap().parent().unwrap(), 674 | ) 675 | .and_then(|n| n.id()); 676 | if token.is_none() { 677 | continue; 678 | } 679 | let token = token.unwrap().0; 680 | if l_symbols.nearest(&token.clone()).is_none() { 681 | let scope_range = ScopeExtend::Current.range(node); 682 | l_symbols.0.insert( 683 | token.range.start().into(), 684 | models::LSymbol { 685 | token, 686 | scope: models::Scope { 687 | kind: models::ScopeKind::MatchTry, 688 | range: scope_range, 689 | }, 690 | value: models::Value { 691 | kind: models::ValueKind::Match, 692 | range: None, 693 | }, 694 | }, 695 | ); 696 | } else { 697 | r_symbols.push(models::RSymbol { 698 | special: models::SpecialKind::Normal, 699 | token, 700 | }); 701 | } 702 | } 703 | } 704 | r_symbols 705 | } 706 | } 707 | 708 | impl MatchAst { 709 | pub(crate) fn l_or_r_symbols( 710 | &self, 711 | l_symbols: &mut models::LSymbols, 712 | ) -> Vec { 713 | match self { 714 | Self::Match(n) => n.l_or_r_symbols(l_symbols), 715 | Self::Catch(n) => n.l_or_r_symbols(l_symbols), 716 | Self::MatchTry(n) => n.l_or_r_symbols(l_symbols), 717 | } 718 | } 719 | } 720 | 721 | #[rustfmt::skip] 722 | ast_assoc!( 723 | ScopeAst, [ 724 | // Root always exists. 725 | Root, 726 | Let, WithOpen, Each, For, Icollect, Collect, Accumulate, 727 | Func, Lambda, 728 | ImportMacros, 729 | Match, MatchTry, Catch, 730 | Macro, Macros, 731 | ] 732 | ); 733 | 734 | impl ScopeAst { 735 | fn end(&self) -> rowan::TextSize { 736 | match self { 737 | Self::MatchTry(n) => n.end(), 738 | _ => self.syntax().text_range().end(), 739 | } 740 | } 741 | } 742 | 743 | impl MatchTry { 744 | fn end(&self) -> rowan::TextSize { 745 | let clause = self 746 | .syntax() 747 | .children() 748 | .filter(|n| n.kind() == SyntaxKind::N_MATCH_TRY_CLAUSE); 749 | if let Some(last_clause) = clause.last() { 750 | if last_clause.first_child().unwrap().kind() 751 | == SyntaxKind::N_CATCH_LIST 752 | { 753 | return last_clause.text_range().start(); 754 | } 755 | } 756 | self.syntax().text_range().end() 757 | } 758 | } 759 | 760 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 761 | pub(crate) enum ScopeExtend { 762 | Current, 763 | This(SyntaxNode), 764 | Outer, 765 | #[allow(unused)] 766 | File, // useless? 767 | } 768 | 769 | impl ScopeExtend { 770 | fn range(&self, node: &SyntaxNode) -> TextRange { 771 | let scope_end = match self { 772 | Self::Current => { 773 | node.ancestors().find_map(ScopeAst::cast).unwrap().end() 774 | } 775 | Self::This(node) => node.text_range().end(), 776 | Self::Outer => { 777 | let mut scopes = node.ancestors().filter_map(ScopeAst::cast); 778 | let first_scope = scopes.next().unwrap(); // safe 779 | if let ScopeAst::Root(n) = first_scope { 780 | n.syntax().text_range().end() 781 | } else { 782 | scopes.next().unwrap().syntax().text_range().end() 783 | } 784 | } 785 | Self::File => { 786 | node.ancestors().last().and_then(ScopeAst::cast).unwrap().end() 787 | } 788 | }; 789 | let start = node.text_range().end(); 790 | TextRange::new(start, scope_end) 791 | } 792 | } 793 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/ast/error.rs: -------------------------------------------------------------------------------- 1 | use rowan::{ast::AstNode, TextRange, WalkEvent}; 2 | 3 | use crate::{ 4 | ast::{eval::EvalAst, func::FuncAst, macros::ast_assoc, nodes::*}, 5 | models, Error, 6 | ErrorKind::*, 7 | SyntaxKind, SyntaxNode, SyntaxToken, 8 | }; 9 | 10 | ast_assoc!(Provider, [ 11 | List, 12 | SubList, 13 | FuncAst, 14 | BindingSymbol, 15 | RightSymbol, 16 | MatchTry, 17 | RequireMacros, 18 | PickArgs, 19 | Global, 20 | IntoClause, 21 | UntilClause, 22 | ]); 23 | 24 | impl List { 25 | fn macro_whitespace(&self) -> Option { 26 | const MACRO_CHAR: &[SyntaxKind] = 27 | &[SyntaxKind::HASHFN, SyntaxKind::COMMA, SyntaxKind::BACKTICK]; 28 | 29 | let node = self.syntax(); 30 | let first_token = node.first_token()?; 31 | if !MACRO_CHAR.contains(&first_token.kind()) { 32 | return None; 33 | } 34 | 35 | let next = first_token.next_token()?; 36 | if next.kind() == SyntaxKind::WHITESPACE { 37 | Some(Error::new(first_token.text_range(), MacroWhitespace)) 38 | } else { 39 | None 40 | } 41 | } 42 | 43 | fn empty_list(&self) -> Option { 44 | let node = self.syntax(); 45 | if !node 46 | .first_token() 47 | .map(|t| t.kind() == SyntaxKind::L_PAREN) 48 | .unwrap_or(false) 49 | { 50 | return None; 51 | } 52 | if !node.children_with_tokens().any(|c| { 53 | let kind = c.kind(); 54 | kind == SyntaxKind::N_SUBLIST || kind == SyntaxKind::ERROR 55 | }) { 56 | Some(Error::new(node.text_range(), EmptyList)) 57 | } else { 58 | None 59 | } 60 | } 61 | } 62 | 63 | impl SubList { 64 | fn literal_call(&self) -> Option { 65 | let eval_ast = self.syntax().first_child().and_then(|n| { 66 | let kind = n.kind(); 67 | if kind == SyntaxKind::N_LIST { 68 | EvalAst::cast(n) 69 | } else if kind == SyntaxKind::N_SYMBOL_CALL { 70 | n.first_child().and_then(EvalAst::cast) 71 | } else { 72 | None 73 | } 74 | }); 75 | if let Some(eval_ast) = eval_ast { 76 | let kind = eval_ast.eval_kind(); 77 | if [ 78 | models::ValueKind::Nil, 79 | models::ValueKind::Bool, 80 | models::ValueKind::Number, 81 | models::ValueKind::String, 82 | models::ValueKind::SeqTable, 83 | models::ValueKind::KvTable, 84 | ] 85 | .contains(&kind) 86 | { 87 | return Some(Error::new( 88 | eval_ast.syntax().text_range(), 89 | LiteralCall(kind), 90 | )); 91 | } 92 | if [models::ValueKind::Module, models::ValueKind::FileHandle] 93 | .contains(&kind) 94 | { 95 | return Some(Error::new( 96 | eval_ast.syntax().text_range(), 97 | DirectCall(kind), 98 | )); 99 | } 100 | } 101 | None 102 | } 103 | } 104 | 105 | impl FuncAst { 106 | fn def_method(&self) -> Option { 107 | self.name().and_then(|(node, is_pure)| { 108 | if is_pure { 109 | return None; 110 | } 111 | node.children_with_tokens() 112 | .find(|t| { 113 | t.as_token().unwrap().kind() == SyntaxKind::SYMBOL_METHOD 114 | }) 115 | .map(|_| Error::new(node.text_range(), MethodNotAllowed)) 116 | }) 117 | } 118 | 119 | fn varargs_errors(&self) -> Option> { 120 | let varargs: Vec = self 121 | .syntax() 122 | .children() 123 | .find(|n| n.kind() == SyntaxKind::N_PARAM_TABLE)? 124 | .descendants_with_tokens() 125 | .filter_map(|n| n.as_token().cloned()) 126 | .filter(|t| t.text() == "...") 127 | .collect(); 128 | if varargs.is_empty() { 129 | let mut res = vec![]; 130 | let mut traverse = self.syntax().preorder_with_tokens(); 131 | traverse.next(); // skip self 132 | while let Some(e) = traverse.next() { 133 | if let WalkEvent::Enter(n) = e { 134 | let kind = n.kind(); 135 | if kind == SyntaxKind::VARARG { 136 | res.push(Error::new(n.text_range(), UnexpectedVarargs)) 137 | } else if Self::can_cast(kind) || Macro::can_cast(kind) { 138 | traverse.skip_subtree() 139 | } 140 | } 141 | } 142 | Some(res) 143 | } else if varargs.len() == 1 { 144 | None 145 | } else { 146 | Some( 147 | varargs 148 | .into_iter() 149 | .map(|t| Error::new(t.text_range(), MultiVarargs)) 150 | .collect(), 151 | ) 152 | } 153 | } 154 | } 155 | 156 | impl BindingSymbol { 157 | pub(crate) fn field_and_method(&self) -> Option { 158 | let node = self.syntax(); 159 | node.children_with_tokens().find_map(|t| { 160 | let kind = t.as_token().unwrap().kind(); 161 | match kind { 162 | SyntaxKind::SYMBOL_FIELD | SyntaxKind::SYMBOL_METHOD => Some( 163 | Error::new(node.text_range(), FieldAndMethodNotAllowed), 164 | ), 165 | _ => None, 166 | } 167 | }) 168 | } 169 | } 170 | 171 | impl RightSymbol { 172 | fn method_call(&self) -> Option { 173 | let node = self.syntax(); 174 | if node.parent().unwrap().kind() == SyntaxKind::N_SYMBOL_CALL { 175 | return None; 176 | } 177 | node.children_with_tokens().find_map(|t| { 178 | let kind = t.as_token().unwrap().kind(); 179 | match kind { 180 | SyntaxKind::SYMBOL_METHOD => { 181 | Some(Error::new(node.text_range(), MethodNotAllowed)) 182 | } 183 | _ => None, 184 | } 185 | }) 186 | } 187 | } 188 | 189 | impl MatchTry { 190 | fn catch(&self) -> Vec> { 191 | let node = self.syntax(); 192 | let catchs: Vec = node 193 | .children() 194 | .filter(|n| n.kind() == SyntaxKind::N_MATCH_TRY_CLAUSE) 195 | .filter(|n| { 196 | n.first_child().unwrap().kind() == SyntaxKind::N_CATCH_LIST 197 | }) 198 | .collect(); 199 | let mut res = vec![]; 200 | 201 | if catchs.len() > 1 { 202 | res.extend( 203 | catchs[..catchs.len() - 1] 204 | .iter() 205 | .map(|n| Some(Error::new(n.text_range(), MultiCatch))), 206 | ); 207 | } 208 | if let Some(last_catch) = catchs.last() { 209 | let last_clause = 210 | self.syntax().last_child().unwrap().first_child().unwrap(); 211 | if last_clause.kind() != SyntaxKind::N_CATCH_LIST { 212 | res.push(Some(Error::new( 213 | last_catch.text_range(), 214 | CatchNotLast, 215 | ))); 216 | } 217 | } 218 | 219 | res 220 | } 221 | } 222 | 223 | impl RequireMacros { 224 | pub(crate) fn depcrated(&self) -> Error { 225 | Error::new( 226 | self.syntax().first_token().unwrap().text_range(), 227 | Deprecated("0.4.0", "import-macros"), 228 | ) 229 | } 230 | } 231 | 232 | impl PickArgs { 233 | pub(crate) fn depcrated(&self) -> Error { 234 | Error::new( 235 | self.syntax().first_token().unwrap().text_range(), 236 | Deprecated("0.10.0", "pick-values"), 237 | ) 238 | } 239 | } 240 | 241 | impl Global { 242 | pub(crate) fn depcrated(&self) -> Error { 243 | Error::new( 244 | self.syntax().first_token().unwrap().text_range(), 245 | Deprecated("1.1.0", "_G table"), 246 | ) 247 | } 248 | } 249 | 250 | impl IntoClause { 251 | pub(crate) fn depcrated(&self) -> Option { 252 | let token = self.syntax().first_token().unwrap(); 253 | if token.text().starts_with(':') { 254 | Some(Error::new(token.text_range(), Deprecated("1.2.0", "&into"))) 255 | } else { 256 | None 257 | } 258 | } 259 | } 260 | 261 | impl UntilClause { 262 | pub(crate) fn depcrated(&self) -> Option { 263 | let token = self.syntax().first_token().unwrap(); 264 | if token.text().starts_with(':') { 265 | Some(Error::new(token.text_range(), Deprecated("1.2.0", "&until"))) 266 | } else { 267 | None 268 | } 269 | } 270 | } 271 | 272 | impl Provider { 273 | pub(crate) fn errors(&self) -> impl Iterator> { 274 | match self { 275 | Self::List(n) => { 276 | vec![n.macro_whitespace(), n.empty_list()].into_iter() 277 | } 278 | Self::SubList(n) => vec![n.literal_call()].into_iter(), 279 | Self::FuncAst(n) => { 280 | let mut res = vec![n.def_method()]; 281 | if let Some(errors) = n.varargs_errors() { 282 | res.extend(errors.into_iter().map(Some)) 283 | }; 284 | res.into_iter() 285 | } 286 | Self::BindingSymbol(n) => vec![n.field_and_method()].into_iter(), 287 | Self::RightSymbol(n) => vec![n.method_call()].into_iter(), 288 | Self::MatchTry(n) => n.catch().into_iter(), 289 | Self::RequireMacros(n) => vec![Some(n.depcrated())].into_iter(), 290 | Self::PickArgs(n) => vec![Some(n.depcrated())].into_iter(), 291 | Self::Global(n) => vec![Some(n.depcrated())].into_iter(), 292 | Self::IntoClause(n) => vec![n.depcrated()].into_iter(), 293 | Self::UntilClause(n) => vec![n.depcrated()].into_iter(), 294 | } 295 | } 296 | } 297 | 298 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 299 | pub(crate) enum SuppressErrorKind { 300 | Unused, 301 | Unterminated, 302 | Undefined, 303 | AllUnexpected, 304 | Unexpected(SyntaxKind), 305 | } 306 | 307 | ast_assoc!(Suppress, [MacroQuote, FuncAst]); 308 | 309 | impl MacroQuote { 310 | pub(crate) fn suppress(&self) -> (TextRange, Vec) { 311 | let range = self.syntax().text_range(); 312 | (range, vec![ 313 | SuppressErrorKind::Unused, 314 | SuppressErrorKind::Unterminated, 315 | SuppressErrorKind::Undefined, 316 | SuppressErrorKind::AllUnexpected, 317 | ]) 318 | } 319 | } 320 | 321 | impl FuncAst { 322 | pub(crate) fn suppress( 323 | &self, 324 | ) -> Option)>> { 325 | let metadata = 326 | self.metadata().and_then(EvalAst::cast)?.cast_kv_table()?; 327 | let res = metadata 328 | .iter() 329 | .filter_map(|(k, v)| { 330 | if k.and_then(|k| k.cast_string()) 331 | .map_or(false, |(s, _)| s == "fnl/arglist") 332 | { 333 | v.map(|args| { 334 | (args.syntax().text_range(), vec![ 335 | SuppressErrorKind::Undefined, 336 | SuppressErrorKind::Unexpected(SyntaxKind::CAPTURE), 337 | ]) 338 | }) 339 | } else { 340 | None 341 | } 342 | }) 343 | .collect(); 344 | Some(res) 345 | } 346 | } 347 | 348 | impl Suppress { 349 | pub(crate) fn suppress(&self) -> Vec<(TextRange, Vec)> { 350 | match self { 351 | Self::MacroQuote(n) => vec![n.suppress()], 352 | Self::FuncAst(n) => n.suppress().unwrap_or_default(), 353 | } 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/ast/eval.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use rowan::ast::AstNode; 4 | 5 | use crate::{ 6 | ast::{func, macros::ast_assoc, models, nodes::*}, 7 | SyntaxKind, SyntaxNode, 8 | }; 9 | 10 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 11 | pub(crate) enum EvalAst { 12 | NilAst(NilAst), 13 | LastReturnAst(LastReturnAst), 14 | CondReturnAst(CondReturnAst), 15 | MultiReturnAst(MultiReturnAst), 16 | FuncAst(func::FuncAst), 17 | Sexp(Sexp), 18 | Atom(Atom), 19 | Literal(Literal), 20 | List(List), 21 | SymbolCall(SymbolCall), 22 | Operation(Operation), 23 | RightSymbol(RightSymbol), 24 | Unknown(SyntaxNode), 25 | } 26 | 27 | #[rustfmt::skip] 28 | ast_assoc!( 29 | NilAst, [ 30 | Var, Set, Tset, Local, Global, ImportMacros, RequireMacros, 31 | Macro, Macros, EvalCompiler, Each, For, Lua, Macrodebug, 32 | ] 33 | ); 34 | 35 | ast_assoc!(LastReturnAst, [Let, WithOpen, When, Do, Thread, Doto]); 36 | 37 | ast_assoc!(CondReturnAst, [Match, If]); 38 | 39 | ast_assoc!(MultiReturnAst, [Values, PickValues]); 40 | 41 | // Cast anything 42 | impl rowan::ast::AstNode for EvalAst { 43 | type Language = crate::FennelLanguage; 44 | 45 | fn cast(syntax_node: SyntaxNode) -> Option { 46 | if let Some(i) = NilAst::cast(syntax_node.clone()) { 47 | return Some(Self::NilAst(i)); 48 | } 49 | if let Some(i) = LastReturnAst::cast(syntax_node.clone()) { 50 | return Some(Self::LastReturnAst(i)); 51 | } 52 | if let Some(i) = CondReturnAst::cast(syntax_node.clone()) { 53 | return Some(Self::CondReturnAst(i)); 54 | } 55 | if let Some(i) = MultiReturnAst::cast(syntax_node.clone()) { 56 | return Some(Self::MultiReturnAst(i)); 57 | } 58 | if let Some(i) = func::FuncAst::cast(syntax_node.clone()) { 59 | return Some(Self::FuncAst(i)); 60 | } 61 | if let Some(i) = Sexp::cast(syntax_node.clone()) { 62 | return Some(Self::Sexp(i)); 63 | } 64 | if let Some(i) = Atom::cast(syntax_node.clone()) { 65 | return Some(Self::Atom(i)); 66 | } 67 | if let Some(i) = Literal::cast(syntax_node.clone()) { 68 | return Some(Self::Literal(i)); 69 | } 70 | if let Some(i) = List::cast(syntax_node.clone()) { 71 | return Some(Self::List(i)); 72 | } 73 | if let Some(i) = SymbolCall::cast(syntax_node.clone()) { 74 | return Some(Self::SymbolCall(i)); 75 | } 76 | if let Some(i) = Operation::cast(syntax_node.clone()) { 77 | return Some(Self::Operation(i)); 78 | } 79 | if let Some(i) = RightSymbol::cast(syntax_node.clone()) { 80 | return Some(Self::RightSymbol(i)); 81 | } 82 | Some(Self::Unknown(syntax_node)) 83 | } 84 | 85 | fn can_cast(_syntax: SyntaxKind) -> bool { 86 | true 87 | } 88 | 89 | fn syntax(&self) -> &SyntaxNode { 90 | match self { 91 | Self::NilAst(n) => n.syntax(), 92 | Self::LastReturnAst(n) => n.syntax(), 93 | Self::CondReturnAst(n) => n.syntax(), 94 | Self::MultiReturnAst(n) => n.syntax(), 95 | Self::FuncAst(n) => n.syntax(), 96 | Self::Sexp(n) => n.syntax(), 97 | Self::Atom(n) => n.syntax(), 98 | Self::Literal(n) => n.syntax(), 99 | Self::List(n) => n.syntax(), 100 | Self::SymbolCall(n) => n.syntax(), 101 | Self::Operation(n) => n.syntax(), 102 | Self::RightSymbol(n) => n.syntax(), 103 | Self::Unknown(n) => n, 104 | } 105 | } 106 | } 107 | 108 | impl EvalAst { 109 | pub fn eval_kind(&self) -> models::ValueKind { 110 | let kind = match self { 111 | Self::NilAst(_) => Some(models::ValueKind::Nil), 112 | Self::LastReturnAst(_) => None, 113 | Self::CondReturnAst(_) => None, 114 | Self::MultiReturnAst(_) => None, 115 | Self::FuncAst(_) => Some(models::ValueKind::Func), 116 | Self::Sexp(n) => n.eval_kind(), 117 | Self::Atom(n) => n.eval_kind(), 118 | Self::Literal(n) => n.eval_kind(), 119 | Self::List(n) => n.eval_kind(), 120 | Self::SymbolCall(n) => n.eval_kind(), 121 | Self::Operation(n) => n.eval_kind(), 122 | Self::RightSymbol(n) => n.eval_kind(), 123 | Self::Unknown(_) => None, 124 | }; 125 | kind.unwrap_or(models::ValueKind::Unknown) 126 | } 127 | } 128 | 129 | impl EvalAst { 130 | pub fn cast_string(&self) -> Option<(String, StringKind)> { 131 | match self { 132 | Self::Sexp(n) => n.cast_string(), 133 | Self::Atom(n) => n.cast_string(), 134 | Self::Literal(n) => n.cast_string(), 135 | _ => None, 136 | } 137 | } 138 | 139 | pub fn cast_kv_table(&self) -> Option { 140 | match self { 141 | Self::Sexp(n) => n.cast_kv_table(), 142 | Self::Atom(n) => n.cast_kv_table(), 143 | _ => None, 144 | } 145 | } 146 | } 147 | 148 | impl Sexp { 149 | pub fn eval_kind(&self) -> Option { 150 | EvalAst::cast(self.0.first_child()?).map(|n| n.eval_kind()) 151 | } 152 | 153 | pub fn cast_string(&self) -> Option<(String, StringKind)> { 154 | EvalAst::cast(self.0.first_child()?).and_then(|n| n.cast_string()) 155 | } 156 | 157 | pub fn cast_kv_table(&self) -> Option { 158 | EvalAst::cast(self.0.first_child()?).and_then(|n| n.cast_kv_table()) 159 | } 160 | } 161 | 162 | impl Atom { 163 | pub fn eval_kind(&self) -> Option { 164 | let child = self.0.first_child()?; 165 | match child.kind() { 166 | SyntaxKind::N_SEQ_TABLE => Some(models::ValueKind::SeqTable), 167 | SyntaxKind::N_KV_TABLE => Some(models::ValueKind::KvTable), 168 | SyntaxKind::N_LITERAL => Literal(child).eval_kind(), 169 | SyntaxKind::N_R_SYMBOL => RightSymbol(child).eval_kind(), 170 | _ => None, 171 | } 172 | } 173 | 174 | pub fn cast_string(&self) -> Option<(String, StringKind)> { 175 | let child = self.0.first_child()?; 176 | match child.kind() { 177 | SyntaxKind::N_LITERAL => Literal(child).cast_string(), 178 | _ => None, 179 | } 180 | } 181 | 182 | pub fn cast_kv_table(&self) -> Option { 183 | let child = self.0.first_child()?; 184 | match child.kind() { 185 | SyntaxKind::N_KV_TABLE => KvTable::cast(child), 186 | _ => None, 187 | } 188 | } 189 | } 190 | 191 | impl Literal { 192 | pub fn eval_kind(&self) -> Option { 193 | match self.0.first_token()?.kind() { 194 | SyntaxKind::FLOAT | SyntaxKind::INTEGER => { 195 | Some(models::ValueKind::Number) 196 | } 197 | SyntaxKind::QUOTE_STRING | SyntaxKind::COLON_STRING => { 198 | Some(models::ValueKind::String) 199 | } 200 | SyntaxKind::BOOL => Some(models::ValueKind::Bool), 201 | SyntaxKind::NIL => Some(models::ValueKind::Nil), 202 | _ => None, 203 | } 204 | } 205 | 206 | pub(crate) fn cast_string(&self) -> Option<(String, StringKind)> { 207 | let token = self.syntax().first_token()?; 208 | match token.kind() { 209 | SyntaxKind::COLON_STRING => { 210 | Some((token.text()[1..].to_string(), StringKind::Colon)) 211 | } 212 | SyntaxKind::QUOTE_STRING => { 213 | let text = token.text(); 214 | Some((text[1..text.len() - 1].to_string(), StringKind::Quote)) 215 | } 216 | _ => None, 217 | } 218 | } 219 | 220 | pub(crate) fn cast_path(&self) -> Option { 221 | let token = self.syntax().first_token()?; 222 | let s = match token.kind() { 223 | SyntaxKind::INTEGER => token.text().to_string(), 224 | SyntaxKind::FLOAT => { 225 | let token = token.text().to_string(); 226 | // TODO: support hex and exponent 227 | if token.starts_with('.') 228 | || token.ends_with('.') 229 | || token.chars().any(|c| !c.is_ascii_digit() && c != '.') 230 | { 231 | return None; 232 | } else { 233 | token 234 | } 235 | } 236 | _ => self.cast_string()?.0, 237 | }; 238 | if s.ends_with('.') || s.ends_with('/') { 239 | return None; 240 | } 241 | let s = s.trim_start_matches(|c| c == '.' || c == '/'); 242 | let path = PathBuf::from(s.replace('.', "/")); 243 | Some(path) 244 | } 245 | } 246 | 247 | impl List { 248 | pub(crate) fn eval_kind(&self) -> Option { 249 | let sublist = self 250 | .0 251 | .children() 252 | .find(|n| n.kind() == SyntaxKind::N_SUBLIST)? 253 | .first_child()?; 254 | match sublist.kind() { 255 | SyntaxKind::N_SYMBOL_CALL => { 256 | SymbolCall::cast(sublist)?.eval_kind() 257 | } 258 | SyntaxKind::N_INCLUDE => None, 259 | SyntaxKind::N_PARTIAL | SyntaxKind::N_PICK_ARGS => { 260 | Some(models::ValueKind::Func) 261 | } 262 | SyntaxKind::N_ICOLLECT => Some(models::ValueKind::SeqTable), 263 | SyntaxKind::N_COLLECT => Some(models::ValueKind::KvTable), 264 | _ => Some(EvalAst::cast(sublist)?.eval_kind()), 265 | } 266 | } 267 | } 268 | 269 | impl SymbolCall { 270 | pub(crate) fn eval_kind(&self) -> Option { 271 | if self.is_require() { 272 | Some( 273 | self.require().map_or(models::ValueKind::Require(None), |p| { 274 | models::ValueKind::Require(Some(p)) 275 | }), 276 | ) 277 | } else { 278 | None 279 | } 280 | } 281 | } 282 | 283 | impl Operation { 284 | pub fn eval_kind(&self) -> Option { 285 | let first_token = self.0.first_token()?; 286 | match first_token.kind() { 287 | SyntaxKind::LENGTH => Some(models::ValueKind::Number), 288 | SyntaxKind::OPERATOR => match first_token.text() { 289 | "-" | "/" | "//" | "lshift" | "rshift" | "band" | "bor" 290 | | "bxor" | "length" => Some(models::ValueKind::Number), 291 | ">" | "<" | ">=" | "<=" | "~=" | "=" | "not=" => { 292 | Some(models::ValueKind::Bool) 293 | } 294 | ".." => Some(models::ValueKind::String), 295 | _ => None, 296 | }, 297 | SyntaxKind::KEYWORD_OR => None, 298 | SyntaxKind::COLON => None, 299 | _ => None, 300 | } 301 | } 302 | } 303 | 304 | impl RightSymbol { 305 | // just wrap self 306 | pub fn eval_kind(&self) -> Option { 307 | let node = self.syntax(); 308 | if node.children_with_tokens().any(|t| { 309 | let kind = t.as_token().unwrap().kind(); 310 | kind == SyntaxKind::SYMBOL_FIELD 311 | || kind == SyntaxKind::SYMBOL_METHOD 312 | }) { 313 | None 314 | } else { 315 | Some(models::ValueKind::Symbol) 316 | } 317 | } 318 | } 319 | 320 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 321 | pub(crate) enum StringKind { 322 | Colon, 323 | Quote, 324 | } 325 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/ast/func.rs: -------------------------------------------------------------------------------- 1 | use rowan::ast::AstNode; 2 | 3 | use super::eval::EvalAst; 4 | use crate::{ 5 | ast::nodes::*, models, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, 6 | }; 7 | 8 | ast_assoc!(FuncAst, [Func, Lambda]); 9 | 10 | impl FuncAst { 11 | pub(crate) fn name(&self) -> Option<(SyntaxNode, bool)> { 12 | let name = self 13 | .syntax() 14 | .children() 15 | .find(|n| n.kind() == SyntaxKind::N_FN_NAME)?; 16 | 17 | let is_pure = !name.children_with_tokens().any(|t| { 18 | [ 19 | SyntaxKind::SYMBOL_METHOD, 20 | SyntaxKind::SYMBOL_FIELD, 21 | SyntaxKind::ERROR, 22 | ] 23 | .contains(&t.kind()) 24 | }); 25 | 26 | Some((name, is_pure)) 27 | } 28 | 29 | #[allow(unused)] 30 | pub(crate) fn l_name(&self) -> Option { 31 | self.name().and_then(|(name, is_pure)| { 32 | if is_pure { 33 | name.first_token().map(models::Token::from) 34 | } else { 35 | None 36 | } 37 | }) 38 | } 39 | 40 | pub(crate) fn r_name(&self) -> Option { 41 | self.name().and_then(|(name, is_pure)| { 42 | if is_pure { 43 | None 44 | } else { 45 | name.first_token().map(models::Token::from) 46 | } 47 | }) 48 | } 49 | 50 | pub(crate) fn varargs(&self) -> Option { 51 | self.syntax() 52 | .children() 53 | .find(|n| n.kind() == SyntaxKind::N_PARAM_TABLE)? 54 | .descendants_with_tokens() 55 | .collect::>() 56 | .into_iter() 57 | .rev() 58 | .find(|n| n.kind() == SyntaxKind::VARARG) 59 | .and_then(|n| n.into_token()) 60 | } 61 | 62 | pub(crate) fn metadata(&self) -> Option { 63 | let body = self 64 | .syntax() 65 | .children() 66 | .skip(1) 67 | .find(|n| n.kind() == SyntaxKind::N_BODY)?; 68 | let mut sexp = 69 | body.children().filter(|n| n.kind() == SyntaxKind::N_SEXP); 70 | 71 | let metadata = sexp.next()?; 72 | // docstring can't be last 73 | sexp.next()?; 74 | Some(metadata) 75 | } 76 | 77 | pub(crate) fn docstring(&self) -> Option { 78 | let eval = self.metadata().and_then(EvalAst::cast)?; 79 | match eval.eval_kind() { 80 | models::ValueKind::KvTable => eval 81 | .cast_kv_table() 82 | .and_then(|t| t.get("fnl/docstring".to_owned())) 83 | .and_then(|eval| eval.cast_string()) 84 | .map(|(s, _)| s), 85 | models::ValueKind::String => eval.cast_string().map(|(s, _)| s), 86 | _ => None, 87 | } 88 | } 89 | } 90 | 91 | pub(crate) fn from(token: SyntaxToken) -> Option { 92 | token.parent()?.parent().and_then(FuncAst::cast) 93 | } 94 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/ast/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! ast_node { 2 | ($ast:ident, $syntax:ident) => { 3 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 4 | pub(crate) struct $ast(pub(crate) SyntaxNode); 5 | 6 | impl rowan::ast::AstNode for $ast { 7 | type Language = crate::FennelLanguage; 8 | 9 | fn cast(syntax_node: SyntaxNode) -> Option { 10 | syntax_node 11 | .kind() 12 | .eq(&SyntaxKind::$syntax) 13 | .then(|| Self(syntax_node)) 14 | } 15 | 16 | fn can_cast(syntax: SyntaxKind) -> bool { 17 | syntax == SyntaxKind::$syntax 18 | } 19 | 20 | fn syntax(&self) -> &SyntaxNode { 21 | &self.0 22 | } 23 | } 24 | }; 25 | } 26 | 27 | macro_rules! ast_assoc { 28 | ( $name:ident, [$($item:ident),* $(,)?] ) => { 29 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 30 | #[allow(clippy::enum_variant_names)] 31 | pub(crate) enum $name { 32 | $($item($item),)* 33 | } 34 | impl rowan::ast::AstNode for $name { 35 | type Language = crate::FennelLanguage; 36 | fn cast(syntax_node: SyntaxNode) -> Option { 37 | $( 38 | if let Some(i) = $item::cast(syntax_node.clone()) { 39 | return Some(Self::$item(i)); 40 | } 41 | )* 42 | None 43 | } 44 | fn can_cast(syntax: SyntaxKind) -> bool { 45 | $( 46 | if $item::can_cast(syntax) { 47 | return true; 48 | } 49 | )* 50 | false 51 | } 52 | fn syntax(&self) -> &SyntaxNode { 53 | match self { 54 | $(Self::$item(n) => n.syntax(),)* 55 | } 56 | } 57 | } 58 | } 59 | } 60 | 61 | pub(crate) use ast_assoc; 62 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/ast/models.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use std::{ 3 | collections::BTreeMap, 4 | ops::Bound::{Excluded, Included}, 5 | path::PathBuf, 6 | }; 7 | 8 | use rowan::TextRange; 9 | 10 | use crate::SyntaxToken; 11 | 12 | // TODO: TextRange as key 13 | #[derive(Default, Debug, Clone, PartialEq, Eq, Hash)] 14 | pub(crate) struct LSymbols(pub(crate) BTreeMap); 15 | 16 | pub(crate) type RSymbols = Vec; 17 | 18 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 19 | pub struct LSymbol { 20 | pub token: Token, 21 | pub scope: Scope, 22 | pub value: Value, 23 | } 24 | 25 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 26 | pub(crate) struct RSymbol { 27 | pub(crate) token: Token, 28 | pub(crate) special: SpecialKind, 29 | } 30 | 31 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 32 | pub struct Token { 33 | pub text: String, 34 | pub range: TextRange, 35 | } 36 | 37 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 38 | pub struct Scope { 39 | pub kind: ScopeKind, 40 | pub range: TextRange, 41 | } 42 | 43 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 44 | pub struct Value { 45 | pub kind: ValueKind, 46 | pub range: Option, 47 | } 48 | 49 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 50 | pub enum ScopeKind { 51 | Func, 52 | Lambda, 53 | Param, 54 | MacroParam, 55 | Local, 56 | Var, 57 | Let, 58 | WithOpen, 59 | IterValue, 60 | AccuValue, 61 | Global, 62 | Macro, 63 | Match, 64 | MatchTry, 65 | Catch, 66 | } 67 | 68 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 69 | pub enum ValueKind { 70 | Func, 71 | Param, 72 | MacroParam, 73 | Match, 74 | Number, 75 | String, 76 | Nil, 77 | Bool, 78 | SeqTable, 79 | KvTable, 80 | Macro, 81 | Module, 82 | Require(Option), 83 | FileHandle, 84 | Unknown, 85 | Symbol, 86 | } 87 | 88 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 89 | pub enum SpecialKind { 90 | Normal, 91 | MacroWrap, 92 | MacroUnquote, 93 | HashArg, 94 | } 95 | 96 | impl LSymbol { 97 | pub(crate) fn contains_token(&self, r_symbol: &Token) -> bool { 98 | self.token.text == r_symbol.text 99 | && self.scope.range.contains_range(r_symbol.range) 100 | } 101 | } 102 | 103 | impl LSymbols { 104 | pub(crate) fn new(symbols: impl Iterator) -> Self { 105 | Self(symbols.map(|s| (s.token.range.start().into(), s)).collect()) 106 | } 107 | 108 | pub(crate) fn range(&self, offset: u32) -> impl Iterator { 109 | self.0.range((Included(0), Excluded(offset))).rev().map(|(_, s)| s) 110 | } 111 | 112 | pub(crate) fn nearest(&self, token: &Token) -> Option<&LSymbol> { 113 | self.range(token.range.start().into()) 114 | .find(|l_symbol| l_symbol.contains_token(token)) 115 | } 116 | 117 | pub(crate) fn get(&self, start: u32) -> Option<&LSymbol> { 118 | self.0.get(&start) 119 | } 120 | } 121 | 122 | impl From for Token { 123 | fn from(token: SyntaxToken) -> Self { 124 | Token { text: token.text().to_string(), range: token.text_range() } 125 | } 126 | } 127 | 128 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 129 | pub enum CompletionKind { 130 | Keyword, 131 | Func, 132 | Module, 133 | Field, 134 | Operator, 135 | Var, 136 | } 137 | 138 | impl fmt::Display for ValueKind { 139 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 140 | let name = match self { 141 | Self::Func => "function", 142 | Self::Param => "parameter", 143 | Self::Match => "match pattern", 144 | Self::Number => "number", 145 | Self::String => "string", 146 | Self::Nil => "nil", 147 | Self::Bool => "bool", 148 | Self::SeqTable => "sequential table", 149 | Self::KvTable => "key/value table", 150 | Self::Macro => "macro", 151 | Self::MacroParam => "macro parameter", 152 | Self::Module => "module", 153 | Self::Require(_) => "require", 154 | Self::FileHandle => "file handle", 155 | Self::Symbol => "symbol", 156 | Self::Unknown => "(lsp:unknown)", 157 | }; 158 | write!(f, "{}", name) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/ast/nodes.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, path::PathBuf}; 2 | 3 | use rowan::{ast::AstNode, TextRange}; 4 | 5 | use crate::{ 6 | ast::{ 7 | bind::MatchAst, 8 | error::{Provider, Suppress, SuppressErrorKind}, 9 | eval, 10 | eval::EvalAst, 11 | func, models, 12 | }, 13 | Error, 14 | ErrorKind::*, 15 | SyntaxKind, SyntaxNode, SyntaxToken, 16 | }; 17 | 18 | ast_node!(Root, ROOT); 19 | ast_node!(Sexp, N_SEXP); 20 | ast_node!(Atom, N_ATOM); 21 | ast_node!(Literal, N_LITERAL); 22 | ast_node!(KvTable, N_KV_TABLE); 23 | 24 | ast_node!(LeftSymbol, N_L_SYMBOL); 25 | ast_node!(RightSymbol, N_R_SYMBOL); 26 | // Be careful that FuncName does not equal to LeftRightSymbol 27 | ast_node!(LeftRightSymbol, N_L_R_SYMBOL); 28 | ast_node!(LeftOrRightSymbol, N_L_OR_R_SYMBOL); 29 | 30 | ast_node!(List, N_LIST); 31 | ast_node!(SubList, N_SUBLIST); 32 | ast_node!(Operation, N_OPERATION); 33 | ast_node!(Func, N_FN); 34 | ast_node!(Lambda, N_LAMBDA); 35 | ast_node!(Var, N_VAR); 36 | ast_node!(Set, N_SET); 37 | ast_node!(Tset, N_TSET); 38 | ast_node!(Local, N_LOCAL); 39 | ast_node!(Global, N_GLOBAL); 40 | ast_node!(Let, N_LET); 41 | ast_node!(Match, N_MATCH); 42 | ast_node!(MatchTry, N_MATCH_TRY); 43 | ast_node!(Catch, N_CATCH); 44 | ast_node!(If, N_IF); 45 | ast_node!(Values, N_VALUES); 46 | ast_node!(PickValues, N_PICK_VALUE); 47 | ast_node!(WithOpen, N_WITH_OPEN); 48 | ast_node!(Each, N_EACH); 49 | ast_node!(For, N_FOR); 50 | ast_node!(Do, N_DO); 51 | ast_node!(Thread, N_THREAD); 52 | ast_node!(Doto, N_DOTO); 53 | ast_node!(When, N_WHEN); 54 | ast_node!(Icollect, N_ICOLLECT); 55 | ast_node!(Fcollect, N_FCOLLECT); 56 | ast_node!(Collect, N_COLLECT); 57 | ast_node!(Accumulate, N_ACCUMULATE); 58 | ast_node!(ImportMacros, N_IMPORT_MACROS); 59 | ast_node!(RequireMacros, N_REQUIRE_MACROS); 60 | ast_node!(PickArgs, N_PICK_ARGS); 61 | ast_node!(Macro, N_MACRO); 62 | ast_node!(Macros, N_MACROS); 63 | ast_node!(EvalCompiler, N_EVAL_COMPILER); 64 | ast_node!(SymbolCall, N_SYMBOL_CALL); 65 | ast_node!(Lua, N_LUA); 66 | ast_node!(Macrodebug, N_MACRODEBUG); 67 | ast_node!(IntoClause, N_INTO_CLAUSE); 68 | ast_node!(UntilClause, N_UNTIL_CLAUSE); 69 | ast_node!(MacroQuote, N_MACRO_QUOTE); 70 | 71 | ast_assoc!(BindingSymbol, [LeftSymbol, LeftRightSymbol]); 72 | 73 | // TODO: merge? 74 | ast_assoc!(Symbol, [LeftSymbol, LeftRightSymbol, LeftOrRightSymbol]); 75 | 76 | impl Symbol { 77 | pub(crate) fn id(&self) -> Option<(models::Token, bool, bool)> { 78 | let mut nodes = self.syntax().children_with_tokens(); 79 | let first_node = nodes.next()?; 80 | let first_token = first_node.as_token()?; 81 | let prefix_is_comma = first_token.kind() == SyntaxKind::COMMA; 82 | if prefix_is_comma { 83 | let first_token = nodes.next()?; 84 | Some(( 85 | first_token.as_token()?.to_owned().into(), 86 | true, 87 | nodes.next().is_none(), 88 | )) 89 | } else { 90 | Some(( 91 | first_token.to_owned().into(), 92 | false, 93 | nodes.next().is_none(), 94 | )) 95 | } 96 | } 97 | } 98 | 99 | impl Root { 100 | pub(crate) fn return_value(&self) -> Option { 101 | let last_node = self.syntax().children().last()?; 102 | eval::EvalAst::cast(last_node) 103 | } 104 | 105 | pub(crate) fn return_kv_table( 106 | &self, 107 | ) -> Option> { 108 | self.return_value() 109 | .and_then(|eval_ast| eval_ast.cast_kv_table()) 110 | .map(|kv_table| kv_table.cast_hashmap()) 111 | } 112 | 113 | pub(crate) fn resources(&self) -> impl Iterator { 114 | self.syntax() 115 | .descendants() 116 | .filter_map(SymbolCall::cast) 117 | .filter_map(|n| n.require()) 118 | } 119 | 120 | pub(crate) fn provide_errors(&self) -> impl Iterator { 121 | self.syntax() 122 | .descendants() 123 | .filter_map(Provider::cast) 124 | .flat_map(|n| n.errors()) 125 | .flatten() 126 | } 127 | 128 | pub(crate) fn suppress_errors( 129 | &self, 130 | ) -> impl Iterator)> { 131 | self.syntax() 132 | .descendants() 133 | .filter_map(Suppress::cast) 134 | .flat_map(|n| n.suppress()) 135 | } 136 | 137 | pub(crate) fn r_symbols(&self) -> Vec { 138 | let mut count_hashfn = 0; 139 | let mut count_macro = 0; 140 | 141 | let event_macro = |kind: SyntaxKind| -> bool { 142 | Macro::can_cast(kind) || Macros::can_cast(kind) 143 | }; 144 | let event_hashfn = 145 | |kind: SyntaxKind| -> bool { kind == SyntaxKind::N_MACRO_HASH }; 146 | self.syntax() 147 | .preorder() 148 | .flat_map(|event| match event { 149 | rowan::WalkEvent::Enter(n) => { 150 | let n_kind = n.kind(); 151 | if event_macro(n_kind) { 152 | count_macro += 1; 153 | } 154 | if event_hashfn(n_kind) { 155 | count_hashfn += 1; 156 | } 157 | let refer_symbol = ReferSymbol::cast(n.clone()) 158 | .and_then(|n| n.name()) 159 | .map(|(token, starts_with_comma)| { 160 | if count_hashfn > 0 && token.text.starts_with('$') 161 | { 162 | return models::RSymbol { 163 | token, 164 | special: models::SpecialKind::HashArg, 165 | }; 166 | } 167 | if count_macro > 0 { 168 | if starts_with_comma { 169 | return models::RSymbol { 170 | token, 171 | special: 172 | models::SpecialKind::MacroUnquote, 173 | }; 174 | } else { 175 | return models::RSymbol { 176 | token, 177 | special: 178 | models::SpecialKind::MacroWrap, 179 | }; 180 | } 181 | } 182 | models::RSymbol { 183 | token, 184 | special: models::SpecialKind::Normal, 185 | } 186 | }); 187 | let fn_name = || { 188 | func::FuncAst::cast(n).and_then(|f| f.r_name()).map( 189 | |s| models::RSymbol { 190 | special: models::SpecialKind::Normal, 191 | token: s, 192 | }, 193 | ) 194 | }; 195 | refer_symbol.or_else(fn_name) 196 | } 197 | rowan::WalkEvent::Leave(n) => { 198 | let n_kind = n.kind(); 199 | if event_macro(n_kind) { 200 | count_macro -= 1; 201 | } 202 | if event_hashfn(n_kind) { 203 | count_hashfn -= 1; 204 | } 205 | None 206 | } 207 | }) 208 | .collect() 209 | } 210 | 211 | pub(crate) fn correct_symbols( 212 | &self, 213 | l_symbols: &mut models::LSymbols, 214 | ) -> Vec { 215 | let mut r_symbols = vec![]; 216 | for r in self 217 | .syntax() 218 | .descendants() 219 | .filter_map(MatchAst::cast) 220 | .map(|n| n.l_or_r_symbols(l_symbols)) 221 | { 222 | r_symbols.extend(r); 223 | } 224 | r_symbols 225 | } 226 | 227 | // TODO: refactor 228 | pub(crate) fn delimiter_whitespace_errors( 229 | &self, 230 | ) -> impl Iterator { 231 | const DELIMITER: &[SyntaxKind] = &[ 232 | SyntaxKind::L_PAREN, 233 | SyntaxKind::L_BRACE, 234 | SyntaxKind::L_BRACKET, 235 | SyntaxKind::HASHFN, 236 | SyntaxKind::COMMA, 237 | SyntaxKind::BACKTICK, 238 | // FIXME: should fix lexer 239 | SyntaxKind::LENGTH, 240 | ]; 241 | 242 | self.syntax() 243 | .children() 244 | .filter_map(Sexp::cast) 245 | .map(|n| n.syntax().descendants_with_tokens()) 246 | .flat_map(|n| n.skip_while(|n| n.as_node().is_some()).skip(1)) 247 | .filter_map(|n| n.into_token()) 248 | .filter(|t| DELIMITER.contains(&t.kind())) 249 | .flat_map(|token| { 250 | let mut prev_token = token.prev_token()?; 251 | loop { 252 | if prev_token.kind() == SyntaxKind::COMMENT { 253 | prev_token = prev_token.prev_token()?; 254 | } else { 255 | break; 256 | } 257 | } 258 | if prev_token.kind() != SyntaxKind::WHITESPACE 259 | && !DELIMITER.contains(&prev_token.kind()) 260 | { 261 | Some(Error::new(token.text_range(), MissingWhitespace)) 262 | } else { 263 | None 264 | } 265 | }) 266 | } 267 | } 268 | 269 | ast_assoc!(ReferSymbol, [RightSymbol, LeftRightSymbol]); 270 | 271 | impl ReferSymbol { 272 | pub(crate) fn name(&self) -> Option<(models::Token, bool)> { 273 | let mut nodes = self.syntax().children_with_tokens(); 274 | let first_node = nodes.next()?; 275 | let first_token = first_node.as_token()?; 276 | let prefix_is_comma = first_token.kind() == SyntaxKind::COMMA; 277 | let prev_is_comma = first_token 278 | .prev_token() 279 | .map(|t| t.kind() == SyntaxKind::COMMA) 280 | .unwrap_or(false); 281 | // TODO: improve syntax 282 | if prefix_is_comma { 283 | Some((nodes.next()?.as_token()?.to_owned().into(), true)) 284 | } else if prev_is_comma { 285 | Some((first_token.to_owned().into(), true)) 286 | } else { 287 | Some((first_token.to_owned().into(), false)) 288 | } 289 | } 290 | } 291 | 292 | impl KvTable { 293 | pub(crate) fn get(&self, key: String) -> Option { 294 | self.syntax() 295 | .children() 296 | .filter(|n| n.kind() == SyntaxKind::N_KV_PAIR) 297 | .map(|n| { 298 | ( 299 | n.first_child().unwrap(), 300 | n.children() 301 | .skip(1) 302 | .find(|n| n.kind() == SyntaxKind::N_VALUE), 303 | ) 304 | }) 305 | .find_map(|(key_node, value_node)| { 306 | let k_str = key_node 307 | .first_child() 308 | .and_then(eval::EvalAst::cast) 309 | .and_then(|n| n.cast_string()); 310 | if k_str.map_or(false, |(s, _)| s == key) { 311 | value_node 312 | .and_then(|v| v.first_child()) 313 | .and_then(EvalAst::cast) 314 | } else { 315 | None 316 | } 317 | }) 318 | } 319 | 320 | pub(crate) fn iter( 321 | &self, 322 | ) -> impl Iterator, Option)> { 323 | self.syntax() 324 | .children() 325 | .filter(|n| n.kind() == SyntaxKind::N_KV_PAIR) 326 | .map(|n| { 327 | ( 328 | n.first_child() 329 | .and_then(|n| n.first_child()) 330 | .and_then(EvalAst::cast), 331 | n.children() 332 | .skip(1) 333 | .find(|n| n.kind() == SyntaxKind::N_VALUE) 334 | .and_then(|n| n.first_child()) 335 | .and_then(EvalAst::cast), 336 | ) 337 | }) 338 | } 339 | 340 | // skip non-string key 341 | pub(crate) fn cast_hashmap(&self) -> HashMap { 342 | let mut res = HashMap::new(); 343 | for (k, v) in self.iter() { 344 | if k.is_none() || v.is_none() { 345 | continue; 346 | } 347 | let (k, v) = (k.unwrap(), v.unwrap()); 348 | if let Some((key, _)) = k.cast_string() { 349 | res.insert(key, v); 350 | } else if k.syntax().text() == ":" { 351 | let l_r_symbol = match Symbol::cast(v.syntax().clone()) { 352 | Some(n) => n, 353 | None => { 354 | continue; 355 | } 356 | }; 357 | let (key, _, is_pure) = match l_r_symbol.id() { 358 | Some(res) => res, 359 | None => { 360 | continue; 361 | } 362 | }; 363 | if is_pure { 364 | res.insert(key.text, v); 365 | } 366 | } 367 | } 368 | res 369 | } 370 | } 371 | 372 | impl SymbolCall { 373 | pub(crate) fn call_name(&self) -> Option { 374 | self.syntax().first_token().map(|t| t.to_string()) 375 | } 376 | 377 | pub(crate) fn is_require(&self) -> bool { 378 | // TODO: follow symbol 379 | self.call_name().map(|name| name == "require").unwrap_or(false) 380 | } 381 | 382 | pub(crate) fn require(&self) -> Option { 383 | if self.is_require() { 384 | let deepest_node = 385 | self.syntax().children().nth(1)?.first_token()?.parent()?; 386 | get_ancestor::(&deepest_node)?.cast_path() 387 | } else { 388 | None 389 | } 390 | } 391 | } 392 | 393 | impl TryFrom for Literal { 394 | type Error = (); 395 | 396 | fn try_from(token: SyntaxToken) -> Result { 397 | token.parent_ancestors().find_map(Self::cast).ok_or(()) 398 | } 399 | } 400 | 401 | pub(crate) fn get_ancestor>( 402 | start: &SyntaxNode, 403 | ) -> Option { 404 | let mut get_sexp = false; 405 | for n in start.ancestors() { 406 | if Sexp::can_cast(n.kind()) { 407 | if get_sexp { 408 | return None; 409 | } else { 410 | get_sexp = true; 411 | continue; 412 | } 413 | } 414 | if let Some(node) = T::cast(n) { 415 | return Some(node); 416 | } 417 | } 418 | None 419 | } 420 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/errors.rs: -------------------------------------------------------------------------------- 1 | use rowan::TextRange; 2 | 3 | use crate::{models::ValueKind, syntax::SyntaxKind}; 4 | 5 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 6 | pub enum ErrorKind { 7 | Unexpected(SyntaxKind), 8 | UnexpectedVarargs, 9 | MultiVarargs, 10 | UnexpectedEof, 11 | EmptyList, 12 | Dismatched, 13 | Unterminated(SyntaxKind), 14 | Undefined, 15 | Unused, 16 | GlobalConflict, 17 | MissingWhitespace, 18 | MacroWhitespace, 19 | InvalidSymbol, 20 | MethodNotAllowed, 21 | FieldAndMethodNotAllowed, 22 | LiteralCall(ValueKind), 23 | DirectCall(ValueKind), 24 | MultiCatch, 25 | CatchNotLast, 26 | Deprecated(&'static str, &'static str), 27 | } 28 | 29 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 30 | pub struct Error { 31 | pub range: TextRange, 32 | pub kind: ErrorKind, 33 | } 34 | 35 | impl Error { 36 | pub(crate) fn new(range: TextRange, kind: ErrorKind) -> Self { 37 | Error { range, kind } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/lexer.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, ops::Range}; 2 | 3 | use once_cell::sync::Lazy; 4 | use regex::Regex; 5 | 6 | use crate::syntax::SyntaxKind; 7 | 8 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 9 | pub(crate) struct Token { 10 | pub(crate) kind: SyntaxKind, 11 | pub(crate) text: String, 12 | pub(crate) range: Range, 13 | } 14 | 15 | impl Token { 16 | #[allow(unused)] 17 | pub(crate) fn new( 18 | kind: SyntaxKind, 19 | text: &str, 20 | range: Range, 21 | ) -> Self { 22 | Self { kind, text: text.to_string(), range } 23 | } 24 | } 25 | 26 | pub(crate) struct Lexer<'a> { 27 | source: Box + 'a>, 28 | offset: usize, 29 | 30 | current: String, 31 | peek: Option, 32 | } 33 | 34 | impl<'a> Lexer<'a> { 35 | pub(crate) fn new( 36 | mut source: Box + 'a>, 37 | ) -> Self { 38 | let peek = source.next(); 39 | 40 | Self { source, offset: 0, current: String::new(), peek } 41 | } 42 | } 43 | 44 | static TABLE: Lazy> = Lazy::new(|| { 45 | let mut table: HashMap = HashMap::new(); 46 | crate::syntax::TOEKN.iter().for_each(|(t, s)| { 47 | table.insert(t.to_string(), *s); 48 | }); 49 | table 50 | }); 51 | 52 | static RE_FLOAT: Lazy = Lazy::new(|| { 53 | Regex::new(&format!( 54 | "^({}|{}|{})({})?$", 55 | r"[+-]?(0x)?\.[0-9][0-9_]*", 56 | r"[+-]?[0-9]+[0-9_]*\.?[0-9_]*", 57 | r"[+-]?0x[0-9a-f]+[0-9a-f_]*\.?[0-9_]*", 58 | r"[eE][+-]?[0-9_]+" 59 | )) 60 | .unwrap() 61 | }); 62 | 63 | const BOUND: [char; 8] = ['(', ')', '[', ']', '{', '}', ',', '`']; 64 | 65 | static RE_INTEGER: Lazy = Lazy::new(|| { 66 | Regex::new(&format!("^{}$", r#"[+-]?(0x)?[0-9][0-9_]*"#)).unwrap() 67 | }); 68 | 69 | static RE_COLON_STRING: Lazy = 70 | Lazy::new(|| Regex::new(&format!("^{}$", r#":[^"'~;@]+"#)).unwrap()); 71 | 72 | static RE_SYMBOL: Lazy = Lazy::new(|| { 73 | Regex::new(&format!("^{}$", r#"[^.:#"'~;,@`&][^"'~;,@`&]*"#)).unwrap() 74 | }); 75 | 76 | impl<'a> Iterator for Lexer<'a> { 77 | type Item = Token; 78 | 79 | fn next(&mut self) -> Option { 80 | if self.current.is_empty() { 81 | if let Some(p) = self.peek.take() { 82 | self.current.push(p); 83 | } else if let Some(next) = self.source.next() { 84 | self.current.push(next); 85 | } else { 86 | return Some(Token { 87 | kind: SyntaxKind::END, 88 | text: "".to_string(), 89 | range: self.offset..self.offset, 90 | }); 91 | } 92 | } 93 | 94 | let head = self.current.chars().next().unwrap(); 95 | let head_kind = kind_by_char(head); 96 | let token_kind = match head_kind { 97 | TokenKind::Word => { 98 | for c in self.source.by_ref() { 99 | let cur_kind = kind_by_char(c); 100 | if head_kind == cur_kind || cur_kind == TokenKind::Hash { 101 | self.current.push(c); 102 | } else { 103 | self.peek = Some(c); 104 | break; 105 | } 106 | } 107 | if let Some(kind) = TABLE.get(&self.current) { 108 | *kind 109 | } else if RE_INTEGER.is_match(&self.current) { 110 | SyntaxKind::INTEGER 111 | } else if RE_FLOAT.is_match(&self.current) { 112 | SyntaxKind::FLOAT 113 | } else if RE_SYMBOL.is_match(&self.current) { 114 | SyntaxKind::SYMBOL 115 | } else if RE_COLON_STRING.is_match(&self.current) { 116 | SyntaxKind::COLON_STRING 117 | } else { 118 | SyntaxKind::ERROR 119 | } 120 | } 121 | TokenKind::Hash => { 122 | self.peek = self.source.next(); 123 | if self 124 | .peek 125 | .map(|p| p.is_whitespace() || p == ')') 126 | .unwrap_or(true) 127 | { 128 | SyntaxKind::LENGTH 129 | } else { 130 | SyntaxKind::HASHFN 131 | } 132 | } 133 | TokenKind::QuoteString => { 134 | let mut escaped = false; 135 | let mut found = false; 136 | 137 | for c in self.source.by_ref() { 138 | self.current.push(c); 139 | if c == '\\' { 140 | escaped = !escaped; 141 | continue; 142 | } 143 | if c == '"' && !escaped { 144 | found = true; 145 | break; 146 | } 147 | escaped = false; 148 | } 149 | if found { 150 | SyntaxKind::QUOTE_STRING 151 | } else { 152 | SyntaxKind::ERROR 153 | } 154 | } 155 | TokenKind::Comment => { 156 | while let Some(c) = self.source.next() { 157 | self.current.push(c); 158 | if c == '\r' { 159 | if let Some(c) = self.source.next() { 160 | if c == '\n' { 161 | self.current.push(c); 162 | } else { 163 | self.peek = Some(c) 164 | } 165 | } 166 | break; 167 | } else if c == '\n' { 168 | break; 169 | } 170 | } 171 | SyntaxKind::COMMENT 172 | } 173 | TokenKind::Whitespace => { 174 | for c in self.source.by_ref() { 175 | if head_kind == kind_by_char(c) { 176 | self.current.push(c); 177 | } else { 178 | self.peek = Some(c); 179 | break; 180 | } 181 | } 182 | SyntaxKind::WHITESPACE 183 | } 184 | TokenKind::Bound => match self.current.as_str() { 185 | "(" => SyntaxKind::L_PAREN, 186 | ")" => SyntaxKind::R_PAREN, 187 | "[" => SyntaxKind::L_BRACKET, 188 | "]" => SyntaxKind::R_BRACKET, 189 | "{" => SyntaxKind::L_BRACE, 190 | "}" => SyntaxKind::R_BRACE, 191 | "`" => SyntaxKind::BACKTICK, 192 | "," => SyntaxKind::COMMA, 193 | _ => SyntaxKind::ERROR, 194 | }, 195 | }; 196 | 197 | let text = std::mem::take(&mut self.current); 198 | let start = self.offset; 199 | self.offset += text.len(); 200 | 201 | Some(Token { kind: token_kind, text, range: start..self.offset }) 202 | } 203 | } 204 | 205 | pub(crate) fn validate(s: &str, kind: SyntaxKind) -> bool { 206 | let mut lexer = Lexer::new(Box::new(s.chars())); 207 | if let Some(token) = lexer.next() { 208 | token.kind == kind && lexer.next().unwrap().kind == SyntaxKind::END 209 | } else { 210 | false 211 | } 212 | } 213 | 214 | pub(crate) fn validate_symbol(symbol: &str) -> bool { 215 | let mut lexer = Lexer::new(Box::new(symbol.chars())); 216 | if let Some(token) = lexer.next() { 217 | if token.kind != SyntaxKind::SYMBOL { 218 | return false; 219 | } 220 | for c in token.text.chars() { 221 | if c == '.' || c == ':' { 222 | return false; 223 | } 224 | } 225 | if lexer.next().unwrap().kind != SyntaxKind::END { 226 | return false; 227 | } 228 | return !Vec::from(include!("static/reserved")) 229 | .contains(&token.text.as_str()); 230 | } 231 | false 232 | } 233 | 234 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 235 | enum TokenKind { 236 | Word, 237 | Whitespace, 238 | Comment, 239 | QuoteString, 240 | Bound, 241 | Hash, 242 | } 243 | 244 | fn kind_by_char(c: char) -> TokenKind { 245 | if c.is_whitespace() { 246 | TokenKind::Whitespace 247 | } else if c == ';' { 248 | TokenKind::Comment 249 | } else if c == '"' { 250 | TokenKind::QuoteString 251 | } else if c == '#' { 252 | TokenKind::Hash 253 | } else if BOUND.contains(&c) { 254 | TokenKind::Bound 255 | } else { 256 | TokenKind::Word 257 | } 258 | } 259 | 260 | #[cfg(test)] 261 | mod tests { 262 | use super::*; 263 | 264 | fn assert_lex(text: &str, tokens: &[Token]) { 265 | let mut lex = Lexer::new(Box::new(text.chars())); 266 | tokens.iter().for_each(|token| { 267 | let n = match lex.next() { 268 | Some(n) if n.kind == SyntaxKind::WHITESPACE => lex.next(), 269 | n => n, 270 | }; 271 | assert_eq!(n, Some(token.to_owned())); 272 | }); 273 | assert_eq!(lex.next().unwrap().kind, SyntaxKind::END); 274 | } 275 | 276 | #[test] 277 | fn symbol() { 278 | assert_lex( 279 | "?我❤️logos#$%^*-+=/|\\ local locals :into :intobreach logos:into \ 280 | _", 281 | &[ 282 | Token::new( 283 | SyntaxKind::SYMBOL, 284 | "?我❤️logos#$%^*-+=/|\\", 285 | 00..26, 286 | ), 287 | Token::new(SyntaxKind::KEYWORD_LOCAL, "local", 27..32), 288 | Token::new(SyntaxKind::SYMBOL, "locals", 33..39), 289 | Token::new(SyntaxKind::KEYWORD_INTO, ":into", 40..45), 290 | Token::new(SyntaxKind::COLON_STRING, ":intobreach", 46..57), 291 | Token::new(SyntaxKind::SYMBOL, "logos:into", 58..68), 292 | Token::new(SyntaxKind::SYMBOL, "_", 69..70), 293 | ], 294 | ); 295 | } 296 | 297 | #[test] 298 | fn number() { 299 | assert_lex("+123 1.23 -1_._2__3E2_ .1", &[ 300 | Token::new(SyntaxKind::INTEGER, "+123", 0..4), 301 | Token::new(SyntaxKind::FLOAT, "1.23", 5..9), 302 | Token::new(SyntaxKind::FLOAT, "-1_._2__3E2_", 10..22), 303 | Token::new(SyntaxKind::FLOAT, ".1", 23..25), 304 | ]); 305 | } 306 | 307 | #[ignore = "FIXME"] 308 | #[test] 309 | fn _number() { 310 | assert_lex("+_1.", &[Token::new(SyntaxKind::FLOAT, "+_1.", 0..4)]); 311 | } 312 | 313 | #[test] 314 | fn string() { 315 | assert_lex( 316 | r#" "logos\"" "multi 317 | lines" :https://github.com/maciejhirsz/logos"#, 318 | &[ 319 | Token::new(SyntaxKind::QUOTE_STRING, r#""logos\"""#, 1..10), 320 | Token::new( 321 | SyntaxKind::QUOTE_STRING, 322 | "\"multi 323 | lines\"", 324 | 11..50, 325 | ), 326 | Token::new( 327 | SyntaxKind::COLON_STRING, 328 | ":https://github.com/maciejhirsz/logos", 329 | 51..88, 330 | ), 331 | ], 332 | ); 333 | } 334 | 335 | #[test] 336 | fn list() { 337 | assert_lex("(-> false (not) (#$) ((fn [x] (# x))))", &[ 338 | Token::new(SyntaxKind::L_PAREN, "(", 0..1), 339 | Token::new(SyntaxKind::THREAD, "->", 1..3), 340 | Token::new(SyntaxKind::BOOL, "false", 4..9), 341 | Token::new(SyntaxKind::L_PAREN, "(", 10..11), 342 | Token::new(SyntaxKind::OPERATOR, "not", 11..14), 343 | Token::new(SyntaxKind::R_PAREN, ")", 14..15), 344 | Token::new(SyntaxKind::L_PAREN, "(", 16..17), 345 | Token::new(SyntaxKind::HASHFN, "#", 17..18), 346 | Token::new(SyntaxKind::SYMBOL, "$", 18..19), 347 | Token::new(SyntaxKind::R_PAREN, ")", 19..20), 348 | Token::new(SyntaxKind::L_PAREN, "(", 21..22), 349 | Token::new(SyntaxKind::L_PAREN, "(", 22..23), 350 | Token::new(SyntaxKind::KEYWORD_FN, "fn", 23..25), 351 | Token::new(SyntaxKind::L_BRACKET, "[", 26..27), 352 | Token::new(SyntaxKind::SYMBOL, "x", 27..28), 353 | Token::new(SyntaxKind::R_BRACKET, "]", 28..29), 354 | Token::new(SyntaxKind::L_PAREN, "(", 30..31), 355 | Token::new(SyntaxKind::LENGTH, "#", 31..32), 356 | Token::new(SyntaxKind::SYMBOL, "x", 33..34), 357 | Token::new(SyntaxKind::R_PAREN, ")", 34..35), 358 | Token::new(SyntaxKind::R_PAREN, ")", 35..36), 359 | Token::new(SyntaxKind::R_PAREN, ")", 36..37), 360 | Token::new(SyntaxKind::R_PAREN, ")", 37..38), 361 | ]); 362 | } 363 | 364 | #[test] 365 | fn comment() { 366 | assert_lex("(print :logos; this is comment)\n\n)", &[ 367 | Token::new(SyntaxKind::L_PAREN, "(", 0..1), 368 | Token::new(SyntaxKind::SYMBOL, "print", 1..6), 369 | Token::new(SyntaxKind::COLON_STRING, ":logos", 7..13), 370 | Token::new(SyntaxKind::COMMENT, "; this is comment)\n", 13..32), 371 | Token::new(SyntaxKind::R_PAREN, ")", 33..34), 372 | ]); 373 | } 374 | 375 | #[test] 376 | fn validate() { 377 | assert!(validate_symbol("abc")); 378 | assert!(!validate_symbol("a.bc")); 379 | assert!(!validate_symbol("a:bc")); 380 | } 381 | } 382 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod ast; 2 | mod errors; 3 | mod lexer; 4 | mod parser; 5 | mod syntax; 6 | 7 | use std::collections::HashSet; 8 | 9 | pub use ast::{models, Action, Ast, Definition}; 10 | pub use errors::{Error, ErrorKind}; 11 | pub use rowan::TextRange; 12 | pub(crate) use syntax::SyntaxKind; 13 | 14 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 15 | pub(crate) enum FennelLanguage {} 16 | impl rowan::Language for FennelLanguage { 17 | type Kind = syntax::SyntaxKind; 18 | 19 | fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind { 20 | assert!(raw.0 <= syntax::SyntaxKind::ROOT as u16); 21 | raw.0.into() 22 | } 23 | 24 | fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind { 25 | kind.into() 26 | } 27 | } 28 | 29 | type SyntaxNode = rowan::SyntaxNode; 30 | type SyntaxToken = rowan::SyntaxToken; 31 | type SyntaxElement = rowan::SyntaxElement; 32 | 33 | pub fn parse( 34 | text: impl Iterator, 35 | globals: HashSet, 36 | ) -> ast::Ast { 37 | let parsed = parser::Parser::new(Box::new(text)).parse(); 38 | ast::Ast::new(parsed.green_node, parsed.errors, globals) 39 | } 40 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/parser/bnf.rs: -------------------------------------------------------------------------------- 1 | use rowan::TextRange; 2 | 3 | use crate::{parser::sets::TokenSet, SyntaxKind}; 4 | 5 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 6 | pub(crate) struct Rule { 7 | pub(crate) expect: SyntaxKind, 8 | pub(crate) notation: Notation, 9 | } 10 | 11 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 12 | pub(crate) enum Notation { 13 | Once, 14 | #[allow(dead_code)] 15 | OncePeek(TokenSet), 16 | Repeat(TokenSet), 17 | Repeat1(TokenSet), 18 | Repeat1Peek(TokenSet, TokenSet), 19 | RepeatPeek(TokenSet, TokenSet), 20 | Optional(TokenSet), 21 | Close(Option), 22 | } 23 | 24 | impl Rule { 25 | pub(crate) fn expand(&self) -> Option { 26 | use Notation::*; 27 | 28 | let kind = self.expect; 29 | match self.notation { 30 | Repeat(l) | Repeat1(l) => { 31 | Some(Rule { expect: kind, notation: Repeat(l) }) 32 | } 33 | RepeatPeek(l1, l2) | Repeat1Peek(l1, l2) => { 34 | Some(Rule { expect: kind, notation: RepeatPeek(l1, l2) }) 35 | } 36 | _ => None, 37 | } 38 | } 39 | } 40 | 41 | macro_rules! notation { 42 | (($kind:ident)) => { 43 | crate::parser::bnf::Rule { expect: $kind, notation: Once } 44 | }; 45 | (($kind:ident ? $set:expr)) => { 46 | crate::parser::bnf::Rule { expect: $kind, notation: Optional($set) } 47 | }; 48 | (($kind:ident * $set:expr)) => { 49 | crate::parser::bnf::Rule { expect: $kind, notation: Repeat($set) } 50 | }; 51 | (($kind:ident + $set:expr)) => { 52 | crate::parser::bnf::Rule { expect: $kind, notation: Repeat1($set) } 53 | }; 54 | (($kind:ident ** $set:expr, $set2:expr)) => { 55 | crate::parser::bnf::Rule { 56 | expect: $kind, 57 | notation: RepeatPeek($set, $set2), 58 | } 59 | }; 60 | (($kind:ident ++ $set:expr, $set2:expr)) => { 61 | crate::parser::bnf::Rule { 62 | expect: $kind, 63 | notation: Repeat1Peek($set, $set2), 64 | } 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/parser/helper.rs: -------------------------------------------------------------------------------- 1 | use rowan::{TextRange, TextSize}; 2 | 3 | pub(crate) fn text_range(range: &std::ops::Range) -> TextRange { 4 | TextRange::new( 5 | TextSize::from(range.start as u32), 6 | TextSize::from(range.end as u32), 7 | ) 8 | } 9 | 10 | pub(crate) fn text_range_with_offset( 11 | range: &std::ops::Range, 12 | offset: (i32, i32), 13 | ) -> TextRange { 14 | TextRange::new( 15 | TextSize::from((range.start as i32 + offset.0) as u32), 16 | TextSize::from((range.end as i32 + offset.1) as u32), 17 | ) 18 | } 19 | 20 | macro_rules! expand_rules { 21 | ( 22 | $self:ident, $callback:ident, 23 | ($cur_rule:ident, $cur_token:ident), 24 | $({$node:ident ::= ($kind:ident $($notation:tt)*) 25 | $(, $rest_rules:tt )* },)* 26 | ) => { 27 | match $cur_rule.expect { 28 | $( $node if crate::parser::sets::first_set( 29 | $kind, $cur_token.kind) => { 30 | $self.$callback( 31 | $cur_token, 32 | vec![ 33 | notation!(($kind $($notation)*)), 34 | $(notation!($rest_rules)),* 35 | ].into_iter()); 36 | return; 37 | }, )* 38 | _ => {}, 39 | } 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/parser/sets.rs: -------------------------------------------------------------------------------- 1 | use crate::SyntaxKind::{self, *}; 2 | 3 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 4 | #[allow(non_camel_case_types)] 5 | #[allow(clippy::upper_case_acronyms)] 6 | pub(crate) enum TokenSet { 7 | END, 8 | R_PAREN, 9 | L_BRACKET, 10 | R_BRACKET, 11 | R_BRACE, 12 | VARARG_BRACKET, 13 | ITERATOR, 14 | UNTIL, 15 | COLLECT, 16 | MATCH_PATTERN, 17 | CATCH, 18 | } 19 | 20 | impl TokenSet { 21 | pub(crate) fn contains(self, k: SyntaxKind) -> bool { 22 | match self { 23 | Self::END => k == END, 24 | Self::R_PAREN => k == R_PAREN, 25 | Self::L_BRACKET => k == L_BRACKET, 26 | Self::R_BRACKET => k == R_BRACKET, 27 | Self::R_BRACE => k == R_BRACE, 28 | Self::VARARG_BRACKET => [VARARG, R_BRACKET].contains(&k), 29 | Self::ITERATOR => [L_PAREN, BACKTICK, COMMA, HASHFN].contains(&k), 30 | Self::UNTIL => [KEYWORD_UNTIL, R_BRACKET].contains(&k), 31 | Self::COLLECT => [KEYWORD_COLLECT, R_BRACKET].contains(&k), 32 | Self::MATCH_PATTERN => [ 33 | FLOAT, 34 | INTEGER, 35 | QUOTE_STRING, 36 | COLON_STRING, 37 | BOOL, 38 | NIL, 39 | SYMBOL, 40 | L_BRACKET, 41 | L_BRACE, 42 | ] 43 | .contains(&k), 44 | Self::CATCH => k == KEYWORD_CATCH, 45 | } 46 | } 47 | } 48 | 49 | // That handwrite first set is ridiculous, but it just works. 50 | #[rustfmt::skip] 51 | pub(crate) fn first_set(syntax: SyntaxKind, cur: SyntaxKind) -> bool { 52 | match syntax { 53 | N_SEXP => [ 54 | SYMBOL, COMMA, VARARG, FLOAT, INTEGER, QUOTE_STRING, COLON_STRING, 55 | BOOL, NIL, L_BRACKET, L_BRACE, L_PAREN, BACKTICK, 56 | HASHFN, 57 | ].contains(&cur), 58 | // No COMMA. 59 | // COMMA -> N_LIST -> N_MACRO_UNQUOTE 60 | N_ATOM => [ 61 | SYMBOL, VARARG, FLOAT, INTEGER, QUOTE_STRING, COLON_STRING, 62 | BOOL, NIL, L_BRACKET, L_BRACE, 63 | ].contains(&cur), 64 | N_LIST => [ 65 | L_PAREN, BACKTICK, COMMA, HASHFN, 66 | ].contains(&cur), 67 | N_LITERAL => [ 68 | FLOAT, INTEGER, QUOTE_STRING, COLON_STRING, BOOL, NIL, 69 | ].contains(&cur), 70 | N_STRING_LITERAL => [ 71 | QUOTE_STRING, COLON_STRING, 72 | ].contains(&cur), 73 | N_NUMBER => [FLOAT, INTEGER].contains(&cur), 74 | N_SEQ_TABLE => [L_BRACKET].contains(&cur), 75 | N_KV_TABLE => [L_BRACE].contains(&cur), 76 | N_L_SYMBOL => [SYMBOL, COMMA].contains(&cur), 77 | N_R_SYMBOL => [SYMBOL, COMMA].contains(&cur), 78 | N_L_R_SYMBOL => [SYMBOL, COMMA].contains(&cur), 79 | N_L_OR_R_SYMBOL => [SYMBOL, COMMA].contains(&cur), 80 | N_KEY => [ 81 | SYMBOL, COMMA, VARARG, FLOAT, INTEGER, QUOTE_STRING, 82 | COLON_STRING, BOOL, NIL, L_BRACKET, L_BRACE, L_PAREN, 83 | BACKTICK, HASHFN, 84 | ].contains(&cur), 85 | N_MACRO_LIST => [BACKTICK, COMMA, HASHFN].contains(&cur), 86 | N_MACRO_HASH => [HASHFN].contains(&cur), 87 | N_MACRO_UNQUOTE => [COMMA].contains(&cur), 88 | N_MACRO_QUOTE => [BACKTICK].contains(&cur), 89 | N_SYMBOL_CALL => [SYMBOL, COMMA].contains(&cur), 90 | N_VAR => [KEYWORD_VAR].contains(&cur), 91 | N_SET => [KEYWORD_SET].contains(&cur), 92 | N_TSET => [KEYWORD_TSET].contains(&cur), 93 | N_LOCAL => [KEYWORD_LOCAL].contains(&cur), 94 | 95 | N_VALUES => [KEYWORD_VALUES].contains(&cur), 96 | N_GLOBAL => [KEYWORD_GLOBAL].contains(&cur), 97 | N_IMPORT_MACROS => [KEYWORD_IMPORT_MACROS].contains(&cur), 98 | N_REQUIRE_MACROS => [KEYWORD_REQUIRE_MACROS].contains(&cur), 99 | N_MACRO => [KEYWORD_MACRO].contains(&cur), 100 | N_MACROS => [KEYWORD_MACROS].contains(&cur), 101 | N_EVAL_COMPILER => [KEYWORD_EVAL_COMPILER].contains(&cur), 102 | N_FN => [KEYWORD_FN].contains(&cur), 103 | N_LAMBDA => [KEYWORD_LAMBDA].contains(&cur), 104 | N_PARTIAL => [KEYWORD_PARTIAL].contains(&cur), 105 | N_LET => [KEYWORD_LET].contains(&cur), 106 | N_MATCH => [KEYWORD_MATCH].contains(&cur), 107 | N_MATCH_TRY => [KEYWORD_MATCH_TRY].contains(&cur), 108 | N_WITH_OPEN => [KEYWORD_WITH_OPEN].contains(&cur), 109 | N_PICK_VALUE => [KEYWORD_PICK_VALUES].contains(&cur), 110 | N_IF => [KEYWORD_IF].contains(&cur), 111 | N_WHEN => [KEYWORD_WHEN].contains(&cur), 112 | N_EACH => [KEYWORD_EACH].contains(&cur), 113 | N_FOR => [KEYWORD_FOR].contains(&cur), 114 | N_COLLECT => [KEYWORD_COLLECT].contains(&cur), 115 | N_ICOLLECT => [KEYWORD_ICOLLECT].contains(&cur), 116 | N_FCOLLECT => [KEYWORD_FCOLLECT].contains(&cur), 117 | N_ACCUMULATE => [KEYWORD_ACCUMULATE].contains(&cur), 118 | N_DO => [KEYWORD_DO].contains(&cur), 119 | N_WHILE => [KEYWORD_WHILE].contains(&cur), 120 | 121 | N_OPERATION => [OPERATOR, KEYWORD_OR, COLON, LENGTH].contains(&cur), 122 | 123 | N_THREAD => [THREAD].contains(&cur), 124 | N_CALL_FN => [ 125 | SYMBOL, COMMA, L_PAREN, BACKTICK, HASHFN, 126 | ].contains(&cur), 127 | 128 | N_DOTO => [KEYWORD_DOTO].contains(&cur), 129 | N_INCLUDE => [KEYWORD_INCLUDE].contains(&cur), 130 | N_LUA => [KEYWORD_LUA].contains(&cur), 131 | N_PICK_ARGS => [KEYWORD_PICK_ARGS].contains(&cur), 132 | N_MACRODEBUG => [KEYWORD_MACRODEBUG].contains(&cur), 133 | 134 | N_ASSIGN_PATTERN => [SYMBOL, COMMA, L_BRACKET, L_BRACE, CAPTURE].contains(&cur), 135 | N_SET_PATTERN => [SYMBOL, COMMA, L_BRACKET, L_BRACE, CAPTURE].contains(&cur), 136 | N_ASSIGN_PATTERN_LIST => [L_PAREN].contains(&cur), 137 | N_SET_PATTERN_LIST => [L_PAREN].contains(&cur), 138 | N_ASSIGN_PATTERN_TABLE => [L_BRACKET].contains(&cur), 139 | N_SET_PATTERN_TABLE => [L_BRACKET].contains(&cur), 140 | N_ASSIGN_PATTERN_KV_TABLE => [L_BRACE].contains(&cur), 141 | N_SET_PATTERN_KV_TABLE => [L_BRACE].contains(&cur), 142 | N_ASSIGN_PATTERN_KEY => [SYMBOL, COMMA, L_PAREN, 143 | FLOAT, INTEGER, QUOTE_STRING, COLON_STRING, BOOL, NIL, 144 | CAPTURE, 145 | ].contains(&cur), 146 | N_SET_PATTERN_KEY => [SYMBOL, COMMA, L_PAREN, 147 | FLOAT, INTEGER, QUOTE_STRING, COLON_STRING, BOOL, NIL, 148 | CAPTURE, 149 | ].contains(&cur), 150 | N_ASSIGN_PATTERN_KEY_NEST => [L_PAREN].contains(&cur), 151 | N_SET_PATTERN_KEY_NEST => [L_PAREN].contains(&cur), 152 | N_RANGE_START => [ 153 | SYMBOL, COMMA, VARARG, FLOAT, INTEGER, QUOTE_STRING, 154 | COLON_STRING, BOOL, NIL, L_BRACKET, L_BRACE, L_PAREN, 155 | BACKTICK, HASHFN, 156 | ].contains(&cur), 157 | N_UNTIL_CLAUSE => [KEYWORD_UNTIL].contains(&cur), 158 | N_INTO_CLAUSE => [KEYWORD_INTO].contains(&cur), 159 | N_MATCH_PATTERN_TOP => [ 160 | FLOAT, INTEGER, QUOTE_STRING, COLON_STRING, BOOL, NIL, 161 | SYMBOL, COMMA, L_BRACKET, L_BRACE, L_PAREN, 162 | ].contains(&cur), 163 | N_MATCH_PATTERN => [ 164 | FLOAT, INTEGER, QUOTE_STRING, COLON_STRING, BOOL, NIL, 165 | SYMBOL, COMMA, L_BRACKET, L_BRACE, CAPTURE, 166 | ].contains(&cur), 167 | N_WHERE_CLAUSE => [KEYWORD_WHERE].contains(&cur), 168 | N_MATCH_CLAUSE => [ 169 | FLOAT, INTEGER, QUOTE_STRING, COLON_STRING, BOOL, NIL, 170 | SYMBOL, COMMA, L_BRACKET, L_BRACE, L_PAREN, 171 | ].contains(&cur), 172 | N_MATCH_TRY_LIST => [L_PAREN].contains(&cur), 173 | N_MATCH_TRY_CLAUSE => [ 174 | FLOAT, INTEGER, QUOTE_STRING, COLON_STRING, BOOL, NIL, 175 | SYMBOL, COMMA, L_BRACKET, L_BRACE, L_PAREN, 176 | ].contains(&cur), 177 | N_CATCH_LIST => [L_PAREN].contains(&cur), 178 | N_CATCH => [KEYWORD_CATCH].contains(&cur), 179 | N_GUARD_CLAUSE => [QUESTION].contains(&cur), 180 | N_OR_CLAUSE_LIST => [L_PAREN].contains(&cur), 181 | N_MATCH_PATTERN_TABLE => [L_BRACKET].contains(&cur), 182 | N_MATCH_PATTERN_KV_TABLE => [L_BRACE].contains(&cur), 183 | N_MATCH_PATTERN_LIST => [L_PAREN].contains(&cur), 184 | N_IMPORT_MACROS_KV_TABLE => [L_BRACE].contains(&cur), 185 | N_IMPORT_MACROS_DESTRUCT => [L_BRACE, SYMBOL, COMMA].contains(&cur), 186 | _ => syntax == cur, 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/static/compiler-macro: -------------------------------------------------------------------------------- 1 | [ 2 | "list", 3 | "sym", 4 | "gensym", 5 | "list?", 6 | "sym?", 7 | "table?", 8 | "sequence?", 9 | "varg?", 10 | "multi-sym?", 11 | "comment?", 12 | "view", 13 | "get-scope", 14 | "assert-compile", 15 | "in-scope?", 16 | "macroexpand", 17 | ] 18 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/static/globals: -------------------------------------------------------------------------------- 1 | [ 2 | "_G", 3 | "_VERSION", 4 | "assert", 5 | "collectgarbage", 6 | "dofile", 7 | "error", 8 | "getfenv", 9 | "getmetatable", 10 | "ipairs", 11 | "load", 12 | "loadfile", 13 | "loadstring", 14 | "module", 15 | "next", 16 | "pairs", 17 | "pcall", 18 | "print", 19 | "rawequal", 20 | "rawget", 21 | "rawset", 22 | "require", 23 | "select", 24 | "setfenv", 25 | "setmetatable", 26 | "tonumber", 27 | "tostring", 28 | "type", 29 | "unpack", 30 | "xpcall", 31 | 32 | "coroutine", 33 | // "coroutine.create", 34 | // "coroutine.resume", 35 | // "coroutine.running", 36 | // "coroutine.status", 37 | // "coroutine.wrap", 38 | // "coroutine.yield", 39 | 40 | "debug", 41 | // "debug.debug", 42 | // "debug.getfenv", 43 | // "debug.gethook", 44 | // "debug.getinfo", 45 | // "debug.getlocal", 46 | // "debug.getmetatable", 47 | // "debug.getregistry", 48 | // "debug.getupvalue", 49 | // "debug.setfenv", 50 | // "debug.sethook", 51 | // "debug.setlocal", 52 | // "debug.setmetatable", 53 | // "debug.setupvalue", 54 | // "debug.traceback", 55 | 56 | "io", 57 | // "io.close", 58 | // "io.flush", 59 | // "io.input", 60 | // "io.lines", 61 | // "io.open", 62 | // "io.output", 63 | // "io.popen", 64 | // "io.read", 65 | // "io.stderr", 66 | // "io.stdin", 67 | // "io.stdout", 68 | // "io.tmpfile", 69 | // "io.type", 70 | // "io.write", 71 | 72 | "file", 73 | // "file:close", 74 | // "file:flush", 75 | // "file:lines", 76 | // "file:read", 77 | // "file:seek", 78 | // "file:setvbuf", 79 | // "file:write", 80 | 81 | "math", 82 | // "math.abs", 83 | // "math.acos", 84 | // "math.asin", 85 | // "math.atan", 86 | // "math.atan2", 87 | // "math.ceil", 88 | // "math.cos", 89 | // "math.cosh", 90 | // "math.deg", 91 | // "math.exp", 92 | // "math.floor", 93 | // "math.fmod", 94 | // "math.frexp", 95 | // "math.huge", 96 | // "math.ldexp", 97 | // "math.log", 98 | // "math.log10", 99 | // "math.max", 100 | // "math.min", 101 | // "math.modf", 102 | // "math.pi", 103 | // "math.pow", 104 | // "math.rad", 105 | // "math.random", 106 | // "math.randomseed", 107 | // "math.sin", 108 | // "math.sinh", 109 | // "math.sqrt", 110 | // "math.tan", 111 | // "math.tanh", 112 | 113 | "os", 114 | // "os.clock", 115 | // "os.date", 116 | // "os.difftime", 117 | // "os.execute", 118 | // "os.exit", 119 | // "os.getenv", 120 | // "os.remove", 121 | // "os.rename", 122 | // "os.setlocale", 123 | // "os.time", 124 | // "os.tmpname", 125 | 126 | "package", 127 | // "package.cpath", 128 | // "package.loaded", 129 | // "package.loaders", 130 | // "package.loadlib", 131 | // "package.path", 132 | // "package.preload", 133 | // "package.seeall", 134 | 135 | "string", 136 | // "string.byte", 137 | // "string.char", 138 | // "string.dump", 139 | // "string.find", 140 | // "string.format", 141 | // "string.gmatch", 142 | // "string.gsub", 143 | // "string.len", 144 | // "string.lower", 145 | // "string.match", 146 | // "string.rep", 147 | // "string.reverse", 148 | // "string.sub", 149 | // "string.upper", 150 | 151 | "table", 152 | // "table.concat", 153 | // "table.insert", 154 | // "table.maxn", 155 | // "table.remove", 156 | // "table.sort", 157 | 158 | "arg", 159 | ] 160 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/static/globals-func: -------------------------------------------------------------------------------- 1 | [ 2 | "assert", 3 | "collectgarbage", 4 | "dofile", 5 | "error", 6 | "getfenv", 7 | "getmetatable", 8 | "ipairs", 9 | "load", 10 | "loadfile", 11 | "loadstring", 12 | "module", 13 | "next", 14 | "pairs", 15 | "pcall", 16 | "print", 17 | "rawequal", 18 | "rawget", 19 | "rawset", 20 | "require", 21 | "select", 22 | "setfenv", 23 | "setmetatable", 24 | "tonumber", 25 | "tostring", 26 | "type", 27 | "unpack", 28 | "xpcall", 29 | ] 30 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/static/globals-module: -------------------------------------------------------------------------------- 1 | [ 2 | "coroutine", 3 | "debug", 4 | "io", 5 | "file", 6 | "math", 7 | "os", 8 | "package", 9 | "string", 10 | "table", 11 | ] 12 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/static/globals-var: -------------------------------------------------------------------------------- 1 | [ 2 | "_G", 3 | "_VERSION", 4 | "arg", 5 | ] 6 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/static/keywords: -------------------------------------------------------------------------------- 1 | [ 2 | "->", 3 | "->>", 4 | "-?>", 5 | "-?>>", 6 | "fn", 7 | "lambda", 8 | "λ", 9 | "local", 10 | "let", 11 | "set", 12 | "global", 13 | "include", 14 | "accumulate", 15 | "catch", 16 | "collect", 17 | "icollect", 18 | "fcollect", 19 | "do", 20 | "doto", 21 | "each", 22 | "eval-compiler", 23 | "for", 24 | "if", 25 | "import-macros", 26 | "lua", 27 | "macro", 28 | "macrodebug", 29 | "macros", 30 | "match", 31 | "match-try", 32 | "partial", 33 | "pick-args", 34 | "values", 35 | "pick-values", 36 | "require-macros", 37 | "tset", 38 | "var", 39 | "when", 40 | "where", 41 | "while", 42 | "with-open", 43 | ] 44 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/static/literals: -------------------------------------------------------------------------------- 1 | [ 2 | "true", 3 | "false", 4 | "nil", 5 | ] 6 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/static/modules/coroutine: -------------------------------------------------------------------------------- 1 | [ 2 | "create", 3 | "resume", 4 | "running", 5 | "status", 6 | "wrap", 7 | "yield", 8 | ] 9 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/static/modules/debug: -------------------------------------------------------------------------------- 1 | [ 2 | "debug", 3 | "getfenv", 4 | "gethook", 5 | "getinfo", 6 | "getlocal", 7 | "getmetatable", 8 | "getregistry", 9 | "getupvalue", 10 | "setfenv", 11 | "sethook", 12 | "setlocal", 13 | "setmetatable", 14 | "setupvalue", 15 | "traceback", 16 | ] 17 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/static/modules/file: -------------------------------------------------------------------------------- 1 | [ 2 | "file:close", 3 | "file:flush", 4 | "file:lines", 5 | "file:read", 6 | "file:seek", 7 | "file:setvbuf", 8 | "file:write", 9 | ] 10 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/static/modules/io: -------------------------------------------------------------------------------- 1 | [ 2 | "close", 3 | "flush", 4 | "input", 5 | "lines", 6 | "open", 7 | "output", 8 | "popen", 9 | "read", 10 | "stderr", 11 | "stdin", 12 | "stdout", 13 | "tmpfile", 14 | "type", 15 | "write", 16 | ] 17 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/static/modules/math: -------------------------------------------------------------------------------- 1 | [ 2 | "abs", 3 | "acos", 4 | "asin", 5 | "atan", 6 | "atan2", 7 | "ceil", 8 | "cos", 9 | "cosh", 10 | "deg", 11 | "exp", 12 | "floor", 13 | "fmod", 14 | "frexp", 15 | "huge", 16 | "ldexp", 17 | "log", 18 | "log10", 19 | "max", 20 | "min", 21 | "modf", 22 | "pi", 23 | "pow", 24 | "rad", 25 | "random", 26 | "randomseed", 27 | "sin", 28 | "sinh", 29 | "sqrt", 30 | "tan", 31 | "tanh", 32 | ] 33 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/static/modules/os: -------------------------------------------------------------------------------- 1 | [ 2 | "clock", 3 | "date", 4 | "difftime", 5 | "execute", 6 | "exit", 7 | "getenv", 8 | "remove", 9 | "rename", 10 | "setlocale", 11 | "time", 12 | "tmpname", 13 | ] 14 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/static/modules/package: -------------------------------------------------------------------------------- 1 | [ 2 | "cpath", 3 | "loaded", 4 | "loaders", 5 | "loadlib", 6 | "path", 7 | "preload", 8 | "seeall", 9 | ] 10 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/static/modules/string: -------------------------------------------------------------------------------- 1 | [ 2 | "byte", 3 | "char", 4 | "dump", 5 | "find", 6 | "format", 7 | "gmatch", 8 | "gsub", 9 | "len", 10 | "lower", 11 | "match", 12 | "rep", 13 | "reverse", 14 | "sub", 15 | "upper", 16 | ] 17 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/static/modules/table: -------------------------------------------------------------------------------- 1 | [ 2 | "concat", 3 | "insert", 4 | "maxn", 5 | "remove", 6 | "sort", 7 | ] 8 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/static/operator: -------------------------------------------------------------------------------- 1 | [ 2 | "and", 3 | "or", 4 | "not", 5 | "not=", 6 | "lshift", 7 | "rshift", 8 | "band", 9 | "bor", 10 | "bxor", 11 | "bnot", 12 | "length", 13 | ] 14 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/static/reserved: -------------------------------------------------------------------------------- 1 | [ 2 | "and", 3 | "or", 4 | "not", 5 | "not=", 6 | "lshift", 7 | "rshift", 8 | "band", 9 | "bor", 10 | "bxor", 11 | "bnot", 12 | "length", 13 | "->", 14 | "->>", 15 | "-?>", 16 | "-?>>", 17 | "fn", 18 | "lambda", 19 | "λ", 20 | "local", 21 | "let", 22 | "set", 23 | "global", 24 | "include", 25 | "accumulate", 26 | "catch", 27 | "collect", 28 | "icollect", 29 | "do", 30 | "doto", 31 | "each", 32 | "eval-compiler", 33 | "for", 34 | "if", 35 | "import-macros", 36 | ":into", 37 | "lua", 38 | "macro", 39 | "macrodebug", 40 | "macros", 41 | "match", 42 | "match-try", 43 | "partial", 44 | "pick-args", 45 | "values", 46 | "pick-values", 47 | "require-macros", 48 | "tset", 49 | ":until", 50 | "&until", 51 | "var", 52 | "when", 53 | "where", 54 | "while", 55 | "with-open", 56 | ] 57 | -------------------------------------------------------------------------------- /crates/fennel-parser/src/syntax.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 4 | #[repr(u16)] 5 | #[allow(non_camel_case_types)] 6 | #[allow(clippy::upper_case_acronyms)] 7 | pub enum SyntaxKind { 8 | SYMBOL, 9 | SYMBOL_FIELD, 10 | SYMBOL_METHOD, 11 | 12 | FLOAT, 13 | INTEGER, 14 | BOOL, 15 | NIL, 16 | QUOTE_STRING, 17 | COLON_STRING, 18 | KEYWORD_OR, 19 | OPERATOR, 20 | COLON, 21 | HASHFN, 22 | CAPTURE, 23 | LENGTH, 24 | THREAD, 25 | L_PAREN, 26 | R_PAREN, 27 | L_BRACKET, 28 | R_BRACKET, 29 | L_BRACE, 30 | R_BRACE, 31 | COMMA, 32 | VARARG, 33 | QUESTION, 34 | KEYWORD_FN, 35 | KEYWORD_LAMBDA, 36 | KEYWORD_LOCAL, 37 | KEYWORD_LET, 38 | KEYWORD_SET, 39 | KEYWORD_INCLUDE, 40 | KEYWORD_ACCUMULATE, 41 | BACKTICK, 42 | KEYWORD_CATCH, 43 | KEYWORD_COLLECT, 44 | KEYWORD_DO, 45 | KEYWORD_DOTO, 46 | KEYWORD_EACH, 47 | KEYWORD_EVAL_COMPILER, 48 | KEYWORD_FOR, 49 | KEYWORD_GLOBAL, 50 | KEYWORD_ICOLLECT, 51 | KEYWORD_FCOLLECT, 52 | KEYWORD_IF, 53 | KEYWORD_IMPORT_MACROS, 54 | KEYWORD_INTO, 55 | KEYWORD_LUA, 56 | KEYWORD_MACRO, 57 | KEYWORD_MACRODEBUG, 58 | KEYWORD_MACROS, 59 | KEYWORD_MATCH, 60 | KEYWORD_MATCH_TRY, 61 | KEYWORD_PARTIAL, 62 | KEYWORD_PICK_ARGS, 63 | KEYWORD_VALUES, 64 | KEYWORD_PICK_VALUES, 65 | KEYWORD_REQUIRE_MACROS, 66 | KEYWORD_TSET, 67 | KEYWORD_UNTIL, 68 | KEYWORD_VAR, 69 | KEYWORD_WHEN, 70 | KEYWORD_WHERE, 71 | KEYWORD_WHILE, 72 | KEYWORD_WITH_OPEN, 73 | 74 | COMMENT, 75 | WHITESPACE, 76 | ERROR, 77 | 78 | N_LIST, 79 | N_SUBLIST, 80 | N_MACRO_LIST, 81 | N_SEXP, 82 | N_ATOM, 83 | N_SYMBOL_CALL, 84 | N_DOT_SYMBOL, 85 | N_COLON_SYMBOL, 86 | N_LITERAL, 87 | N_STRING_LITERAL, 88 | N_NUMBER, 89 | N_PARAM, 90 | N_SEQ_TABLE, 91 | N_KV_TABLE, 92 | N_KV_PAIR, 93 | N_KEY, 94 | N_VALUE, 95 | N_L_SYMBOL, 96 | N_R_SYMBOL, 97 | N_L_R_SYMBOL, 98 | N_L_OR_R_SYMBOL, 99 | N_FN_NAME, 100 | N_ARGS, 101 | N_BODY, 102 | N_COND, 103 | N_ITERATOR, 104 | N_LEAF_LIST, 105 | N_NODE_LIST, 106 | N_TSET, 107 | N_LOCAL, 108 | N_VALUES, 109 | N_GLOBAL, 110 | N_IMPORT_MACROS, 111 | N_REQUIRE_MACROS, 112 | N_MACRO, 113 | N_MACRO_NAME, 114 | N_MACROS, 115 | N_EVAL_COMPILER, 116 | N_VAR, 117 | N_SET, 118 | N_FOR, 119 | N_FOR_TABLE, 120 | N_ITERATION, 121 | N_ITERATION_VALUE, 122 | N_FN, 123 | N_LAMBDA, 124 | N_HASHFN, 125 | N_MACRO_HASH, 126 | N_MACRO_UNQUOTE, 127 | N_MACRO_QUOTE, 128 | N_PARTIAL, 129 | N_LET, 130 | N_MATCH, 131 | N_WITH_OPEN, 132 | N_PICK_VALUE, 133 | N_IF, 134 | N_WHEN, 135 | N_WHILE, 136 | N_EACH, 137 | N_EACH_TABLE, 138 | N_ICOLLECT, 139 | N_ICOLLECT_TABLE, 140 | N_COLLECT, 141 | N_COLLECT_TABLE, 142 | N_FCOLLECT, 143 | N_FCOLLECT_TABLE, 144 | N_ACCUMULATE, 145 | N_ACCUMULATE_TABLE, 146 | N_DO, 147 | N_OPERATION, 148 | N_THREAD, 149 | N_DOTO, 150 | N_CALL_FN, 151 | N_INCLUDE, 152 | N_LUA, 153 | N_PICK_ARGS, 154 | N_MACRODEBUG, 155 | N_NV_PAIR, 156 | N_NV_PAIR_TABLE, 157 | N_ASSIGN_TABLE, 158 | N_ASSIGN_PAIR, 159 | N_SET_PAIR, 160 | N_ASSIGN_PATTERN, 161 | N_SET_PATTERN, 162 | N_ASSIGN_PATTERN_LIST, 163 | N_SET_PATTERN_LIST, 164 | N_ASSIGN_PATTERN_KV_TABLE, 165 | N_SET_PATTERN_KV_TABLE, 166 | N_ASSIGN_PATTERN_TABLE, 167 | N_SET_PATTERN_TABLE, 168 | N_ASSIGN_PATTERN_KV, 169 | N_SET_PATTERN_KV, 170 | N_ASSIGN_PATTERN_KEY, 171 | N_SET_PATTERN_KEY, 172 | N_ASSIGN_PATTERN_KEY_NEST, 173 | N_SET_PATTERN_KEY_NEST, 174 | N_PARAM_TABLE, 175 | N_RANGE, 176 | N_RANGE_START, 177 | N_RANGE_STOP, 178 | N_RANGE_STEP, 179 | N_COLLECT_CLAUSE, 180 | N_ACCUMULATOR, 181 | N_INITIAL, 182 | N_UNTIL_CLAUSE, 183 | N_GUARD_CLAUSE, 184 | N_INTO_CLAUSE, 185 | N_MATCH_CLAUSE, 186 | N_MATCH_PATTERN, 187 | N_MATCH_PATTERN_TOP, 188 | N_MATCH_PATTERN_LIST, 189 | N_MATCH_PATTERN_LIST_REAL, 190 | N_MATCH_PATTERN_LIST_REST, 191 | N_MATCH_CLAUSE_LIST, 192 | N_MATCH_CLAUSE_LIST_REAL, 193 | N_MATCH_CLAUSE_LIST_REST, 194 | N_MATCH_TRY, 195 | N_MATCH_TRY_CLAUSE, 196 | N_MATCH_TRY_LIST, 197 | N_MATCH_TRY_LIST_REAL, 198 | N_MATCH_PATTERN_TABLE, 199 | N_MATCH_PATTERN_KV, 200 | N_MATCH_PATTERN_KV_TABLE, 201 | N_IMPORT_MACROS_PAIR, 202 | N_IMPORT_MACROS_DESTRUCT, 203 | N_IMPORT_MACROS_KV_TABLE, 204 | N_IMPORT_MACROS_KV_PAIR, 205 | N_WHERE_PATTERN, 206 | N_WHERE_CLAUSE, 207 | N_OR_CLAUSE, 208 | N_OR_CLAUSE_LIST, 209 | N_CATCH, 210 | N_CATCH_LIST, 211 | N_VARARG, 212 | 213 | END, 214 | ROOT, 215 | } 216 | 217 | impl fmt::Display for SyntaxKind { 218 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 219 | use SyntaxKind::*; 220 | let name = match self { 221 | L_PAREN => "`(`".into(), 222 | R_PAREN => "`)`".into(), 223 | L_BRACE => "`{`".into(), 224 | R_BRACE => "`}`".into(), 225 | L_BRACKET => "`[`".into(), 226 | R_BRACKET => "`]`".into(), 227 | 228 | N_PARAM_TABLE => "parameter table".into(), 229 | N_ASSIGN_PATTERN_LIST 230 | | N_MATCH_PATTERN_LIST 231 | | N_ASSIGN_PATTERN_TABLE 232 | | N_MATCH_PATTERN_TABLE 233 | | N_MATCH_TRY_LIST => "pattern table".into(), 234 | N_OR_CLAUSE_LIST => "`or` clause".into(), 235 | N_CATCH_LIST => "`catch` clause".into(), 236 | 237 | N_KV_TABLE 238 | | N_ASSIGN_PATTERN_KV_TABLE 239 | | N_MATCH_PATTERN_KV_TABLE 240 | | N_IMPORT_MACROS_KV_TABLE => "key/value table".into(), 241 | 242 | N_NV_PAIR => "name/value pair".into(), 243 | N_KV_PAIR => "key/value pair".into(), 244 | N_IMPORT_MACROS_PAIR | N_IMPORT_MACROS_KV_PAIR | N_ASSIGN_PAIR => { 245 | "binding pair".into() 246 | } 247 | 248 | N_SEQ_TABLE => "sequential table".into(), 249 | 250 | N_ASSIGN_TABLE => "binding table".into(), 251 | 252 | N_ACCUMULATE => "`accumulate` expression".into(), 253 | N_FOR => "`for` expression".into(), 254 | N_EACH => "`each` expression".into(), 255 | N_ICOLLECT => "`icollect` expression".into(), 256 | N_COLLECT => "`collect` expression".into(), 257 | 258 | N_ACCUMULATE_TABLE => "`accumulate` binding table".into(), 259 | N_FOR_TABLE => "`for` binding table".into(), 260 | N_EACH_TABLE => "`each` binding table".into(), 261 | N_ICOLLECT_TABLE => "`icollect` binding table".into(), 262 | N_COLLECT_TABLE => "`collect` binding table".into(), 263 | 264 | _ => { 265 | let raw = format!("{:?}", self); 266 | if raw.starts_with("N_") { 267 | format!( 268 | "node `{}`", 269 | raw.strip_prefix("N_") 270 | .unwrap() 271 | .to_lowercase() 272 | .replace('_', " ") 273 | ) 274 | } else if raw.starts_with("KEYWORD_") { 275 | format!( 276 | "keyword `{}`", 277 | raw.strip_prefix("KEYWORD_") 278 | .unwrap() 279 | .to_lowercase() 280 | .replace('_', "-") 281 | ) 282 | } else { 283 | raw 284 | } 285 | } 286 | }; 287 | write!(f, "{}", name) 288 | } 289 | } 290 | 291 | pub(crate) mod lists { 292 | use super::SyntaxKind::{self, *}; 293 | 294 | pub(crate) const L_DELIMITERS: &[SyntaxKind] = 295 | &[L_BRACE, L_PAREN, L_BRACKET]; 296 | pub(crate) const OUTBAND: &[SyntaxKind] = &[WHITESPACE, COMMENT]; 297 | pub(crate) const LIST: &[SyntaxKind] = &[ 298 | N_LIST, 299 | N_ASSIGN_PATTERN_LIST, 300 | N_MATCH_PATTERN_LIST, 301 | N_OR_CLAUSE_LIST, 302 | N_MATCH_TRY_LIST, 303 | N_CATCH_LIST, 304 | ]; 305 | pub(crate) const KV_TABLE: &[SyntaxKind] = &[ 306 | N_KV_TABLE, 307 | N_ASSIGN_PATTERN_KV_TABLE, 308 | N_MATCH_PATTERN_KV_TABLE, 309 | N_IMPORT_MACROS_KV_TABLE, 310 | ]; 311 | pub(crate) const TABLE: &[SyntaxKind] = &[ 312 | N_SEQ_TABLE, 313 | N_NV_PAIR_TABLE, 314 | N_ASSIGN_TABLE, 315 | N_PARAM_TABLE, 316 | N_ASSIGN_PATTERN_TABLE, 317 | N_MATCH_PATTERN_TABLE, 318 | N_FOR_TABLE, 319 | N_EACH_TABLE, 320 | N_ICOLLECT_TABLE, 321 | N_COLLECT_TABLE, 322 | N_ACCUMULATE_TABLE, 323 | ]; 324 | } 325 | 326 | pub(crate) const TOEKN: &[(&str, SyntaxKind)] = &[ 327 | ("true", SyntaxKind::BOOL), 328 | ("false", SyntaxKind::BOOL), 329 | ("nil", SyntaxKind::NIL), 330 | ("or", SyntaxKind::KEYWORD_OR), 331 | ("and", SyntaxKind::OPERATOR), 332 | ("+", SyntaxKind::OPERATOR), 333 | ("-", SyntaxKind::OPERATOR), 334 | ("*", SyntaxKind::OPERATOR), 335 | ("/", SyntaxKind::OPERATOR), 336 | ("//", SyntaxKind::OPERATOR), 337 | ("%", SyntaxKind::OPERATOR), 338 | ("^", SyntaxKind::OPERATOR), 339 | (">", SyntaxKind::OPERATOR), 340 | ("<", SyntaxKind::OPERATOR), 341 | (">=", SyntaxKind::OPERATOR), 342 | ("<=", SyntaxKind::OPERATOR), 343 | ("~=", SyntaxKind::OPERATOR), 344 | ("=", SyntaxKind::OPERATOR), 345 | ("not=", SyntaxKind::OPERATOR), 346 | ("lshift", SyntaxKind::OPERATOR), 347 | ("rshift", SyntaxKind::OPERATOR), 348 | ("band", SyntaxKind::OPERATOR), 349 | ("bor", SyntaxKind::OPERATOR), 350 | ("bxor", SyntaxKind::OPERATOR), 351 | ("..", SyntaxKind::OPERATOR), 352 | ("not", SyntaxKind::OPERATOR), 353 | ("bnot", SyntaxKind::OPERATOR), 354 | ("length", SyntaxKind::OPERATOR), 355 | (".", SyntaxKind::OPERATOR), 356 | ("?.", SyntaxKind::OPERATOR), 357 | (":", SyntaxKind::COLON), 358 | ("#", SyntaxKind::HASHFN), 359 | ("&", SyntaxKind::CAPTURE), 360 | ("&as", SyntaxKind::CAPTURE), 361 | ("->", SyntaxKind::THREAD), 362 | ("->>", SyntaxKind::THREAD), 363 | ("-?>", SyntaxKind::THREAD), 364 | ("-?>>", SyntaxKind::THREAD), 365 | ("...", SyntaxKind::VARARG), 366 | ("?", SyntaxKind::QUESTION), 367 | ("fn", SyntaxKind::KEYWORD_FN), 368 | ("lambda", SyntaxKind::KEYWORD_LAMBDA), 369 | ("λ", SyntaxKind::KEYWORD_LAMBDA), 370 | ("local", SyntaxKind::KEYWORD_LOCAL), 371 | ("let", SyntaxKind::KEYWORD_LET), 372 | ("set", SyntaxKind::KEYWORD_SET), 373 | ("include", SyntaxKind::KEYWORD_INCLUDE), 374 | ("accumulate", SyntaxKind::KEYWORD_ACCUMULATE), 375 | ("catch", SyntaxKind::KEYWORD_CATCH), 376 | ("collect", SyntaxKind::KEYWORD_COLLECT), 377 | ("do", SyntaxKind::KEYWORD_DO), 378 | ("doto", SyntaxKind::KEYWORD_DOTO), 379 | ("each", SyntaxKind::KEYWORD_EACH), 380 | ("eval-compiler", SyntaxKind::KEYWORD_EVAL_COMPILER), 381 | ("for", SyntaxKind::KEYWORD_FOR), 382 | ("global", SyntaxKind::KEYWORD_GLOBAL), 383 | ("icollect", SyntaxKind::KEYWORD_ICOLLECT), 384 | ("fcollect", SyntaxKind::KEYWORD_FCOLLECT), 385 | ("if", SyntaxKind::KEYWORD_IF), 386 | ("import-macros", SyntaxKind::KEYWORD_IMPORT_MACROS), 387 | (":into", SyntaxKind::KEYWORD_INTO), 388 | ("&into", SyntaxKind::KEYWORD_INTO), 389 | ("lua", SyntaxKind::KEYWORD_LUA), 390 | ("macro", SyntaxKind::KEYWORD_MACRO), 391 | ("macrodebug", SyntaxKind::KEYWORD_MACRODEBUG), 392 | ("macros", SyntaxKind::KEYWORD_MACROS), 393 | ("match", SyntaxKind::KEYWORD_MATCH), 394 | ("match-try", SyntaxKind::KEYWORD_MATCH_TRY), 395 | ("partial", SyntaxKind::KEYWORD_PARTIAL), 396 | ("pick-args", SyntaxKind::KEYWORD_PICK_ARGS), 397 | ("values", SyntaxKind::KEYWORD_VALUES), 398 | ("pick-values", SyntaxKind::KEYWORD_PICK_VALUES), 399 | ("require-macros", SyntaxKind::KEYWORD_REQUIRE_MACROS), 400 | ("tset", SyntaxKind::KEYWORD_TSET), 401 | (":until", SyntaxKind::KEYWORD_UNTIL), 402 | ("&until", SyntaxKind::KEYWORD_UNTIL), 403 | ("var", SyntaxKind::KEYWORD_VAR), 404 | ("when", SyntaxKind::KEYWORD_WHEN), 405 | ("where", SyntaxKind::KEYWORD_WHERE), 406 | ("while", SyntaxKind::KEYWORD_WHILE), 407 | ("with-open", SyntaxKind::KEYWORD_WITH_OPEN), 408 | ]; 409 | 410 | impl From for SyntaxKind { 411 | #[inline] 412 | fn from(d: u16) -> Self { 413 | assert!(d <= (Self::ROOT as u16)); 414 | unsafe { std::mem::transmute::(d) } 415 | } 416 | } 417 | 418 | impl From for rowan::SyntaxKind { 419 | fn from(kind: SyntaxKind) -> Self { 420 | Self(kind as u16) 421 | } 422 | } 423 | -------------------------------------------------------------------------------- /crates/fennel-parser/testdata/parser/cst: -------------------------------------------------------------------------------- 1 | ROOT@0..19 2 | WHITESPACE@0..1 " " 3 | N_SEXP@1..18 4 | N_LIST@1..18 5 | L_PAREN@1..2 "(" 6 | N_SUBLIST@2..17 7 | N_LOCAL@2..17 8 | KEYWORD_LOCAL@2..7 "local" 9 | WHITESPACE@7..8 " " 10 | N_ASSIGN_PAIR@8..17 11 | N_ASSIGN_PATTERN@8..9 12 | N_L_SYMBOL@8..9 13 | SYMBOL@8..9 "x" 14 | WHITESPACE@9..10 " " 15 | N_SEXP@10..17 16 | N_LIST@10..17 17 | L_PAREN@10..11 "(" 18 | N_SUBLIST@11..16 19 | N_OPERATION@11..16 20 | OPERATOR@11..12 "+" 21 | WHITESPACE@12..13 " " 22 | N_ARGS@13..16 23 | N_SEXP@13..14 24 | N_ATOM@13..14 25 | N_LITERAL@13..14 26 | INTEGER@13..14 "1" 27 | WHITESPACE@14..15 " " 28 | N_SEXP@15..16 29 | N_ATOM@15..16 30 | N_LITERAL@15..16 31 | INTEGER@15..16 "2" 32 | R_PAREN@16..17 ")" 33 | R_PAREN@17..18 ")" 34 | WHITESPACE@18..19 "\n" 35 | END@19..19 "" 36 | -------------------------------------------------------------------------------- /crates/fennel-parser/testdata/parser/raw.fnl: -------------------------------------------------------------------------------- 1 | (local x (+ 1 2)) 2 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | newline_style = "Unix" 2 | max_width = 79 3 | use_small_heuristics = "Max" 4 | format_strings = true 5 | wrap_comments = true 6 | overflow_delimited_expr = true 7 | 8 | condense_wildcard_suffixes = true 9 | format_code_in_doc_comments = true 10 | group_imports = "StdExternalCrate" 11 | imports_granularity = "Crate" 12 | normalize_comments = true 13 | reorder_impl_items = true 14 | use_field_init_shorthand = true 15 | use_try_shorthand = true 16 | --------------------------------------------------------------------------------