├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── Cargo.lock ├── Cargo.toml ├── README.md ├── editors └── code │ ├── language-configuration.json │ ├── out │ ├── extension.js │ └── extension.js.map │ ├── package-lock.json │ ├── package.json │ ├── snippets.json │ ├── src │ └── extension.ts │ ├── syntaxes │ └── pika.tmLanguage.json │ └── tsconfig.json ├── src ├── args.rs ├── common.rs ├── elab │ ├── cxt.rs │ ├── elaborate.rs │ ├── ide_support.rs │ ├── metas.rs │ ├── mod.rs │ ├── pattern.rs │ ├── term.rs │ ├── unify.rs │ ├── val.rs │ └── var.rs ├── lib.rs ├── main.rs ├── parsing │ ├── ast.rs │ ├── lexer.rs │ ├── mod.rs │ ├── parser.rs │ ├── splitter.rs │ └── syntax.rs ├── pretty.rs └── server.rs └── tests ├── Capabilities.pk ├── DepErrors.pk ├── GADTs.pk ├── Iterator.pk ├── References.pk ├── ReferencesErr.pk ├── Smalltt.pk ├── Structs.pk ├── Traits.pk └── runner.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "next" ] 6 | pull_request: 7 | branches: [ "next" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | RUST_BACKTRACE: 1 12 | 13 | jobs: 14 | tests: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | 21 | - name: Download cargo-tarpaulin 22 | run: cargo install cargo-tarpaulin 23 | 24 | - name: Run tests and generate coverage 25 | run: cargo tarpaulin --out lcov 26 | 27 | - name: Upload code coverage 28 | uses: coverallsapp/github-action@v2 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | node_modules 3 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "args": [ 6 | "--extensionDevelopmentPath=${workspaceFolder}/editors/code" 7 | ], 8 | "runtimeExecutable": "${execPath}", 9 | "name": "Launch Extension", 10 | "outFiles": [ 11 | "${workspaceFolder}/editors/code/out/**/*.js" 12 | ], 13 | "preLaunchTask": "Build Extension", 14 | "request": "launch", 15 | "type": "extensionHost" 16 | }, 17 | { 18 | // Used to attach LLDB to a running LSP server. 19 | // NOTE: Might require root permissions. For this run: 20 | // 21 | // `echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope` 22 | // 23 | // Don't forget to set `debug = 2` in `Cargo.toml` before building the server 24 | 25 | "name": "Attach To Server", 26 | "type": "lldb", 27 | "request": "attach", 28 | "program": "${workspaceFolder}/target/debug/pika2", 29 | "pid": "${command:pickMyProcess}", 30 | "sourceLanguages": [ 31 | "rust" 32 | ] 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Build Extension", 6 | "group": "build", 7 | "type": "npm", 8 | "script": "build", 9 | "path": "editors/code/", 10 | "problemMatcher": { 11 | "base": "$tsc", 12 | "fileLocation": ["relative", "${workspaceFolder}/editors/code"], 13 | } 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /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 = "ariadne" 7 | version = "0.3.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "72fe02fc62033df9ba41cba57ee19acf5e742511a140c7dbc3a873e19a19a1bd" 10 | dependencies = [ 11 | "unicode-width", 12 | "yansi", 13 | ] 14 | 15 | [[package]] 16 | name = "autocfg" 17 | version = "1.1.0" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 20 | 21 | [[package]] 22 | name = "bitflags" 23 | version = "1.3.2" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 26 | 27 | [[package]] 28 | name = "cfg-if" 29 | version = "1.0.0" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 32 | 33 | [[package]] 34 | name = "countme" 35 | version = "3.0.1" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" 38 | 39 | [[package]] 40 | name = "crossbeam-channel" 41 | version = "0.5.8" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" 44 | dependencies = [ 45 | "cfg-if", 46 | "crossbeam-utils", 47 | ] 48 | 49 | [[package]] 50 | name = "crossbeam-utils" 51 | version = "0.8.16" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" 54 | dependencies = [ 55 | "cfg-if", 56 | ] 57 | 58 | [[package]] 59 | name = "form_urlencoded" 60 | version = "1.2.0" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" 63 | dependencies = [ 64 | "percent-encoding", 65 | ] 66 | 67 | [[package]] 68 | name = "hashbrown" 69 | version = "0.12.3" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 72 | 73 | [[package]] 74 | name = "heck" 75 | version = "0.3.3" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 78 | dependencies = [ 79 | "unicode-segmentation", 80 | ] 81 | 82 | [[package]] 83 | name = "idna" 84 | version = "0.4.0" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" 87 | dependencies = [ 88 | "unicode-bidi", 89 | "unicode-normalization", 90 | ] 91 | 92 | [[package]] 93 | name = "indexmap" 94 | version = "1.9.3" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 97 | dependencies = [ 98 | "autocfg", 99 | "hashbrown", 100 | ] 101 | 102 | [[package]] 103 | name = "instant" 104 | version = "0.1.12" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 107 | dependencies = [ 108 | "cfg-if", 109 | ] 110 | 111 | [[package]] 112 | name = "itoa" 113 | version = "1.0.9" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 116 | 117 | [[package]] 118 | name = "libc" 119 | version = "0.2.149" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" 122 | 123 | [[package]] 124 | name = "lock_api" 125 | version = "0.4.11" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" 128 | dependencies = [ 129 | "autocfg", 130 | "scopeguard", 131 | ] 132 | 133 | [[package]] 134 | name = "log" 135 | version = "0.4.20" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 138 | 139 | [[package]] 140 | name = "lsp-server" 141 | version = "0.7.4" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "b52dccdf3302eefab8c8a1273047f0a3c3dca4b527c8458d00c09484c8371928" 144 | dependencies = [ 145 | "crossbeam-channel", 146 | "log", 147 | "serde", 148 | "serde_json", 149 | ] 150 | 151 | [[package]] 152 | name = "lsp-types" 153 | version = "0.94.1" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "c66bfd44a06ae10647fe3f8214762e9369fd4248df1350924b4ef9e770a85ea1" 156 | dependencies = [ 157 | "bitflags", 158 | "serde", 159 | "serde_json", 160 | "serde_repr", 161 | "url", 162 | ] 163 | 164 | [[package]] 165 | name = "memoffset" 166 | version = "0.9.0" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" 169 | dependencies = [ 170 | "autocfg", 171 | ] 172 | 173 | [[package]] 174 | name = "oorandom" 175 | version = "11.1.3" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" 178 | 179 | [[package]] 180 | name = "parking_lot" 181 | version = "0.11.2" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" 184 | dependencies = [ 185 | "instant", 186 | "lock_api", 187 | "parking_lot_core", 188 | ] 189 | 190 | [[package]] 191 | name = "parking_lot_core" 192 | version = "0.8.6" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" 195 | dependencies = [ 196 | "cfg-if", 197 | "instant", 198 | "libc", 199 | "redox_syscall", 200 | "smallvec", 201 | "winapi", 202 | ] 203 | 204 | [[package]] 205 | name = "percent-encoding" 206 | version = "2.3.0" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" 209 | 210 | [[package]] 211 | name = "pika2" 212 | version = "0.1.0" 213 | dependencies = [ 214 | "ariadne", 215 | "lsp-server", 216 | "lsp-types", 217 | "ropey", 218 | "rowan", 219 | "salsa", 220 | "serde", 221 | "serde_json", 222 | "yansi", 223 | ] 224 | 225 | [[package]] 226 | name = "proc-macro2" 227 | version = "1.0.69" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" 230 | dependencies = [ 231 | "unicode-ident", 232 | ] 233 | 234 | [[package]] 235 | name = "quote" 236 | version = "1.0.33" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 239 | dependencies = [ 240 | "proc-macro2", 241 | ] 242 | 243 | [[package]] 244 | name = "redox_syscall" 245 | version = "0.2.16" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 248 | dependencies = [ 249 | "bitflags", 250 | ] 251 | 252 | [[package]] 253 | name = "ropey" 254 | version = "1.6.1" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "93411e420bcd1a75ddd1dc3caf18c23155eda2c090631a85af21ba19e97093b5" 257 | dependencies = [ 258 | "smallvec", 259 | "str_indices", 260 | ] 261 | 262 | [[package]] 263 | name = "rowan" 264 | version = "0.15.13" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "906057e449592587bf6724f00155bf82a6752c868d78a8fb3aa41f4e6357cfe8" 267 | dependencies = [ 268 | "countme", 269 | "hashbrown", 270 | "memoffset", 271 | "rustc-hash", 272 | "text-size", 273 | ] 274 | 275 | [[package]] 276 | name = "rustc-hash" 277 | version = "1.1.0" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 280 | 281 | [[package]] 282 | name = "ryu" 283 | version = "1.0.15" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 286 | 287 | [[package]] 288 | name = "salsa" 289 | version = "0.16.1" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "4b84d9f96071f3f3be0dc818eae3327625d8ebc95b58da37d6850724f31d3403" 292 | dependencies = [ 293 | "crossbeam-utils", 294 | "indexmap", 295 | "lock_api", 296 | "log", 297 | "oorandom", 298 | "parking_lot", 299 | "rustc-hash", 300 | "salsa-macros", 301 | "smallvec", 302 | ] 303 | 304 | [[package]] 305 | name = "salsa-macros" 306 | version = "0.16.0" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "cd3904a4ba0a9d0211816177fd34b04c7095443f8cdacd11175064fe541c8fe2" 309 | dependencies = [ 310 | "heck", 311 | "proc-macro2", 312 | "quote", 313 | "syn 1.0.109", 314 | ] 315 | 316 | [[package]] 317 | name = "scopeguard" 318 | version = "1.2.0" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 321 | 322 | [[package]] 323 | name = "serde" 324 | version = "1.0.189" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" 327 | dependencies = [ 328 | "serde_derive", 329 | ] 330 | 331 | [[package]] 332 | name = "serde_derive" 333 | version = "1.0.189" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" 336 | dependencies = [ 337 | "proc-macro2", 338 | "quote", 339 | "syn 2.0.38", 340 | ] 341 | 342 | [[package]] 343 | name = "serde_json" 344 | version = "1.0.107" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" 347 | dependencies = [ 348 | "itoa", 349 | "ryu", 350 | "serde", 351 | ] 352 | 353 | [[package]] 354 | name = "serde_repr" 355 | version = "0.1.16" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" 358 | dependencies = [ 359 | "proc-macro2", 360 | "quote", 361 | "syn 2.0.38", 362 | ] 363 | 364 | [[package]] 365 | name = "smallvec" 366 | version = "1.11.1" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" 369 | 370 | [[package]] 371 | name = "str_indices" 372 | version = "0.4.2" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "b8eeaedde8e50d8a331578c9fa9a288df146ce5e16173ad26ce82f6e263e2be4" 375 | 376 | [[package]] 377 | name = "syn" 378 | version = "1.0.109" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 381 | dependencies = [ 382 | "proc-macro2", 383 | "quote", 384 | "unicode-ident", 385 | ] 386 | 387 | [[package]] 388 | name = "syn" 389 | version = "2.0.38" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" 392 | dependencies = [ 393 | "proc-macro2", 394 | "quote", 395 | "unicode-ident", 396 | ] 397 | 398 | [[package]] 399 | name = "text-size" 400 | version = "1.1.1" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" 403 | 404 | [[package]] 405 | name = "tinyvec" 406 | version = "1.6.0" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 409 | dependencies = [ 410 | "tinyvec_macros", 411 | ] 412 | 413 | [[package]] 414 | name = "tinyvec_macros" 415 | version = "0.1.1" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 418 | 419 | [[package]] 420 | name = "unicode-bidi" 421 | version = "0.3.13" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" 424 | 425 | [[package]] 426 | name = "unicode-ident" 427 | version = "1.0.12" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 430 | 431 | [[package]] 432 | name = "unicode-normalization" 433 | version = "0.1.22" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 436 | dependencies = [ 437 | "tinyvec", 438 | ] 439 | 440 | [[package]] 441 | name = "unicode-segmentation" 442 | version = "1.10.1" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 445 | 446 | [[package]] 447 | name = "unicode-width" 448 | version = "0.1.11" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" 451 | 452 | [[package]] 453 | name = "url" 454 | version = "2.4.1" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" 457 | dependencies = [ 458 | "form_urlencoded", 459 | "idna", 460 | "percent-encoding", 461 | "serde", 462 | ] 463 | 464 | [[package]] 465 | name = "winapi" 466 | version = "0.3.9" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 469 | dependencies = [ 470 | "winapi-i686-pc-windows-gnu", 471 | "winapi-x86_64-pc-windows-gnu", 472 | ] 473 | 474 | [[package]] 475 | name = "winapi-i686-pc-windows-gnu" 476 | version = "0.4.0" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 479 | 480 | [[package]] 481 | name = "winapi-x86_64-pc-windows-gnu" 482 | version = "0.4.0" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 485 | 486 | [[package]] 487 | name = "yansi" 488 | version = "0.5.1" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" 491 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pika2" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | lsp-server = "*" 10 | lsp-types = "*" 11 | serde = "*" 12 | serde_json = "*" 13 | ropey = "*" 14 | salsa = "*" 15 | ariadne = "*" 16 | yansi = "*" 17 | rowan = "*" 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pika 2 | Pika is a small, performance-oriented, dependently typed ML with algebraic effects and unboxed types. 3 | This is the rewritten (again, although some code was copied over) version of the compiler, as a language server/VSCode extension first, since it wasn't really working to add that part on later. A more complete (but obsolete) version can be found [in the master branch](https://github.com/tolziplohu/pika/tree/master). 4 | The typechecker is heavily inspired by [smalltt](https://github.com/AndrasKovacs/smalltt). 5 | 6 | The VSCode extension is in `editors/code`, but currently only supports being run from the development environment (press F5). 7 | It supports inline type and syntax errors, plus shows types when hovering on an expression or binding. 8 | 9 | Pika previously compiled to native code with LLVM (through [Durin](https://github.com/tolziplohu/durin), a dependently typed optimizing intermediate language). 10 | This new implementation doesn't currently get past elaboration, but it will do the same eventually. 11 | 12 | ### Example 13 | ```cr 14 | # Syntax is similar to Standard ML or OCaml, but comments use # 15 | # Pika doesn't have universes, so Type has type Type 16 | let U : Type = Type 17 | 18 | # Functions can have implicit parameters with [] 19 | fun id [T] (x : T) : T = x 20 | 21 | # And types can be inferred 22 | fun one (x : Type) = x 23 | fun two y = one y 24 | 25 | # You can pass implicit parameters implicitly or explicitly 26 | let test = id Type 27 | let test2 = id [Type] Type 28 | 29 | # And you can use explicit lambdas instead of `fun` 30 | # Also, `_` introduces a hole filled by unification 31 | let id2 : [T] T -> _ = x => x 32 | 33 | # Pika has datatypes and pattern matching as well 34 | # With explicit boxing and unboxing (but things are unboxed by default) 35 | type List T of 36 | Nil 37 | Cons (T, box List T) 38 | where 39 | # This is in List's "associated namespace", as are the constructors, like `impl` in Rust. 40 | # Code outside of the associated namespace needs to qualify the constructors when matching, like `List.Nil` 41 | fun len [T] (self : List T) : I32 = case self of 42 | Nil => 0 43 | Cons (x, rest) => 1 + len rest 44 | # Pika uses significant indentation for blocks 45 | 46 | let _ = List.len (List.Cons (Type, List.Nil)) 47 | 48 | # And algebraic effects 49 | eff Console of 50 | Print String : () 51 | Read () : String 52 | 53 | fun greet () : () with Console = do 54 | Console.Print "Hello, " 55 | let name : String = Console.Read () 56 | Console.Print name 57 | 58 | # Now we handle the effects 59 | # Print out what `greet` tells us to, but make `Read` always return "Pika" 60 | fun main () with IO = catch greet () of 61 | () => () 62 | eff Console.Print s, k => do 63 | puts s 64 | k () 65 | eff Console.Read (), k => k "Pika" 66 | ``` 67 | 68 | ### Significant indentation 69 | 70 | #### Why does Pika have significant indentation? 71 | 72 | Part of it is because I don't want semicolons, I want newlines to delimit statements, but I also want it to be easy to continue a statement on the next line. 73 | This is why Python has `\`, but that's not a good solution; and some languages use properties of the syntax so it's (somewhat) unambiguous, like Lua or JavaScript, but that's doesn't work for ML-like languages with juxtaposition as function application. 74 | 75 | Also, using `end` like Pika used to do often looks weird when indentation for things other than blocks is used, for example here where there are three dedents but only one has an `end`: 76 | ```cr 77 | fun do_something (x, y, z) = 78 | case find_the_thing (x, y, z) of 79 | Some a => a 80 | None => 81 | x + y * 2 + z * 3 82 | end 83 | ``` 84 | 85 | It's also a lot nicer when passing lambdas as arguments. Compare: 86 | ```cr 87 | list 88 | .iter 89 | .filter 90 | x => x % 2 == 0 and x % 5 == 0 91 | .map 92 | x => x * 2 + 3 93 | ``` 94 | to either of the lambdas here: 95 | ```cr 96 | list 97 | .iter 98 | # Remember, the lambda can't be on another line without a backslash! 99 | .filter (x => x % 2 == 0 and x % 5 == 0) 100 | # This is the special multiline lambda syntax, which mostly exists for 101 | # this sort of thing, but it's still not great for this short lambda. 102 | .map do x => 103 | x * 2 + 3 104 | end 105 | ``` 106 | 107 | #### How does Pika's significant indentation work? 108 | 109 | Pika's significant indentation isn't quite like other languages with significant indentation. 110 | It's more like some of the proposals for significant indentation in Scheme, like [wisp](https://srfi.schemers.org/srfi-119/); and unlike Haskell, there aren't complex layout rules using column numbers, it's just INDENT and DEDENT tokens when the indentation goes up and down. 111 | It's designed so that code usually does what it looks like - indentation should never be misleading. 112 | 113 | There are generally three cases for what indentation means in Pika: 114 | 115 | 1. Blocks, like `do`, `where`, `case-of`, etc., are delimited by indentation. This is simple, and works like Python: 116 | ```ml 117 | fun unwrap_and_print_or (self, default) = case self of 118 | Some x => do 119 | print x 120 | x 121 | None => default 122 | ``` 123 | 124 | 2. In expressions, indentation can be used for grouping: more-indented lines are essentially wrapped in parentheses. This is especially useful for function calls with many or very long arguments. For example: 125 | ```cr 126 | Map.from 127 | List.zip 128 | context.keys, 129 | List.Cons 130 | 1, 131 | get_list_one () 132 | # --> 133 | Map.from (List.zip (context.keys, List.Cons (1, get_list_one ())) 134 | 135 | 3 + 4 * 136 | 5 + 6 137 | # --> 138 | 3 + 4 * (5 + 6) 139 | ``` 140 | 141 | 3. If the more-indented line begins with a binary operator like `+` or `.`, the previous lines are grouped. This is handy for operator chaining, especially with `.` method syntax. 142 | ```cr 143 | range 0 100 144 | .reverse 145 | .filter is_even 146 | .map 147 | x => x * 3 148 | .collect 149 | 150 | term_one * 0.5 + term_two * 0.3 + term_three * 0.2 151 | + adj 152 | * scale 153 | # --> 154 | ((term_one * 0.5 + term_two * 0.3 + term_three * 0.2) + adj) * scale 155 | ``` 156 | 157 | 158 | ### Why "Pika"? 159 | Lots of languages have animal mascots, so Pika's is the pika. 160 | Pikas are little mammals that live on mountains, close relatives of rabbits. 161 | Pika the language is also small, but it isn't a close relative of any rabbits. 162 | Since it has dependent types, it has pi types, and "Pika" has "Pi" in it, so that's something else. 163 | -------------------------------------------------------------------------------- /editors/code/language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | // symbol used for single line comment. Remove this entry if your language does not support line comments 4 | "lineComment": "#" 5 | }, 6 | // symbols used as brackets 7 | "brackets": [ 8 | ["{", "}"], 9 | ["[", "]"], 10 | ["(", ")"], 11 | ], 12 | // symbols that are auto closed when typing 13 | "autoClosingPairs": [ 14 | ["{", "}"], 15 | ["[", "]"], 16 | ["(", ")"], 17 | ["\"", "\""], 18 | ["'", "'"], 19 | ], 20 | // symbols that can be used to surround a selection 21 | "surroundingPairs": [ 22 | ["{", "}"], 23 | ["[", "]"], 24 | ["(", ")"], 25 | ["\"", "\""], 26 | ["'", "'"], 27 | ] 28 | } -------------------------------------------------------------------------------- /editors/code/out/extension.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.deactivate = exports.activate = void 0; 4 | const path = require("path"); 5 | const vscode_1 = require("vscode"); 6 | const node_1 = require("vscode-languageclient/node"); 7 | let client; 8 | function activate(context) { 9 | // The server is implemented in node 10 | let serverModule = context.asAbsolutePath(path.join('..', '..', 'target', 'debug', 'pika2')); 11 | // If the extension is launched in debug mode then the debug server options are used 12 | // Otherwise the run options are used 13 | let serverOptions = { 14 | run: { 15 | command: serverModule, 16 | args: ["server"], 17 | options: {} 18 | }, 19 | debug: { 20 | command: serverModule, 21 | args: ["server"], 22 | options: {} 23 | } 24 | }; 25 | // Options to control the language client 26 | let clientOptions = { 27 | // Register the server for Pika files 28 | documentSelector: [{ scheme: 'file', language: 'pika' }], 29 | synchronize: { 30 | // Notify the server about file changes to '.clientrc files contained in the workspace 31 | fileEvents: vscode_1.workspace.createFileSystemWatcher('**/.clientrc') 32 | } 33 | }; 34 | // Create the language client and start the client. 35 | client = new node_1.LanguageClient('pikaLanguageServer', 'Pika Language Server', serverOptions, clientOptions); 36 | // Start the client. This will also launch the server 37 | client.start(); 38 | } 39 | exports.activate = activate; 40 | function deactivate() { 41 | if (!client) { 42 | return undefined; 43 | } 44 | return client.stop(); 45 | } 46 | exports.deactivate = deactivate; 47 | //# sourceMappingURL=extension.js.map -------------------------------------------------------------------------------- /editors/code/out/extension.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":";;;AAAA,6BAA6B;AAC7B,mCAAqD;AAErD,qDAKoC;AAEpC,IAAI,MAAsB,CAAC;AAE3B,SAAgB,QAAQ,CAAC,OAAyB;IAChD,oCAAoC;IACpC,IAAI,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IAE7F,oFAAoF;IACpF,qCAAqC;IACrC,IAAI,aAAa,GAAkB;QACjC,GAAG,EAAE;YACH,OAAO,EAAE,YAAY;YACrB,IAAI,EAAE,CAAC,QAAQ,CAAC;YAChB,OAAO,EAAE,EAAE;SACZ;QACD,KAAK,EAAE;YACL,OAAO,EAAE,YAAY;YACrB,IAAI,EAAE,CAAC,QAAQ,CAAC;YAChB,OAAO,EAAE,EAAE;SACZ;KACF,CAAC;IAEF,yCAAyC;IACzC,IAAI,aAAa,GAA0B;QACzC,qCAAqC;QACrC,gBAAgB,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;QACxD,WAAW,EAAE;YACX,sFAAsF;YACtF,UAAU,EAAE,kBAAS,CAAC,uBAAuB,CAAC,cAAc,CAAC;SAC9D;KACF,CAAC;IAEF,mDAAmD;IACnD,MAAM,GAAG,IAAI,qBAAc,CACzB,oBAAoB,EACpB,sBAAsB,EACtB,aAAa,EACb,aAAa,CACd,CAAC;IAEF,qDAAqD;IACrD,MAAM,CAAC,KAAK,EAAE,CAAC;AACjB,CAAC;AAvCD,4BAuCC;AAED,SAAgB,UAAU;IACxB,IAAI,CAAC,MAAM,EAAE;QACX,OAAO,SAAS,CAAC;KAClB;IACD,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;AACvB,CAAC;AALD,gCAKC"} -------------------------------------------------------------------------------- /editors/code/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lsp-sample-client", 3 | "description": "VSCode part of a language server", 4 | "author": "Microsoft Corporation", 5 | "license": "MIT", 6 | "version": "0.0.1", 7 | "publisher": "vscode", 8 | "main": "./out/extension.js", 9 | "categories": [ 10 | "Programming Languages" 11 | ], 12 | "activationEvents": [ 13 | "onLanguage:pika" 14 | ], 15 | "contributes": { 16 | "languages": [{ 17 | "id": "pika", 18 | "aliases": ["Pika", "pika"], 19 | "extensions": [".pk"], 20 | "configuration": "./language-configuration.json" 21 | }], 22 | "grammars": [{ 23 | "language": "pika", 24 | "scopeName": "source.pika", 25 | "path": "./syntaxes/pika.tmLanguage.json" 26 | }], 27 | "snippets": [{ 28 | "language": "pika", 29 | "path": "./snippets.json" 30 | }] 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "https://github.com/Microsoft/vscode-extension-samples" 35 | }, 36 | "engines": { 37 | "vscode": "^1.63.0" 38 | }, 39 | "scripts": { 40 | "vscode:prepublish": "npm run build", 41 | "build": "tsc -p ./", 42 | "watch": "tsc -watch -p ./" 43 | }, 44 | "dependencies": { 45 | "vscode-languageclient": "^7.0.0" 46 | }, 47 | "devDependencies": { 48 | "@types/node": "^17.0.23", 49 | "@types/vscode": "^1.63.0", 50 | "@vscode/test-electron": "^2.1.2", 51 | "typescript": "^4.6.3" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /editors/code/snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "Do block": { 3 | "prefix": ["do"], 4 | "body": ["do", "\t$0"] 5 | }, 6 | "Case-of expression": { 7 | "prefix": ["case", "case-of"], 8 | "body": ["case ${1:value} of", "\t$0"] 9 | } 10 | } -------------------------------------------------------------------------------- /editors/code/src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import { workspace, ExtensionContext } from 'vscode'; 3 | 4 | import { 5 | LanguageClient, 6 | LanguageClientOptions, 7 | ServerOptions, 8 | TransportKind 9 | } from 'vscode-languageclient/node'; 10 | 11 | let client: LanguageClient; 12 | 13 | export function activate(context: ExtensionContext) { 14 | // The server is implemented in node 15 | let serverModule = context.asAbsolutePath(path.join('..', '..', 'target', 'debug', 'pika2')); 16 | 17 | // If the extension is launched in debug mode then the debug server options are used 18 | // Otherwise the run options are used 19 | let serverOptions: ServerOptions = { 20 | run: { 21 | command: serverModule, 22 | args: ["server"], 23 | options: {} 24 | }, 25 | debug: { 26 | command: serverModule, 27 | args: ["server"], 28 | options: {} 29 | } 30 | }; 31 | 32 | // Options to control the language client 33 | let clientOptions: LanguageClientOptions = { 34 | // Register the server for Pika files 35 | documentSelector: [{ scheme: 'file', language: 'pika' }], 36 | synchronize: { 37 | // Notify the server about file changes to '.clientrc files contained in the workspace 38 | fileEvents: workspace.createFileSystemWatcher('**/.clientrc') 39 | } 40 | }; 41 | 42 | // Create the language client and start the client. 43 | client = new LanguageClient( 44 | 'pikaLanguageServer', 45 | 'Pika Language Server', 46 | serverOptions, 47 | clientOptions 48 | ); 49 | 50 | // Start the client. This will also launch the server 51 | client.start(); 52 | } 53 | 54 | export function deactivate(): Thenable | undefined { 55 | if (!client) { 56 | return undefined; 57 | } 58 | return client.stop(); 59 | } -------------------------------------------------------------------------------- /editors/code/syntaxes/pika.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", 3 | "name": "Pika", 4 | "patterns": [ 5 | { "include": "#expression" } 6 | ], 7 | "repository": { 8 | "expression": { 9 | "patterns": [ 10 | { "include": "#keywords" }, 11 | { "include": "#symbols" }, 12 | { "include": "#types" }, 13 | { "include": "#function-call" }, 14 | { "include": "#vars" }, 15 | { "include": "#literals" }, 16 | { "include": "#comments" }, 17 | { "include": "#strings" } 18 | ] 19 | }, 20 | "attributes": { 21 | "begin": "\\@\\[", 22 | "end": "\\]", 23 | "name": "entity.other.attribute-name.pika", 24 | "patterns": [ 25 | {"include": "#strings" }, 26 | {"include": "#literals" } 27 | ] 28 | }, 29 | "vars": { 30 | "match": "\\b[a-z_][a-zA-Z_0-9]*\\b", 31 | "name": "variable.other.pika" 32 | }, 33 | "literals": { 34 | "match": "\\d+(\\.\\d+)?", 35 | "name": "constant.numeric.pika" 36 | }, 37 | "comments": { 38 | "match": "#.*$", 39 | "name": "comment.line.number-sign.pika" 40 | }, 41 | "types": { 42 | "match": "\\b([A-Z][A-Za-z_0-9]*)", 43 | "name": "entity.name.class.pika" 44 | }, 45 | "function-call": { 46 | "match": "\\b([a-z_][a-zA-Z_0-9]*)\\b\\s*(?=\\(|\\[|do\\s*)", 47 | "captures": { 48 | "1": { 49 | "name": "entity.name.function.pika" 50 | } 51 | } 52 | }, 53 | "keywords": { 54 | "patterns": [{ 55 | "name": "keyword.control.pika", 56 | "match": "\\b(catch|match|if|then|else)\\b" 57 | }, { 58 | "name": "keyword.other.pika", 59 | "match": "\\b(type|eff|of|with|struct|let|fun|where|impl|box|unbox|mut|do|self|trait|own|imm|ref|as|is)\\b" 60 | }] 61 | }, 62 | "symbols": { 63 | "patterns": [{ 64 | "match": "->|=>|<\\|\\|>", 65 | "name": "keyword.operator.function.pika" 66 | }, 67 | { 68 | "match": "=>|\\|", 69 | "name": "keyword.operator.pattern.pikas" 70 | }, 71 | { 72 | "match": "\\+|-|\\*\\*|\\*|/|\\^\\^|&|<<|>>", 73 | "name": "keyword.operator.arithmetic.pika" 74 | }, 75 | { 76 | "match": ">=|<=|>|<|==|!=", 77 | "name": "keyword.operator.comparison.pika" 78 | }, 79 | { 80 | "match": "\\b(and|or)\\b", 81 | "name": "keyword.other.logic.pika" 82 | }, 83 | { 84 | "match": "=", 85 | "name": "keyword.operator.definition.pika" 86 | }, 87 | { 88 | "match": ":", 89 | "name": "keyword.operator.type.pika" 90 | }] 91 | }, 92 | "strings": { 93 | "name": "string.quoted.double.pika", 94 | "begin": "\"", 95 | "end": "\"", 96 | "patterns": [ 97 | { 98 | "name": "constant.character.escape.pika", 99 | "match": "\\\\." 100 | } 101 | ] 102 | } 103 | }, 104 | "scopeName": "source.pika" 105 | } 106 | -------------------------------------------------------------------------------- /editors/code/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2020", 5 | "lib": ["es2020", "DOM"], 6 | "outDir": "out", 7 | "sourceMap": true, 8 | "strict": true, 9 | "rootDir": "src" 10 | }, 11 | "exclude": ["node_modules", ".vscode-test"] 12 | } -------------------------------------------------------------------------------- /src/args.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::{collections::VecDeque, path::PathBuf}; 3 | 4 | const HELP: &str = "Usage: 5 | pika [flags] command [files] 6 | 7 | Commands: 8 | server Start the Pika language server 9 | build Compile files but don't run them 10 | run Compile files and run the resulting executable 11 | check Perform typechecking but don't compile to machine code 12 | repl Run the interactive Read-Evaluate-Print-Loop 13 | 14 | Flags: 15 | -h, --help Show this help message 16 | --release Build in release mode with optimizations 17 | --emit-durin Print out the Durin IR for the input files 18 | -o, --output PATH Place the output binary at PATH 19 | "; 20 | 21 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 22 | pub enum Command { 23 | Server, 24 | Check, 25 | Build, 26 | Run, 27 | Repl, 28 | } 29 | impl Command { 30 | fn parse(s: &str) -> Option { 31 | match s { 32 | "server" => Some(Command::Server), 33 | "check" => Some(Command::Check), 34 | "build" => Some(Command::Build), 35 | "run" => Some(Command::Run), 36 | "repl" => Some(Command::Repl), 37 | _ => None, 38 | } 39 | } 40 | } 41 | 42 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 43 | pub enum Flag { 44 | Help, 45 | EmitDurin, 46 | EmitLLVM, 47 | Release, 48 | ShowParseTree, 49 | } 50 | impl Flag { 51 | fn short(c: char) -> Option { 52 | match c { 53 | 'h' => Some(Flag::Help), 54 | _ => None, 55 | } 56 | } 57 | fn long(s: &str) -> Option { 58 | match s { 59 | "help" => Some(Flag::Help), 60 | "emit-durin" => Some(Flag::EmitDurin), 61 | "emit-llvm" => Some(Flag::EmitLLVM), 62 | "release" => Some(Flag::Release), 63 | "show-parse-tree" => Some(Flag::ShowParseTree), 64 | _ => None, 65 | } 66 | } 67 | } 68 | 69 | #[derive(Clone, Debug, PartialEq, Eq)] 70 | enum Opt { 71 | Output(PathBuf), 72 | } 73 | impl Opt { 74 | fn short(c: char, val: &mut Option) -> Option { 75 | match c { 76 | 'o' => Some(Opt::Output(val.take().unwrap().into())), 77 | _ => None, 78 | } 79 | } 80 | fn long(s: &str, val: &mut Option) -> Option { 81 | match s { 82 | "output" => Some(Opt::Output(val.take().unwrap().into())), 83 | _ => None, 84 | } 85 | } 86 | } 87 | 88 | #[derive(Clone, Debug)] 89 | pub struct Config { 90 | pub command: Command, 91 | pub files: Vec, 92 | pub output: Option, 93 | pub flags: HashSet, 94 | } 95 | impl Config { 96 | pub fn flag(&self, f: Flag) -> bool { 97 | self.flags.contains(&f) 98 | } 99 | 100 | fn new(args: Args, options: Vec) -> Config { 101 | let Args { 102 | command, 103 | flags, 104 | files, 105 | } = args; 106 | 107 | let mut output = None; 108 | for i in options { 109 | match i { 110 | Opt::Output(s) => output = Some(s), 111 | } 112 | } 113 | 114 | Config { 115 | command: command.unwrap_or(Command::Repl), 116 | files, 117 | output, 118 | flags, 119 | } 120 | } 121 | 122 | pub fn from_cmd_args() -> Config { 123 | let mut args = Args::default(); 124 | let mut options = Vec::new(); 125 | let mut error = false; 126 | let mut sargs: VecDeque<_> = std::env::args().skip(1).collect(); 127 | while let Some(i) = sargs.pop_front() { 128 | if i.starts_with("--") { 129 | if let Some(flag) = Flag::long(&i[2..]) { 130 | args.flags.insert(flag); 131 | } else { 132 | // Support both `--flag=val` and `--flag val` 133 | if let Some(eq_idx) = i.find('=') { 134 | let (i, val) = i.split_at(eq_idx); 135 | let mut val = Some(val[1..].to_string()); 136 | if let Some(opt) = Opt::long(i, &mut val) { 137 | options.push(opt); 138 | } else { 139 | eprintln!("Unrecognized option '{}', ignoring", i); 140 | error = true; 141 | } 142 | } else { 143 | let mut val = sargs.pop_front(); 144 | if let Some(opt) = Opt::long(&i, &mut val) { 145 | options.push(opt); 146 | } else { 147 | eprintln!("Unrecognized option or flag '{}', ignoring", i); 148 | error = true; 149 | sargs.push_front(val.unwrap()); 150 | } 151 | } 152 | } 153 | } else if i.starts_with('-') { 154 | for (idx, c) in i.char_indices().skip(1) { 155 | if let Some(flag) = Flag::short(c) { 156 | args.flags.insert(flag); 157 | } else if idx + 1 == i.len() { 158 | let mut val = sargs.pop_front(); 159 | if let Some(opt) = Opt::short(c, &mut val) { 160 | options.push(opt); 161 | } else { 162 | eprintln!("Unrecognized short flag '{}', ignoring; use '--flag' for long flags", c); 163 | error = true; 164 | if let Some(val) = val { 165 | sargs.push_front(val); 166 | } 167 | } 168 | } else if idx + 2 == i.len() && i.chars().nth(idx + 1).unwrap().is_ascii_digit() 169 | { 170 | // Allow -O2 etc 171 | let mut val = Some(i[idx + 1..].to_string()); 172 | if let Some(opt) = Opt::short(c, &mut val) { 173 | options.push(opt); 174 | } else { 175 | eprintln!("Unrecognized short flag '{}', ignoring; use '--flag' for long flags", c); 176 | error = true; 177 | } 178 | break; 179 | } else { 180 | eprintln!( 181 | "Unrecognized short flag '{}', ignoring; use '--flag' for long flags", 182 | c 183 | ); 184 | error = true; 185 | } 186 | } 187 | } else if args.command.is_none() { 188 | if let Some(cmd) = Command::parse(&i) { 189 | args.command = Some(cmd); 190 | } else { 191 | eprintln!("Unrecognized command '{}', interpreting it as a file name and defaulting to 'check'; specify a command before input file names", i); 192 | args.command = Some(Command::Check); 193 | args.files.push(i.into()); 194 | error = true; 195 | } 196 | } else { 197 | args.files.push(i.into()); 198 | } 199 | } 200 | 201 | if args.flags.contains(&Flag::Help) { 202 | eprintln!("{}", HELP); 203 | std::process::exit(0); 204 | } else if error { 205 | eprintln!("Use -h or --help for help"); 206 | } 207 | 208 | Config::new(args, options) 209 | } 210 | } 211 | 212 | #[derive(Default, Clone)] 213 | struct Args { 214 | command: Option, 215 | flags: HashSet, 216 | files: Vec, 217 | } 218 | -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | pub use std::collections::{HashMap, VecDeque}; 2 | 3 | pub use crate::parsing::{ast, FileLoc, ParserExt, SplitId}; 4 | pub use crate::pretty::{Doc, Pretty}; 5 | pub use ast::AstNode; 6 | 7 | use ariadne::Color; 8 | pub use ariadne::Fmt; 9 | pub use std::borrow::Cow; 10 | 11 | #[macro_export] 12 | macro_rules! intern_key { 13 | ($n:ident) => { 14 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] 15 | pub struct $n(salsa::InternId); 16 | impl salsa::InternKey for $n { 17 | fn from_intern_id(id: salsa::InternId) -> Self { 18 | Self(id) 19 | } 20 | 21 | fn as_intern_id(&self) -> salsa::InternId { 22 | self.0 23 | } 24 | } 25 | }; 26 | } 27 | intern_key!(File); 28 | intern_key!(Name); 29 | intern_key!(Def); 30 | 31 | impl Name { 32 | pub fn inaccessible(self, db: &(impl crate::parsing::Parser + ?Sized)) -> Name { 33 | let str = db.lookup_name(self); 34 | db.name(format!("{}'", str)) 35 | } 36 | } 37 | 38 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 39 | pub enum DefLoc { 40 | Root(File, SplitId), 41 | Child(Def, SplitId), 42 | } 43 | 44 | impl Def { 45 | pub fn fallback_repr(self, db: &T) -> String { 46 | match db.lookup_def(self) { 47 | DefLoc::Root(file, split) => match split { 48 | SplitId::Named(n) => format!("{}.{}", db.lookup_file_id(file), db.lookup_name(n)), 49 | SplitId::Idx(i) => format!("{}.%{}", db.lookup_file_id(file), i), 50 | }, 51 | DefLoc::Child(a, split) => match split { 52 | SplitId::Named(n) => format!("{}.{}", a.fallback_repr(db), db.lookup_name(n)), 53 | SplitId::Idx(i) => format!("{}.%{}", a.fallback_repr(db), i), 54 | }, 55 | } 56 | } 57 | } 58 | 59 | /// Uses byte positions 60 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 61 | pub struct RelSpan { 62 | pub start: u32, 63 | pub end: u32, 64 | } 65 | impl RelSpan { 66 | pub fn empty() -> RelSpan { 67 | RelSpan::new(0, 0) 68 | } 69 | 70 | pub fn new(start: u32, end: u32) -> RelSpan { 71 | RelSpan { start, end } 72 | } 73 | 74 | pub fn contains(self, pos: u32) -> bool { 75 | pos >= self.start && pos < self.end 76 | } 77 | 78 | pub fn superset(self, other: RelSpan) -> bool { 79 | self.start <= other.start && self.end >= other.end 80 | } 81 | } 82 | impl From for RelSpan { 83 | fn from(range: rowan::TextRange) -> Self { 84 | RelSpan { 85 | start: range.start().into(), 86 | end: range.end().into(), 87 | } 88 | } 89 | } 90 | impl From> for RelSpan { 91 | fn from(range: std::ops::Range) -> Self { 92 | RelSpan { 93 | start: range.start, 94 | end: range.end, 95 | } 96 | } 97 | } 98 | pub type Spanned = (T, RelSpan); 99 | pub type SName = Spanned; 100 | impl Pretty for Name { 101 | fn pretty(&self, db: &(impl crate::elab::Elaborator + ?Sized)) -> Doc { 102 | Doc::start(db.lookup_name(*self)) 103 | } 104 | } 105 | impl Pretty for SName { 106 | fn pretty(&self, db: &(impl crate::elab::Elaborator + ?Sized)) -> Doc { 107 | self.0.pretty(db) 108 | } 109 | } 110 | 111 | /// Uses byte positions 112 | #[derive(Clone, Debug, PartialEq, Eq)] 113 | pub struct AbsSpan(pub File, pub std::ops::Range); 114 | impl AbsSpan { 115 | pub fn add(&self, other: RelSpan) -> Self { 116 | AbsSpan( 117 | self.0.clone(), 118 | self.1.start + other.start..self.1.start + other.end, 119 | ) 120 | } 121 | 122 | pub fn chars(self, db: &(impl crate::parsing::Parser + ?Sized)) -> CharSpan { 123 | let text = db.input_file(self.0); 124 | let start = text.byte_to_char(self.1.start as usize) as u32; 125 | let end = text.byte_to_char(self.1.end as usize) as u32; 126 | CharSpan(self.0, start..end) 127 | } 128 | 129 | pub fn lsp_range(&self, files: &HashMap) -> lsp_types::Range { 130 | let text = files.get(&self.0).unwrap(); 131 | let start = text.byte_to_char(self.1.start as usize) as u32; 132 | let end = text.byte_to_char(self.1.end as usize) as u32; 133 | let start_line = text.char_to_line(start as usize); 134 | let start_line_start = text.line_to_char(start_line); 135 | let end_line = text.char_to_line(end as usize); 136 | let end_line_start = text.line_to_char(end_line); 137 | lsp_types::Range { 138 | start: lsp_types::Position { 139 | line: start_line as u32, 140 | character: start - start_line_start as u32, 141 | }, 142 | end: lsp_types::Position { 143 | line: end_line as u32, 144 | character: end - end_line_start as u32, 145 | }, 146 | } 147 | } 148 | } 149 | 150 | #[derive(Clone, Debug, PartialEq, Eq)] 151 | pub struct CharSpan(pub File, pub std::ops::Range); 152 | impl ariadne::Span for CharSpan { 153 | type SourceId = File; 154 | 155 | fn source(&self) -> &Self::SourceId { 156 | &self.0 157 | } 158 | 159 | fn start(&self) -> usize { 160 | self.1.start as usize 161 | } 162 | 163 | fn end(&self) -> usize { 164 | self.1.end as usize 165 | } 166 | } 167 | 168 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 169 | pub enum Severity { 170 | Error, 171 | Warning, 172 | } 173 | impl Severity { 174 | fn ariadne(self) -> ariadne::ReportKind<'static> { 175 | match self { 176 | Severity::Error => ariadne::ReportKind::Error, 177 | Severity::Warning => ariadne::ReportKind::Warning, 178 | } 179 | } 180 | 181 | fn lsp(self) -> lsp_types::DiagnosticSeverity { 182 | match self { 183 | Severity::Error => lsp_types::DiagnosticSeverity::ERROR, 184 | Severity::Warning => lsp_types::DiagnosticSeverity::WARNING, 185 | } 186 | } 187 | } 188 | 189 | #[derive(Clone, Debug, PartialEq, Eq)] 190 | pub struct Label { 191 | pub span: RelSpan, 192 | pub message: Doc, 193 | pub color: Option, 194 | } 195 | impl Label { 196 | fn ariadne( 197 | self, 198 | split_span: &AbsSpan, 199 | db: &(impl crate::parsing::Parser + ?Sized), 200 | ) -> ariadne::Label { 201 | let span = split_span.add(self.span).chars(db); 202 | let mut l = ariadne::Label::new(span).with_message(self.message.to_string(true)); 203 | if let Some(color) = self.color { 204 | l = l.with_color(color); 205 | } 206 | l 207 | } 208 | } 209 | 210 | #[derive(Clone, Debug, PartialEq, Eq)] 211 | pub struct Error { 212 | pub severity: Severity, 213 | pub message_lsp: Option, 214 | pub message: Doc, 215 | pub primary: Label, 216 | pub secondary: Vec