refactor(users/glittershark): Rename to grfn
Rename my //users directory and all places that refer to glittershark to grfn, including nix references and documentation. This may require some extra attention inside of gerrit's database after it lands to allow me to actually push things. Change-Id: I4728b7ec2c60024392c1c1fa6e0d4a59b3e266fa Reviewed-on: https://cl.tvl.fyi/c/depot/+/2933 Tested-by: BuildkiteCI Reviewed-by: tazjin <mail@tazj.in> Reviewed-by: lukegb <lukegb@tvl.fyi> Reviewed-by: glittershark <grfn@gws.fyi>
This commit is contained in:
parent
968effb5dc
commit
6266c5d32f
362 changed files with 52 additions and 56 deletions
3
users/grfn/OWNERS
Normal file
3
users/grfn/OWNERS
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
inherited: false
|
||||
owners:
|
||||
- grfn
|
||||
1
users/grfn/achilles/.envrc
Normal file
1
users/grfn/achilles/.envrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
eval "$(lorri direnv)"
|
||||
1
users/grfn/achilles/.gitignore
vendored
Normal file
1
users/grfn/achilles/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
868
users/grfn/achilles/Cargo.lock
generated
Normal file
868
users/grfn/achilles/Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,868 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "achilles"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bimap",
|
||||
"clap",
|
||||
"crate-root",
|
||||
"derive_more",
|
||||
"inkwell",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"llvm-sys",
|
||||
"nom",
|
||||
"nom-trace",
|
||||
"pratt",
|
||||
"pretty_assertions",
|
||||
"proptest",
|
||||
"test-strategy",
|
||||
"thiserror",
|
||||
"void",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "bimap"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f92b72b8f03128773278bf74418b9205f3d2a12c39a61f92395f47af390c32bf"
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "0.19.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321"
|
||||
dependencies = [
|
||||
"funty",
|
||||
"radium",
|
||||
"tap",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.67"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.0.0-beta.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
"clap_derive",
|
||||
"indexmap",
|
||||
"lazy_static",
|
||||
"os_str_bytes",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "3.0.0-beta.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "370f715b81112975b1b69db93e0b56ea4cd4e5002ac43b2da8474106a54096a1"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crate-root"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59c6fe4622b269032d2c5140a592d67a9c409031d286174fcde172fbed86f0d3"
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8f45d9ad417bcef4817d614a501ab55cdd96a6fdb24f49aab89a54acfd66b19"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inkwell"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/TheDan64/inkwell?branch=master#a2db15b0bd1c06d71763585ae10d9ea4e775da0c"
|
||||
dependencies = [
|
||||
"either",
|
||||
"inkwell_internals",
|
||||
"libc",
|
||||
"llvm-sys",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inkwell_internals"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/TheDan64/inkwell?branch=master#a2db15b0bd1c06d71763585ae10d9ea4e775da0c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "lexical-core"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21f866863575d0e1d654fbeeabdc927292fdf862873dc3c96c6f753357e13374"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"ryu",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03b07a082330a35e43f63177cc01689da34fbffa0105e1246cf0311472cac73a"
|
||||
|
||||
[[package]]
|
||||
name = "llvm-sys"
|
||||
version = "110.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21ede189444b8c78907e5d36da5dabcf153170fcff9c1dba48afc4b33c7e19f0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"regex",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "6.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"funty",
|
||||
"lexical-core",
|
||||
"memchr",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom-trace"
|
||||
version = "0.2.1"
|
||||
source = "git+https://github.com/glittershark/nom-trace?branch=nom-6#6168d2e15cc51efd12d80260159b76a764dba138"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85"
|
||||
|
||||
[[package]]
|
||||
name = "output_vt100"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
|
||||
dependencies = [
|
||||
"instant",
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"instant",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
|
||||
dependencies = [
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
|
||||
|
||||
[[package]]
|
||||
name = "pratt"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e31bbc12f7936a7b195790dd6d9b982b66c54f45ff6766decf25c44cac302dce"
|
||||
|
||||
[[package]]
|
||||
name = "pretty_assertions"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f297542c27a7df8d45de2b0e620308ab883ad232d06c14b76ac3e144bda50184"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"ctor",
|
||||
"diff",
|
||||
"output_vt100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proptest"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e0d9cc07f18492d879586c92b485def06bc850da3118075cd45d50e9c95b0e5"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"lazy_static",
|
||||
"num-traits",
|
||||
"quick-error 2.0.0",
|
||||
"rand",
|
||||
"rand_chacha",
|
||||
"rand_xorshift",
|
||||
"regex-syntax",
|
||||
"rusty-fork",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ac73b1112776fc109b2e61909bc46c7e1bf0d7f690ffb1676553acce16d5cda"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_hc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_xorshift"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
"thread_local",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusty-fork"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"quick-error 1.2.3",
|
||||
"tempfile",
|
||||
"wait-timeout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
|
||||
dependencies = [
|
||||
"semver-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
|
||||
dependencies = [
|
||||
"pest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed22b90a0e734a23a7610f4283ac9e5acfb96cbb30dfefa540d66f866f1c09c5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"rand",
|
||||
"redox_syscall",
|
||||
"remove_dir_all",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "test-strategy"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2328963c69243416e811c88066d18f670792b2e36e17fa57f4b1a124f85d18a8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
|
||||
|
||||
[[package]]
|
||||
name = "void"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
|
||||
26
users/grfn/achilles/Cargo.toml
Normal file
26
users/grfn/achilles/Cargo.toml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
[package]
|
||||
name = "achilles"
|
||||
version = "0.1.0"
|
||||
authors = ["Griffin Smith <root@gws.fyi>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.38"
|
||||
bimap = "0.6.0"
|
||||
clap = "3.0.0-beta.2"
|
||||
derive_more = "0.99.11"
|
||||
inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "master", features = ["llvm11-0"] }
|
||||
itertools = "0.10.0"
|
||||
lazy_static = "1.4.0"
|
||||
llvm-sys = "110.0.1"
|
||||
nom = "6.1.2"
|
||||
nom-trace = { git = "https://github.com/glittershark/nom-trace", branch = "nom-6" }
|
||||
pratt = "0.3.0"
|
||||
proptest = "1.0.0"
|
||||
test-strategy = "0.1.1"
|
||||
thiserror = "1.0.24"
|
||||
void = "1.0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
crate-root = "0.1.3"
|
||||
pretty_assertions = "0.7.1"
|
||||
7
users/grfn/achilles/ach/.gitignore
vendored
Normal file
7
users/grfn/achilles/ach/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
*.ll
|
||||
*.o
|
||||
|
||||
functions
|
||||
simple
|
||||
externs
|
||||
units
|
||||
15
users/grfn/achilles/ach/Makefile
Normal file
15
users/grfn/achilles/ach/Makefile
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
default: simple
|
||||
|
||||
%.ll: %.ach
|
||||
cargo run -- compile $< -o $@ -f llvm
|
||||
|
||||
%.o: %.ll
|
||||
llc $< -o $@ -filetype=obj
|
||||
|
||||
%: %.o
|
||||
clang $< -o $@
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
clean:
|
||||
@rm -f *.ll *.o simple functions
|
||||
5
users/grfn/achilles/ach/externs.ach
Normal file
5
users/grfn/achilles/ach/externs.ach
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
extern puts : fn cstring -> int
|
||||
|
||||
fn main =
|
||||
let _ = puts "foobar"
|
||||
in 0
|
||||
8
users/grfn/achilles/ach/functions.ach
Normal file
8
users/grfn/achilles/ach/functions.ach
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
ty id : fn a -> a
|
||||
fn id x = x
|
||||
|
||||
ty plus : fn int -> int
|
||||
fn plus (x: int) (y: int) = x + y
|
||||
|
||||
ty main : fn -> int
|
||||
fn main = plus (id 2) 7
|
||||
1
users/grfn/achilles/ach/simple.ach
Normal file
1
users/grfn/achilles/ach/simple.ach
Normal file
|
|
@ -0,0 +1 @@
|
|||
fn main = let x = 2; y = 3 in x + y
|
||||
7
users/grfn/achilles/ach/units.ach
Normal file
7
users/grfn/achilles/ach/units.ach
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
extern puts : fn cstring -> int
|
||||
|
||||
ty print : fn cstring -> ()
|
||||
fn print x = let _ = puts x in ()
|
||||
|
||||
ty main : fn -> int
|
||||
fn main = let _ = print "hi" in 0
|
||||
20
users/grfn/achilles/default.nix
Normal file
20
users/grfn/achilles/default.nix
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{ depot, pkgs, ... }:
|
||||
|
||||
depot.third_party.naersk.buildPackage {
|
||||
src = ./.;
|
||||
|
||||
buildInputs = with pkgs; [
|
||||
clang_11
|
||||
llvmPackages.llvm
|
||||
llvmPackages.bintools
|
||||
llvmPackages.clang
|
||||
llvmPackages.libclang.lib
|
||||
zlib
|
||||
ncurses
|
||||
libxml2
|
||||
libffi
|
||||
pkgconfig
|
||||
];
|
||||
|
||||
doCheck = true;
|
||||
}
|
||||
20
users/grfn/achilles/shell.nix
Normal file
20
users/grfn/achilles/shell.nix
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
with import (builtins.fetchTarball {
|
||||
url = "https://github.com/nixos/nixpkgs/archive/93a812bb9f9c398bd5b9636ab3674dcfe8cfb884.tar.gz";
|
||||
sha256 = "14zzsgnigd7vjbrpzm1s4qsknm73sci38ss00x96wamz6psaxyah";
|
||||
}) {};
|
||||
|
||||
mkShell {
|
||||
buildInputs = [
|
||||
clang_11
|
||||
llvm_11.lib
|
||||
llvmPackages_11.bintools
|
||||
llvmPackages_11.clang
|
||||
llvmPackages_11.libclang.lib
|
||||
zlib
|
||||
ncurses
|
||||
libxml2
|
||||
libffi
|
||||
pkg-config
|
||||
];
|
||||
|
||||
}
|
||||
320
users/grfn/achilles/src/ast/hir.rs
Normal file
320
users/grfn/achilles/src/ast/hir.rs
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::{BinaryOperator, Ident, Literal, UnaryOperator};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Binding<'a, T> {
|
||||
pub ident: Ident<'a>,
|
||||
pub type_: T,
|
||||
pub body: Expr<'a, T>,
|
||||
}
|
||||
|
||||
impl<'a, T> Binding<'a, T> {
|
||||
fn to_owned(&self) -> Binding<'static, T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
Binding {
|
||||
ident: self.ident.to_owned(),
|
||||
type_: self.type_.clone(),
|
||||
body: self.body.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Expr<'a, T> {
|
||||
Ident(Ident<'a>, T),
|
||||
|
||||
Literal(Literal<'a>, T),
|
||||
|
||||
UnaryOp {
|
||||
op: UnaryOperator,
|
||||
rhs: Box<Expr<'a, T>>,
|
||||
type_: T,
|
||||
},
|
||||
|
||||
BinaryOp {
|
||||
lhs: Box<Expr<'a, T>>,
|
||||
op: BinaryOperator,
|
||||
rhs: Box<Expr<'a, T>>,
|
||||
type_: T,
|
||||
},
|
||||
|
||||
Let {
|
||||
bindings: Vec<Binding<'a, T>>,
|
||||
body: Box<Expr<'a, T>>,
|
||||
type_: T,
|
||||
},
|
||||
|
||||
If {
|
||||
condition: Box<Expr<'a, T>>,
|
||||
then: Box<Expr<'a, T>>,
|
||||
else_: Box<Expr<'a, T>>,
|
||||
type_: T,
|
||||
},
|
||||
|
||||
Fun {
|
||||
type_args: Vec<Ident<'a>>,
|
||||
args: Vec<(Ident<'a>, T)>,
|
||||
body: Box<Expr<'a, T>>,
|
||||
type_: T,
|
||||
},
|
||||
|
||||
Call {
|
||||
fun: Box<Expr<'a, T>>,
|
||||
type_args: HashMap<Ident<'a>, T>,
|
||||
args: Vec<Expr<'a, T>>,
|
||||
type_: T,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a, T> Expr<'a, T> {
|
||||
pub fn type_(&self) -> &T {
|
||||
match self {
|
||||
Expr::Ident(_, t) => t,
|
||||
Expr::Literal(_, t) => t,
|
||||
Expr::UnaryOp { type_, .. } => type_,
|
||||
Expr::BinaryOp { type_, .. } => type_,
|
||||
Expr::Let { type_, .. } => type_,
|
||||
Expr::If { type_, .. } => type_,
|
||||
Expr::Fun { type_, .. } => type_,
|
||||
Expr::Call { type_, .. } => type_,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn traverse_type<F, U, E>(self, f: F) -> Result<Expr<'a, U>, E>
|
||||
where
|
||||
F: Fn(T) -> Result<U, E> + Clone,
|
||||
{
|
||||
match self {
|
||||
Expr::Ident(id, t) => Ok(Expr::Ident(id, f(t)?)),
|
||||
Expr::Literal(lit, t) => Ok(Expr::Literal(lit, f(t)?)),
|
||||
Expr::UnaryOp { op, rhs, type_ } => Ok(Expr::UnaryOp {
|
||||
op,
|
||||
rhs: Box::new(rhs.traverse_type(f.clone())?),
|
||||
type_: f(type_)?,
|
||||
}),
|
||||
Expr::BinaryOp {
|
||||
lhs,
|
||||
op,
|
||||
rhs,
|
||||
type_,
|
||||
} => Ok(Expr::BinaryOp {
|
||||
lhs: Box::new(lhs.traverse_type(f.clone())?),
|
||||
op,
|
||||
rhs: Box::new(rhs.traverse_type(f.clone())?),
|
||||
type_: f(type_)?,
|
||||
}),
|
||||
Expr::Let {
|
||||
bindings,
|
||||
body,
|
||||
type_,
|
||||
} => Ok(Expr::Let {
|
||||
bindings: bindings
|
||||
.into_iter()
|
||||
.map(|Binding { ident, type_, body }| {
|
||||
Ok(Binding {
|
||||
ident,
|
||||
type_: f(type_)?,
|
||||
body: body.traverse_type(f.clone())?,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, E>>()?,
|
||||
body: Box::new(body.traverse_type(f.clone())?),
|
||||
type_: f(type_)?,
|
||||
}),
|
||||
Expr::If {
|
||||
condition,
|
||||
then,
|
||||
else_,
|
||||
type_,
|
||||
} => Ok(Expr::If {
|
||||
condition: Box::new(condition.traverse_type(f.clone())?),
|
||||
then: Box::new(then.traverse_type(f.clone())?),
|
||||
else_: Box::new(else_.traverse_type(f.clone())?),
|
||||
type_: f(type_)?,
|
||||
}),
|
||||
Expr::Fun {
|
||||
args,
|
||||
type_args,
|
||||
body,
|
||||
type_,
|
||||
} => Ok(Expr::Fun {
|
||||
args: args
|
||||
.into_iter()
|
||||
.map(|(id, t)| Ok((id, f.clone()(t)?)))
|
||||
.collect::<Result<Vec<_>, E>>()?,
|
||||
type_args,
|
||||
body: Box::new(body.traverse_type(f.clone())?),
|
||||
type_: f(type_)?,
|
||||
}),
|
||||
Expr::Call {
|
||||
fun,
|
||||
type_args,
|
||||
args,
|
||||
type_,
|
||||
} => Ok(Expr::Call {
|
||||
fun: Box::new(fun.traverse_type(f.clone())?),
|
||||
type_args: type_args
|
||||
.into_iter()
|
||||
.map(|(id, ty)| Ok((id, f.clone()(ty)?)))
|
||||
.collect::<Result<HashMap<_, _>, E>>()?,
|
||||
args: args
|
||||
.into_iter()
|
||||
.map(|e| e.traverse_type(f.clone()))
|
||||
.collect::<Result<Vec<_>, E>>()?,
|
||||
type_: f(type_)?,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_owned(&self) -> Expr<'static, T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
match self {
|
||||
Expr::Ident(id, t) => Expr::Ident(id.to_owned(), t.clone()),
|
||||
Expr::Literal(lit, t) => Expr::Literal(lit.to_owned(), t.clone()),
|
||||
Expr::UnaryOp { op, rhs, type_ } => Expr::UnaryOp {
|
||||
op: *op,
|
||||
rhs: Box::new((**rhs).to_owned()),
|
||||
type_: type_.clone(),
|
||||
},
|
||||
Expr::BinaryOp {
|
||||
lhs,
|
||||
op,
|
||||
rhs,
|
||||
type_,
|
||||
} => Expr::BinaryOp {
|
||||
lhs: Box::new((**lhs).to_owned()),
|
||||
op: *op,
|
||||
rhs: Box::new((**rhs).to_owned()),
|
||||
type_: type_.clone(),
|
||||
},
|
||||
Expr::Let {
|
||||
bindings,
|
||||
body,
|
||||
type_,
|
||||
} => Expr::Let {
|
||||
bindings: bindings.iter().map(|b| b.to_owned()).collect(),
|
||||
body: Box::new((**body).to_owned()),
|
||||
type_: type_.clone(),
|
||||
},
|
||||
Expr::If {
|
||||
condition,
|
||||
then,
|
||||
else_,
|
||||
type_,
|
||||
} => Expr::If {
|
||||
condition: Box::new((**condition).to_owned()),
|
||||
then: Box::new((**then).to_owned()),
|
||||
else_: Box::new((**else_).to_owned()),
|
||||
type_: type_.clone(),
|
||||
},
|
||||
Expr::Fun {
|
||||
args,
|
||||
type_args,
|
||||
body,
|
||||
type_,
|
||||
} => Expr::Fun {
|
||||
args: args
|
||||
.iter()
|
||||
.map(|(id, t)| (id.to_owned(), t.clone()))
|
||||
.collect(),
|
||||
type_args: type_args.iter().map(|arg| arg.to_owned()).collect(),
|
||||
body: Box::new((**body).to_owned()),
|
||||
type_: type_.clone(),
|
||||
},
|
||||
Expr::Call {
|
||||
fun,
|
||||
type_args,
|
||||
args,
|
||||
type_,
|
||||
} => Expr::Call {
|
||||
fun: Box::new((**fun).to_owned()),
|
||||
type_args: type_args
|
||||
.iter()
|
||||
.map(|(id, t)| (id.to_owned(), t.clone()))
|
||||
.collect(),
|
||||
args: args.iter().map(|e| e.to_owned()).collect(),
|
||||
type_: type_.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Decl<'a, T> {
|
||||
Fun {
|
||||
name: Ident<'a>,
|
||||
type_args: Vec<Ident<'a>>,
|
||||
args: Vec<(Ident<'a>, T)>,
|
||||
body: Box<Expr<'a, T>>,
|
||||
type_: T,
|
||||
},
|
||||
|
||||
Extern {
|
||||
name: Ident<'a>,
|
||||
arg_types: Vec<T>,
|
||||
ret_type: T,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a, T> Decl<'a, T> {
|
||||
pub fn name(&self) -> &Ident<'a> {
|
||||
match self {
|
||||
Decl::Fun { name, .. } => name,
|
||||
Decl::Extern { name, .. } => name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_name(&mut self, new_name: Ident<'a>) {
|
||||
match self {
|
||||
Decl::Fun { name, .. } => *name = new_name,
|
||||
Decl::Extern { name, .. } => *name = new_name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_(&self) -> Option<&T> {
|
||||
match self {
|
||||
Decl::Fun { type_, .. } => Some(type_),
|
||||
Decl::Extern { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn traverse_type<F, U, E>(self, f: F) -> Result<Decl<'a, U>, E>
|
||||
where
|
||||
F: Fn(T) -> Result<U, E> + Clone,
|
||||
{
|
||||
match self {
|
||||
Decl::Fun {
|
||||
name,
|
||||
type_args,
|
||||
args,
|
||||
body,
|
||||
type_,
|
||||
} => Ok(Decl::Fun {
|
||||
name,
|
||||
type_args,
|
||||
args: args
|
||||
.into_iter()
|
||||
.map(|(id, t)| Ok((id, f(t)?)))
|
||||
.try_collect()?,
|
||||
body: Box::new(body.traverse_type(f.clone())?),
|
||||
type_: f(type_)?,
|
||||
}),
|
||||
Decl::Extern {
|
||||
name,
|
||||
arg_types,
|
||||
ret_type,
|
||||
} => Ok(Decl::Extern {
|
||||
name,
|
||||
arg_types: arg_types.into_iter().map(f.clone()).try_collect()?,
|
||||
ret_type: f(ret_type)?,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
447
users/grfn/achilles/src/ast/mod.rs
Normal file
447
users/grfn/achilles/src/ast/mod.rs
Normal file
|
|
@ -0,0 +1,447 @@
|
|||
pub(crate) mod hir;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct InvalidIdentifier<'a>(Cow<'a, str>);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||
pub struct Ident<'a>(pub Cow<'a, str>);
|
||||
|
||||
impl<'a> From<&'a Ident<'a>> for &'a str {
|
||||
fn from(id: &'a Ident<'a>) -> Self {
|
||||
id.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Ident<'a> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Ident<'a> {
|
||||
pub fn to_owned(&self) -> Ident<'static> {
|
||||
Ident(Cow::Owned(self.0.clone().into_owned()))
|
||||
}
|
||||
|
||||
/// Construct an identifier from a &str without checking that it's a valid identifier
|
||||
pub fn from_str_unchecked(s: &'a str) -> Self {
|
||||
debug_assert!(is_valid_identifier(s));
|
||||
Self(Cow::Borrowed(s))
|
||||
}
|
||||
|
||||
pub fn from_string_unchecked(s: String) -> Self {
|
||||
debug_assert!(is_valid_identifier(&s));
|
||||
Self(Cow::Owned(s))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_valid_identifier<S>(s: &S) -> bool
|
||||
where
|
||||
S: AsRef<str> + ?Sized,
|
||||
{
|
||||
s.as_ref()
|
||||
.chars()
|
||||
.any(|c| !c.is_alphanumeric() || !"_".contains(c))
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for Ident<'a> {
|
||||
type Error = InvalidIdentifier<'a>;
|
||||
|
||||
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
|
||||
if is_valid_identifier(s) {
|
||||
Ok(Ident(Cow::Borrowed(s)))
|
||||
} else {
|
||||
Err(InvalidIdentifier(Cow::Borrowed(s)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<String> for Ident<'a> {
|
||||
type Error = InvalidIdentifier<'static>;
|
||||
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||
if is_valid_identifier(&s) {
|
||||
Ok(Ident(Cow::Owned(s)))
|
||||
} else {
|
||||
Err(InvalidIdentifier(Cow::Owned(s)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum BinaryOperator {
|
||||
/// `+`
|
||||
Add,
|
||||
|
||||
/// `-`
|
||||
Sub,
|
||||
|
||||
/// `*`
|
||||
Mul,
|
||||
|
||||
/// `/`
|
||||
Div,
|
||||
|
||||
/// `^`
|
||||
Pow,
|
||||
|
||||
/// `==`
|
||||
Equ,
|
||||
|
||||
/// `!=`
|
||||
Neq,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum UnaryOperator {
|
||||
/// !
|
||||
Not,
|
||||
|
||||
/// -
|
||||
Neg,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Literal<'a> {
|
||||
Unit,
|
||||
Int(u64),
|
||||
Bool(bool),
|
||||
String(Cow<'a, str>),
|
||||
}
|
||||
|
||||
impl<'a> Literal<'a> {
|
||||
pub fn to_owned(&self) -> Literal<'static> {
|
||||
match self {
|
||||
Literal::Int(i) => Literal::Int(*i),
|
||||
Literal::Bool(b) => Literal::Bool(*b),
|
||||
Literal::String(s) => Literal::String(Cow::Owned(s.clone().into_owned())),
|
||||
Literal::Unit => Literal::Unit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Binding<'a> {
|
||||
pub ident: Ident<'a>,
|
||||
pub type_: Option<Type<'a>>,
|
||||
pub body: Expr<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Binding<'a> {
|
||||
fn to_owned(&self) -> Binding<'static> {
|
||||
Binding {
|
||||
ident: self.ident.to_owned(),
|
||||
type_: self.type_.as_ref().map(|t| t.to_owned()),
|
||||
body: self.body.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Expr<'a> {
|
||||
Ident(Ident<'a>),
|
||||
|
||||
Literal(Literal<'a>),
|
||||
|
||||
UnaryOp {
|
||||
op: UnaryOperator,
|
||||
rhs: Box<Expr<'a>>,
|
||||
},
|
||||
|
||||
BinaryOp {
|
||||
lhs: Box<Expr<'a>>,
|
||||
op: BinaryOperator,
|
||||
rhs: Box<Expr<'a>>,
|
||||
},
|
||||
|
||||
Let {
|
||||
bindings: Vec<Binding<'a>>,
|
||||
body: Box<Expr<'a>>,
|
||||
},
|
||||
|
||||
If {
|
||||
condition: Box<Expr<'a>>,
|
||||
then: Box<Expr<'a>>,
|
||||
else_: Box<Expr<'a>>,
|
||||
},
|
||||
|
||||
Fun(Box<Fun<'a>>),
|
||||
|
||||
Call {
|
||||
fun: Box<Expr<'a>>,
|
||||
args: Vec<Expr<'a>>,
|
||||
},
|
||||
|
||||
Ascription {
|
||||
expr: Box<Expr<'a>>,
|
||||
type_: Type<'a>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> Expr<'a> {
|
||||
pub fn to_owned(&self) -> Expr<'static> {
|
||||
match self {
|
||||
Expr::Ident(ref id) => Expr::Ident(id.to_owned()),
|
||||
Expr::Literal(ref lit) => Expr::Literal(lit.to_owned()),
|
||||
Expr::UnaryOp { op, rhs } => Expr::UnaryOp {
|
||||
op: *op,
|
||||
rhs: Box::new((**rhs).to_owned()),
|
||||
},
|
||||
Expr::BinaryOp { lhs, op, rhs } => Expr::BinaryOp {
|
||||
lhs: Box::new((**lhs).to_owned()),
|
||||
op: *op,
|
||||
rhs: Box::new((**rhs).to_owned()),
|
||||
},
|
||||
Expr::Let { bindings, body } => Expr::Let {
|
||||
bindings: bindings.iter().map(|binding| binding.to_owned()).collect(),
|
||||
body: Box::new((**body).to_owned()),
|
||||
},
|
||||
Expr::If {
|
||||
condition,
|
||||
then,
|
||||
else_,
|
||||
} => Expr::If {
|
||||
condition: Box::new((**condition).to_owned()),
|
||||
then: Box::new((**then).to_owned()),
|
||||
else_: Box::new((**else_).to_owned()),
|
||||
},
|
||||
Expr::Fun(fun) => Expr::Fun(Box::new((**fun).to_owned())),
|
||||
Expr::Call { fun, args } => Expr::Call {
|
||||
fun: Box::new((**fun).to_owned()),
|
||||
args: args.iter().map(|arg| arg.to_owned()).collect(),
|
||||
},
|
||||
Expr::Ascription { expr, type_ } => Expr::Ascription {
|
||||
expr: Box::new((**expr).to_owned()),
|
||||
type_: type_.to_owned(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Arg<'a> {
|
||||
pub ident: Ident<'a>,
|
||||
pub type_: Option<Type<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Arg<'a> {
|
||||
pub fn to_owned(&self) -> Arg<'static> {
|
||||
Arg {
|
||||
ident: self.ident.to_owned(),
|
||||
type_: self.type_.as_ref().map(Type::to_owned),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for Arg<'a> {
|
||||
type Error = <Ident<'a> as TryFrom<&'a str>>::Error;
|
||||
|
||||
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
|
||||
Ok(Arg {
|
||||
ident: Ident::try_from(value)?,
|
||||
type_: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Fun<'a> {
|
||||
pub args: Vec<Arg<'a>>,
|
||||
pub body: Expr<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Fun<'a> {
|
||||
pub fn to_owned(&self) -> Fun<'static> {
|
||||
Fun {
|
||||
args: self.args.iter().map(|arg| arg.to_owned()).collect(),
|
||||
body: self.body.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Decl<'a> {
|
||||
Fun {
|
||||
name: Ident<'a>,
|
||||
body: Fun<'a>,
|
||||
},
|
||||
Ascription {
|
||||
name: Ident<'a>,
|
||||
type_: Type<'a>,
|
||||
},
|
||||
Extern {
|
||||
name: Ident<'a>,
|
||||
type_: FunctionType<'a>,
|
||||
},
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct FunctionType<'a> {
|
||||
pub args: Vec<Type<'a>>,
|
||||
pub ret: Box<Type<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> FunctionType<'a> {
|
||||
pub fn to_owned(&self) -> FunctionType<'static> {
|
||||
FunctionType {
|
||||
args: self.args.iter().map(|a| a.to_owned()).collect(),
|
||||
ret: Box::new((*self.ret).to_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for FunctionType<'a> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "fn {} -> {}", self.args.iter().join(", "), self.ret)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Type<'a> {
|
||||
Int,
|
||||
Float,
|
||||
Bool,
|
||||
CString,
|
||||
Unit,
|
||||
Var(Ident<'a>),
|
||||
Function(FunctionType<'a>),
|
||||
}
|
||||
|
||||
impl<'a> Type<'a> {
|
||||
pub fn to_owned(&self) -> Type<'static> {
|
||||
match self {
|
||||
Type::Int => Type::Int,
|
||||
Type::Float => Type::Float,
|
||||
Type::Bool => Type::Bool,
|
||||
Type::CString => Type::CString,
|
||||
Type::Unit => Type::Unit,
|
||||
Type::Var(v) => Type::Var(v.to_owned()),
|
||||
Type::Function(f) => Type::Function(f.to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alpha_equiv(&self, other: &Self) -> bool {
|
||||
fn do_alpha_equiv<'a>(
|
||||
substs: &mut HashMap<&'a Ident<'a>, &'a Ident<'a>>,
|
||||
lhs: &'a Type,
|
||||
rhs: &'a Type,
|
||||
) -> bool {
|
||||
match (lhs, rhs) {
|
||||
(Type::Var(v1), Type::Var(v2)) => substs.entry(v1).or_insert(v2) == &v2,
|
||||
(
|
||||
Type::Function(FunctionType {
|
||||
args: args1,
|
||||
ret: ret1,
|
||||
}),
|
||||
Type::Function(FunctionType {
|
||||
args: args2,
|
||||
ret: ret2,
|
||||
}),
|
||||
) => {
|
||||
args1.len() == args2.len()
|
||||
&& args1
|
||||
.iter()
|
||||
.zip(args2)
|
||||
.all(|(a1, a2)| do_alpha_equiv(substs, a1, a2))
|
||||
&& do_alpha_equiv(substs, ret1, ret2)
|
||||
}
|
||||
_ => lhs == rhs,
|
||||
}
|
||||
}
|
||||
|
||||
let mut substs = HashMap::new();
|
||||
do_alpha_equiv(&mut substs, self, other)
|
||||
}
|
||||
|
||||
pub fn traverse_type_vars<'b, F>(self, mut f: F) -> Type<'b>
|
||||
where
|
||||
F: FnMut(Ident<'a>) -> Type<'b> + Clone,
|
||||
{
|
||||
match self {
|
||||
Type::Var(tv) => f(tv),
|
||||
Type::Function(FunctionType { args, ret }) => Type::Function(FunctionType {
|
||||
args: args
|
||||
.into_iter()
|
||||
.map(|t| t.traverse_type_vars(f.clone()))
|
||||
.collect(),
|
||||
ret: Box::new(ret.traverse_type_vars(f)),
|
||||
}),
|
||||
Type::Int => Type::Int,
|
||||
Type::Float => Type::Float,
|
||||
Type::Bool => Type::Bool,
|
||||
Type::CString => Type::CString,
|
||||
Type::Unit => Type::Unit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Type<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Type::Int => f.write_str("int"),
|
||||
Type::Float => f.write_str("float"),
|
||||
Type::Bool => f.write_str("bool"),
|
||||
Type::CString => f.write_str("cstring"),
|
||||
Type::Unit => f.write_str("()"),
|
||||
Type::Var(v) => v.fmt(f),
|
||||
Type::Function(ft) => ft.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn type_var(n: &str) -> Type<'static> {
|
||||
Type::Var(Ident::try_from(n.to_owned()).unwrap())
|
||||
}
|
||||
|
||||
mod alpha_equiv {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn trivial() {
|
||||
assert!(Type::Int.alpha_equiv(&Type::Int));
|
||||
assert!(!Type::Int.alpha_equiv(&Type::Bool));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_type_var() {
|
||||
assert!(type_var("a").alpha_equiv(&type_var("b")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_with_type_vars_equiv() {
|
||||
assert!(Type::Function(FunctionType {
|
||||
args: vec![type_var("a")],
|
||||
ret: Box::new(type_var("b")),
|
||||
})
|
||||
.alpha_equiv(&Type::Function(FunctionType {
|
||||
args: vec![type_var("b")],
|
||||
ret: Box::new(type_var("a")),
|
||||
})))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_with_type_vars_non_equiv() {
|
||||
assert!(!Type::Function(FunctionType {
|
||||
args: vec![type_var("a")],
|
||||
ret: Box::new(type_var("a")),
|
||||
})
|
||||
.alpha_equiv(&Type::Function(FunctionType {
|
||||
args: vec![type_var("b")],
|
||||
ret: Box::new(type_var("a")),
|
||||
})))
|
||||
}
|
||||
}
|
||||
}
|
||||
436
users/grfn/achilles/src/codegen/llvm.rs
Normal file
436
users/grfn/achilles/src/codegen/llvm.rs
Normal file
|
|
@ -0,0 +1,436 @@
|
|||
use std::convert::{TryFrom, TryInto};
|
||||
use std::path::Path;
|
||||
use std::result;
|
||||
|
||||
use inkwell::basic_block::BasicBlock;
|
||||
use inkwell::builder::Builder;
|
||||
pub use inkwell::context::Context;
|
||||
use inkwell::module::Module;
|
||||
use inkwell::support::LLVMString;
|
||||
use inkwell::types::{BasicType, BasicTypeEnum, FunctionType, IntType};
|
||||
use inkwell::values::{AnyValueEnum, BasicValueEnum, FunctionValue};
|
||||
use inkwell::{AddressSpace, IntPredicate};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::ast::hir::{Binding, Decl, Expr};
|
||||
use crate::ast::{BinaryOperator, Ident, Literal, Type, UnaryOperator};
|
||||
use crate::common::env::Env;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Error)]
|
||||
pub enum Error {
|
||||
#[error("Undefined variable {0}")]
|
||||
UndefinedVariable(Ident<'static>),
|
||||
|
||||
#[error("LLVM Error: {0}")]
|
||||
LLVMError(String),
|
||||
}
|
||||
|
||||
impl From<LLVMString> for Error {
|
||||
fn from(s: LLVMString) -> Self {
|
||||
Self::LLVMError(s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
pub struct Codegen<'ctx, 'ast> {
|
||||
context: &'ctx Context,
|
||||
pub module: Module<'ctx>,
|
||||
builder: Builder<'ctx>,
|
||||
env: Env<&'ast Ident<'ast>, AnyValueEnum<'ctx>>,
|
||||
function_stack: Vec<FunctionValue<'ctx>>,
|
||||
identifier_counter: u32,
|
||||
}
|
||||
|
||||
impl<'ctx, 'ast> Codegen<'ctx, 'ast> {
|
||||
pub fn new(context: &'ctx Context, module_name: &str) -> Self {
|
||||
let module = context.create_module(module_name);
|
||||
let builder = context.create_builder();
|
||||
Self {
|
||||
context,
|
||||
module,
|
||||
builder,
|
||||
env: Default::default(),
|
||||
function_stack: Default::default(),
|
||||
identifier_counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_function<'a>(
|
||||
&'a mut self,
|
||||
name: &str,
|
||||
ty: FunctionType<'ctx>,
|
||||
) -> &'a FunctionValue<'ctx> {
|
||||
self.function_stack
|
||||
.push(self.module.add_function(name, ty, None));
|
||||
let basic_block = self.append_basic_block("entry");
|
||||
self.builder.position_at_end(basic_block);
|
||||
self.function_stack.last().unwrap()
|
||||
}
|
||||
|
||||
pub fn finish_function(&mut self, res: Option<&BasicValueEnum<'ctx>>) -> FunctionValue<'ctx> {
|
||||
self.builder.build_return(match res {
|
||||
// lol
|
||||
Some(val) => Some(val),
|
||||
None => None,
|
||||
});
|
||||
self.function_stack.pop().unwrap()
|
||||
}
|
||||
|
||||
pub fn append_basic_block(&self, name: &str) -> BasicBlock<'ctx> {
|
||||
self.context
|
||||
.append_basic_block(*self.function_stack.last().unwrap(), name)
|
||||
}
|
||||
|
||||
pub fn codegen_expr(
|
||||
&mut self,
|
||||
expr: &'ast Expr<'ast, Type>,
|
||||
) -> Result<Option<AnyValueEnum<'ctx>>> {
|
||||
match expr {
|
||||
Expr::Ident(id, _) => self
|
||||
.env
|
||||
.resolve(id)
|
||||
.cloned()
|
||||
.ok_or_else(|| Error::UndefinedVariable(id.to_owned()))
|
||||
.map(Some),
|
||||
Expr::Literal(lit, ty) => {
|
||||
let ty = self.codegen_int_type(ty);
|
||||
match lit {
|
||||
Literal::Int(i) => Ok(Some(AnyValueEnum::IntValue(ty.const_int(*i, false)))),
|
||||
Literal::Bool(b) => Ok(Some(AnyValueEnum::IntValue(
|
||||
ty.const_int(if *b { 1 } else { 0 }, false),
|
||||
))),
|
||||
Literal::String(s) => Ok(Some(
|
||||
self.builder
|
||||
.build_global_string_ptr(s, "s")
|
||||
.as_pointer_value()
|
||||
.into(),
|
||||
)),
|
||||
Literal::Unit => Ok(None),
|
||||
}
|
||||
}
|
||||
Expr::UnaryOp { op, rhs, .. } => {
|
||||
let rhs = self.codegen_expr(rhs)?.unwrap();
|
||||
match op {
|
||||
UnaryOperator::Not => unimplemented!(),
|
||||
UnaryOperator::Neg => Ok(Some(AnyValueEnum::IntValue(
|
||||
self.builder.build_int_neg(rhs.into_int_value(), "neg"),
|
||||
))),
|
||||
}
|
||||
}
|
||||
Expr::BinaryOp { lhs, op, rhs, .. } => {
|
||||
let lhs = self.codegen_expr(lhs)?.unwrap();
|
||||
let rhs = self.codegen_expr(rhs)?.unwrap();
|
||||
match op {
|
||||
BinaryOperator::Add => {
|
||||
Ok(Some(AnyValueEnum::IntValue(self.builder.build_int_add(
|
||||
lhs.into_int_value(),
|
||||
rhs.into_int_value(),
|
||||
"add",
|
||||
))))
|
||||
}
|
||||
BinaryOperator::Sub => {
|
||||
Ok(Some(AnyValueEnum::IntValue(self.builder.build_int_sub(
|
||||
lhs.into_int_value(),
|
||||
rhs.into_int_value(),
|
||||
"add",
|
||||
))))
|
||||
}
|
||||
BinaryOperator::Mul => {
|
||||
Ok(Some(AnyValueEnum::IntValue(self.builder.build_int_sub(
|
||||
lhs.into_int_value(),
|
||||
rhs.into_int_value(),
|
||||
"add",
|
||||
))))
|
||||
}
|
||||
BinaryOperator::Div => Ok(Some(AnyValueEnum::IntValue(
|
||||
self.builder.build_int_signed_div(
|
||||
lhs.into_int_value(),
|
||||
rhs.into_int_value(),
|
||||
"add",
|
||||
),
|
||||
))),
|
||||
BinaryOperator::Pow => unimplemented!(),
|
||||
BinaryOperator::Equ => Ok(Some(AnyValueEnum::IntValue(
|
||||
self.builder.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
lhs.into_int_value(),
|
||||
rhs.into_int_value(),
|
||||
"eq",
|
||||
),
|
||||
))),
|
||||
BinaryOperator::Neq => todo!(),
|
||||
}
|
||||
}
|
||||
Expr::Let { bindings, body, .. } => {
|
||||
self.env.push();
|
||||
for Binding { ident, body, .. } in bindings {
|
||||
if let Some(val) = self.codegen_expr(body)? {
|
||||
self.env.set(ident, val);
|
||||
}
|
||||
}
|
||||
let res = self.codegen_expr(body);
|
||||
self.env.pop();
|
||||
res
|
||||
}
|
||||
Expr::If {
|
||||
condition,
|
||||
then,
|
||||
else_,
|
||||
type_,
|
||||
} => {
|
||||
let then_block = self.append_basic_block("then");
|
||||
let else_block = self.append_basic_block("else");
|
||||
let join_block = self.append_basic_block("join");
|
||||
let condition = self.codegen_expr(condition)?.unwrap();
|
||||
self.builder.build_conditional_branch(
|
||||
condition.into_int_value(),
|
||||
then_block,
|
||||
else_block,
|
||||
);
|
||||
self.builder.position_at_end(then_block);
|
||||
let then_res = self.codegen_expr(then)?;
|
||||
self.builder.build_unconditional_branch(join_block);
|
||||
|
||||
self.builder.position_at_end(else_block);
|
||||
let else_res = self.codegen_expr(else_)?;
|
||||
self.builder.build_unconditional_branch(join_block);
|
||||
|
||||
self.builder.position_at_end(join_block);
|
||||
if let Some(phi_type) = self.codegen_type(type_) {
|
||||
let phi = self.builder.build_phi(phi_type, "join");
|
||||
phi.add_incoming(&[
|
||||
(
|
||||
&BasicValueEnum::try_from(then_res.unwrap()).unwrap(),
|
||||
then_block,
|
||||
),
|
||||
(
|
||||
&BasicValueEnum::try_from(else_res.unwrap()).unwrap(),
|
||||
else_block,
|
||||
),
|
||||
]);
|
||||
Ok(Some(phi.as_basic_value().into()))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Expr::Call { fun, args, .. } => {
|
||||
if let Expr::Ident(id, _) = &**fun {
|
||||
let function = self
|
||||
.module
|
||||
.get_function(id.into())
|
||||
.or_else(|| self.env.resolve(id)?.clone().try_into().ok())
|
||||
.ok_or_else(|| Error::UndefinedVariable(id.to_owned()))?;
|
||||
let args = args
|
||||
.iter()
|
||||
.map(|arg| Ok(self.codegen_expr(arg)?.unwrap().try_into().unwrap()))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
Ok(self
|
||||
.builder
|
||||
.build_call(function, &args, "call")
|
||||
.try_as_basic_value()
|
||||
.left()
|
||||
.map(|val| val.into()))
|
||||
} else {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
Expr::Fun { args, body, .. } => {
|
||||
let fname = self.fresh_ident("f");
|
||||
let cur_block = self.builder.get_insert_block().unwrap();
|
||||
let env = self.env.save(); // TODO: closures
|
||||
let function = self.codegen_function(&fname, args, body)?;
|
||||
self.builder.position_at_end(cur_block);
|
||||
self.env.restore(env);
|
||||
Ok(Some(function.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn codegen_function(
|
||||
&mut self,
|
||||
name: &str,
|
||||
args: &'ast [(Ident<'ast>, Type)],
|
||||
body: &'ast Expr<'ast, Type>,
|
||||
) -> Result<FunctionValue<'ctx>> {
|
||||
let arg_types = args
|
||||
.iter()
|
||||
.filter_map(|(_, at)| self.codegen_type(at))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
self.new_function(
|
||||
name,
|
||||
match self.codegen_type(body.type_()) {
|
||||
Some(ret_ty) => ret_ty.fn_type(&arg_types, false),
|
||||
None => self.context.void_type().fn_type(&arg_types, false),
|
||||
},
|
||||
);
|
||||
self.env.push();
|
||||
for (i, (arg, _)) in args.iter().enumerate() {
|
||||
self.env.set(
|
||||
arg,
|
||||
self.cur_function().get_nth_param(i as u32).unwrap().into(),
|
||||
);
|
||||
}
|
||||
let res = self.codegen_expr(body)?;
|
||||
self.env.pop();
|
||||
Ok(self.finish_function(res.map(|av| av.try_into().unwrap()).as_ref()))
|
||||
}
|
||||
|
||||
pub fn codegen_extern(
|
||||
&mut self,
|
||||
name: &str,
|
||||
args: &'ast [Type],
|
||||
ret: &'ast Type,
|
||||
) -> Result<()> {
|
||||
let arg_types = args
|
||||
.iter()
|
||||
.map(|t| self.codegen_type(t).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
self.module.add_function(
|
||||
name,
|
||||
match self.codegen_type(ret) {
|
||||
Some(ret_ty) => ret_ty.fn_type(&arg_types, false),
|
||||
None => self.context.void_type().fn_type(&arg_types, false),
|
||||
},
|
||||
None,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn codegen_decl(&mut self, decl: &'ast Decl<'ast, Type>) -> Result<()> {
|
||||
match decl {
|
||||
Decl::Fun {
|
||||
name, args, body, ..
|
||||
} => {
|
||||
self.codegen_function(name.into(), args, body)?;
|
||||
Ok(())
|
||||
}
|
||||
Decl::Extern {
|
||||
name,
|
||||
arg_types,
|
||||
ret_type,
|
||||
} => self.codegen_extern(name.into(), arg_types, ret_type),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn codegen_main(&mut self, expr: &'ast Expr<'ast, Type>) -> Result<()> {
|
||||
self.new_function("main", self.context.i64_type().fn_type(&[], false));
|
||||
let res = self.codegen_expr(expr)?;
|
||||
if *expr.type_() != Type::Int {
|
||||
self.builder
|
||||
.build_return(Some(&self.context.i64_type().const_int(0, false)));
|
||||
} else {
|
||||
self.finish_function(res.map(|r| r.try_into().unwrap()).as_ref());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn codegen_type(&self, type_: &'ast Type) -> Option<BasicTypeEnum<'ctx>> {
|
||||
// TODO
|
||||
match type_ {
|
||||
Type::Int => Some(self.context.i64_type().into()),
|
||||
Type::Float => Some(self.context.f64_type().into()),
|
||||
Type::Bool => Some(self.context.bool_type().into()),
|
||||
Type::CString => Some(
|
||||
self.context
|
||||
.i8_type()
|
||||
.ptr_type(AddressSpace::Generic)
|
||||
.into(),
|
||||
),
|
||||
Type::Function(_) => todo!(),
|
||||
Type::Var(_) => unreachable!(),
|
||||
Type::Unit => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn codegen_int_type(&self, type_: &'ast Type) -> IntType<'ctx> {
|
||||
// TODO
|
||||
self.context.i64_type()
|
||||
}
|
||||
|
||||
pub fn print_to_file<P>(&self, path: P) -> Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
Ok(self.module.print_to_file(path)?)
|
||||
}
|
||||
|
||||
pub fn binary_to_file<P>(&self, path: P) -> Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
if self.module.write_bitcode_to_path(path.as_ref()) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::LLVMError(
|
||||
"Error writing bitcode to output path".to_owned(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn fresh_ident(&mut self, prefix: &str) -> String {
|
||||
self.identifier_counter += 1;
|
||||
format!("{}{}", prefix, self.identifier_counter)
|
||||
}
|
||||
|
||||
fn cur_function(&self) -> &FunctionValue<'ctx> {
|
||||
self.function_stack.last().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use inkwell::execution_engine::JitFunction;
|
||||
use inkwell::OptimizationLevel;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn jit_eval<T>(expr: &str) -> anyhow::Result<T> {
|
||||
let expr = crate::parser::expr(expr).unwrap().1;
|
||||
|
||||
let expr = crate::tc::typecheck_expr(expr).unwrap();
|
||||
|
||||
let context = Context::create();
|
||||
let mut codegen = Codegen::new(&context, "test");
|
||||
let execution_engine = codegen
|
||||
.module
|
||||
.create_jit_execution_engine(OptimizationLevel::None)
|
||||
.unwrap();
|
||||
|
||||
codegen.codegen_function("test", &[], &expr)?;
|
||||
|
||||
unsafe {
|
||||
let fun: JitFunction<unsafe extern "C" fn() -> T> =
|
||||
execution_engine.get_function("test")?;
|
||||
Ok(fun.call())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_literals() {
|
||||
assert_eq!(jit_eval::<i64>("1 + 2").unwrap(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variable_shadowing() {
|
||||
assert_eq!(
|
||||
jit_eval::<i64>("let x = 1 in (let x = 2 in x) + x").unwrap(),
|
||||
3
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eq() {
|
||||
assert_eq!(
|
||||
jit_eval::<i64>("let x = 1 in if x == 1 then 2 else 4").unwrap(),
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_call() {
|
||||
let res = jit_eval::<i64>("let id = fn x = x in id 1").unwrap();
|
||||
assert_eq!(res, 1);
|
||||
}
|
||||
}
|
||||
25
users/grfn/achilles/src/codegen/mod.rs
Normal file
25
users/grfn/achilles/src/codegen/mod.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
pub mod llvm;
|
||||
|
||||
use inkwell::execution_engine::JitFunction;
|
||||
use inkwell::OptimizationLevel;
|
||||
pub use llvm::*;
|
||||
|
||||
use crate::ast::hir::Expr;
|
||||
use crate::ast::Type;
|
||||
use crate::common::Result;
|
||||
|
||||
pub fn jit_eval<T>(expr: &Expr<Type>) -> Result<T> {
|
||||
let context = Context::create();
|
||||
let mut codegen = Codegen::new(&context, "eval");
|
||||
let execution_engine = codegen
|
||||
.module
|
||||
.create_jit_execution_engine(OptimizationLevel::None)
|
||||
.map_err(Error::from)?;
|
||||
codegen.codegen_function("test", &[], &expr)?;
|
||||
|
||||
unsafe {
|
||||
let fun: JitFunction<unsafe extern "C" fn() -> T> =
|
||||
execution_engine.get_function("eval").unwrap();
|
||||
Ok(fun.call())
|
||||
}
|
||||
}
|
||||
39
users/grfn/achilles/src/commands/check.rs
Normal file
39
users/grfn/achilles/src/commands/check.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
use clap::Clap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::ast::Type;
|
||||
use crate::{parser, tc, Result};
|
||||
|
||||
/// Typecheck a file or expression
|
||||
#[derive(Clap)]
|
||||
pub struct Check {
|
||||
/// File to check
|
||||
path: Option<PathBuf>,
|
||||
|
||||
/// Expression to check
|
||||
#[clap(long, short = 'e')]
|
||||
expr: Option<String>,
|
||||
}
|
||||
|
||||
fn run_expr(expr: String) -> Result<Type<'static>> {
|
||||
let (_, parsed) = parser::expr(&expr)?;
|
||||
let hir_expr = tc::typecheck_expr(parsed)?;
|
||||
Ok(hir_expr.type_().to_owned())
|
||||
}
|
||||
|
||||
fn run_path(path: PathBuf) -> Result<Type<'static>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
impl Check {
|
||||
pub fn run(self) -> Result<()> {
|
||||
let type_ = match (self.path, self.expr) {
|
||||
(None, None) => Err("Must specify either a file or expression to check".into()),
|
||||
(Some(_), Some(_)) => Err("Cannot specify both a file and expression to check".into()),
|
||||
(None, Some(expr)) => run_expr(expr),
|
||||
(Some(path), None) => run_path(path),
|
||||
}?;
|
||||
println!("type: {}", type_);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
31
users/grfn/achilles/src/commands/compile.rs
Normal file
31
users/grfn/achilles/src/commands/compile.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use clap::Clap;
|
||||
|
||||
use crate::common::Result;
|
||||
use crate::compiler::{self, CompilerOptions};
|
||||
|
||||
/// Compile a source file
|
||||
#[derive(Clap)]
|
||||
pub struct Compile {
|
||||
/// File to compile
|
||||
file: PathBuf,
|
||||
|
||||
/// Output file
|
||||
#[clap(short = 'o')]
|
||||
out_file: PathBuf,
|
||||
|
||||
#[clap(flatten)]
|
||||
options: CompilerOptions,
|
||||
}
|
||||
|
||||
impl Compile {
|
||||
pub fn run(self) -> Result<()> {
|
||||
eprintln!(
|
||||
">>> {} -> {}",
|
||||
&self.file.to_string_lossy(),
|
||||
self.out_file.to_string_lossy()
|
||||
);
|
||||
compiler::compile_file(&self.file, &self.out_file, &self.options)
|
||||
}
|
||||
}
|
||||
32
users/grfn/achilles/src/commands/eval.rs
Normal file
32
users/grfn/achilles/src/commands/eval.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use clap::Clap;
|
||||
|
||||
use crate::codegen;
|
||||
use crate::interpreter;
|
||||
use crate::parser;
|
||||
use crate::tc;
|
||||
use crate::Result;
|
||||
|
||||
/// Evaluate an expression and print its result
|
||||
#[derive(Clap)]
|
||||
pub struct Eval {
|
||||
/// JIT-compile with LLVM instead of interpreting
|
||||
#[clap(long)]
|
||||
jit: bool,
|
||||
|
||||
/// Expression to evaluate
|
||||
expr: String,
|
||||
}
|
||||
|
||||
impl Eval {
|
||||
pub fn run(self) -> Result<()> {
|
||||
let (_, parsed) = parser::expr(&self.expr)?;
|
||||
let hir = tc::typecheck_expr(parsed)?;
|
||||
let result = if self.jit {
|
||||
codegen::jit_eval::<i64>(&hir)?.into()
|
||||
} else {
|
||||
interpreter::eval(&hir)?
|
||||
};
|
||||
println!("{}", result);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
7
users/grfn/achilles/src/commands/mod.rs
Normal file
7
users/grfn/achilles/src/commands/mod.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
pub mod check;
|
||||
pub mod compile;
|
||||
pub mod eval;
|
||||
|
||||
pub use check::Check;
|
||||
pub use compile::Compile;
|
||||
pub use eval::Eval;
|
||||
59
users/grfn/achilles/src/common/env.rs
Normal file
59
users/grfn/achilles/src/common/env.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
use std::borrow::Borrow;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
use std::mem;
|
||||
|
||||
/// A lexical environment
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Env<K: Eq + Hash, V>(Vec<HashMap<K, V>>);
|
||||
|
||||
impl<K, V> Default for Env<K, V>
|
||||
where
|
||||
K: Eq + Hash,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> Env<K, V>
|
||||
where
|
||||
K: Eq + Hash,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
Self(vec![Default::default()])
|
||||
}
|
||||
|
||||
pub fn push(&mut self) {
|
||||
self.0.push(Default::default());
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) {
|
||||
self.0.pop();
|
||||
}
|
||||
|
||||
pub fn save(&mut self) -> Self {
|
||||
mem::take(self)
|
||||
}
|
||||
|
||||
pub fn restore(&mut self, saved: Self) {
|
||||
*self = saved;
|
||||
}
|
||||
|
||||
pub fn set(&mut self, k: K, v: V) {
|
||||
self.0.last_mut().unwrap().insert(k, v);
|
||||
}
|
||||
|
||||
pub fn resolve<'a, Q>(&'a self, k: &Q) -> Option<&'a V>
|
||||
where
|
||||
K: Borrow<Q>,
|
||||
Q: Hash + Eq + ?Sized,
|
||||
{
|
||||
for ctx in self.0.iter().rev() {
|
||||
if let Some(res) = ctx.get(k) {
|
||||
return Some(res);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
59
users/grfn/achilles/src/common/error.rs
Normal file
59
users/grfn/achilles/src/common/error.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
use std::{io, result};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{codegen, interpreter, parser, tc};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
IOError(#[from] io::Error),
|
||||
|
||||
#[error("Error parsing input: {0}")]
|
||||
ParseError(#[from] parser::Error),
|
||||
|
||||
#[error("Error evaluating expression: {0}")]
|
||||
EvalError(#[from] interpreter::Error),
|
||||
|
||||
#[error("Compile error: {0}")]
|
||||
CodegenError(#[from] codegen::Error),
|
||||
|
||||
#[error("Type error: {0}")]
|
||||
TypeError(#[from] tc::Error),
|
||||
|
||||
#[error("{0}")]
|
||||
Message(String),
|
||||
}
|
||||
|
||||
impl From<String> for Error {
|
||||
fn from(s: String) -> Self {
|
||||
Self::Message(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Error {
|
||||
fn from(s: &'a str) -> Self {
|
||||
Self::Message(s.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<nom::Err<nom::error::Error<&'a str>>> for Error {
|
||||
fn from(e: nom::Err<nom::error::Error<&'a str>>) -> Self {
|
||||
use nom::error::Error as NomError;
|
||||
use nom::Err::*;
|
||||
|
||||
Self::ParseError(match e {
|
||||
Incomplete(i) => Incomplete(i),
|
||||
Error(NomError { input, code }) => Error(NomError {
|
||||
input: input.to_owned(),
|
||||
code,
|
||||
}),
|
||||
Failure(NomError { input, code }) => Failure(NomError {
|
||||
input: input.to_owned(),
|
||||
code,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
6
users/grfn/achilles/src/common/mod.rs
Normal file
6
users/grfn/achilles/src/common/mod.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
pub(crate) mod env;
|
||||
pub(crate) mod error;
|
||||
pub(crate) mod namer;
|
||||
|
||||
pub use error::{Error, Result};
|
||||
pub use namer::{Namer, NamerOf};
|
||||
122
users/grfn/achilles/src/common/namer.rs
Normal file
122
users/grfn/achilles/src/common/namer.rs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
use std::fmt::Display;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub struct Namer<T, F> {
|
||||
make_name: F,
|
||||
counter: u64,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, F> Namer<T, F> {
|
||||
pub fn new(make_name: F) -> Self {
|
||||
Namer {
|
||||
make_name,
|
||||
counter: 0,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Namer<String, Box<dyn Fn(u64) -> String>> {
|
||||
pub fn with_prefix<T>(prefix: T) -> Self
|
||||
where
|
||||
T: Display + 'static,
|
||||
{
|
||||
Namer::new(move |i| format!("{}{}", prefix, i)).boxed()
|
||||
}
|
||||
|
||||
pub fn with_suffix<T>(suffix: T) -> Self
|
||||
where
|
||||
T: Display + 'static,
|
||||
{
|
||||
Namer::new(move |i| format!("{}{}", i, suffix)).boxed()
|
||||
}
|
||||
|
||||
pub fn alphabetic() -> Self {
|
||||
Namer::new(|i| {
|
||||
if i <= 26 {
|
||||
std::char::from_u32((i + 96) as u32).unwrap().to_string()
|
||||
} else {
|
||||
format!(
|
||||
"{}{}",
|
||||
std::char::from_u32(((i % 26) + 96) as u32).unwrap(),
|
||||
i - 26
|
||||
)
|
||||
}
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, F> Namer<T, F>
|
||||
where
|
||||
F: Fn(u64) -> T,
|
||||
{
|
||||
pub fn make_name(&mut self) -> T {
|
||||
self.counter += 1;
|
||||
(self.make_name)(self.counter)
|
||||
}
|
||||
|
||||
pub fn boxed(self) -> NamerOf<T>
|
||||
where
|
||||
F: 'static,
|
||||
{
|
||||
Namer {
|
||||
make_name: Box::new(self.make_name),
|
||||
counter: self.counter,
|
||||
_phantom: self._phantom,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map<G, U>(self, f: G) -> NamerOf<U>
|
||||
where
|
||||
G: Fn(T) -> U + 'static,
|
||||
T: 'static,
|
||||
F: 'static,
|
||||
{
|
||||
Namer {
|
||||
counter: self.counter,
|
||||
make_name: Box::new(move |x| f((self.make_name)(x))),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type NamerOf<T> = Namer<T, Box<dyn Fn(u64) -> T>>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn prefix() {
|
||||
let mut namer = Namer::with_prefix("t");
|
||||
assert_eq!(namer.make_name(), "t1");
|
||||
assert_eq!(namer.make_name(), "t2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suffix() {
|
||||
let mut namer = Namer::with_suffix("t");
|
||||
assert_eq!(namer.make_name(), "1t");
|
||||
assert_eq!(namer.make_name(), "2t");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alphabetic() {
|
||||
let mut namer = Namer::alphabetic();
|
||||
assert_eq!(namer.make_name(), "a");
|
||||
assert_eq!(namer.make_name(), "b");
|
||||
(0..25).for_each(|_| {
|
||||
namer.make_name();
|
||||
});
|
||||
assert_eq!(namer.make_name(), "b2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_callback() {
|
||||
let mut namer = Namer::new(|n| n + 1);
|
||||
assert_eq!(namer.make_name(), 2);
|
||||
assert_eq!(namer.make_name(), 3);
|
||||
}
|
||||
}
|
||||
89
users/grfn/achilles/src/compiler.rs
Normal file
89
users/grfn/achilles/src/compiler.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
use std::fmt::{self, Display};
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use std::{fs, result};
|
||||
|
||||
use clap::Clap;
|
||||
use test_strategy::Arbitrary;
|
||||
|
||||
use crate::codegen::{self, Codegen};
|
||||
use crate::common::Result;
|
||||
use crate::passes::hir::{monomorphize, strip_positive_units};
|
||||
use crate::{parser, tc};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Arbitrary)]
|
||||
pub enum OutputFormat {
|
||||
LLVM,
|
||||
Bitcode,
|
||||
}
|
||||
|
||||
impl Default for OutputFormat {
|
||||
fn default() -> Self {
|
||||
Self::Bitcode
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for OutputFormat {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> result::Result<Self, Self::Err> {
|
||||
match s {
|
||||
"llvm" => Ok(Self::LLVM),
|
||||
"binary" => Ok(Self::Bitcode),
|
||||
_ => Err(format!(
|
||||
"Invalid output format {}, expected one of {{llvm, binary}}",
|
||||
s
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for OutputFormat {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
OutputFormat::LLVM => f.write_str("llvm"),
|
||||
OutputFormat::Bitcode => f.write_str("binary"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clap, Debug, PartialEq, Eq, Default)]
|
||||
pub struct CompilerOptions {
|
||||
#[clap(long, short = 'f', default_value)]
|
||||
format: OutputFormat,
|
||||
}
|
||||
|
||||
pub fn compile_file(input: &Path, output: &Path, options: &CompilerOptions) -> Result<()> {
|
||||
let src = fs::read_to_string(input)?;
|
||||
let (_, decls) = parser::toplevel(&src)?;
|
||||
let mut decls = tc::typecheck_toplevel(decls)?;
|
||||
monomorphize::run_toplevel(&mut decls);
|
||||
strip_positive_units::run_toplevel(&mut decls);
|
||||
|
||||
let context = codegen::Context::create();
|
||||
let mut codegen = Codegen::new(
|
||||
&context,
|
||||
&input
|
||||
.file_stem()
|
||||
.map_or("UNKNOWN".to_owned(), |s| s.to_string_lossy().into_owned()),
|
||||
);
|
||||
for decl in &decls {
|
||||
codegen.codegen_decl(decl)?;
|
||||
}
|
||||
match options.format {
|
||||
OutputFormat::LLVM => codegen.print_to_file(output)?,
|
||||
OutputFormat::Bitcode => codegen.binary_to_file(output)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use test_strategy::proptest;
|
||||
|
||||
#[proptest]
|
||||
fn output_format_display_from_str_round_trip(of: OutputFormat) {
|
||||
assert_eq!(OutputFormat::from_str(&of.to_string()), Ok(of));
|
||||
}
|
||||
}
|
||||
19
users/grfn/achilles/src/interpreter/error.rs
Normal file
19
users/grfn/achilles/src/interpreter/error.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
use std::result;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::ast::{Ident, Type};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Error)]
|
||||
pub enum Error {
|
||||
#[error("Undefined variable {0}")]
|
||||
UndefinedVariable(Ident<'static>),
|
||||
|
||||
#[error("Unexpected type {actual}, expected type {expected}")]
|
||||
InvalidType {
|
||||
actual: Type<'static>,
|
||||
expected: Type<'static>,
|
||||
},
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
182
users/grfn/achilles/src/interpreter/mod.rs
Normal file
182
users/grfn/achilles/src/interpreter/mod.rs
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
mod error;
|
||||
mod value;
|
||||
|
||||
pub use self::error::{Error, Result};
|
||||
pub use self::value::{Function, Value};
|
||||
use crate::ast::hir::{Binding, Expr};
|
||||
use crate::ast::{BinaryOperator, FunctionType, Ident, Literal, Type, UnaryOperator};
|
||||
use crate::common::env::Env;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Interpreter<'a> {
|
||||
env: Env<&'a Ident<'a>, Value<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Interpreter<'a> {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn resolve(&self, var: &'a Ident<'a>) -> Result<Value<'a>> {
|
||||
self.env
|
||||
.resolve(var)
|
||||
.cloned()
|
||||
.ok_or_else(|| Error::UndefinedVariable(var.to_owned()))
|
||||
}
|
||||
|
||||
pub fn eval(&mut self, expr: &'a Expr<'a, Type>) -> Result<Value<'a>> {
|
||||
let res = match expr {
|
||||
Expr::Ident(id, _) => self.resolve(id),
|
||||
Expr::Literal(Literal::Int(i), _) => Ok((*i).into()),
|
||||
Expr::Literal(Literal::Bool(b), _) => Ok((*b).into()),
|
||||
Expr::Literal(Literal::String(s), _) => Ok(s.clone().into()),
|
||||
Expr::Literal(Literal::Unit, _) => unreachable!(),
|
||||
Expr::UnaryOp { op, rhs, .. } => {
|
||||
let rhs = self.eval(rhs)?;
|
||||
match op {
|
||||
UnaryOperator::Neg => -rhs,
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
Expr::BinaryOp { lhs, op, rhs, .. } => {
|
||||
let lhs = self.eval(lhs)?;
|
||||
let rhs = self.eval(rhs)?;
|
||||
match op {
|
||||
BinaryOperator::Add => lhs + rhs,
|
||||
BinaryOperator::Sub => lhs - rhs,
|
||||
BinaryOperator::Mul => lhs * rhs,
|
||||
BinaryOperator::Div => lhs / rhs,
|
||||
BinaryOperator::Pow => todo!(),
|
||||
BinaryOperator::Equ => Ok(lhs.eq(&rhs).into()),
|
||||
BinaryOperator::Neq => todo!(),
|
||||
}
|
||||
}
|
||||
Expr::Let { bindings, body, .. } => {
|
||||
self.env.push();
|
||||
for Binding { ident, body, .. } in bindings {
|
||||
let val = self.eval(body)?;
|
||||
self.env.set(ident, val);
|
||||
}
|
||||
let res = self.eval(body)?;
|
||||
self.env.pop();
|
||||
Ok(res)
|
||||
}
|
||||
Expr::If {
|
||||
condition,
|
||||
then,
|
||||
else_,
|
||||
..
|
||||
} => {
|
||||
let condition = self.eval(condition)?;
|
||||
if *(condition.as_type::<bool>()?) {
|
||||
self.eval(then)
|
||||
} else {
|
||||
self.eval(else_)
|
||||
}
|
||||
}
|
||||
Expr::Call { ref fun, args, .. } => {
|
||||
let fun = self.eval(fun)?;
|
||||
let expected_type = FunctionType {
|
||||
args: args.iter().map(|_| Type::Int).collect(),
|
||||
ret: Box::new(Type::Int),
|
||||
};
|
||||
|
||||
let Function {
|
||||
args: function_args,
|
||||
body,
|
||||
..
|
||||
} = fun.as_function(expected_type)?;
|
||||
let arg_values = function_args.iter().zip(
|
||||
args.iter()
|
||||
.map(|v| self.eval(v))
|
||||
.collect::<Result<Vec<_>>>()?,
|
||||
);
|
||||
let mut interpreter = Interpreter::new();
|
||||
for (arg_name, arg_value) in arg_values {
|
||||
interpreter.env.set(arg_name, arg_value);
|
||||
}
|
||||
Ok(Value::from(*interpreter.eval(body)?.as_type::<i64>()?))
|
||||
}
|
||||
Expr::Fun {
|
||||
type_args: _,
|
||||
args,
|
||||
body,
|
||||
type_,
|
||||
} => {
|
||||
let type_ = match type_ {
|
||||
Type::Function(ft) => ft.clone(),
|
||||
_ => unreachable!("Function expression without function type"),
|
||||
};
|
||||
|
||||
Ok(Value::from(value::Function {
|
||||
// TODO
|
||||
type_,
|
||||
args: args.iter().map(|(arg, _)| arg.to_owned()).collect(),
|
||||
body: (**body).to_owned(),
|
||||
}))
|
||||
}
|
||||
}?;
|
||||
debug_assert_eq!(&res.type_(), expr.type_());
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval<'a>(expr: &'a Expr<'a, Type>) -> Result<Value<'a>> {
|
||||
let mut interpreter = Interpreter::new();
|
||||
interpreter.eval(expr)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use super::value::{TypeOf, Val};
|
||||
use super::*;
|
||||
use BinaryOperator::*;
|
||||
|
||||
fn int_lit(i: u64) -> Box<Expr<'static, Type<'static>>> {
|
||||
Box::new(Expr::Literal(Literal::Int(i), Type::Int))
|
||||
}
|
||||
|
||||
fn do_eval<T>(src: &str) -> T
|
||||
where
|
||||
for<'a> &'a T: TryFrom<&'a Val<'a>>,
|
||||
T: Clone + TypeOf,
|
||||
{
|
||||
let expr = crate::parser::expr(src).unwrap().1;
|
||||
let hir = crate::tc::typecheck_expr(expr).unwrap();
|
||||
let res = eval(&hir).unwrap();
|
||||
res.as_type::<T>().unwrap().clone()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_addition() {
|
||||
let expr = Expr::BinaryOp {
|
||||
lhs: int_lit(1),
|
||||
op: Mul,
|
||||
rhs: int_lit(2),
|
||||
type_: Type::Int,
|
||||
};
|
||||
let res = eval(&expr).unwrap();
|
||||
assert_eq!(*res.as_type::<i64>().unwrap(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variable_shadowing() {
|
||||
let res = do_eval::<i64>("let x = 1 in (let x = 2 in x) + x");
|
||||
assert_eq!(res, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conditional_with_equals() {
|
||||
let res = do_eval::<i64>("let x = 1 in if x == 1 then 2 else 4");
|
||||
assert_eq!(res, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn function_call() {
|
||||
let res = do_eval::<i64>("let id = fn x = x in id 1");
|
||||
assert_eq!(res, 1);
|
||||
}
|
||||
}
|
||||
203
users/grfn/achilles/src/interpreter/value.rs
Normal file
203
users/grfn/achilles/src/interpreter/value.rs
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
use std::borrow::Cow;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::{self, Display};
|
||||
use std::ops::{Add, Div, Mul, Neg, Sub};
|
||||
use std::rc::Rc;
|
||||
use std::result;
|
||||
|
||||
use derive_more::{Deref, From, TryInto};
|
||||
|
||||
use super::{Error, Result};
|
||||
use crate::ast::hir::Expr;
|
||||
use crate::ast::{FunctionType, Ident, Type};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Function<'a> {
|
||||
pub type_: FunctionType<'a>,
|
||||
pub args: Vec<Ident<'a>>,
|
||||
pub body: Expr<'a, Type<'a>>,
|
||||
}
|
||||
|
||||
#[derive(From, TryInto)]
|
||||
#[try_into(owned, ref)]
|
||||
pub enum Val<'a> {
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
String(Cow<'a, str>),
|
||||
Function(Function<'a>),
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<Val<'a>> for String {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: Val<'a>) -> result::Result<Self, Self::Error> {
|
||||
match value {
|
||||
Val::String(s) => Ok(s.into_owned()),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Debug for Val<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Val::Int(x) => f.debug_tuple("Int").field(x).finish(),
|
||||
Val::Float(x) => f.debug_tuple("Float").field(x).finish(),
|
||||
Val::Bool(x) => f.debug_tuple("Bool").field(x).finish(),
|
||||
Val::String(s) => f.debug_tuple("String").field(s).finish(),
|
||||
Val::Function(Function { type_, .. }) => {
|
||||
f.debug_struct("Function").field("type_", type_).finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PartialEq for Val<'a> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Val::Int(x), Val::Int(y)) => x == y,
|
||||
(Val::Float(x), Val::Float(y)) => x == y,
|
||||
(Val::Bool(x), Val::Bool(y)) => x == y,
|
||||
(Val::Function(_), Val::Function(_)) => false,
|
||||
(_, _) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<u64> for Val<'a> {
|
||||
fn from(i: u64) -> Self {
|
||||
Self::from(i as i64)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Val<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Val::Int(x) => x.fmt(f),
|
||||
Val::Float(x) => x.fmt(f),
|
||||
Val::Bool(x) => x.fmt(f),
|
||||
Val::String(s) => write!(f, "{:?}", s),
|
||||
Val::Function(Function { type_, .. }) => write!(f, "<{}>", type_),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Val<'a> {
|
||||
pub fn type_(&self) -> Type {
|
||||
match self {
|
||||
Val::Int(_) => Type::Int,
|
||||
Val::Float(_) => Type::Float,
|
||||
Val::Bool(_) => Type::Bool,
|
||||
Val::String(_) => Type::CString,
|
||||
Val::Function(Function { type_, .. }) => Type::Function(type_.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_type<'b, T>(&'b self) -> Result<&'b T>
|
||||
where
|
||||
T: TypeOf + 'b + Clone,
|
||||
&'b T: TryFrom<&'b Self>,
|
||||
{
|
||||
<&T>::try_from(self).map_err(|_| Error::InvalidType {
|
||||
actual: self.type_().to_owned(),
|
||||
expected: <T as TypeOf>::type_of(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn as_function<'b>(&'b self, function_type: FunctionType) -> Result<&'b Function<'a>> {
|
||||
match self {
|
||||
Val::Function(f) if f.type_ == function_type => Ok(&f),
|
||||
_ => Err(Error::InvalidType {
|
||||
actual: self.type_().to_owned(),
|
||||
expected: Type::Function(function_type.to_owned()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Deref)]
|
||||
pub struct Value<'a>(Rc<Val<'a>>);
|
||||
|
||||
impl<'a> Display for Value<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> From<T> for Value<'a>
|
||||
where
|
||||
Val<'a>: From<T>,
|
||||
{
|
||||
fn from(x: T) -> Self {
|
||||
Self(Rc::new(x.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Neg for Value<'a> {
|
||||
type Output = Result<Value<'a>>;
|
||||
|
||||
fn neg(self) -> Self::Output {
|
||||
Ok((-self.as_type::<i64>()?).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Add for Value<'a> {
|
||||
type Output = Result<Value<'a>>;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
Ok((self.as_type::<i64>()? + rhs.as_type::<i64>()?).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Sub for Value<'a> {
|
||||
type Output = Result<Value<'a>>;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
Ok((self.as_type::<i64>()? - rhs.as_type::<i64>()?).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Mul for Value<'a> {
|
||||
type Output = Result<Value<'a>>;
|
||||
|
||||
fn mul(self, rhs: Self) -> Self::Output {
|
||||
Ok((self.as_type::<i64>()? * rhs.as_type::<i64>()?).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Div for Value<'a> {
|
||||
type Output = Result<Value<'a>>;
|
||||
|
||||
fn div(self, rhs: Self) -> Self::Output {
|
||||
Ok((self.as_type::<f64>()? / rhs.as_type::<f64>()?).into())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TypeOf {
|
||||
fn type_of() -> Type<'static>;
|
||||
}
|
||||
|
||||
impl TypeOf for i64 {
|
||||
fn type_of() -> Type<'static> {
|
||||
Type::Int
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOf for bool {
|
||||
fn type_of() -> Type<'static> {
|
||||
Type::Bool
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOf for f64 {
|
||||
fn type_of() -> Type<'static> {
|
||||
Type::Float
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOf for String {
|
||||
fn type_of() -> Type<'static> {
|
||||
Type::CString
|
||||
}
|
||||
}
|
||||
36
users/grfn/achilles/src/main.rs
Normal file
36
users/grfn/achilles/src/main.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
use clap::Clap;
|
||||
|
||||
pub mod ast;
|
||||
pub mod codegen;
|
||||
pub(crate) mod commands;
|
||||
pub(crate) mod common;
|
||||
pub mod compiler;
|
||||
pub mod interpreter;
|
||||
pub(crate) mod passes;
|
||||
#[macro_use]
|
||||
pub mod parser;
|
||||
pub mod tc;
|
||||
|
||||
pub use common::{Error, Result};
|
||||
|
||||
#[derive(Clap)]
|
||||
struct Opts {
|
||||
#[clap(subcommand)]
|
||||
subcommand: Command,
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
enum Command {
|
||||
Eval(commands::Eval),
|
||||
Compile(commands::Compile),
|
||||
Check(commands::Check),
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let opts = Opts::parse();
|
||||
match opts.subcommand {
|
||||
Command::Eval(eval) => Ok(eval.run()?),
|
||||
Command::Compile(compile) => Ok(compile.run()?),
|
||||
Command::Check(check) => Ok(check.run()?),
|
||||
}
|
||||
}
|
||||
645
users/grfn/achilles/src/parser/expr.rs
Normal file
645
users/grfn/achilles/src/parser/expr.rs
Normal file
|
|
@ -0,0 +1,645 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use nom::alt;
|
||||
use nom::character::complete::{digit1, multispace0, multispace1};
|
||||
use nom::{
|
||||
call, char, complete, delimited, do_parse, flat_map, many0, map, named, opt, parse_to,
|
||||
preceded, separated_list0, separated_list1, tag, tuple,
|
||||
};
|
||||
use pratt::{Affix, Associativity, PrattParser, Precedence};
|
||||
|
||||
use crate::ast::{BinaryOperator, Binding, Expr, Fun, Literal, UnaryOperator};
|
||||
use crate::parser::{arg, ident, type_};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum TokenTree<'a> {
|
||||
Prefix(UnaryOperator),
|
||||
// Postfix(char),
|
||||
Infix(BinaryOperator),
|
||||
Primary(Expr<'a>),
|
||||
Group(Vec<TokenTree<'a>>),
|
||||
}
|
||||
|
||||
named!(prefix(&str) -> TokenTree, map!(alt!(
|
||||
complete!(char!('-')) => { |_| UnaryOperator::Neg } |
|
||||
complete!(char!('!')) => { |_| UnaryOperator::Not }
|
||||
), TokenTree::Prefix));
|
||||
|
||||
named!(infix(&str) -> TokenTree, map!(alt!(
|
||||
complete!(tag!("==")) => { |_| BinaryOperator::Equ } |
|
||||
complete!(tag!("!=")) => { |_| BinaryOperator::Neq } |
|
||||
complete!(char!('+')) => { |_| BinaryOperator::Add } |
|
||||
complete!(char!('-')) => { |_| BinaryOperator::Sub } |
|
||||
complete!(char!('*')) => { |_| BinaryOperator::Mul } |
|
||||
complete!(char!('/')) => { |_| BinaryOperator::Div } |
|
||||
complete!(char!('^')) => { |_| BinaryOperator::Pow }
|
||||
), TokenTree::Infix));
|
||||
|
||||
named!(primary(&str) -> TokenTree, alt!(
|
||||
do_parse!(
|
||||
multispace0 >>
|
||||
char!('(') >>
|
||||
multispace0 >>
|
||||
group: group >>
|
||||
multispace0 >>
|
||||
char!(')') >>
|
||||
multispace0 >>
|
||||
(TokenTree::Group(group))
|
||||
) |
|
||||
delimited!(multispace0, simple_expr, multispace0) => { |s| TokenTree::Primary(s) }
|
||||
));
|
||||
|
||||
named!(
|
||||
rest(&str) -> Vec<(TokenTree, Vec<TokenTree>, TokenTree)>,
|
||||
many0!(tuple!(
|
||||
infix,
|
||||
delimited!(multispace0, many0!(prefix), multispace0),
|
||||
primary
|
||||
// many0!(postfix)
|
||||
))
|
||||
);
|
||||
|
||||
named!(group(&str) -> Vec<TokenTree>, do_parse!(
|
||||
prefix: many0!(prefix)
|
||||
>> primary: primary
|
||||
// >> postfix: many0!(postfix)
|
||||
>> rest: rest
|
||||
>> ({
|
||||
let mut res = prefix;
|
||||
res.push(primary);
|
||||
// res.append(&mut postfix);
|
||||
for (infix, mut prefix, primary/*, mut postfix*/) in rest {
|
||||
res.push(infix);
|
||||
res.append(&mut prefix);
|
||||
res.push(primary);
|
||||
// res.append(&mut postfix);
|
||||
}
|
||||
res
|
||||
})
|
||||
));
|
||||
|
||||
fn token_tree(i: &str) -> nom::IResult<&str, Vec<TokenTree>> {
|
||||
group(i)
|
||||
}
|
||||
|
||||
struct ExprParser;
|
||||
|
||||
impl<'a, I> PrattParser<I> for ExprParser
|
||||
where
|
||||
I: Iterator<Item = TokenTree<'a>>,
|
||||
{
|
||||
type Error = pratt::NoError;
|
||||
type Input = TokenTree<'a>;
|
||||
type Output = Expr<'a>;
|
||||
|
||||
fn query(&mut self, input: &Self::Input) -> Result<Affix, Self::Error> {
|
||||
use BinaryOperator::*;
|
||||
use UnaryOperator::*;
|
||||
|
||||
Ok(match input {
|
||||
TokenTree::Infix(Add) => Affix::Infix(Precedence(6), Associativity::Left),
|
||||
TokenTree::Infix(Sub) => Affix::Infix(Precedence(6), Associativity::Left),
|
||||
TokenTree::Infix(Mul) => Affix::Infix(Precedence(7), Associativity::Left),
|
||||
TokenTree::Infix(Div) => Affix::Infix(Precedence(7), Associativity::Left),
|
||||
TokenTree::Infix(Pow) => Affix::Infix(Precedence(8), Associativity::Right),
|
||||
TokenTree::Infix(Equ) => Affix::Infix(Precedence(4), Associativity::Right),
|
||||
TokenTree::Infix(Neq) => Affix::Infix(Precedence(4), Associativity::Right),
|
||||
TokenTree::Prefix(Neg) => Affix::Prefix(Precedence(6)),
|
||||
TokenTree::Prefix(Not) => Affix::Prefix(Precedence(6)),
|
||||
TokenTree::Primary(_) => Affix::Nilfix,
|
||||
TokenTree::Group(_) => Affix::Nilfix,
|
||||
})
|
||||
}
|
||||
|
||||
fn primary(&mut self, input: Self::Input) -> Result<Self::Output, Self::Error> {
|
||||
Ok(match input {
|
||||
TokenTree::Primary(expr) => expr,
|
||||
TokenTree::Group(group) => self.parse(&mut group.into_iter()).unwrap(),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
|
||||
fn infix(
|
||||
&mut self,
|
||||
lhs: Self::Output,
|
||||
op: Self::Input,
|
||||
rhs: Self::Output,
|
||||
) -> Result<Self::Output, Self::Error> {
|
||||
let op = match op {
|
||||
TokenTree::Infix(op) => op,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
Ok(Expr::BinaryOp {
|
||||
lhs: Box::new(lhs),
|
||||
op,
|
||||
rhs: Box::new(rhs),
|
||||
})
|
||||
}
|
||||
|
||||
fn prefix(&mut self, op: Self::Input, rhs: Self::Output) -> Result<Self::Output, Self::Error> {
|
||||
let op = match op {
|
||||
TokenTree::Prefix(op) => op,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
Ok(Expr::UnaryOp {
|
||||
op,
|
||||
rhs: Box::new(rhs),
|
||||
})
|
||||
}
|
||||
|
||||
fn postfix(
|
||||
&mut self,
|
||||
_lhs: Self::Output,
|
||||
_op: Self::Input,
|
||||
) -> Result<Self::Output, Self::Error> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
named!(int(&str) -> Literal, map!(flat_map!(digit1, parse_to!(u64)), Literal::Int));
|
||||
|
||||
named!(bool_(&str) -> Literal, alt!(
|
||||
complete!(tag!("true")) => { |_| Literal::Bool(true) } |
|
||||
complete!(tag!("false")) => { |_| Literal::Bool(false) }
|
||||
));
|
||||
|
||||
fn string_internal(i: &str) -> nom::IResult<&str, Cow<'_, str>, nom::error::Error<&str>> {
|
||||
// TODO(grfn): use String::split_once when that's stable
|
||||
let (s, rem) = if let Some(pos) = i.find('"') {
|
||||
(&i[..pos], &i[(pos + 1)..])
|
||||
} else {
|
||||
return Err(nom::Err::Error(nom::error::Error::new(
|
||||
i,
|
||||
nom::error::ErrorKind::Tag,
|
||||
)));
|
||||
};
|
||||
|
||||
Ok((rem, Cow::Borrowed(s)))
|
||||
}
|
||||
|
||||
named!(string(&str) -> Literal, preceded!(
|
||||
complete!(char!('"')),
|
||||
map!(
|
||||
string_internal,
|
||||
|s| Literal::String(s)
|
||||
)
|
||||
));
|
||||
|
||||
named!(unit(&str) -> Literal, map!(complete!(tag!("()")), |_| Literal::Unit));
|
||||
|
||||
named!(literal(&str) -> Literal, alt!(int | bool_ | string | unit));
|
||||
|
||||
named!(literal_expr(&str) -> Expr, map!(literal, Expr::Literal));
|
||||
|
||||
named!(binding(&str) -> Binding, do_parse!(
|
||||
multispace0
|
||||
>> ident: ident
|
||||
>> multispace0
|
||||
>> type_: opt!(preceded!(tuple!(tag!(":"), multispace0), type_))
|
||||
>> multispace0
|
||||
>> char!('=')
|
||||
>> multispace0
|
||||
>> body: expr
|
||||
>> (Binding {
|
||||
ident,
|
||||
type_,
|
||||
body
|
||||
})
|
||||
));
|
||||
|
||||
named!(let_(&str) -> Expr, do_parse!(
|
||||
tag!("let")
|
||||
>> multispace0
|
||||
>> bindings: separated_list1!(alt!(char!(';') | char!('\n')), binding)
|
||||
>> multispace0
|
||||
>> tag!("in")
|
||||
>> multispace0
|
||||
>> body: expr
|
||||
>> (Expr::Let {
|
||||
bindings,
|
||||
body: Box::new(body)
|
||||
})
|
||||
));
|
||||
|
||||
named!(if_(&str) -> Expr, do_parse! (
|
||||
tag!("if")
|
||||
>> multispace0
|
||||
>> condition: expr
|
||||
>> multispace0
|
||||
>> tag!("then")
|
||||
>> multispace0
|
||||
>> then: expr
|
||||
>> multispace0
|
||||
>> tag!("else")
|
||||
>> multispace0
|
||||
>> else_: expr
|
||||
>> (Expr::If {
|
||||
condition: Box::new(condition),
|
||||
then: Box::new(then),
|
||||
else_: Box::new(else_)
|
||||
})
|
||||
));
|
||||
|
||||
named!(ident_expr(&str) -> Expr, map!(ident, Expr::Ident));
|
||||
|
||||
fn ascripted<'a>(
|
||||
p: impl Fn(&'a str) -> nom::IResult<&'a str, Expr, nom::error::Error<&'a str>> + 'a,
|
||||
) -> impl Fn(&'a str) -> nom::IResult<&str, Expr, nom::error::Error<&'a str>> {
|
||||
move |i| {
|
||||
do_parse!(
|
||||
i,
|
||||
expr: p
|
||||
>> multispace0
|
||||
>> complete!(tag!(":"))
|
||||
>> multispace0
|
||||
>> type_: type_
|
||||
>> (Expr::Ascription {
|
||||
expr: Box::new(expr),
|
||||
type_
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
named!(paren_expr(&str) -> Expr,
|
||||
delimited!(complete!(tag!("(")), expr, complete!(tag!(")"))));
|
||||
|
||||
named!(funcref(&str) -> Expr, alt!(
|
||||
ident_expr |
|
||||
paren_expr
|
||||
));
|
||||
|
||||
named!(no_arg_call(&str) -> Expr, do_parse!(
|
||||
fun: funcref
|
||||
>> complete!(tag!("()"))
|
||||
>> (Expr::Call {
|
||||
fun: Box::new(fun),
|
||||
args: vec![],
|
||||
})
|
||||
));
|
||||
|
||||
named!(fun_expr(&str) -> Expr, do_parse!(
|
||||
tag!("fn")
|
||||
>> multispace1
|
||||
>> args: separated_list0!(multispace1, arg)
|
||||
>> multispace0
|
||||
>> char!('=')
|
||||
>> multispace0
|
||||
>> body: expr
|
||||
>> (Expr::Fun(Box::new(Fun {
|
||||
args,
|
||||
body
|
||||
})))
|
||||
));
|
||||
|
||||
named!(fn_arg(&str) -> Expr, alt!(
|
||||
ident_expr |
|
||||
literal_expr |
|
||||
paren_expr
|
||||
));
|
||||
|
||||
named!(call_with_args(&str) -> Expr, do_parse!(
|
||||
fun: funcref
|
||||
>> multispace1
|
||||
>> args: separated_list1!(multispace1, fn_arg)
|
||||
>> (Expr::Call {
|
||||
fun: Box::new(fun),
|
||||
args
|
||||
})
|
||||
));
|
||||
|
||||
named!(simple_expr_unascripted(&str) -> Expr, alt!(
|
||||
let_ |
|
||||
if_ |
|
||||
fun_expr |
|
||||
literal_expr |
|
||||
ident_expr
|
||||
));
|
||||
|
||||
named!(simple_expr(&str) -> Expr, alt!(
|
||||
call!(ascripted(simple_expr_unascripted)) |
|
||||
simple_expr_unascripted
|
||||
));
|
||||
|
||||
named!(pub expr(&str) -> Expr, alt!(
|
||||
no_arg_call |
|
||||
call_with_args |
|
||||
map!(token_tree, |tt| {
|
||||
ExprParser.parse(&mut tt.into_iter()).unwrap()
|
||||
}) |
|
||||
simple_expr
|
||||
));
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::ast::{Arg, Ident, Type};
|
||||
use std::convert::TryFrom;
|
||||
use BinaryOperator::*;
|
||||
use Expr::{BinaryOp, If, Let, UnaryOp};
|
||||
use UnaryOperator::*;
|
||||
|
||||
pub(crate) fn ident_expr(s: &str) -> Box<Expr> {
|
||||
Box::new(Expr::Ident(Ident::try_from(s).unwrap()))
|
||||
}
|
||||
|
||||
mod operators {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn mul_plus() {
|
||||
let (rem, res) = expr("x*y+z").unwrap();
|
||||
assert!(rem.is_empty());
|
||||
assert_eq!(
|
||||
res,
|
||||
BinaryOp {
|
||||
lhs: Box::new(BinaryOp {
|
||||
lhs: ident_expr("x"),
|
||||
op: Mul,
|
||||
rhs: ident_expr("y")
|
||||
}),
|
||||
op: Add,
|
||||
rhs: ident_expr("z")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mul_plus_ws() {
|
||||
let (rem, res) = expr("x * y + z").unwrap();
|
||||
assert!(rem.is_empty(), "non-empty remainder: \"{}\"", rem);
|
||||
assert_eq!(
|
||||
res,
|
||||
BinaryOp {
|
||||
lhs: Box::new(BinaryOp {
|
||||
lhs: ident_expr("x"),
|
||||
op: Mul,
|
||||
rhs: ident_expr("y")
|
||||
}),
|
||||
op: Add,
|
||||
rhs: ident_expr("z")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unary() {
|
||||
let (rem, res) = expr("x * -z").unwrap();
|
||||
assert!(rem.is_empty(), "non-empty remainder: \"{}\"", rem);
|
||||
assert_eq!(
|
||||
res,
|
||||
BinaryOp {
|
||||
lhs: ident_expr("x"),
|
||||
op: Mul,
|
||||
rhs: Box::new(UnaryOp {
|
||||
op: Neg,
|
||||
rhs: ident_expr("z"),
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mul_literal() {
|
||||
let (rem, res) = expr("x * 3").unwrap();
|
||||
assert!(rem.is_empty());
|
||||
assert_eq!(
|
||||
res,
|
||||
BinaryOp {
|
||||
lhs: ident_expr("x"),
|
||||
op: Mul,
|
||||
rhs: Box::new(Expr::Literal(Literal::Int(3))),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn equ() {
|
||||
let res = test_parse!(expr, "x * 7 == 7");
|
||||
assert_eq!(
|
||||
res,
|
||||
BinaryOp {
|
||||
lhs: Box::new(BinaryOp {
|
||||
lhs: ident_expr("x"),
|
||||
op: Mul,
|
||||
rhs: Box::new(Expr::Literal(Literal::Int(7)))
|
||||
}),
|
||||
op: Equ,
|
||||
rhs: Box::new(Expr::Literal(Literal::Int(7)))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit() {
|
||||
assert_eq!(test_parse!(expr, "()"), Expr::Literal(Literal::Unit));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bools() {
|
||||
assert_eq!(
|
||||
test_parse!(expr, "true"),
|
||||
Expr::Literal(Literal::Bool(true))
|
||||
);
|
||||
assert_eq!(
|
||||
test_parse!(expr, "false"),
|
||||
Expr::Literal(Literal::Bool(false))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_string_lit() {
|
||||
assert_eq!(
|
||||
test_parse!(expr, "\"foobar\""),
|
||||
Expr::Literal(Literal::String(Cow::Borrowed("foobar")))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_complex() {
|
||||
let res = test_parse!(expr, "let x = 1; y = x * 7 in (x + y) * 4");
|
||||
assert_eq!(
|
||||
res,
|
||||
Let {
|
||||
bindings: vec![
|
||||
Binding {
|
||||
ident: Ident::try_from("x").unwrap(),
|
||||
type_: None,
|
||||
body: Expr::Literal(Literal::Int(1))
|
||||
},
|
||||
Binding {
|
||||
ident: Ident::try_from("y").unwrap(),
|
||||
type_: None,
|
||||
body: Expr::BinaryOp {
|
||||
lhs: ident_expr("x"),
|
||||
op: Mul,
|
||||
rhs: Box::new(Expr::Literal(Literal::Int(7)))
|
||||
}
|
||||
}
|
||||
],
|
||||
body: Box::new(Expr::BinaryOp {
|
||||
lhs: Box::new(Expr::BinaryOp {
|
||||
lhs: ident_expr("x"),
|
||||
op: Add,
|
||||
rhs: ident_expr("y"),
|
||||
}),
|
||||
op: Mul,
|
||||
rhs: Box::new(Expr::Literal(Literal::Int(4))),
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_simple() {
|
||||
let res = test_parse!(expr, "if x == 8 then 9 else 20");
|
||||
assert_eq!(
|
||||
res,
|
||||
If {
|
||||
condition: Box::new(BinaryOp {
|
||||
lhs: ident_expr("x"),
|
||||
op: Equ,
|
||||
rhs: Box::new(Expr::Literal(Literal::Int(8))),
|
||||
}),
|
||||
then: Box::new(Expr::Literal(Literal::Int(9))),
|
||||
else_: Box::new(Expr::Literal(Literal::Int(20)))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_arg_call() {
|
||||
let res = test_parse!(expr, "f()");
|
||||
assert_eq!(
|
||||
res,
|
||||
Expr::Call {
|
||||
fun: ident_expr("f"),
|
||||
args: vec![]
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_call() {
|
||||
let res = test_parse!(expr, "f ()");
|
||||
assert_eq!(
|
||||
res,
|
||||
Expr::Call {
|
||||
fun: ident_expr("f"),
|
||||
args: vec![Expr::Literal(Literal::Unit)]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_with_args() {
|
||||
let res = test_parse!(expr, "f x 1");
|
||||
assert_eq!(
|
||||
res,
|
||||
Expr::Call {
|
||||
fun: ident_expr("f"),
|
||||
args: vec![*ident_expr("x"), Expr::Literal(Literal::Int(1))]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_funcref() {
|
||||
let res = test_parse!(expr, "(let x = 1 in x) 2");
|
||||
assert_eq!(
|
||||
res,
|
||||
Expr::Call {
|
||||
fun: Box::new(Expr::Let {
|
||||
bindings: vec![Binding {
|
||||
ident: Ident::try_from("x").unwrap(),
|
||||
type_: None,
|
||||
body: Expr::Literal(Literal::Int(1))
|
||||
}],
|
||||
body: ident_expr("x")
|
||||
}),
|
||||
args: vec![Expr::Literal(Literal::Int(2))]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn anon_function() {
|
||||
let res = test_parse!(expr, "let id = fn x = x in id 1");
|
||||
assert_eq!(
|
||||
res,
|
||||
Expr::Let {
|
||||
bindings: vec![Binding {
|
||||
ident: Ident::try_from("id").unwrap(),
|
||||
type_: None,
|
||||
body: Expr::Fun(Box::new(Fun {
|
||||
args: vec![Arg::try_from("x").unwrap()],
|
||||
body: *ident_expr("x")
|
||||
}))
|
||||
}],
|
||||
body: Box::new(Expr::Call {
|
||||
fun: ident_expr("id"),
|
||||
args: vec![Expr::Literal(Literal::Int(1))],
|
||||
})
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
mod ascriptions {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn bare_ascription() {
|
||||
let res = test_parse!(expr, "1: float");
|
||||
assert_eq!(
|
||||
res,
|
||||
Expr::Ascription {
|
||||
expr: Box::new(Expr::Literal(Literal::Int(1))),
|
||||
type_: Type::Float
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_body_ascription() {
|
||||
let res = test_parse!(expr, "let const_1 = fn x = 1: int in const_1 2");
|
||||
assert_eq!(
|
||||
res,
|
||||
Expr::Let {
|
||||
bindings: vec![Binding {
|
||||
ident: Ident::try_from("const_1").unwrap(),
|
||||
type_: None,
|
||||
body: Expr::Fun(Box::new(Fun {
|
||||
args: vec![Arg::try_from("x").unwrap()],
|
||||
body: Expr::Ascription {
|
||||
expr: Box::new(Expr::Literal(Literal::Int(1))),
|
||||
type_: Type::Int,
|
||||
}
|
||||
}))
|
||||
}],
|
||||
body: Box::new(Expr::Call {
|
||||
fun: ident_expr("const_1"),
|
||||
args: vec![Expr::Literal(Literal::Int(2))]
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_binding_ascripted() {
|
||||
let res = test_parse!(expr, "let x: int = 1 in x");
|
||||
assert_eq!(
|
||||
res,
|
||||
Expr::Let {
|
||||
bindings: vec![Binding {
|
||||
ident: Ident::try_from("x").unwrap(),
|
||||
type_: Some(Type::Int),
|
||||
body: Expr::Literal(Literal::Int(1))
|
||||
}],
|
||||
body: ident_expr("x")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
16
users/grfn/achilles/src/parser/macros.rs
Normal file
16
users/grfn/achilles/src/parser/macros.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
macro_rules! test_parse {
|
||||
($parser: ident, $src: expr) => {{
|
||||
let res = $parser($src);
|
||||
nom_trace::print_trace!();
|
||||
let (rem, res) = res.unwrap();
|
||||
assert!(
|
||||
rem.is_empty(),
|
||||
"non-empty remainder: \"{}\", parsed: {:?}",
|
||||
rem,
|
||||
res
|
||||
);
|
||||
res
|
||||
}};
|
||||
}
|
||||
239
users/grfn/achilles/src/parser/mod.rs
Normal file
239
users/grfn/achilles/src/parser/mod.rs
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
use nom::character::complete::{multispace0, multispace1};
|
||||
use nom::error::{ErrorKind, ParseError};
|
||||
use nom::{alt, char, complete, do_parse, eof, many0, named, separated_list0, tag, terminated};
|
||||
|
||||
#[macro_use]
|
||||
pub(crate) mod macros;
|
||||
mod expr;
|
||||
mod type_;
|
||||
|
||||
use crate::ast::{Arg, Decl, Fun, Ident};
|
||||
pub use expr::expr;
|
||||
use type_::function_type;
|
||||
pub use type_::type_;
|
||||
|
||||
pub type Error = nom::Err<nom::error::Error<String>>;
|
||||
|
||||
pub(crate) fn is_reserved(s: &str) -> bool {
|
||||
matches!(
|
||||
s,
|
||||
"if" | "then"
|
||||
| "else"
|
||||
| "let"
|
||||
| "in"
|
||||
| "fn"
|
||||
| "ty"
|
||||
| "int"
|
||||
| "float"
|
||||
| "bool"
|
||||
| "true"
|
||||
| "false"
|
||||
| "cstring"
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn ident<'a, E>(i: &'a str) -> nom::IResult<&'a str, Ident, E>
|
||||
where
|
||||
E: ParseError<&'a str>,
|
||||
{
|
||||
let mut chars = i.chars();
|
||||
if let Some(f) = chars.next() {
|
||||
if f.is_alphabetic() || f == '_' {
|
||||
let mut idx = 1;
|
||||
for c in chars {
|
||||
if !(c.is_alphanumeric() || c == '_') {
|
||||
break;
|
||||
}
|
||||
idx += 1;
|
||||
}
|
||||
let id = &i[..idx];
|
||||
if is_reserved(id) {
|
||||
Err(nom::Err::Error(E::from_error_kind(i, ErrorKind::Satisfy)))
|
||||
} else {
|
||||
Ok((&i[idx..], Ident::from_str_unchecked(id)))
|
||||
}
|
||||
} else {
|
||||
Err(nom::Err::Error(E::from_error_kind(i, ErrorKind::Satisfy)))
|
||||
}
|
||||
} else {
|
||||
Err(nom::Err::Error(E::from_error_kind(i, ErrorKind::Eof)))
|
||||
}
|
||||
}
|
||||
|
||||
named!(ascripted_arg(&str) -> Arg, do_parse!(
|
||||
complete!(char!('(')) >>
|
||||
multispace0 >>
|
||||
ident: ident >>
|
||||
multispace0 >>
|
||||
complete!(char!(':')) >>
|
||||
multispace0 >>
|
||||
type_: type_ >>
|
||||
multispace0 >>
|
||||
complete!(char!(')')) >>
|
||||
(Arg {
|
||||
ident,
|
||||
type_: Some(type_)
|
||||
})
|
||||
));
|
||||
|
||||
named!(arg(&str) -> Arg, alt!(
|
||||
ident => { |ident| Arg {ident, type_: None}} |
|
||||
ascripted_arg
|
||||
));
|
||||
|
||||
named!(extern_decl(&str) -> Decl, do_parse!(
|
||||
complete!(tag!("extern"))
|
||||
>> multispace1
|
||||
>> name: ident
|
||||
>> multispace0
|
||||
>> char!(':')
|
||||
>> multispace0
|
||||
>> type_: function_type
|
||||
>> multispace0
|
||||
>> (Decl::Extern {
|
||||
name,
|
||||
type_
|
||||
})
|
||||
));
|
||||
|
||||
named!(fun_decl(&str) -> Decl, do_parse!(
|
||||
complete!(tag!("fn"))
|
||||
>> multispace1
|
||||
>> name: ident
|
||||
>> multispace1
|
||||
>> args: separated_list0!(multispace1, arg)
|
||||
>> multispace0
|
||||
>> char!('=')
|
||||
>> multispace0
|
||||
>> body: expr
|
||||
>> (Decl::Fun {
|
||||
name,
|
||||
body: Fun {
|
||||
args,
|
||||
body
|
||||
}
|
||||
})
|
||||
));
|
||||
|
||||
named!(ascription_decl(&str) -> Decl, do_parse!(
|
||||
complete!(tag!("ty"))
|
||||
>> multispace1
|
||||
>> name: ident
|
||||
>> multispace0
|
||||
>> complete!(char!(':'))
|
||||
>> multispace0
|
||||
>> type_: type_
|
||||
>> multispace0
|
||||
>> (Decl::Ascription {
|
||||
name,
|
||||
type_
|
||||
})
|
||||
));
|
||||
|
||||
named!(pub decl(&str) -> Decl, alt!(
|
||||
ascription_decl |
|
||||
fun_decl |
|
||||
extern_decl
|
||||
));
|
||||
|
||||
named!(pub toplevel(&str) -> Vec<Decl>, do_parse!(
|
||||
decls: many0!(decl)
|
||||
>> multispace0
|
||||
>> eof!()
|
||||
>> (decls)));
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::convert::TryInto;
|
||||
|
||||
use crate::ast::{BinaryOperator, Expr, FunctionType, Literal, Type};
|
||||
|
||||
use super::*;
|
||||
use expr::tests::ident_expr;
|
||||
|
||||
#[test]
|
||||
fn fn_decl() {
|
||||
let res = test_parse!(decl, "fn id x = x");
|
||||
assert_eq!(
|
||||
res,
|
||||
Decl::Fun {
|
||||
name: "id".try_into().unwrap(),
|
||||
body: Fun {
|
||||
args: vec!["x".try_into().unwrap()],
|
||||
body: *ident_expr("x"),
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ascripted_fn_args() {
|
||||
test_parse!(ascripted_arg, "(x : int)");
|
||||
let res = test_parse!(decl, "fn plus1 (x : int) = x + 1");
|
||||
assert_eq!(
|
||||
res,
|
||||
Decl::Fun {
|
||||
name: "plus1".try_into().unwrap(),
|
||||
body: Fun {
|
||||
args: vec![Arg {
|
||||
ident: "x".try_into().unwrap(),
|
||||
type_: Some(Type::Int),
|
||||
}],
|
||||
body: Expr::BinaryOp {
|
||||
lhs: ident_expr("x"),
|
||||
op: BinaryOperator::Add,
|
||||
rhs: Box::new(Expr::Literal(Literal::Int(1))),
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_decls() {
|
||||
let res = test_parse!(
|
||||
toplevel,
|
||||
"fn id x = x
|
||||
fn plus x y = x + y
|
||||
fn main = plus (id 2) 7"
|
||||
);
|
||||
assert_eq!(res.len(), 3);
|
||||
let res = test_parse!(
|
||||
toplevel,
|
||||
"fn id x = x\nfn plus x y = x + y\nfn main = plus (id 2) 7\n"
|
||||
);
|
||||
assert_eq!(res.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn top_level_ascription() {
|
||||
let res = test_parse!(toplevel, "ty id : fn a -> a");
|
||||
assert_eq!(
|
||||
res,
|
||||
vec![Decl::Ascription {
|
||||
name: "id".try_into().unwrap(),
|
||||
type_: Type::Function(FunctionType {
|
||||
args: vec![Type::Var("a".try_into().unwrap())],
|
||||
ret: Box::new(Type::Var("a".try_into().unwrap()))
|
||||
})
|
||||
}]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn return_unit() {
|
||||
assert_eq!(
|
||||
test_parse!(decl, "fn g _ = ()"),
|
||||
Decl::Fun {
|
||||
name: "g".try_into().unwrap(),
|
||||
body: Fun {
|
||||
args: vec![Arg {
|
||||
ident: "_".try_into().unwrap(),
|
||||
type_: None,
|
||||
}],
|
||||
body: Expr::Literal(Literal::Unit),
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
127
users/grfn/achilles/src/parser/type_.rs
Normal file
127
users/grfn/achilles/src/parser/type_.rs
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
use nom::character::complete::{multispace0, multispace1};
|
||||
use nom::{alt, delimited, do_parse, map, named, opt, separated_list0, tag, terminated, tuple};
|
||||
|
||||
use super::ident;
|
||||
use crate::ast::{FunctionType, Type};
|
||||
|
||||
named!(pub function_type(&str) -> FunctionType, do_parse!(
|
||||
tag!("fn")
|
||||
>> multispace1
|
||||
>> args: map!(opt!(terminated!(separated_list0!(
|
||||
tuple!(
|
||||
multispace0,
|
||||
tag!(","),
|
||||
multispace0
|
||||
),
|
||||
type_
|
||||
), multispace1)), |args| args.unwrap_or_default())
|
||||
>> tag!("->")
|
||||
>> multispace1
|
||||
>> ret: type_
|
||||
>> (FunctionType {
|
||||
args,
|
||||
ret: Box::new(ret)
|
||||
})
|
||||
));
|
||||
|
||||
named!(pub type_(&str) -> Type, alt!(
|
||||
tag!("int") => { |_| Type::Int } |
|
||||
tag!("float") => { |_| Type::Float } |
|
||||
tag!("bool") => { |_| Type::Bool } |
|
||||
tag!("cstring") => { |_| Type::CString } |
|
||||
tag!("()") => { |_| Type::Unit } |
|
||||
function_type => { |ft| Type::Function(ft) }|
|
||||
ident => { |id| Type::Var(id) } |
|
||||
delimited!(
|
||||
tuple!(tag!("("), multispace0),
|
||||
type_,
|
||||
tuple!(tag!(")"), multispace0)
|
||||
)
|
||||
));
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use super::*;
|
||||
use crate::ast::Ident;
|
||||
|
||||
#[test]
|
||||
fn simple_types() {
|
||||
assert_eq!(test_parse!(type_, "int"), Type::Int);
|
||||
assert_eq!(test_parse!(type_, "float"), Type::Float);
|
||||
assert_eq!(test_parse!(type_, "bool"), Type::Bool);
|
||||
assert_eq!(test_parse!(type_, "cstring"), Type::CString);
|
||||
assert_eq!(test_parse!(type_, "()"), Type::Unit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_arg_fn_type() {
|
||||
assert_eq!(
|
||||
test_parse!(type_, "fn -> int"),
|
||||
Type::Function(FunctionType {
|
||||
args: vec![],
|
||||
ret: Box::new(Type::Int)
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_type_with_args() {
|
||||
assert_eq!(
|
||||
test_parse!(type_, "fn int, bool -> int"),
|
||||
Type::Function(FunctionType {
|
||||
args: vec![Type::Int, Type::Bool],
|
||||
ret: Box::new(Type::Int)
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_taking_fn() {
|
||||
assert_eq!(
|
||||
test_parse!(type_, "fn fn int, bool -> bool, float -> float"),
|
||||
Type::Function(FunctionType {
|
||||
args: vec![
|
||||
Type::Function(FunctionType {
|
||||
args: vec![Type::Int, Type::Bool],
|
||||
ret: Box::new(Type::Bool)
|
||||
}),
|
||||
Type::Float
|
||||
],
|
||||
ret: Box::new(Type::Float)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parenthesized() {
|
||||
assert_eq!(
|
||||
test_parse!(type_, "fn (fn int, bool -> bool), float -> float"),
|
||||
Type::Function(FunctionType {
|
||||
args: vec![
|
||||
Type::Function(FunctionType {
|
||||
args: vec![Type::Int, Type::Bool],
|
||||
ret: Box::new(Type::Bool)
|
||||
}),
|
||||
Type::Float
|
||||
],
|
||||
ret: Box::new(Type::Float)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_vars() {
|
||||
assert_eq!(
|
||||
test_parse!(type_, "fn x, y -> x"),
|
||||
Type::Function(FunctionType {
|
||||
args: vec![
|
||||
Type::Var(Ident::try_from("x").unwrap()),
|
||||
Type::Var(Ident::try_from("y").unwrap()),
|
||||
],
|
||||
ret: Box::new(Type::Var(Ident::try_from("x").unwrap())),
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
197
users/grfn/achilles/src/passes/hir/mod.rs
Normal file
197
users/grfn/achilles/src/passes/hir/mod.rs
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::ast::hir::{Binding, Decl, Expr};
|
||||
use crate::ast::{BinaryOperator, Ident, Literal, UnaryOperator};
|
||||
|
||||
pub(crate) mod monomorphize;
|
||||
pub(crate) mod strip_positive_units;
|
||||
|
||||
pub(crate) trait Visitor<'a, 'ast, T: 'ast>: Sized + 'a {
|
||||
type Error;
|
||||
|
||||
fn visit_type(&mut self, _type: &mut T) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn visit_ident(&mut self, _ident: &mut Ident<'ast>) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn visit_literal(&mut self, _literal: &mut Literal<'ast>) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn visit_unary_operator(&mut self, _op: &mut UnaryOperator) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn visit_binary_operator(&mut self, _op: &mut BinaryOperator) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn visit_binding(&mut self, binding: &mut Binding<'ast, T>) -> Result<(), Self::Error> {
|
||||
self.visit_ident(&mut binding.ident)?;
|
||||
self.visit_type(&mut binding.type_)?;
|
||||
self.visit_expr(&mut binding.body)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn post_visit_call(
|
||||
&mut self,
|
||||
_fun: &mut Expr<'ast, T>,
|
||||
_type_args: &mut HashMap<Ident<'ast>, T>,
|
||||
_args: &mut Vec<Expr<'ast, T>>,
|
||||
) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pre_visit_call(
|
||||
&mut self,
|
||||
_fun: &mut Expr<'ast, T>,
|
||||
_type_args: &mut HashMap<Ident<'ast>, T>,
|
||||
_args: &mut Vec<Expr<'ast, T>>,
|
||||
) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pre_visit_expr(&mut self, _expr: &mut Expr<'ast, T>) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &mut Expr<'ast, T>) -> Result<(), Self::Error> {
|
||||
self.pre_visit_expr(expr)?;
|
||||
match expr {
|
||||
Expr::Ident(id, t) => {
|
||||
self.visit_ident(id)?;
|
||||
self.visit_type(t)?;
|
||||
}
|
||||
Expr::Literal(lit, t) => {
|
||||
self.visit_literal(lit)?;
|
||||
self.visit_type(t)?;
|
||||
}
|
||||
Expr::UnaryOp { op, rhs, type_ } => {
|
||||
self.visit_unary_operator(op)?;
|
||||
self.visit_expr(rhs)?;
|
||||
self.visit_type(type_)?;
|
||||
}
|
||||
Expr::BinaryOp {
|
||||
lhs,
|
||||
op,
|
||||
rhs,
|
||||
type_,
|
||||
} => {
|
||||
self.visit_expr(lhs)?;
|
||||
self.visit_binary_operator(op)?;
|
||||
self.visit_expr(rhs)?;
|
||||
self.visit_type(type_)?;
|
||||
}
|
||||
Expr::Let {
|
||||
bindings,
|
||||
body,
|
||||
type_,
|
||||
} => {
|
||||
for binding in bindings.iter_mut() {
|
||||
self.visit_binding(binding)?;
|
||||
}
|
||||
self.visit_expr(body)?;
|
||||
self.visit_type(type_)?;
|
||||
}
|
||||
Expr::If {
|
||||
condition,
|
||||
then,
|
||||
else_,
|
||||
type_,
|
||||
} => {
|
||||
self.visit_expr(condition)?;
|
||||
self.visit_expr(then)?;
|
||||
self.visit_expr(else_)?;
|
||||
self.visit_type(type_)?;
|
||||
}
|
||||
Expr::Fun {
|
||||
args,
|
||||
body,
|
||||
type_args,
|
||||
type_,
|
||||
} => {
|
||||
for (ident, t) in args {
|
||||
self.visit_ident(ident)?;
|
||||
self.visit_type(t)?;
|
||||
}
|
||||
for ta in type_args {
|
||||
self.visit_ident(ta)?;
|
||||
}
|
||||
self.visit_expr(body)?;
|
||||
self.visit_type(type_)?;
|
||||
}
|
||||
Expr::Call {
|
||||
fun,
|
||||
args,
|
||||
type_args,
|
||||
type_,
|
||||
} => {
|
||||
self.pre_visit_call(fun, type_args, args)?;
|
||||
self.visit_expr(fun)?;
|
||||
for arg in args.iter_mut() {
|
||||
self.visit_expr(arg)?;
|
||||
}
|
||||
self.visit_type(type_)?;
|
||||
self.post_visit_call(fun, type_args, args)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn post_visit_decl(&mut self, decl: &'a Decl<'ast, T>) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn post_visit_fun_decl(
|
||||
&mut self,
|
||||
_name: &mut Ident<'ast>,
|
||||
_type_args: &mut Vec<Ident>,
|
||||
_args: &mut Vec<(Ident, T)>,
|
||||
_body: &mut Box<Expr<T>>,
|
||||
_type_: &mut T,
|
||||
) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn visit_decl(&mut self, decl: &'a mut Decl<'ast, T>) -> Result<(), Self::Error> {
|
||||
match decl {
|
||||
Decl::Fun {
|
||||
name,
|
||||
type_args,
|
||||
args,
|
||||
body,
|
||||
type_,
|
||||
} => {
|
||||
self.visit_ident(name)?;
|
||||
for type_arg in type_args.iter_mut() {
|
||||
self.visit_ident(type_arg)?;
|
||||
}
|
||||
for (arg, t) in args.iter_mut() {
|
||||
self.visit_ident(arg)?;
|
||||
self.visit_type(t)?;
|
||||
}
|
||||
self.visit_expr(body)?;
|
||||
self.visit_type(type_)?;
|
||||
self.post_visit_fun_decl(name, type_args, args, body, type_)?;
|
||||
}
|
||||
Decl::Extern {
|
||||
name,
|
||||
arg_types,
|
||||
ret_type,
|
||||
} => {
|
||||
self.visit_ident(name)?;
|
||||
for arg_t in arg_types {
|
||||
self.visit_type(arg_t)?;
|
||||
}
|
||||
self.visit_type(ret_type)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.post_visit_decl(decl)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
139
users/grfn/achilles/src/passes/hir/monomorphize.rs
Normal file
139
users/grfn/achilles/src/passes/hir/monomorphize.rs
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
use std::cell::RefCell;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::convert::TryInto;
|
||||
use std::mem;
|
||||
|
||||
use void::{ResultVoidExt, Void};
|
||||
|
||||
use crate::ast::hir::{Decl, Expr};
|
||||
use crate::ast::{self, Ident};
|
||||
|
||||
use super::Visitor;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Monomorphize<'a, 'ast> {
|
||||
decls: HashMap<&'a Ident<'ast>, &'a Decl<'ast, ast::Type<'ast>>>,
|
||||
extra_decls: Vec<Decl<'ast, ast::Type<'ast>>>,
|
||||
remove_decls: HashSet<Ident<'ast>>,
|
||||
}
|
||||
|
||||
impl<'a, 'ast> Monomorphize<'a, 'ast> {
|
||||
pub(crate) fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'ast> Visitor<'a, 'ast, ast::Type<'ast>> for Monomorphize<'a, 'ast> {
|
||||
type Error = Void;
|
||||
|
||||
fn post_visit_call(
|
||||
&mut self,
|
||||
fun: &mut Expr<'ast, ast::Type<'ast>>,
|
||||
type_args: &mut HashMap<Ident<'ast>, ast::Type<'ast>>,
|
||||
args: &mut Vec<Expr<'ast, ast::Type<'ast>>>,
|
||||
) -> Result<(), Self::Error> {
|
||||
let new_fun = match fun {
|
||||
Expr::Ident(id, _) => {
|
||||
let decl: Decl<_> = (**self.decls.get(id).unwrap()).clone();
|
||||
let name = RefCell::new(id.to_string());
|
||||
let type_args = mem::take(type_args);
|
||||
let mut monomorphized = decl
|
||||
.traverse_type(|ty| -> Result<_, Void> {
|
||||
Ok(ty.clone().traverse_type_vars(|v| {
|
||||
let concrete = type_args.get(&v).unwrap();
|
||||
name.borrow_mut().push_str(&concrete.to_string());
|
||||
concrete.clone()
|
||||
}))
|
||||
})
|
||||
.void_unwrap();
|
||||
let name: Ident = name.into_inner().try_into().unwrap();
|
||||
if name != *id {
|
||||
self.remove_decls.insert(id.clone());
|
||||
monomorphized.set_name(name.clone());
|
||||
let type_ = monomorphized.type_().unwrap().clone();
|
||||
self.extra_decls.push(monomorphized);
|
||||
Some(Expr::Ident(name, type_))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => todo!(),
|
||||
};
|
||||
if let Some(new_fun) = new_fun {
|
||||
*fun = new_fun;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn post_visit_decl(
|
||||
&mut self,
|
||||
decl: &'a Decl<'ast, ast::Type<'ast>>,
|
||||
) -> Result<(), Self::Error> {
|
||||
self.decls.insert(decl.name(), decl);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn run_toplevel<'a>(toplevel: &mut Vec<Decl<'a, ast::Type<'a>>>) {
|
||||
let mut pass = Monomorphize::new();
|
||||
for decl in toplevel.iter_mut() {
|
||||
pass.visit_decl(decl).void_unwrap();
|
||||
}
|
||||
let remove_decls = mem::take(&mut pass.remove_decls);
|
||||
let mut extra_decls = mem::take(&mut pass.extra_decls);
|
||||
toplevel.retain(|decl| !remove_decls.contains(decl.name()));
|
||||
extra_decls.append(toplevel);
|
||||
*toplevel = extra_decls;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use super::*;
|
||||
use crate::parser::toplevel;
|
||||
use crate::tc::typecheck_toplevel;
|
||||
|
||||
#[test]
|
||||
fn call_id_decl() {
|
||||
let (_, program) = toplevel(
|
||||
"ty id : fn a -> a
|
||||
fn id x = x
|
||||
|
||||
ty main : fn -> int
|
||||
fn main = id 0",
|
||||
)
|
||||
.unwrap();
|
||||
let mut program = typecheck_toplevel(program).unwrap();
|
||||
run_toplevel(&mut program);
|
||||
|
||||
let find_decl = |ident: &str| {
|
||||
program.iter().find(|decl| {
|
||||
matches!(decl, Decl::Fun {name, ..} if name == &Ident::try_from(ident).unwrap())
|
||||
}).unwrap()
|
||||
};
|
||||
|
||||
let main = find_decl("main");
|
||||
let body = match main {
|
||||
Decl::Fun { body, .. } => body,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let expected_type = ast::Type::Function(ast::FunctionType {
|
||||
args: vec![ast::Type::Int],
|
||||
ret: Box::new(ast::Type::Int),
|
||||
});
|
||||
|
||||
match &**body {
|
||||
Expr::Call { fun, .. } => {
|
||||
let fun = match &**fun {
|
||||
Expr::Ident(fun, _) => fun,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let called_decl = find_decl(fun.into());
|
||||
assert_eq!(called_decl.type_().unwrap(), &expected_type);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
189
users/grfn/achilles/src/passes/hir/strip_positive_units.rs
Normal file
189
users/grfn/achilles/src/passes/hir/strip_positive_units.rs
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
|
||||
use ast::hir::Binding;
|
||||
use ast::Literal;
|
||||
use void::{ResultVoidExt, Void};
|
||||
|
||||
use crate::ast::hir::{Decl, Expr};
|
||||
use crate::ast::{self, Ident};
|
||||
|
||||
use super::Visitor;
|
||||
|
||||
/// Strip all values with a unit type in positive (non-return) position
|
||||
pub(crate) struct StripPositiveUnits {}
|
||||
|
||||
impl<'a, 'ast> Visitor<'a, 'ast, ast::Type<'ast>> for StripPositiveUnits {
|
||||
type Error = Void;
|
||||
|
||||
fn pre_visit_expr(
|
||||
&mut self,
|
||||
expr: &mut Expr<'ast, ast::Type<'ast>>,
|
||||
) -> Result<(), Self::Error> {
|
||||
let mut extracted = vec![];
|
||||
if let Expr::Call { args, .. } = expr {
|
||||
// TODO(grfn): replace with drain_filter once it's stabilized
|
||||
let mut i = 0;
|
||||
while i != args.len() {
|
||||
if args[i].type_() == &ast::Type::Unit {
|
||||
let expr = args.remove(i);
|
||||
if !matches!(expr, Expr::Literal(Literal::Unit, _)) {
|
||||
extracted.push(expr)
|
||||
};
|
||||
} else {
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !extracted.is_empty() {
|
||||
let body = mem::replace(expr, Expr::Literal(Literal::Unit, ast::Type::Unit));
|
||||
*expr = Expr::Let {
|
||||
bindings: extracted
|
||||
.into_iter()
|
||||
.map(|expr| Binding {
|
||||
ident: Ident::from_str_unchecked("___discarded"),
|
||||
type_: expr.type_().clone(),
|
||||
body: expr,
|
||||
})
|
||||
.collect(),
|
||||
type_: body.type_().clone(),
|
||||
body: Box::new(body),
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn post_visit_call(
|
||||
&mut self,
|
||||
_fun: &mut Expr<'ast, ast::Type<'ast>>,
|
||||
_type_args: &mut HashMap<Ident<'ast>, ast::Type<'ast>>,
|
||||
args: &mut Vec<Expr<'ast, ast::Type<'ast>>>,
|
||||
) -> Result<(), Self::Error> {
|
||||
args.retain(|arg| arg.type_() != &ast::Type::Unit);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn visit_type(&mut self, type_: &mut ast::Type<'ast>) -> Result<(), Self::Error> {
|
||||
if let ast::Type::Function(ft) = type_ {
|
||||
ft.args.retain(|a| a != &ast::Type::Unit);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn post_visit_fun_decl(
|
||||
&mut self,
|
||||
_name: &mut Ident<'ast>,
|
||||
_type_args: &mut Vec<Ident>,
|
||||
args: &mut Vec<(Ident, ast::Type<'ast>)>,
|
||||
_body: &mut Box<Expr<ast::Type<'ast>>>,
|
||||
_type_: &mut ast::Type<'ast>,
|
||||
) -> Result<(), Self::Error> {
|
||||
args.retain(|(_, ty)| ty != &ast::Type::Unit);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn run_toplevel<'a>(toplevel: &mut Vec<Decl<'a, ast::Type<'a>>>) {
|
||||
let mut pass = StripPositiveUnits {};
|
||||
for decl in toplevel.iter_mut() {
|
||||
pass.visit_decl(decl).void_unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::parser::toplevel;
|
||||
use crate::tc::typecheck_toplevel;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn unit_only_arg() {
|
||||
let (_, program) = toplevel(
|
||||
"ty f : fn () -> int
|
||||
fn f _ = 1
|
||||
|
||||
ty main : fn -> int
|
||||
fn main = f ()",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (_, expected) = toplevel(
|
||||
"ty f : fn -> int
|
||||
fn f = 1
|
||||
|
||||
ty main : fn -> int
|
||||
fn main = f()",
|
||||
)
|
||||
.unwrap();
|
||||
let expected = typecheck_toplevel(expected).unwrap();
|
||||
|
||||
let mut program = typecheck_toplevel(program).unwrap();
|
||||
run_toplevel(&mut program);
|
||||
|
||||
assert_eq!(program, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_and_other_arg() {
|
||||
let (_, program) = toplevel(
|
||||
"ty f : fn (), int -> int
|
||||
fn f _ x = x
|
||||
|
||||
ty main : fn -> int
|
||||
fn main = f () 1",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (_, expected) = toplevel(
|
||||
"ty f : fn int -> int
|
||||
fn f x = x
|
||||
|
||||
ty main : fn -> int
|
||||
fn main = f 1",
|
||||
)
|
||||
.unwrap();
|
||||
let expected = typecheck_toplevel(expected).unwrap();
|
||||
|
||||
let mut program = typecheck_toplevel(program).unwrap();
|
||||
run_toplevel(&mut program);
|
||||
|
||||
assert_eq!(program, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_expr_and_other_arg() {
|
||||
let (_, program) = toplevel(
|
||||
"ty f : fn (), int -> int
|
||||
fn f _ x = x
|
||||
|
||||
ty g : fn int -> ()
|
||||
fn g _ = ()
|
||||
|
||||
ty main : fn -> int
|
||||
fn main = f (g 2) 1",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (_, expected) = toplevel(
|
||||
"ty f : fn int -> int
|
||||
fn f x = x
|
||||
|
||||
ty g : fn int -> ()
|
||||
fn g _ = ()
|
||||
|
||||
ty main : fn -> int
|
||||
fn main = let ___discarded = g 2 in f 1",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(expected.len(), 6);
|
||||
let expected = typecheck_toplevel(expected).unwrap();
|
||||
|
||||
let mut program = typecheck_toplevel(program).unwrap();
|
||||
run_toplevel(&mut program);
|
||||
|
||||
assert_eq!(program, expected);
|
||||
}
|
||||
}
|
||||
1
users/grfn/achilles/src/passes/mod.rs
Normal file
1
users/grfn/achilles/src/passes/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub(crate) mod hir;
|
||||
745
users/grfn/achilles/src/tc/mod.rs
Normal file
745
users/grfn/achilles/src/tc/mod.rs
Normal file
|
|
@ -0,0 +1,745 @@
|
|||
use bimap::BiMap;
|
||||
use derive_more::From;
|
||||
use itertools::Itertools;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::fmt::{self, Display};
|
||||
use std::{mem, result};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::ast::{self, hir, Arg, BinaryOperator, Ident, Literal};
|
||||
use crate::common::env::Env;
|
||||
use crate::common::{Namer, NamerOf};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("Undefined variable {0}")]
|
||||
UndefinedVariable(Ident<'static>),
|
||||
|
||||
#[error("Mismatched types: expected {expected}, but got {actual}")]
|
||||
TypeMismatch { expected: Type, actual: Type },
|
||||
|
||||
#[error("Mismatched types, expected numeric type, but got {0}")]
|
||||
NonNumeric(Type),
|
||||
|
||||
#[error("Ambiguous type {0}")]
|
||||
AmbiguousType(TyVar),
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||
pub struct TyVar(u64);
|
||||
|
||||
impl Display for TyVar {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "t{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
pub struct NullaryType(String);
|
||||
|
||||
impl Display for NullaryType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum PrimType {
|
||||
Int,
|
||||
Float,
|
||||
Bool,
|
||||
CString,
|
||||
}
|
||||
|
||||
impl<'a> From<PrimType> for ast::Type<'a> {
|
||||
fn from(pr: PrimType) -> Self {
|
||||
match pr {
|
||||
PrimType::Int => ast::Type::Int,
|
||||
PrimType::Float => ast::Type::Float,
|
||||
PrimType::Bool => ast::Type::Bool,
|
||||
PrimType::CString => ast::Type::CString,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PrimType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
PrimType::Int => f.write_str("int"),
|
||||
PrimType::Float => f.write_str("float"),
|
||||
PrimType::Bool => f.write_str("bool"),
|
||||
PrimType::CString => f.write_str("cstring"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, From)]
|
||||
pub enum Type {
|
||||
#[from(ignore)]
|
||||
Univ(TyVar),
|
||||
#[from(ignore)]
|
||||
Exist(TyVar),
|
||||
Nullary(NullaryType),
|
||||
Prim(PrimType),
|
||||
Unit,
|
||||
Fun {
|
||||
args: Vec<Type>,
|
||||
ret: Box<Type>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<Type> for ast::Type<'a> {
|
||||
type Error = Type;
|
||||
|
||||
fn try_from(value: Type) -> result::Result<Self, Self::Error> {
|
||||
match value {
|
||||
Type::Unit => Ok(ast::Type::Unit),
|
||||
Type::Univ(_) => todo!(),
|
||||
Type::Exist(_) => Err(value),
|
||||
Type::Nullary(_) => todo!(),
|
||||
Type::Prim(p) => Ok(p.into()),
|
||||
Type::Fun { ref args, ref ret } => Ok(ast::Type::Function(ast::FunctionType {
|
||||
args: args
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(Self::try_from)
|
||||
.try_collect()
|
||||
.map_err(|_| value.clone())?,
|
||||
ret: Box::new((*ret.clone()).try_into().map_err(|_| value.clone())?),
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const INT: Type = Type::Prim(PrimType::Int);
|
||||
const FLOAT: Type = Type::Prim(PrimType::Float);
|
||||
const BOOL: Type = Type::Prim(PrimType::Bool);
|
||||
const CSTRING: Type = Type::Prim(PrimType::CString);
|
||||
|
||||
impl Display for Type {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Type::Nullary(nt) => nt.fmt(f),
|
||||
Type::Prim(p) => p.fmt(f),
|
||||
Type::Univ(TyVar(n)) => write!(f, "∀{}", n),
|
||||
Type::Exist(TyVar(n)) => write!(f, "∃{}", n),
|
||||
Type::Fun { args, ret } => write!(f, "fn {} -> {}", args.iter().join(", "), ret),
|
||||
Type::Unit => write!(f, "()"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Typechecker<'ast> {
|
||||
ty_var_namer: NamerOf<TyVar>,
|
||||
ctx: HashMap<TyVar, Type>,
|
||||
env: Env<Ident<'ast>, Type>,
|
||||
|
||||
/// AST type var -> type
|
||||
instantiations: Env<Ident<'ast>, Type>,
|
||||
|
||||
/// AST type-var -> universal TyVar
|
||||
type_vars: RefCell<(BiMap<Ident<'ast>, TyVar>, NamerOf<Ident<'static>>)>,
|
||||
}
|
||||
|
||||
impl<'ast> Typechecker<'ast> {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
ty_var_namer: Namer::new(TyVar).boxed(),
|
||||
type_vars: RefCell::new((
|
||||
Default::default(),
|
||||
Namer::alphabetic().map(|n| Ident::try_from(n).unwrap()),
|
||||
)),
|
||||
ctx: Default::default(),
|
||||
env: Default::default(),
|
||||
instantiations: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn tc_expr(&mut self, expr: ast::Expr<'ast>) -> Result<hir::Expr<'ast, Type>> {
|
||||
match expr {
|
||||
ast::Expr::Ident(ident) => {
|
||||
let type_ = self
|
||||
.env
|
||||
.resolve(&ident)
|
||||
.ok_or_else(|| Error::UndefinedVariable(ident.to_owned()))?
|
||||
.clone();
|
||||
Ok(hir::Expr::Ident(ident, type_))
|
||||
}
|
||||
ast::Expr::Literal(lit) => {
|
||||
let type_ = match lit {
|
||||
Literal::Int(_) => Type::Prim(PrimType::Int),
|
||||
Literal::Bool(_) => Type::Prim(PrimType::Bool),
|
||||
Literal::String(_) => Type::Prim(PrimType::CString),
|
||||
Literal::Unit => Type::Unit,
|
||||
};
|
||||
Ok(hir::Expr::Literal(lit.to_owned(), type_))
|
||||
}
|
||||
ast::Expr::UnaryOp { op, rhs } => todo!(),
|
||||
ast::Expr::BinaryOp { lhs, op, rhs } => {
|
||||
let lhs = self.tc_expr(*lhs)?;
|
||||
let rhs = self.tc_expr(*rhs)?;
|
||||
let type_ = match op {
|
||||
BinaryOperator::Equ | BinaryOperator::Neq => {
|
||||
self.unify(lhs.type_(), rhs.type_())?;
|
||||
Type::Prim(PrimType::Bool)
|
||||
}
|
||||
BinaryOperator::Add | BinaryOperator::Sub | BinaryOperator::Mul => {
|
||||
let ty = self.unify(lhs.type_(), rhs.type_())?;
|
||||
// if !matches!(ty, Type::Int | Type::Float) {
|
||||
// return Err(Error::NonNumeric(ty));
|
||||
// }
|
||||
ty
|
||||
}
|
||||
BinaryOperator::Div => todo!(),
|
||||
BinaryOperator::Pow => todo!(),
|
||||
};
|
||||
Ok(hir::Expr::BinaryOp {
|
||||
lhs: Box::new(lhs),
|
||||
op,
|
||||
rhs: Box::new(rhs),
|
||||
type_,
|
||||
})
|
||||
}
|
||||
ast::Expr::Let { bindings, body } => {
|
||||
self.env.push();
|
||||
let bindings = bindings
|
||||
.into_iter()
|
||||
.map(
|
||||
|ast::Binding { ident, type_, body }| -> Result<hir::Binding<Type>> {
|
||||
let body = self.tc_expr(body)?;
|
||||
if let Some(type_) = type_ {
|
||||
let type_ = self.type_from_ast_type(type_);
|
||||
self.unify(body.type_(), &type_)?;
|
||||
}
|
||||
self.env.set(ident.clone(), body.type_().clone());
|
||||
Ok(hir::Binding {
|
||||
ident,
|
||||
type_: body.type_().clone(),
|
||||
body,
|
||||
})
|
||||
},
|
||||
)
|
||||
.collect::<Result<Vec<hir::Binding<Type>>>>()?;
|
||||
let body = self.tc_expr(*body)?;
|
||||
self.env.pop();
|
||||
Ok(hir::Expr::Let {
|
||||
bindings,
|
||||
type_: body.type_().clone(),
|
||||
body: Box::new(body),
|
||||
})
|
||||
}
|
||||
ast::Expr::If {
|
||||
condition,
|
||||
then,
|
||||
else_,
|
||||
} => {
|
||||
let condition = self.tc_expr(*condition)?;
|
||||
self.unify(&Type::Prim(PrimType::Bool), condition.type_())?;
|
||||
let then = self.tc_expr(*then)?;
|
||||
let else_ = self.tc_expr(*else_)?;
|
||||
let type_ = self.unify(then.type_(), else_.type_())?;
|
||||
Ok(hir::Expr::If {
|
||||
condition: Box::new(condition),
|
||||
then: Box::new(then),
|
||||
else_: Box::new(else_),
|
||||
type_,
|
||||
})
|
||||
}
|
||||
ast::Expr::Fun(f) => {
|
||||
let ast::Fun { args, body } = *f;
|
||||
self.env.push();
|
||||
let args: Vec<_> = args
|
||||
.into_iter()
|
||||
.map(|Arg { ident, type_ }| {
|
||||
let ty = match type_ {
|
||||
Some(t) => self.type_from_ast_type(t),
|
||||
None => self.fresh_ex(),
|
||||
};
|
||||
self.env.set(ident.clone(), ty.clone());
|
||||
(ident, ty)
|
||||
})
|
||||
.collect();
|
||||
let body = self.tc_expr(body)?;
|
||||
self.env.pop();
|
||||
Ok(hir::Expr::Fun {
|
||||
type_: Type::Fun {
|
||||
args: args.iter().map(|(_, ty)| ty.clone()).collect(),
|
||||
ret: Box::new(body.type_().clone()),
|
||||
},
|
||||
type_args: vec![], // TODO fill in once we do let generalization
|
||||
args,
|
||||
body: Box::new(body),
|
||||
})
|
||||
}
|
||||
ast::Expr::Call { fun, args } => {
|
||||
let ret_ty = self.fresh_ex();
|
||||
let arg_tys = args.iter().map(|_| self.fresh_ex()).collect::<Vec<_>>();
|
||||
let ft = Type::Fun {
|
||||
args: arg_tys.clone(),
|
||||
ret: Box::new(ret_ty.clone()),
|
||||
};
|
||||
let fun = self.tc_expr(*fun)?;
|
||||
self.instantiations.push();
|
||||
self.unify(&ft, fun.type_())?;
|
||||
let args = args
|
||||
.into_iter()
|
||||
.zip(arg_tys)
|
||||
.map(|(arg, ty)| {
|
||||
let arg = self.tc_expr(arg)?;
|
||||
self.unify(&ty, arg.type_())?;
|
||||
Ok(arg)
|
||||
})
|
||||
.try_collect()?;
|
||||
let type_args = self.commit_instantiations();
|
||||
Ok(hir::Expr::Call {
|
||||
fun: Box::new(fun),
|
||||
type_args,
|
||||
args,
|
||||
type_: ret_ty,
|
||||
})
|
||||
}
|
||||
ast::Expr::Ascription { expr, type_ } => {
|
||||
let expr = self.tc_expr(*expr)?;
|
||||
let type_ = self.type_from_ast_type(type_);
|
||||
self.unify(expr.type_(), &type_)?;
|
||||
Ok(expr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn tc_decl(
|
||||
&mut self,
|
||||
decl: ast::Decl<'ast>,
|
||||
) -> Result<Option<hir::Decl<'ast, Type>>> {
|
||||
match decl {
|
||||
ast::Decl::Fun { name, body } => {
|
||||
let mut expr = ast::Expr::Fun(Box::new(body));
|
||||
if let Some(type_) = self.env.resolve(&name) {
|
||||
expr = ast::Expr::Ascription {
|
||||
expr: Box::new(expr),
|
||||
type_: self.finalize_type(type_.clone())?,
|
||||
};
|
||||
}
|
||||
|
||||
self.env.push();
|
||||
let body = self.tc_expr(expr)?;
|
||||
let type_ = body.type_().clone();
|
||||
self.env.set(name.clone(), type_);
|
||||
self.env.pop();
|
||||
match body {
|
||||
hir::Expr::Fun {
|
||||
type_args,
|
||||
args,
|
||||
body,
|
||||
type_,
|
||||
} => Ok(Some(hir::Decl::Fun {
|
||||
name,
|
||||
type_args,
|
||||
args,
|
||||
body,
|
||||
type_,
|
||||
})),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
ast::Decl::Ascription { name, type_ } => {
|
||||
let type_ = self.type_from_ast_type(type_);
|
||||
self.env.set(name.clone(), type_);
|
||||
Ok(None)
|
||||
}
|
||||
ast::Decl::Extern { name, type_ } => {
|
||||
let type_ = self.type_from_ast_type(ast::Type::Function(type_));
|
||||
self.env.set(name.clone(), type_.clone());
|
||||
let (arg_types, ret_type) = match type_ {
|
||||
Type::Fun { args, ret } => (args, *ret),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
Ok(Some(hir::Decl::Extern {
|
||||
name,
|
||||
arg_types,
|
||||
ret_type,
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fresh_tv(&mut self) -> TyVar {
|
||||
self.ty_var_namer.make_name()
|
||||
}
|
||||
|
||||
fn fresh_ex(&mut self) -> Type {
|
||||
Type::Exist(self.fresh_tv())
|
||||
}
|
||||
|
||||
fn fresh_univ(&mut self) -> Type {
|
||||
Type::Univ(self.fresh_tv())
|
||||
}
|
||||
|
||||
fn unify(&mut self, ty1: &Type, ty2: &Type) -> Result<Type> {
|
||||
match (ty1, ty2) {
|
||||
(Type::Unit, Type::Unit) => Ok(Type::Unit),
|
||||
(Type::Exist(tv), ty) | (ty, Type::Exist(tv)) => match self.resolve_tv(*tv) {
|
||||
Some(existing_ty) if self.types_match(ty, &existing_ty) => Ok(ty.clone()),
|
||||
Some(var @ ast::Type::Var(_)) => {
|
||||
let var = self.type_from_ast_type(var);
|
||||
self.unify(&var, ty)
|
||||
}
|
||||
Some(existing_ty) => match ty {
|
||||
Type::Exist(_) => {
|
||||
let rhs = self.type_from_ast_type(existing_ty);
|
||||
self.unify(ty, &rhs)
|
||||
}
|
||||
_ => Err(Error::TypeMismatch {
|
||||
expected: ty.clone(),
|
||||
actual: self.type_from_ast_type(existing_ty),
|
||||
}),
|
||||
},
|
||||
None => match self.ctx.insert(*tv, ty.clone()) {
|
||||
Some(existing) => self.unify(&existing, ty),
|
||||
None => Ok(ty.clone()),
|
||||
},
|
||||
},
|
||||
(Type::Univ(u1), Type::Univ(u2)) if u1 == u2 => Ok(ty2.clone()),
|
||||
(Type::Univ(u), ty) | (ty, Type::Univ(u)) => {
|
||||
let ident = self.name_univ(*u);
|
||||
match self.instantiations.resolve(&ident) {
|
||||
Some(existing_ty) if ty == existing_ty => Ok(ty.clone()),
|
||||
Some(existing_ty) => Err(Error::TypeMismatch {
|
||||
expected: ty.clone(),
|
||||
actual: existing_ty.clone(),
|
||||
}),
|
||||
None => {
|
||||
self.instantiations.set(ident, ty.clone());
|
||||
Ok(ty.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
(Type::Prim(p1), Type::Prim(p2)) if p1 == p2 => Ok(ty2.clone()),
|
||||
(
|
||||
Type::Fun {
|
||||
args: args1,
|
||||
ret: ret1,
|
||||
},
|
||||
Type::Fun {
|
||||
args: args2,
|
||||
ret: ret2,
|
||||
},
|
||||
) => {
|
||||
let args = args1
|
||||
.iter()
|
||||
.zip(args2)
|
||||
.map(|(t1, t2)| self.unify(t1, t2))
|
||||
.try_collect()?;
|
||||
let ret = self.unify(ret1, ret2)?;
|
||||
Ok(Type::Fun {
|
||||
args,
|
||||
ret: Box::new(ret),
|
||||
})
|
||||
}
|
||||
(Type::Nullary(_), _) | (_, Type::Nullary(_)) => todo!(),
|
||||
_ => Err(Error::TypeMismatch {
|
||||
expected: ty1.clone(),
|
||||
actual: ty2.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn finalize_expr(
|
||||
&self,
|
||||
expr: hir::Expr<'ast, Type>,
|
||||
) -> Result<hir::Expr<'ast, ast::Type<'ast>>> {
|
||||
expr.traverse_type(|ty| self.finalize_type(ty))
|
||||
}
|
||||
|
||||
fn finalize_decl(
|
||||
&mut self,
|
||||
decl: hir::Decl<'ast, Type>,
|
||||
) -> Result<hir::Decl<'ast, ast::Type<'ast>>> {
|
||||
let res = decl.traverse_type(|ty| self.finalize_type(ty))?;
|
||||
if let Some(type_) = res.type_() {
|
||||
let ty = self.type_from_ast_type(type_.clone());
|
||||
self.env.set(res.name().clone(), ty);
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn finalize_type(&self, ty: Type) -> Result<ast::Type<'static>> {
|
||||
let ret = match ty {
|
||||
Type::Exist(tv) => self.resolve_tv(tv).ok_or(Error::AmbiguousType(tv)),
|
||||
Type::Univ(tv) => Ok(ast::Type::Var(self.name_univ(tv))),
|
||||
Type::Unit => Ok(ast::Type::Unit),
|
||||
Type::Nullary(_) => todo!(),
|
||||
Type::Prim(pr) => Ok(pr.into()),
|
||||
Type::Fun { args, ret } => Ok(ast::Type::Function(ast::FunctionType {
|
||||
args: args
|
||||
.into_iter()
|
||||
.map(|ty| self.finalize_type(ty))
|
||||
.try_collect()?,
|
||||
ret: Box::new(self.finalize_type(*ret)?),
|
||||
})),
|
||||
};
|
||||
ret
|
||||
}
|
||||
|
||||
fn resolve_tv(&self, tv: TyVar) -> Option<ast::Type<'static>> {
|
||||
let mut res = &Type::Exist(tv);
|
||||
loop {
|
||||
match res {
|
||||
Type::Exist(tv) => {
|
||||
res = self.ctx.get(tv)?;
|
||||
}
|
||||
Type::Univ(tv) => {
|
||||
let ident = self.name_univ(*tv);
|
||||
if let Some(r) = self.instantiations.resolve(&ident) {
|
||||
res = r;
|
||||
} else {
|
||||
break Some(ast::Type::Var(ident));
|
||||
}
|
||||
}
|
||||
Type::Nullary(_) => todo!(),
|
||||
Type::Prim(pr) => break Some((*pr).into()),
|
||||
Type::Unit => break Some(ast::Type::Unit),
|
||||
Type::Fun { args, ret } => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn type_from_ast_type(&mut self, ast_type: ast::Type<'ast>) -> Type {
|
||||
match ast_type {
|
||||
ast::Type::Unit => Type::Unit,
|
||||
ast::Type::Int => INT,
|
||||
ast::Type::Float => FLOAT,
|
||||
ast::Type::Bool => BOOL,
|
||||
ast::Type::CString => CSTRING,
|
||||
ast::Type::Function(ast::FunctionType { args, ret }) => Type::Fun {
|
||||
args: args
|
||||
.into_iter()
|
||||
.map(|t| self.type_from_ast_type(t))
|
||||
.collect(),
|
||||
ret: Box::new(self.type_from_ast_type(*ret)),
|
||||
},
|
||||
ast::Type::Var(id) => Type::Univ({
|
||||
let opt_tv = { self.type_vars.borrow_mut().0.get_by_left(&id).copied() };
|
||||
opt_tv.unwrap_or_else(|| {
|
||||
let tv = self.fresh_tv();
|
||||
self.type_vars
|
||||
.borrow_mut()
|
||||
.0
|
||||
.insert_no_overwrite(id, tv)
|
||||
.unwrap();
|
||||
tv
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn name_univ(&self, tv: TyVar) -> Ident<'static> {
|
||||
let mut vars = self.type_vars.borrow_mut();
|
||||
vars.0
|
||||
.get_by_right(&tv)
|
||||
.map(Ident::to_owned)
|
||||
.unwrap_or_else(|| {
|
||||
let name = loop {
|
||||
let name = vars.1.make_name();
|
||||
if !vars.0.contains_left(&name) {
|
||||
break name;
|
||||
}
|
||||
};
|
||||
vars.0.insert_no_overwrite(name.clone(), tv).unwrap();
|
||||
name
|
||||
})
|
||||
}
|
||||
|
||||
fn commit_instantiations(&mut self) -> HashMap<Ident<'ast>, Type> {
|
||||
let mut res = HashMap::new();
|
||||
let mut ctx = mem::take(&mut self.ctx);
|
||||
for (_, v) in ctx.iter_mut() {
|
||||
if let Type::Univ(tv) = v {
|
||||
let tv_name = self.name_univ(*tv);
|
||||
if let Some(concrete) = self.instantiations.resolve(&tv_name) {
|
||||
res.insert(tv_name, concrete.clone());
|
||||
*v = concrete.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
self.ctx = ctx;
|
||||
self.instantiations.pop();
|
||||
res
|
||||
}
|
||||
|
||||
fn types_match(&self, type_: &Type, ast_type: &ast::Type<'ast>) -> bool {
|
||||
match (type_, ast_type) {
|
||||
(Type::Univ(u), ast::Type::Var(v)) => {
|
||||
Some(u) == self.type_vars.borrow().0.get_by_left(v)
|
||||
}
|
||||
(Type::Univ(_), _) => false,
|
||||
(Type::Exist(_), _) => false,
|
||||
(Type::Unit, ast::Type::Unit) => true,
|
||||
(Type::Unit, _) => false,
|
||||
(Type::Nullary(_), _) => todo!(),
|
||||
(Type::Prim(pr), ty) => ast::Type::from(*pr) == *ty,
|
||||
(Type::Fun { args, ret }, ast::Type::Function(ft)) => {
|
||||
args.len() == ft.args.len()
|
||||
&& args
|
||||
.iter()
|
||||
.zip(&ft.args)
|
||||
.all(|(a1, a2)| self.types_match(a1, &a2))
|
||||
&& self.types_match(&*ret, &*ft.ret)
|
||||
}
|
||||
(Type::Fun { .. }, _) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn typecheck_expr(expr: ast::Expr) -> Result<hir::Expr<ast::Type>> {
|
||||
let mut typechecker = Typechecker::new();
|
||||
let typechecked = typechecker.tc_expr(expr)?;
|
||||
typechecker.finalize_expr(typechecked)
|
||||
}
|
||||
|
||||
pub fn typecheck_toplevel(decls: Vec<ast::Decl>) -> Result<Vec<hir::Decl<ast::Type>>> {
|
||||
let mut typechecker = Typechecker::new();
|
||||
let mut res = Vec::with_capacity(decls.len());
|
||||
for decl in decls {
|
||||
if let Some(hir_decl) = typechecker.tc_decl(decl)? {
|
||||
let hir_decl = typechecker.finalize_decl(hir_decl)?;
|
||||
res.push(hir_decl);
|
||||
}
|
||||
typechecker.ctx.clear();
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
macro_rules! assert_type {
|
||||
($expr: expr, $type: expr) => {
|
||||
use crate::parser::{expr, type_};
|
||||
let parsed_expr = test_parse!(expr, $expr);
|
||||
let parsed_type = test_parse!(type_, $type);
|
||||
let res = typecheck_expr(parsed_expr).unwrap_or_else(|e| panic!("{}", e));
|
||||
assert!(
|
||||
res.type_().alpha_equiv(&parsed_type),
|
||||
"{} inferred type {}, but expected {}",
|
||||
$expr,
|
||||
res.type_(),
|
||||
$type
|
||||
);
|
||||
};
|
||||
|
||||
(toplevel($program: expr), $($decl: ident => $type: expr),+ $(,)?) => {{
|
||||
use crate::parser::{toplevel, type_};
|
||||
let program = test_parse!(toplevel, $program);
|
||||
let res = typecheck_toplevel(program).unwrap_or_else(|e| panic!("{}", e));
|
||||
$(
|
||||
let parsed_type = test_parse!(type_, $type);
|
||||
let ident = Ident::try_from(::std::stringify!($decl)).unwrap();
|
||||
let decl = res.iter().find(|decl| {
|
||||
matches!(decl, crate::ast::hir::Decl::Fun { name, .. } if name == &ident)
|
||||
}).unwrap_or_else(|| panic!("Could not find declaration for {}", ident));
|
||||
assert!(
|
||||
decl.type_().unwrap().alpha_equiv(&parsed_type),
|
||||
"inferred type {} for {}, but expected {}",
|
||||
decl.type_().unwrap(),
|
||||
ident,
|
||||
$type
|
||||
);
|
||||
)+
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! assert_type_error {
|
||||
($expr: expr) => {
|
||||
use crate::parser::expr;
|
||||
let parsed_expr = test_parse!(expr, $expr);
|
||||
let res = typecheck_expr(parsed_expr);
|
||||
assert!(
|
||||
res.is_err(),
|
||||
"Expected type error, but got type: {}",
|
||||
res.unwrap().type_()
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_int() {
|
||||
assert_type!("1", "int");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conditional() {
|
||||
assert_type!("if 1 == 2 then 3 else 4", "int");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn add_bools() {
|
||||
assert_type_error!("true + false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_generic_function() {
|
||||
assert_type!("(fn x = x) 1", "int");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_let_bound_generic() {
|
||||
assert_type!("let id = fn x = x in id 1", "int");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn universal_ascripted_let() {
|
||||
assert_type!("let id: fn a -> a = fn x = x in id 1", "int");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_generic_function_toplevel() {
|
||||
assert_type!(
|
||||
toplevel(
|
||||
"ty id : fn a -> a
|
||||
fn id x = x
|
||||
|
||||
fn main = id 0"
|
||||
),
|
||||
main => "fn -> int",
|
||||
id => "fn a -> a",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn let_generalization() {
|
||||
assert_type!("let id = fn x = x in if id true then id 1 else 2", "int");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concrete_function() {
|
||||
assert_type!("fn x = x + 1", "fn int -> int");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arg_ascriptions() {
|
||||
assert_type!("fn (x: int) = x", "fn int -> int");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_concrete_function() {
|
||||
assert_type!("(fn x = x + 1) 2", "int");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conditional_non_bool() {
|
||||
assert_type_error!("if 3 then true else false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_int() {
|
||||
assert_type!("let x = 1 in x", "int");
|
||||
}
|
||||
}
|
||||
79
users/grfn/achilles/tests/compile.rs
Normal file
79
users/grfn/achilles/tests/compile.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
use std::process::Command;
|
||||
|
||||
use crate_root::root;
|
||||
|
||||
struct Fixture {
|
||||
name: &'static str,
|
||||
exit_code: i32,
|
||||
expected_output: &'static str,
|
||||
}
|
||||
|
||||
const FIXTURES: &[Fixture] = &[
|
||||
Fixture {
|
||||
name: "simple",
|
||||
exit_code: 5,
|
||||
expected_output: "",
|
||||
},
|
||||
Fixture {
|
||||
name: "functions",
|
||||
exit_code: 9,
|
||||
expected_output: "",
|
||||
},
|
||||
Fixture {
|
||||
name: "externs",
|
||||
exit_code: 0,
|
||||
expected_output: "foobar\n",
|
||||
},
|
||||
Fixture {
|
||||
name: "units",
|
||||
exit_code: 0,
|
||||
expected_output: "hi\n",
|
||||
},
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn compile_and_run_files() {
|
||||
let ach = root().unwrap().join("ach");
|
||||
|
||||
println!("Running: `make clean`");
|
||||
assert!(
|
||||
Command::new("make")
|
||||
.arg("clean")
|
||||
.current_dir(&ach)
|
||||
.spawn()
|
||||
.unwrap()
|
||||
.wait()
|
||||
.unwrap()
|
||||
.success(),
|
||||
"make clean failed"
|
||||
);
|
||||
|
||||
for Fixture {
|
||||
name,
|
||||
exit_code,
|
||||
expected_output,
|
||||
} in FIXTURES
|
||||
{
|
||||
println!(">>> Testing: {}", name);
|
||||
|
||||
println!(" Running: `make {}`", name);
|
||||
assert!(
|
||||
Command::new("make")
|
||||
.arg(name)
|
||||
.current_dir(&ach)
|
||||
.spawn()
|
||||
.unwrap()
|
||||
.wait()
|
||||
.unwrap()
|
||||
.success(),
|
||||
"make failed"
|
||||
);
|
||||
|
||||
let out_path = ach.join(name);
|
||||
println!(" Running: `{}`", out_path.to_str().unwrap());
|
||||
let output = Command::new(out_path).output().unwrap();
|
||||
assert_eq!(output.status.code().unwrap(), *exit_code,);
|
||||
assert_eq!(output.stdout, expected_output.as_bytes());
|
||||
println!(" OK");
|
||||
}
|
||||
}
|
||||
1420
users/grfn/emacs.d/+bindings.el
Normal file
1420
users/grfn/emacs.d/+bindings.el
Normal file
File diff suppressed because it is too large
Load diff
149
users/grfn/emacs.d/+commands.el
Normal file
149
users/grfn/emacs.d/+commands.el
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
;; -*- lexical-binding: t; -*-
|
||||
|
||||
(defalias 'ex! 'evil-ex-define-cmd)
|
||||
|
||||
(defun delete-file-and-buffer ()
|
||||
"Kill the current buffer and deletes the file it is visiting."
|
||||
(interactive)
|
||||
(let ((filename (buffer-file-name)))
|
||||
(when filename
|
||||
(if (vc-backend filename)
|
||||
(vc-delete-file filename)
|
||||
(progn
|
||||
(delete-file filename)
|
||||
(message "Deleted file %s" filename)
|
||||
(kill-buffer))))))
|
||||
|
||||
;;; Commands defined elsewhere
|
||||
;;(ex! "al[ign]" #'+evil:align)
|
||||
;;(ex! "g[lobal]" #'+evil:global)
|
||||
|
||||
;;; Custom commands
|
||||
;; Editing
|
||||
(ex! "@" #'+evil:macro-on-all-lines) ; TODO Test me
|
||||
(ex! "al[ign]" #'+evil:align)
|
||||
(ex! "enhtml" #'+web:encode-html-entities)
|
||||
(ex! "dehtml" #'+web:decode-html-entities)
|
||||
(ex! "mc" #'+evil:mc)
|
||||
(ex! "iedit" #'evil-multiedit-ex-match)
|
||||
(ex! "na[rrow]" #'+evil:narrow-buffer)
|
||||
(ex! "retab" #'+evil:retab)
|
||||
|
||||
(ex! "glog" #'magit-log-buffer-file)
|
||||
|
||||
;; External resources
|
||||
;; TODO (ex! "db" #'doom:db)
|
||||
;; TODO (ex! "dbu[se]" #'doom:db-select)
|
||||
;; TODO (ex! "go[ogle]" #'doom:google-search)
|
||||
(ex! "lo[okup]" #'+jump:online)
|
||||
(ex! "dash" #'+lookup:dash)
|
||||
(ex! "dd" #'+lookup:devdocs)
|
||||
(ex! "http" #'httpd-start) ; start http server
|
||||
(ex! "repl" #'+eval:repl) ; invoke or send to repl
|
||||
;; TODO (ex! "rx" 'doom:regex) ; open re-builder
|
||||
(ex! "sh[ell]" #'+eshell:run)
|
||||
(ex! "t[mux]" #'+tmux:run) ; send to tmux
|
||||
(ex! "tcd" #'+tmux:cd-here) ; cd to default-directory in tmux
|
||||
(ex! "x" #'doom/open-project-scratch-buffer)
|
||||
|
||||
;; GIT
|
||||
(ex! "gist" #'+gist:send) ; send current buffer/region to gist
|
||||
(ex! "gistl" #'+gist:list) ; list gists by user
|
||||
(ex! "gbrowse" #'+vcs/git-browse) ; show file in github/gitlab
|
||||
(ex! "gissues" #'+vcs/git-browse-issues) ; show github issues
|
||||
(ex! "git" #'magit-status) ; open magit status window
|
||||
(ex! "gstage" #'magit-stage)
|
||||
(ex! "gunstage" #'magit-unstage)
|
||||
(ex! "gblame" #'magit-blame)
|
||||
(ex! "grevert" #'git-gutter:revert-hunk)
|
||||
|
||||
;; Dealing with buffers
|
||||
(ex! "clean[up]" #'doom/cleanup-buffers)
|
||||
(ex! "k[ill]" #'doom/kill-this-buffer)
|
||||
(ex! "k[ill]all" #'+hlissner:kill-all-buffers)
|
||||
(ex! "k[ill]m" #'+hlissner:kill-matching-buffers)
|
||||
(ex! "k[ill]o" #'doom/kill-other-buffers)
|
||||
(ex! "l[ast]" #'doom/popup-restore)
|
||||
(ex! "m[sg]" #'view-echo-area-messages)
|
||||
(ex! "pop[up]" #'doom/popup-this-buffer)
|
||||
|
||||
;; Project navigation
|
||||
(ex! "a" #'projectile-toggle-between-implementation-and-test)
|
||||
(ex! "as" #'projectile-find-implementation-or-test-other-window)
|
||||
(ex! "av" #'projectile-find-implementation-or-test-other-window)
|
||||
(ex! "cd" #'+hlissner:cd)
|
||||
(cond ((featurep! :completion ivy)
|
||||
(ex! "ag" #'+ivy:ag)
|
||||
(ex! "agc[wd]" #'+ivy:ag-cwd)
|
||||
(ex! "rg" #'+ivy:rg)
|
||||
(ex! "rgc[wd]" #'+ivy:rg-cwd)
|
||||
(ex! "sw[iper]" #'+ivy:swiper)
|
||||
(ex! "todo" #'+ivy:todo))
|
||||
((featurep! :completion helm)
|
||||
(ex! "ag" #'+helm:ag)
|
||||
(ex! "agc[wd]" #'+helm:ag-cwd)
|
||||
(ex! "rg" #'+helm:rg)
|
||||
(ex! "rgc[wd]" #'+helm:rg-cwd)
|
||||
(ex! "sw[oop]" #'+helm:swoop)
|
||||
(ex! "todo" #'+helm:todo)))
|
||||
|
||||
;; Project tools
|
||||
(ex! "build" #'+eval/build)
|
||||
(ex! "debug" #'+debug/run)
|
||||
(ex! "er[rors]" #'flycheck-list-errors)
|
||||
|
||||
;; File operations
|
||||
(ex! "cp" #'+evil:copy-this-file)
|
||||
(ex! "mv" #'+evil:move-this-file)
|
||||
(ex! "rm" #'+evil:delete-this-file)
|
||||
|
||||
;; Sessions/tabs
|
||||
(ex! "sclear" #'+workspace/kill-session)
|
||||
(ex! "sl[oad]" #'+workspace:load-session)
|
||||
(ex! "ss[ave]" #'+workspace:save-session)
|
||||
(ex! "tabcl[ose]" #'+workspace:delete)
|
||||
(ex! "tabclear" #'doom/kill-all-buffers)
|
||||
(ex! "tabl[ast]" #'+workspace/switch-to-last)
|
||||
(ex! "tabload" #'+workspace:load)
|
||||
(ex! "tabn[ew]" #'+workspace:new)
|
||||
(ex! "tabn[ext]" #'+workspace:switch-next)
|
||||
(ex! "tabp[rev]" #'+workspace:switch-previous)
|
||||
(ex! "tabr[ename]" #'+workspace:rename)
|
||||
(ex! "tabs" #'+workspace/display)
|
||||
(ex! "tabsave" #'+workspace:save)
|
||||
|
||||
(ex! "scr[atch]" #'cider-scratch)
|
||||
|
||||
;; Org-mode
|
||||
(ex! "cap" #'+org-capture/dwim)
|
||||
|
||||
(evil-define-command evil-alembic-revision (args)
|
||||
(interactive "<a>")
|
||||
(apply
|
||||
#'generate-alembic-migration
|
||||
(read-string "Message: ")
|
||||
(s-split "\\s+" (or args ""))))
|
||||
(ex! "arev[ision]" #'evil-alembic-revision)
|
||||
|
||||
(evil-define-command evil-alembic-upgrade (&optional revision)
|
||||
(interactive "<a>")
|
||||
(alembic-upgrade (or revision "head")))
|
||||
|
||||
(ex! "aup[grade]" #'evil-alembic-upgrade)
|
||||
|
||||
(evil-define-command evil-alembic-downgrade (&optional revision)
|
||||
(interactive "<a>")
|
||||
(alembic-downgrade revision))
|
||||
|
||||
(ex! "adown[grade]" #'evil-alembic-downgrade)
|
||||
|
||||
(evil-define-command evil-alembic (args)
|
||||
(interactive "<a>")
|
||||
(run-alembic args))
|
||||
|
||||
(ex! "alemb[ic]" #'evil-alembic)
|
||||
|
||||
;; Elixir
|
||||
(add-hook! elixir-mode
|
||||
(ex! "AV" #'alchemist-project-toggle-file-and-tests-other-window)
|
||||
(ex! "A" #'alchemist-project-toggle-file-and-tests))
|
||||
BIN
users/grfn/emacs.d/+private.el.gpg
Normal file
BIN
users/grfn/emacs.d/+private.el.gpg
Normal file
Binary file not shown.
2
users/grfn/emacs.d/.gitignore
vendored
Normal file
2
users/grfn/emacs.d/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
.authinfo.gpg
|
||||
+private.el
|
||||
37
users/grfn/emacs.d/autoload/evil.el
Normal file
37
users/grfn/emacs.d/autoload/evil.el
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
;;; /autoload/evil.el -*- lexical-binding: t; -*-
|
||||
;;;###if (featurep! :feature evil)
|
||||
|
||||
;;;###autoload (autoload '+hlissner:multi-next-line "/autoload/evil" nil t)
|
||||
(evil-define-motion +hlissner:multi-next-line (count)
|
||||
"Move down 6 lines."
|
||||
:type line
|
||||
(let ((line-move-visual (or visual-line-mode (derived-mode-p 'text-mode))))
|
||||
(evil-line-move (* 6 (or count 1)))))
|
||||
|
||||
;;;###autoload (autoload '+hlissner:multi-previous-line "/autoload/evil" nil t)
|
||||
(evil-define-motion +hlissner:multi-previous-line (count)
|
||||
"Move up 6 lines."
|
||||
:type line
|
||||
(let ((line-move-visual (or visual-line-mode (derived-mode-p 'text-mode))))
|
||||
(evil-line-move (- (* 6 (or count 1))))))
|
||||
|
||||
;;;###autoload (autoload '+hlissner:cd "/autoload/evil" nil t)
|
||||
(evil-define-command +hlissner:cd ()
|
||||
"Change `default-directory' with `cd'."
|
||||
(interactive "<f>")
|
||||
(cd input))
|
||||
|
||||
;;;###autoload (autoload '+hlissner:kill-all-buffers "/autoload/evil" nil t)
|
||||
(evil-define-command +hlissner:kill-all-buffers (&optional bang)
|
||||
"Kill all buffers. If BANG, kill current session too."
|
||||
(interactive "<!>")
|
||||
(if bang
|
||||
(+workspace/kill-session)
|
||||
(doom/kill-all-buffers)))
|
||||
|
||||
;;;###autoload (autoload '+hlissner:kill-matching-buffers "/autoload/evil" nil t)
|
||||
(evil-define-command +hlissner:kill-matching-buffers (&optional bang pattern)
|
||||
"Kill all buffers matching PATTERN regexp. If BANG, only match project
|
||||
buffers."
|
||||
(interactive "<a>")
|
||||
(doom/kill-matching-buffers pattern bang))
|
||||
53
users/grfn/emacs.d/autoload/hlissner.el
Normal file
53
users/grfn/emacs.d/autoload/hlissner.el
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
;;; autoload/hlissner.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;;###autoload
|
||||
(defun +hlissner/install-snippets ()
|
||||
"Install my snippets from https://github.com/hlissner/emacs-snippets into
|
||||
private/hlissner/snippets."
|
||||
(interactive)
|
||||
(doom-fetch :github "hlissner/emacs-snippets"
|
||||
(expand-file-name "snippets" (doom-module-path :private 'hlissner))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +hlissner/yank-buffer-filename ()
|
||||
"Copy the current buffer's path to the kill ring."
|
||||
(interactive)
|
||||
(if-let* ((filename (or buffer-file-name (bound-and-true-p list-buffers-directory))))
|
||||
(message (kill-new (abbreviate-file-name filename)))
|
||||
(error "Couldn't find filename in current buffer")))
|
||||
|
||||
(defmacro +hlissner-def-finder! (name dir)
|
||||
"Define a pair of find-file and browse functions."
|
||||
`(progn
|
||||
(defun ,(intern (format "+hlissner/find-in-%s" name)) ()
|
||||
(interactive)
|
||||
(let ((default-directory ,dir)
|
||||
projectile-project-name
|
||||
projectile-require-project-root
|
||||
projectile-cached-buffer-file-name
|
||||
projectile-cached-project-root)
|
||||
(call-interactively (command-remapping #'projectile-find-file))))
|
||||
(defun ,(intern (format "+hlissner/browse-%s" name)) ()
|
||||
(interactive)
|
||||
(let ((default-directory ,dir))
|
||||
(call-interactively (command-remapping #'find-file))))))
|
||||
|
||||
;;;###autoload (autoload '+hlissner/find-in-templates "autoload/hlissner" nil t)
|
||||
;;;###autoload (autoload '+hlissner/browse-templates "autoload/hlissner" nil t)
|
||||
(+hlissner-def-finder! templates +file-templates-dir)
|
||||
|
||||
;;;###autoload (autoload '+hlissner/find-in-snippets "autoload/hlissner" nil t)
|
||||
;;;###autoload (autoload '+hlissner/browse-snippets "autoload/hlissner" nil t)
|
||||
(+hlissner-def-finder! snippets +hlissner-snippets-dir)
|
||||
|
||||
;;;###autoload (autoload '+hlissner/find-in-dotfiles "autoload/hlissner" nil t)
|
||||
;;;###autoload (autoload '+hlissner/browse-dotfiles "autoload/hlissner" nil t)
|
||||
(+hlissner-def-finder! dotfiles (expand-file-name ".dotfiles" "~"))
|
||||
|
||||
;;;###autoload (autoload '+hlissner/find-in-emacsd "autoload/hlissner" nil t)
|
||||
;;;###autoload (autoload '+hlissner/browse-emacsd "autoload/hlissner" nil t)
|
||||
(+hlissner-def-finder! emacsd doom-emacs-dir)
|
||||
|
||||
;;;###autoload (autoload '+hlissner/find-in-notes "autoload/hlissner" nil t)
|
||||
;;;###autoload (autoload '+hlissner/browse-notes "autoload/hlissner" nil t)
|
||||
(+hlissner-def-finder! notes +org-dir)
|
||||
17
users/grfn/emacs.d/clocked-in-elt.el
Normal file
17
users/grfn/emacs.d/clocked-in-elt.el
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
;;; -*- lexical-binding: t; -*-
|
||||
(load (expand-file-name "init" (or (getenv "EMACSDIR")
|
||||
(expand-file-name
|
||||
"../.emacs.d"
|
||||
(file-name-directory (file-truename load-file-name))))))
|
||||
|
||||
(require 'org-clock)
|
||||
(require 'org-element)
|
||||
|
||||
(let ((item (or org-clock-marker
|
||||
(car org-clock-history))))
|
||||
(when item
|
||||
(with-current-buffer (marker-buffer item)
|
||||
(goto-char (marker-position item))
|
||||
(let ((element (org-element-at-point)))
|
||||
(when (eq 'headline (car element))
|
||||
(message "%s" (plist-get (cadr element) :raw-value)))))))
|
||||
52
users/grfn/emacs.d/clojure.el
Normal file
52
users/grfn/emacs.d/clojure.el
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
;;; -*- lexical-binding: t; -*-
|
||||
|
||||
(defun clojure-thing-at-point-setup ()
|
||||
(interactive)
|
||||
;; Used by cider-find-dwim to parse the symbol at point
|
||||
(setq-local
|
||||
thing-at-point-file-name-chars
|
||||
(concat thing-at-point-file-name-chars
|
||||
"><!?")))
|
||||
|
||||
(defun +grfn/clojure-setup ()
|
||||
;; (flycheck-select-checker 'clj-kondo)
|
||||
(push 'clojure-cider-kibit flycheck-disabled-checkers)
|
||||
(push 'clojure-cider-eastwood flycheck-disabled-checkers)
|
||||
(push 'clojure-cider-typed flycheck-disabled-checkers)
|
||||
)
|
||||
|
||||
(after! clojure-mode
|
||||
(define-clojure-indent
|
||||
(PUT 2)
|
||||
(POST 2)
|
||||
(GET 2)
|
||||
(PATCH 2)
|
||||
(DELETE 2)
|
||||
(context 2)
|
||||
(checking 3)
|
||||
(match 1)
|
||||
(domonad 0)
|
||||
(describe 1)
|
||||
(before 1)
|
||||
(it 2))
|
||||
|
||||
(add-hook 'clojure-mode-hook #'clojure-thing-at-point-setup)
|
||||
(add-hook 'clojure-mode-hook #'+grfn/clojure-setup))
|
||||
|
||||
(use-package! flycheck-clojure
|
||||
;; :disabled t
|
||||
:after (flycheck cider)
|
||||
:config
|
||||
(flycheck-clojure-setup))
|
||||
|
||||
(after! clj-refactor
|
||||
(setq cljr-magic-requires :prompt
|
||||
cljr-clojure-test-declaration "[clojure.test :refer :all]"
|
||||
cljr-cljc-clojure-test-declaration"#?(:clj [clojure.test :refer :all]
|
||||
:cljs [cljs.test :refer-macros [deftest is testing]])"
|
||||
)
|
||||
(add-to-list
|
||||
'cljr-magic-require-namespaces
|
||||
'("s" . "clojure.spec.alpha")))
|
||||
|
||||
(set-popup-rule! "^\\*cider-test-report" :size 0.4)
|
||||
299
users/grfn/emacs.d/company-sql.el
Normal file
299
users/grfn/emacs.d/company-sql.el
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
;;; Commentary:
|
||||
;;; TODO
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'emacsql)
|
||||
(require 'emacsql-psql)
|
||||
(require 'dash)
|
||||
(require 's)
|
||||
(require 'cl-lib)
|
||||
|
||||
;;; Config
|
||||
|
||||
(defvar-local company-sql-db-host "localhost"
|
||||
"Host of the postgresql database to query for autocomplete information")
|
||||
|
||||
(defvar-local company-sql-db-port 5432
|
||||
"Port of the postgresql database to query for autocomplete information")
|
||||
|
||||
(defvar-local company-sql-db-user "postgres"
|
||||
"Username of the postgresql database to query for autocomplete information")
|
||||
|
||||
(defvar-local company-sql-db-name nil
|
||||
"PostgreSQL database name to query for autocomplete information")
|
||||
|
||||
;;; DB Connection
|
||||
|
||||
(defvar-local company-sql/connection nil)
|
||||
|
||||
(defun company-sql/connect ()
|
||||
(unless company-sql/connection
|
||||
(setq-local company-sql/connection
|
||||
(emacsql-psql company-sql-db-name
|
||||
:hostname company-sql-db-host
|
||||
:username company-sql-db-user
|
||||
:port (number-to-string company-sql-db-port))))
|
||||
company-sql/connection)
|
||||
|
||||
;;; Utils
|
||||
|
||||
(defmacro comment (&rest _))
|
||||
|
||||
(defun ->string (x)
|
||||
(cond
|
||||
((stringp x) x)
|
||||
((symbolp x) (symbol-name x))))
|
||||
|
||||
(defun alist-get-equal (key alist)
|
||||
"Like `alist-get', but uses `equal' instead of `eq' for comparing keys"
|
||||
(->> alist
|
||||
(-find (lambda (pair) (equal key (car pair))))
|
||||
(cdr)))
|
||||
|
||||
;;; Listing relations
|
||||
|
||||
(cl-defun company-sql/list-tables (conn)
|
||||
(with-timeout (3)
|
||||
(-map (-compose 'symbol-name 'car)
|
||||
(emacsql conn
|
||||
[:select [tablename]
|
||||
:from pg_catalog:pg_tables
|
||||
:where (and (!= schemaname '"information_schema")
|
||||
(!= schemaname '"pg_catalog"))]))))
|
||||
|
||||
(cl-defun company-sql/list-columns (conn)
|
||||
(with-timeout (3)
|
||||
(-map
|
||||
(lambda (row)
|
||||
(propertize (symbol-name (nth 0 row))
|
||||
'table-name (nth 1 row)
|
||||
'data-type (nth 2 row)))
|
||||
(emacsql conn
|
||||
[:select [column_name
|
||||
table_name
|
||||
data_type]
|
||||
:from information_schema:columns]))))
|
||||
|
||||
;;; Keywords
|
||||
|
||||
(defvar company-postgresql/keywords
|
||||
(list
|
||||
"a" "abort" "abs" "absent" "absolute" "access" "according" "action" "ada" "add"
|
||||
"admin" "after" "aggregate" "all" "allocate" "also" "alter" "always" "analyse"
|
||||
"analyze" "and" "any" "are" "array" "array_agg" "array_max_cardinality" "as"
|
||||
"asc" "asensitive" "assertion" "assignment" "asymmetric" "at" "atomic" "attach"
|
||||
"attribute" "attributes" "authorization" "avg" "backward" "base64" "before"
|
||||
"begin" "begin_frame" "begin_partition" "bernoulli" "between" "bigint" "binary"
|
||||
"bit" "bit_length" "blob" "blocked" "bom" "boolean" "both" "breadth" "by" "c"
|
||||
"cache" "call" "called" "cardinality" "cascade" "cascaded" "case" "cast"
|
||||
"catalog" "catalog_name" "ceil" "ceiling" "chain" "char" "character"
|
||||
"characteristics" "characters" "character_length" "character_set_catalog"
|
||||
"character_set_name" "character_set_schema" "char_length" "check" "checkpoint"
|
||||
"class" "class_origin" "clob" "close" "cluster" "coalesce" "cobol" "collate"
|
||||
"collation" "collation_catalog" "collation_name" "collation_schema" "collect"
|
||||
"column" "columns" "column_name" "command_function" "command_function_code"
|
||||
"comment" "comments" "commit" "committed" "concurrently" "condition"
|
||||
"condition_number" "configuration" "conflict" "connect" "connection"
|
||||
"connection_name" "constraint" "constraints" "constraint_catalog"
|
||||
"constraint_name" "constraint_schema" "constructor" "contains" "content"
|
||||
"continue" "control" "conversion" "convert" "copy" "corr" "corresponding" "cost"
|
||||
"count" "covar_pop" "covar_samp" "create" "cross" "csv" "cube" "cume_dist"
|
||||
"current" "current_catalog" "current_date" "current_default_transform_group"
|
||||
"current_path" "current_role" "current_row" "current_schema" "current_time"
|
||||
"current_timestamp" "current_transform_group_for_type" "current_user" "cursor"
|
||||
"cursor_name" "cycle" "data" "database" "datalink" "date"
|
||||
"datetime_interval_code" "datetime_interval_precision" "day" "db" "deallocate"
|
||||
"dec" "decimal" "declare" "default" "defaults" "deferrable" "deferred" "defined"
|
||||
"definer" "degree" "delete" "delimiter" "delimiters" "dense_rank" "depends"
|
||||
"depth" "deref" "derived" "desc" "describe" "descriptor" "detach"
|
||||
"deterministic" "diagnostics" "dictionary" "disable" "discard" "disconnect"
|
||||
"dispatch" "distinct" "dlnewcopy" "dlpreviouscopy" "dlurlcomplete"
|
||||
"dlurlcompleteonly" "dlurlcompletewrite" "dlurlpath" "dlurlpathonly"
|
||||
"dlurlpathwrite" "dlurlscheme" "dlurlserver" "dlvalue" "do" "document" "domain"
|
||||
"double" "drop" "dynamic" "dynamic_function" "dynamic_function_code" "each"
|
||||
"element" "else" "empty" "enable" "encoding" "encrypted" "end" "end-exec"
|
||||
"end_frame" "end_partition" "enforced" "enum" "equals" "escape" "event" "every"
|
||||
"except" "exception" "exclude" "excluding" "exclusive" "exec" "execute" "exists"
|
||||
"exp" "explain" "expression" "extension" "external" "extract" "false" "family"
|
||||
"fetch" "file" "filter" "final" "first" "first_value" "flag" "float" "floor"
|
||||
"following" "for" "force" "foreign" "fortran" "forward" "found" "frame_row"
|
||||
"free" "freeze" "from" "fs" "full" "function" "functions" "fusion" "g" "general"
|
||||
"generated" "get" "global" "go" "goto" "grant" "granted" "greatest" "group"
|
||||
"grouping" "groups" "handler" "having" "header" "hex" "hierarchy" "hold" "hour"
|
||||
"id" "identity" "if" "ignore" "ilike" "immediate" "immediately" "immutable"
|
||||
"implementation" "implicit" "import" "in" "include" "including" "increment"
|
||||
"indent" "index" "indexes" "indicator" "inherit" "inherits" "initially" "inline"
|
||||
"inner" "inout" "input" "insensitive" "insert" "instance" "instantiable"
|
||||
"instead" "int" "integer" "integrity" "intersect" "intersection" "interval"
|
||||
"into" "invoker" "is" "isnull" "isolation" "join" "k" "key" "key_member"
|
||||
"key_type" "label" "lag" "language" "large" "last" "last_value" "lateral" "lead"
|
||||
"leading" "leakproof" "least" "left" "length" "level" "library" "like"
|
||||
"like_regex" "limit" "link" "listen" "ln" "load" "local" "localtime"
|
||||
"localtimestamp" "location" "locator" "lock" "locked" "logged" "lower" "m" "map"
|
||||
"mapping" "match" "matched" "materialized" "max" "maxvalue" "max_cardinality"
|
||||
"member" "merge" "message_length" "message_octet_length" "message_text" "method"
|
||||
"min" "minute" "minvalue" "mod" "mode" "modifies" "module" "month" "more" "move"
|
||||
"multiset" "mumps" "name" "names" "namespace" "national" "natural" "nchar"
|
||||
"nclob" "nesting" "new" "next" "nfc" "nfd" "nfkc" "nfkd" "nil" "no" "none"
|
||||
"normalize" "normalized" "not" "nothing" "notify" "notnull" "nowait" "nth_value"
|
||||
"ntile" "null" "nullable" "nullif" "nulls" "number" "numeric" "object"
|
||||
"occurrences_regex" "octets" "octet_length" "of" "off" "offset" "oids" "old"
|
||||
"on" "only" "open" "operator" "option" "options" "or" "order" "ordering"
|
||||
"ordinality" "others" "out" "outer" "output" "over" "overlaps" "overlay"
|
||||
"overriding" "owned" "owner" "p" "pad" "parallel" "parameter" "parameter_mode"
|
||||
"parameter_name" "parameter_ordinal_position" "parameter_specific_catalog"
|
||||
"parameter_specific_name" "parameter_specific_schema" "parser" "partial"
|
||||
"partition" "pascal" "passing" "passthrough" "password" "path" "percent"
|
||||
"percentile_cont" "percentile_disc" "percent_rank" "period" "permission"
|
||||
"placing" "plans" "pli" "policy" "portion" "position" "position_regex" "power"
|
||||
"precedes" "preceding" "precision" "prepare" "prepared" "preserve" "primary"
|
||||
"prior" "privileges" "procedural" "procedure" "procedures" "program" "public"
|
||||
"publication" "quote" "range" "rank" "read" "reads" "real" "reassign" "recheck"
|
||||
"recovery" "recursive" "ref" "references" "referencing" "refresh" "regr_avgx"
|
||||
"regr_avgy" "regr_count" "regr_intercept" "regr_r2" "regr_slope" "regr_sxx"
|
||||
"regr_sxy" "regr_syy" "reindex" "relative" "release" "rename" "repeatable"
|
||||
"replace" "replica" "requiring" "reset" "respect" "restart" "restore" "restrict"
|
||||
"result" "return" "returned_cardinality" "returned_length"
|
||||
"returned_octet_length" "returned_sqlstate" "returning" "returns" "revoke"
|
||||
"right" "role" "rollback" "rollup" "routine" "routines" "routine_catalog"
|
||||
"routine_name" "routine_schema" "row" "rows" "row_count" "row_number" "rule"
|
||||
"savepoint" "scale" "schema" "schemas" "schema_name" "scope" "scope_catalog"
|
||||
"scope_name" "scope_schema" "scroll" "search" "second" "section" "security"
|
||||
"select" "selective" "self" "sensitive" "sequence" "sequences" "serializable"
|
||||
"server" "server_name" "session" "session_user" "set" "setof" "sets" "share"
|
||||
"show" "similar" "simple" "size" "skip" "smallint" "snapshot" "some" "source"
|
||||
"space" "specific" "specifictype" "specific_name" "sql" "sqlcode" "sqlerror"
|
||||
"sqlexception" "sqlstate" "sqlwarning" "sqrt" "stable" "standalone" "start"
|
||||
"state" "statement" "static" "statistics" "stddev_pop" "stddev_samp" "stdin"
|
||||
"stdout" "storage" "strict" "strip" "structure" "style" "subclass_origin"
|
||||
"submultiset" "subscription" "substring" "substring_regex" "succeeds" "sum"
|
||||
"symmetric" "sysid" "system" "system_time" "system_user" "t" "table" "tables"
|
||||
"tablesample" "tablespace" "table_name" "temp" "template" "temporary" "text"
|
||||
"then" "ties" "time" "timestamp" "timezone_hour" "timezone_minute" "to" "token"
|
||||
"top_level_count" "trailing" "transaction" "transactions_committed"
|
||||
"transactions_rolled_back" "transaction_active" "transform" "transforms"
|
||||
"translate" "translate_regex" "translation" "treat" "trigger" "trigger_catalog"
|
||||
"trigger_name" "trigger_schema" "trim" "trim_array" "true" "truncate" "trusted"
|
||||
"type" "types" "uescape" "unbounded" "uncommitted" "under" "unencrypted" "union"
|
||||
"unique" "unknown" "unlink" "unlisten" "unlogged" "unnamed" "unnest" "until"
|
||||
"untyped" "update" "upper" "uri" "usage" "user" "user_defined_type_catalog"
|
||||
"user_defined_type_code" "user_defined_type_name" "user_defined_type_schema"
|
||||
"using" "vacuum" "valid" "validate" "validator" "value" "values" "value_of"
|
||||
"varbinary" "varchar" "variadic" "varying" "var_pop" "var_samp" "verbose"
|
||||
"version" "versioning" "view" "views" "volatile" "when" "whenever" "where"
|
||||
"whitespace" "width_bucket" "window" "with" "within" "without" "work" "wrapper"
|
||||
"write" "xml" "xmlagg" "xmlattributes" "xmlbinary" "xmlcast" "xmlcomment"
|
||||
"xmlconcat" "xmldeclaration" "xmldocument" "xmlelement" "xmlexists" "xmlforest"
|
||||
"xmliterate" "xmlnamespaces" "xmlparse" "xmlpi" "xmlquery" "xmlroot" "xmlschema"
|
||||
"xmlserialize" "xmltable" "xmltext" "xmlvalidate" "year" "yes" "zone"))
|
||||
|
||||
;;; Company backend
|
||||
|
||||
(cl-defun company-postgresql/candidates (prefix conn)
|
||||
(-filter
|
||||
(apply-partially #'s-starts-with? prefix)
|
||||
(append (-map (lambda (s)
|
||||
(propertize s 'company-postgresql-annotation "table"))
|
||||
|
||||
(-map (lambda (s)
|
||||
(propertize s 'company-postgresql-annotation
|
||||
(format "%s.%s %s"
|
||||
(get-text-property 0 'table-name s)
|
||||
s
|
||||
(->
|
||||
(get-text-property 0 'data-type s)
|
||||
(->string)
|
||||
(upcase)))))
|
||||
(company-sql/list-columns conn))
|
||||
(-map (lambda (s)
|
||||
(propertize s 'company-postgresql-annotation "keyword"))
|
||||
company-postgresql/keywords)))))
|
||||
|
||||
(defun company-postgresql (command &optional arg &rest _)
|
||||
(interactive (list 'interactive))
|
||||
(cl-case command
|
||||
(interactive (company-begin-backend 'company-postgresql))
|
||||
(init (company-sql/connect))
|
||||
(prefix (company-grab-symbol))
|
||||
(annotation
|
||||
(get-text-property 0 'company-postgresql-annotation arg))
|
||||
(candidates (company-postgresql/candidates
|
||||
arg
|
||||
(company-sql/connect)))
|
||||
(duplicates t)
|
||||
(ignore-case t)))
|
||||
|
||||
;;; org-babel company sql
|
||||
|
||||
(defvar-local org-company-sql/connections
|
||||
())
|
||||
|
||||
(defun org-company-sql/connect (conn-params)
|
||||
(or (alist-get-equal conn-params org-company-sql/connections)
|
||||
(let ((conn (apply 'emacsql-psql conn-params)))
|
||||
(add-to-list 'org-company-sql/connections (cons conn-params conn))
|
||||
conn)))
|
||||
|
||||
(defun org-company-sql/in-sql-source-block-p ()
|
||||
(let ((org-elt (org-element-at-point)))
|
||||
(and (eq 'src-block (car org-elt))
|
||||
(equal "sql" (plist-get (cadr org-elt)
|
||||
:language)))))
|
||||
|
||||
(defun org-company-sql/parse-cmdline (cmdline)
|
||||
(let* ((lexed (s-split (rx (one-or-more blank)) cmdline))
|
||||
(go (lambda (state tokens)
|
||||
(if (null tokens) ()
|
||||
(let ((token (car tokens))
|
||||
(tokens (cdr tokens)))
|
||||
(if (null state)
|
||||
(if (s-starts-with? "-" token)
|
||||
(funcall go token tokens)
|
||||
(cons token (funcall go state tokens)))
|
||||
(cons (cons state token) ; ("-h" . "localhost")
|
||||
(funcall go nil tokens)))))))
|
||||
(opts (funcall go nil lexed)))
|
||||
opts))
|
||||
|
||||
(defun org-company-sql/source-block-conn-params ()
|
||||
(let* ((block-info (org-babel-get-src-block-info))
|
||||
(params (caddr block-info))
|
||||
(cmdline (alist-get :cmdline params))
|
||||
(parsed (org-company-sql/parse-cmdline cmdline))
|
||||
(opts (-filter #'listp parsed))
|
||||
(positional (-filter #'stringp parsed))
|
||||
(host (alist-get-equal "-h" opts))
|
||||
(port (or (alist-get-equal "-p" opts)
|
||||
"5432"))
|
||||
(dbname (or (alist-get-equal "-d" opts)
|
||||
(car positional)))
|
||||
(username (or (alist-get-equal "-U" opts)
|
||||
(cadr positional))))
|
||||
(list dbname
|
||||
:hostname host
|
||||
:username username
|
||||
:port port)))
|
||||
|
||||
(defun org-company-sql/connection-for-source-block ()
|
||||
(org-company-sql/connect
|
||||
(org-company-sql/source-block-conn-params)))
|
||||
|
||||
|
||||
(defun company-ob-postgresql (command &optional arg &rest _)
|
||||
(interactive (list 'interactive))
|
||||
(cl-case command
|
||||
(interactive (company-begin-backend 'company-ob-postgresql))
|
||||
(prefix (and (org-company-sql/in-sql-source-block-p)
|
||||
(company-grab-symbol)))
|
||||
(annotation (get-text-property 0 'company-postgresql-annotation arg))
|
||||
(candidates
|
||||
(company-postgresql/candidates
|
||||
arg
|
||||
(org-company-sql/connection-for-source-block)))
|
||||
(duplicates t)
|
||||
(ignore-case t)))
|
||||
|
||||
;;;
|
||||
|
||||
(provide 'company-sql)
|
||||
1109
users/grfn/emacs.d/config.el
Normal file
1109
users/grfn/emacs.d/config.el
Normal file
File diff suppressed because it is too large
Load diff
39
users/grfn/emacs.d/cpp.el
Normal file
39
users/grfn/emacs.d/cpp.el
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
;;; -*- lexical-binding: t; -*-
|
||||
|
||||
|
||||
(load! "google-c-style")
|
||||
|
||||
(after! flycheck
|
||||
(add-to-list 'flycheck-disabled-checkers 'c/c++-gcc)
|
||||
(add-to-list 'flycheck-disabled-checkers 'c/c++-clang))
|
||||
|
||||
(defun +grfn/cpp-setup ()
|
||||
(when (s-starts-with?
|
||||
"/home/grfn/code/depot/third_party/nix"
|
||||
(buffer-file-name))
|
||||
(setq lsp-clients-clangd-executable "/home/grfn/code/depot/users/grfn/emacs.d/nix-clangd.sh"
|
||||
lsp-clients-clangd-args nil)
|
||||
(google-set-c-style)
|
||||
(lsp)
|
||||
(add-to-list 'flycheck-disabled-checkers 'c/c++-gcc)
|
||||
(add-to-list 'flycheck-disabled-checkers 'c/c++-clang)))
|
||||
|
||||
(add-hook 'c++-mode-hook #'+grfn/cpp-setup)
|
||||
|
||||
(use-package! protobuf-mode)
|
||||
|
||||
(use-package! clang-format+
|
||||
:config
|
||||
(add-hook 'c-mode-common-hook #'clang-format+-mode))
|
||||
|
||||
(map!
|
||||
(:map c++-mode-map
|
||||
:leader
|
||||
(:n "/ i" #'counsel-semantic-or-imenu)))
|
||||
|
||||
(comment
|
||||
(setq
|
||||
lsp-clients-clangd-executable
|
||||
"/home/grfn/code/depot/third_party/nix/clangd.sh"
|
||||
lsp-clients-clangd-args nil)
|
||||
)
|
||||
28
users/grfn/emacs.d/email.el
Normal file
28
users/grfn/emacs.d/email.el
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
;;; -*- lexical-binding: t; -*-
|
||||
|
||||
(after! notmuch
|
||||
(setq notmuch-saved-searches
|
||||
'((:name "inbox" :query "tag:inbox tag:important not tag:trash" :key "i")
|
||||
(:name "flagged" :query "tag:flagged" :key "f")
|
||||
(:name "sent" :query "tag:sent" :key "s")
|
||||
(:name "drafts" :query "tag:draft" :key "d")
|
||||
|
||||
(:name "work" :query "tag:inbox and tag:important and path:work/**"
|
||||
:key "w")
|
||||
(:name "personal" :query "tag:inbox and tag:important and path:personal/**"
|
||||
:key "p"))
|
||||
message-send-mail-function 'message-send-mail-with-sendmail)
|
||||
|
||||
(add-hook! notmuch-message-mode-hook #'notmuch-company-setup))
|
||||
|
||||
(setq notmuch-saved-searches
|
||||
'((:name "inbox" :query "tag:inbox tag:important not tag:trash" :key "i")
|
||||
(:name "flagged" :query "tag:flagged" :key "f")
|
||||
(:name "sent" :query "tag:sent" :key "s")
|
||||
(:name "drafts" :query "tag:draft" :key "d")
|
||||
|
||||
(:name "work" :query "tag:inbox and tag:important and path:work/**"
|
||||
:key "w")
|
||||
(:name "personal" :query "tag:inbox and tag:important and path:personal/**"
|
||||
:key "p"))
|
||||
message-send-mail-function 'message-send-mail-with-sendmail)
|
||||
99
users/grfn/emacs.d/github-org.el
Normal file
99
users/grfn/emacs.d/github-org.el
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
;;; -*- lexical-binding: t; -*-
|
||||
|
||||
(require 'ghub)
|
||||
|
||||
(defun grfn/alist->plist (alist)
|
||||
(->> alist
|
||||
(-mapcat (lambda (pair)
|
||||
(list (intern (concat ":" (symbol-name (car pair))))
|
||||
(cdr pair))))))
|
||||
|
||||
;;;
|
||||
|
||||
(cl-defstruct pull-request url number title author repository)
|
||||
|
||||
(defun grfn/query-pulls (query)
|
||||
(let ((resp (ghub-graphql "query reviewRequests($query: String!) {
|
||||
reviewRequests: search(
|
||||
type:ISSUE,
|
||||
query: $query,
|
||||
first: 100
|
||||
) {
|
||||
issueCount
|
||||
nodes {
|
||||
... on PullRequest {
|
||||
url
|
||||
number
|
||||
title
|
||||
author {
|
||||
login
|
||||
... on User { name }
|
||||
}
|
||||
repository {
|
||||
name
|
||||
owner { login }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}" `((query . ,query)))))
|
||||
(->> resp
|
||||
(alist-get 'data)
|
||||
(alist-get 'reviewRequests)
|
||||
(alist-get 'nodes)
|
||||
(-map
|
||||
(lambda (pr)
|
||||
(apply
|
||||
#'make-pull-request
|
||||
(grfn/alist->plist pr)))))))
|
||||
|
||||
(defun grfn/requested-changes ())
|
||||
|
||||
(defun grfn/pull-request->org-headline (format-string level pr)
|
||||
(check-type format-string string)
|
||||
(check-type level integer)
|
||||
(check-type pr pull-request)
|
||||
(s-format (concat (make-string level ?*) " " format-string)
|
||||
'aget
|
||||
`((author . ,(or (->> pr (pull-request-author) (alist-get 'name))
|
||||
"no author"))
|
||||
(owner . ,(->> pr (pull-request-repository)
|
||||
(alist-get 'owner)
|
||||
(alist-get 'login)))
|
||||
(repo . ,(->> pr (pull-request-repository) (alist-get 'name)))
|
||||
(pr-link . ,(org-make-link-string
|
||||
(pull-request-url pr)
|
||||
(pull-request-title pr)))
|
||||
(today . ,(format-time-string "%Y-%m-%d %a")))))
|
||||
|
||||
(defun grfn/org-headlines-from-review-requests (level)
|
||||
"Create org-mode headlines at LEVEL from all review-requested PRs on Github"
|
||||
(interactive "*nLevel: ")
|
||||
(let* ((prs (grfn/query-pulls
|
||||
"is:open is:pr review-requested:glittershark archived:false"))
|
||||
(text (mapconcat
|
||||
(apply-partially
|
||||
#'grfn/pull-request->org-headline
|
||||
"TODO Review ${author}'s PR on ${owner}/${repo}: ${pr-link} :pr:
|
||||
SCHEDULED: <${today}>"
|
||||
level) prs "\n")))
|
||||
(save-mark-and-excursion
|
||||
(insert text))
|
||||
(org-align-tags 't)))
|
||||
|
||||
(defun grfn/org-headlines-from-requested-changes (level)
|
||||
"Create org-mode headlines at LEVEL from all PRs with changes requested
|
||||
on Github"
|
||||
(interactive "*nLevel: ")
|
||||
(let* ((prs (grfn/query-pulls
|
||||
(concat "is:pr is:open author:glittershark archived:false "
|
||||
"sort:updated-desc review:changes-requested")))
|
||||
(text (mapconcat
|
||||
(apply-partially
|
||||
#'grfn/pull-request->org-headline
|
||||
"TODO Address review comments on ${pr-link} :pr:
|
||||
SCHEDULED: <${today}>"
|
||||
level) prs "\n")))
|
||||
(save-mark-and-excursion
|
||||
(insert text))
|
||||
(org-align-tags 't)))
|
||||
151
users/grfn/emacs.d/google-c-style.el
Normal file
151
users/grfn/emacs.d/google-c-style.el
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
;;; google-c-style.el --- Google's C/C++ style for c-mode
|
||||
|
||||
;; Keywords: c, tools
|
||||
|
||||
;; google-c-style.el is Copyright (C) 2008 Google Inc. All Rights Reserved.
|
||||
;;
|
||||
;; It is free software; you can redistribute it and/or modify it under the
|
||||
;; terms of either:
|
||||
;;
|
||||
;; a) the GNU General Public License as published by the Free Software
|
||||
;; Foundation; either version 1, or (at your option) any later version, or
|
||||
;;
|
||||
;; b) the "Artistic License".
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Provides the google C/C++ coding style. You may wish to add
|
||||
;; `google-set-c-style' to your `c-mode-common-hook' after requiring this
|
||||
;; file. For example:
|
||||
;;
|
||||
;; (add-hook 'c-mode-common-hook 'google-set-c-style)
|
||||
;;
|
||||
;; If you want the RETURN key to go to the next line and space over
|
||||
;; to the right place, add this to your .emacs right after the load-file:
|
||||
;;
|
||||
;; (add-hook 'c-mode-common-hook 'google-make-newline-indent)
|
||||
|
||||
;;; Code:
|
||||
|
||||
;; For some reason 1) c-backward-syntactic-ws is a macro and 2) under Emacs 22
|
||||
;; bytecode cannot call (unexpanded) macros at run time:
|
||||
(eval-when-compile (require 'cc-defs))
|
||||
|
||||
;; Wrapper function needed for Emacs 21 and XEmacs (Emacs 22 offers the more
|
||||
;; elegant solution of composing a list of lineup functions or quantities with
|
||||
;; operators such as "add")
|
||||
(defun google-c-lineup-expression-plus-4 (langelem)
|
||||
"Indents to the beginning of the current C expression plus 4 spaces.
|
||||
|
||||
This implements title \"Function Declarations and Definitions\"
|
||||
of the Google C++ Style Guide for the case where the previous
|
||||
line ends with an open parenthese.
|
||||
|
||||
\"Current C expression\", as per the Google Style Guide and as
|
||||
clarified by subsequent discussions, means the whole expression
|
||||
regardless of the number of nested parentheses, but excluding
|
||||
non-expression material such as \"if(\" and \"for(\" control
|
||||
structures.
|
||||
|
||||
Suitable for inclusion in `c-offsets-alist'."
|
||||
(save-excursion
|
||||
(back-to-indentation)
|
||||
;; Go to beginning of *previous* line:
|
||||
(c-backward-syntactic-ws)
|
||||
(back-to-indentation)
|
||||
(cond
|
||||
;; We are making a reasonable assumption that if there is a control
|
||||
;; structure to indent past, it has to be at the beginning of the line.
|
||||
((looking-at "\\(\\(if\\|for\\|while\\)\\s *(\\)")
|
||||
(goto-char (match-end 1)))
|
||||
;; For constructor initializer lists, the reference point for line-up is
|
||||
;; the token after the initial colon.
|
||||
((looking-at ":\\s *")
|
||||
(goto-char (match-end 0))))
|
||||
(vector (+ 4 (current-column)))))
|
||||
|
||||
;;;###autoload
|
||||
(defconst google-c-style
|
||||
`((c-recognize-knr-p . nil)
|
||||
(c-enable-xemacs-performance-kludge-p . t) ; speed up indentation in XEmacs
|
||||
(c-basic-offset . 2)
|
||||
(indent-tabs-mode . nil)
|
||||
(c-comment-only-line-offset . 0)
|
||||
(c-hanging-braces-alist . ((defun-open after)
|
||||
(defun-close before after)
|
||||
(class-open after)
|
||||
(class-close before after)
|
||||
(inexpr-class-open after)
|
||||
(inexpr-class-close before)
|
||||
(namespace-open after)
|
||||
(inline-open after)
|
||||
(inline-close before after)
|
||||
(block-open after)
|
||||
(block-close . c-snug-do-while)
|
||||
(extern-lang-open after)
|
||||
(extern-lang-close after)
|
||||
(statement-case-open after)
|
||||
(substatement-open after)))
|
||||
(c-hanging-colons-alist . ((case-label)
|
||||
(label after)
|
||||
(access-label after)
|
||||
(member-init-intro before)
|
||||
(inher-intro)))
|
||||
(c-hanging-semi&comma-criteria
|
||||
. (c-semi&comma-no-newlines-for-oneline-inliners
|
||||
c-semi&comma-inside-parenlist
|
||||
c-semi&comma-no-newlines-before-nonblanks))
|
||||
(c-indent-comments-syntactically-p . t)
|
||||
(comment-column . 40)
|
||||
(c-indent-comment-alist . ((other . (space . 2))))
|
||||
(c-cleanup-list . (brace-else-brace
|
||||
brace-elseif-brace
|
||||
brace-catch-brace
|
||||
empty-defun-braces
|
||||
defun-close-semi
|
||||
list-close-comma
|
||||
scope-operator))
|
||||
(c-offsets-alist . ((arglist-intro google-c-lineup-expression-plus-4)
|
||||
(func-decl-cont . ++)
|
||||
(member-init-intro . ++)
|
||||
(inher-intro . ++)
|
||||
(comment-intro . 0)
|
||||
(arglist-close . c-lineup-arglist)
|
||||
(topmost-intro . 0)
|
||||
(block-open . 0)
|
||||
(inline-open . 0)
|
||||
(substatement-open . 0)
|
||||
(statement-cont
|
||||
.
|
||||
(,(when (fboundp 'c-no-indent-after-java-annotations)
|
||||
'c-no-indent-after-java-annotations)
|
||||
,(when (fboundp 'c-lineup-assignments)
|
||||
'c-lineup-assignments)
|
||||
++))
|
||||
(label . /)
|
||||
(case-label . +)
|
||||
(statement-case-open . +)
|
||||
(statement-case-intro . +) ; case w/o {
|
||||
(access-label . /)
|
||||
(innamespace . 0))))
|
||||
"Google C/C++ Programming Style.")
|
||||
|
||||
;;;###autoload
|
||||
(defun google-set-c-style ()
|
||||
"Set the current buffer's c-style to Google C/C++ Programming
|
||||
Style. Meant to be added to `c-mode-common-hook'."
|
||||
(interactive)
|
||||
(make-local-variable 'c-tab-always-indent)
|
||||
(setq c-tab-always-indent t)
|
||||
(c-add-style "Google" google-c-style t))
|
||||
|
||||
;;;###autoload
|
||||
(defun google-make-newline-indent ()
|
||||
"Sets up preferred newline behavior. Not set by default. Meant
|
||||
to be added to `c-mode-common-hook'."
|
||||
(interactive)
|
||||
(define-key c-mode-base-map "\C-m" 'newline-and-indent)
|
||||
(define-key c-mode-base-map [ret] 'newline-and-indent))
|
||||
|
||||
(provide 'google-c-style)
|
||||
;;; google-c-style.el ends here
|
||||
128
users/grfn/emacs.d/grid.el
Normal file
128
users/grfn/emacs.d/grid.el
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
;;; -*- lexical-binding: t; -*-
|
||||
|
||||
(require 's)
|
||||
|
||||
(defun grfn/all-match-groups (s)
|
||||
(loop for n from 1
|
||||
for x = (match-string n s)
|
||||
while x
|
||||
collect x))
|
||||
|
||||
(defun projectile-grid-ff (path &optional ask)
|
||||
"Call `find-file' function on PATH when it is not nil and the file exists.
|
||||
If file does not exist and ASK in not nil it will ask user to proceed."
|
||||
(if (or (and path (file-exists-p path))
|
||||
(and ask (yes-or-no-p
|
||||
(s-lex-format
|
||||
"File does not exists. Create a new buffer ${path} ?"))))
|
||||
(find-file path)))
|
||||
|
||||
(defun projectile-grid-goto-file (filepath &optional ask)
|
||||
"Find FILEPATH after expanding root. ASK is passed straight to `projectile-grid-ff'."
|
||||
(projectile-grid-ff (projectile-expand-root filepath) ask))
|
||||
|
||||
(defun projectile-grid-choices (ds)
|
||||
"Uses `projectile-dir-files' function to find files in directories.
|
||||
The DIRS is list of lists consisting of a directory path and regexp to filter files from that directory.
|
||||
Optional third element can be present in the DS list. The third element will be a prefix to be placed before
|
||||
the filename in the resulting choice.
|
||||
Returns a hash table with keys being short names (choices) and values being relative paths to the files."
|
||||
(loop with hash = (make-hash-table :test 'equal)
|
||||
for (dir re prefix) in ds do
|
||||
(loop for file in (projectile-dir-files (projectile-expand-root dir)) do
|
||||
(when (string-match re file)
|
||||
(puthash
|
||||
(concat (or prefix "")
|
||||
(s-join "/" (grfn/all-match-groups file)))
|
||||
(concat dir file)
|
||||
hash)))
|
||||
finally return hash))
|
||||
|
||||
(defmacro projectile-grid-find-resource (prompt dirs &optional newfile-template)
|
||||
"Presents files from DIRS with PROMPT to the user using `projectile-completing-read'.
|
||||
If users chooses a non existant file and NEWFILE-TEMPLATE is not nil
|
||||
it will use that variable to interpolate the name for the new file.
|
||||
NEWFILE-TEMPLATE will be the argument for `s-lex-format'.
|
||||
The bound variable is \"filename\"."
|
||||
`(lexical-let ((choices (projectile-grid-choices ,dirs)))
|
||||
(projectile-completing-read
|
||||
,prompt
|
||||
(hash-table-keys choices)
|
||||
:action
|
||||
(lambda (c)
|
||||
(let* ((filepath (gethash c choices))
|
||||
(filename c)) ;; so `s-lex-format' can interpolate FILENAME
|
||||
(if filepath
|
||||
(projectile-grid-goto-file filepath)
|
||||
(when-let ((newfile-template ,newfile-template))
|
||||
(projectile-grid-goto-file
|
||||
(funcall newfile-template filepath)
|
||||
;; (cond
|
||||
;; ((functionp newfile-template) (funcall newfile-template filepath))
|
||||
;; ((stringp newfile-template) (s-lex-format newfile-template)))
|
||||
t))))))))
|
||||
|
||||
(defun projectile-grid-find-model ()
|
||||
"Find a model."
|
||||
(interactive)
|
||||
(projectile-grid-find-resource
|
||||
"model: "
|
||||
'(("python/urbint_lib/models/"
|
||||
"\\(.+\\)\\.py$")
|
||||
("python/urbint_lib/"
|
||||
"\\(.+\\)/models/\\(.+\\).py$"))
|
||||
(lambda (filename)
|
||||
(pcase (s-split "/" filename)
|
||||
(`(,model)
|
||||
(s-lex-format "python/urbint_lib/models/${model}.py"))
|
||||
(`(,app ,model)
|
||||
(s-lex-format "python/urbint_lib/${app}/models/${model}.py"))))))
|
||||
|
||||
(defun projectile-grid-find-repository ()
|
||||
"Find a repository."
|
||||
(interactive)
|
||||
(projectile-grid-find-resource
|
||||
"repository: "
|
||||
'(("python/urbint_lib/repositories/"
|
||||
"\\(.+\\)\\.py$")
|
||||
("python/urbint_lib/"
|
||||
"\\(.+\\)/repositories/\\(.+\\).py$"))
|
||||
(lambda (filename)
|
||||
(pcase (s-split "/" filename)
|
||||
(`(,repository)
|
||||
(s-lex-format "python/urbint_lib/repositories/${repository}.py"))
|
||||
(`(,app ,repository)
|
||||
(s-lex-format "python/urbint_lib/${app}/repositories/${repository}.py"))))))
|
||||
|
||||
(defun projectile-grid-find-controller ()
|
||||
"Find a controller."
|
||||
(interactive)
|
||||
(projectile-grid-find-resource
|
||||
"controller: "
|
||||
'(("backend/src/grid/api/controllers/"
|
||||
"\\(.+\\)\\.py$")
|
||||
("backend/src/grid/api/apps/"
|
||||
"\\(.+\\)/controllers/\\(.+\\).py$"))
|
||||
(lambda (filename)
|
||||
(pcase (s-split "/" filename)
|
||||
(`(,controller)
|
||||
(s-lex-format "backend/src/grid/api/controllers/${controller}.py"))
|
||||
(`(,app ,controller)
|
||||
(s-lex-format "backend/src/grid/api/apps/${app}/controllers/${controller}.py"))))))
|
||||
|
||||
(setq projectile-grid-mode-map
|
||||
(let ((map (make-keymap)))
|
||||
(map!
|
||||
(:map map
|
||||
(:leader
|
||||
(:desc "Edit..." :prefix "e"
|
||||
:desc "Model" :n "m" #'projectile-grid-find-model
|
||||
:desc "Controller" :n "c" #'projectile-grid-find-controller
|
||||
:desc "Repository" :n "r" #'projectile-grid-find-repository))))
|
||||
map))
|
||||
|
||||
(define-minor-mode projectile-grid-mode
|
||||
"Minor mode for finding files in GRID"
|
||||
:init-value nil
|
||||
:lighter " GRID"
|
||||
:keymap projectile-grid-mode-map)
|
||||
234
users/grfn/emacs.d/init.el
Normal file
234
users/grfn/emacs.d/init.el
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
;;; -*- lexical-binding: t; -*-
|
||||
|
||||
(doom! :completion
|
||||
company ; the ultimate code completion backend
|
||||
;;helm ; the *other* search engine for love and life
|
||||
;;ido ; the other *other* search engine...
|
||||
ivy ; a search engine for love and life
|
||||
|
||||
:ui
|
||||
;;deft ; notational velocity for Emacs
|
||||
doom ; what makes DOOM look the way it does
|
||||
;doom-dashboard ; a nifty splash screen for Emacs
|
||||
doom-quit ; DOOM quit-message prompts when you quit Emacs
|
||||
;fill-column ; a `fill-column' indicator
|
||||
hl-todo ; highlight TODO/FIXME/NOTE tags
|
||||
;;indent-guides ; highlighted indent columns
|
||||
modeline ; snazzy, Atom-inspired modeline, plus API
|
||||
nav-flash ; blink the current line after jumping
|
||||
;;neotree ; a project drawer, like NERDTree for vim
|
||||
ophints ; highlight the region an operation acts on
|
||||
(popup ; tame sudden yet inevitable temporary windows
|
||||
+all ; catch all popups that start with an asterix
|
||||
+defaults) ; default popup rules
|
||||
ligatures ; replace bits of code with pretty symbols
|
||||
;;tabbar ; FIXME an (incomplete) tab bar for Emacs
|
||||
;;treemacs ; a project drawer, like neotree but cooler
|
||||
unicode ; extended unicode support for various languages
|
||||
vc-gutter ; vcs diff in the fringe
|
||||
vi-tilde-fringe ; fringe tildes to mark beyond EOB
|
||||
window-select ; visually switch windows
|
||||
workspaces ; tab emulation, persistence & separate workspaces
|
||||
|
||||
:editor
|
||||
(evil +everywhere); come to the dark side, we have cookies
|
||||
file-templates ; auto-snippets for empty files
|
||||
fold ; (nigh) universal code folding
|
||||
;;(format +onsave) ; automated prettiness
|
||||
;;lispy ; vim for lisp, for people who dont like vim
|
||||
multiple-cursors ; editing in many places at once
|
||||
;;parinfer ; turn lisp into python, sort of
|
||||
rotate-text ; cycle region at point between text candidates
|
||||
snippets ; my elves. They type so I don't have to
|
||||
word-wrap
|
||||
|
||||
:emacs
|
||||
(dired ; making dired pretty [functional]
|
||||
;;+ranger ; bringing the goodness of ranger to dired
|
||||
;;+icons ; colorful icons for dired-mode
|
||||
)
|
||||
electric ; smarter, keyword-based electric-indent
|
||||
;;eshell ; a consistent, cross-platform shell (WIP)
|
||||
;;term ; terminals in Emacs
|
||||
vc ; version-control and Emacs, sitting in a tree
|
||||
(undo +tree)
|
||||
|
||||
:tools
|
||||
;;ansible
|
||||
;;debugger ; FIXME stepping through code, to help you add bugs
|
||||
;;direnv
|
||||
docker
|
||||
;;editorconfig ; let someone else argue about tabs vs spaces
|
||||
;; ein ; tame Jupyter notebooks with emacs
|
||||
(eval +overlay) ; run code, run (also, repls)
|
||||
gist ; interacting with github gists
|
||||
(lookup ; helps you navigate your code and documentation
|
||||
+docsets) ; ...or in Dash docsets locally
|
||||
lsp
|
||||
;;macos ; MacOS-specific commands
|
||||
magit ; a git porcelain for Emacs
|
||||
make ; run make tasks from Emacs
|
||||
pass ; password manager for nerds
|
||||
pdf ; pdf enhancements
|
||||
;;prodigy ; FIXME managing external services & code builders
|
||||
;;rgb ; creating color strings
|
||||
;;terraform ; infrastructure as code
|
||||
;;tmux ; an API for interacting with tmux
|
||||
;;upload ; map local to remote projects via ssh/ftp
|
||||
;;wakatime
|
||||
;;vterm ; another terminals in Emacs
|
||||
|
||||
:checkers
|
||||
syntax ; tasing you for every semicolon you forget
|
||||
; spell ; tasing you for misspelling mispelling
|
||||
|
||||
:lang
|
||||
agda ; types of types of types of types...
|
||||
;;assembly ; assembly for fun or debugging
|
||||
cc ; C/C++/Obj-C madness
|
||||
clojure ; java with a lisp
|
||||
common-lisp ; if you've seen one lisp, you've seen them all
|
||||
; coq ; proofs-as-programs
|
||||
;;crystal ; ruby at the speed of c
|
||||
;;csharp ; unity, .NET, and mono shenanigans
|
||||
data ; config/data formats
|
||||
erlang ; an elegant language for a more civilized age
|
||||
elixir ; erlang done right
|
||||
;;elm ; care for a cup of TEA?
|
||||
emacs-lisp ; drown in parentheses
|
||||
;;ess ; emacs speaks statistics
|
||||
;;go ; the hipster dialect
|
||||
;; (haskell +intero) ; a language that's lazier than I am
|
||||
haskell ; a language that's lazier than I am
|
||||
;;hy ; readability of scheme w/ speed of python
|
||||
idris ;
|
||||
;;(java +meghanada) ; the poster child for carpal tunnel syndrome
|
||||
javascript ; all(hope(abandon(ye(who(enter(here))))))
|
||||
julia ; a better, faster MATLAB
|
||||
;;kotlin ; a better, slicker Java(Script)
|
||||
latex ; writing papers in Emacs has never been so fun
|
||||
;;ledger ; an accounting system in Emacs
|
||||
lua ; one-based indices? one-based indices
|
||||
markdown ; writing docs for people to ignore
|
||||
;;nim ; python + lisp at the speed of c
|
||||
nix ; I hereby declare "nix geht mehr!"
|
||||
;;ocaml ; an objective camel
|
||||
(org ; organize your plain life in plain text
|
||||
+dragndrop ; drag & drop files/images into org buffers
|
||||
+attach ; custom attachment system
|
||||
+babel ; running code in org
|
||||
+capture ; org-capture in and outside of Emacs
|
||||
+export ; Exporting org to whatever you want
|
||||
;; +habit ; Keep track of your habits
|
||||
+present ; Emacs for presentations
|
||||
+pretty
|
||||
+brain
|
||||
+protocol) ; Support for org-protocol:// links
|
||||
;;perl ; write code no one else can comprehend
|
||||
;;php ; perl's insecure younger brother
|
||||
;;plantuml ; diagrams for confusing people more
|
||||
purescript ; javascript, but functional
|
||||
(python +lsp) ; beautiful is better than ugly
|
||||
;;qt ; the 'cutest' gui framework ever
|
||||
racket ; a DSL for DSLs
|
||||
rest ; Emacs as a REST client
|
||||
;;ruby ; 1.step do {|i| p "Ruby is #{i.even? ? 'love' : 'life'}"}
|
||||
rust ; Fe2O3.unwrap().unwrap().unwrap().unwrap()
|
||||
;;scala ; java, but good
|
||||
(sh +fish) ; she sells (ba|z|fi)sh shells on the C xor
|
||||
;;solidity ; do you need a blockchain? No.
|
||||
;;swift ; who asked for emoji variables?
|
||||
;;terra ; Earth and Moon in alignment for performance.
|
||||
;;web ; the tubes
|
||||
;;vala ; GObjective-C
|
||||
|
||||
;; Applications are complex and opinionated modules that transform Emacs
|
||||
;; toward a specific purpose. They may have additional dependencies and
|
||||
;; should be loaded late.
|
||||
:app
|
||||
;;(email +gmail) ; emacs as an email client
|
||||
irc ; how neckbeards socialize
|
||||
;;(rss +org) ; emacs as an RSS reader
|
||||
twitter ; twitter client https://twitter.com/vnought
|
||||
;;(write ; emacs as a word processor (latex + org + markdown)
|
||||
;; +wordnut ; wordnet (wn) search
|
||||
;; +langtool) ; a proofreader (grammar/style check) for Emacs
|
||||
|
||||
:email
|
||||
;; (mu4e +gmail)
|
||||
notmuch
|
||||
|
||||
:collab
|
||||
;;floobits ; peer programming for a price
|
||||
;;impatient-mode ; show off code over HTTP
|
||||
|
||||
:config
|
||||
;; For literate config users. This will tangle+compile a config.org
|
||||
;; literate config in your `doom-private-dir' whenever it changes.
|
||||
;;literate
|
||||
|
||||
;; The default module sets reasonable defaults for Emacs. It also
|
||||
;; provides a Spacemacs-inspired keybinding scheme and a smartparens
|
||||
;; config. Use it as a reference for your own modules.
|
||||
(default +bindings +smartparens))
|
||||
(custom-set-variables
|
||||
;; custom-set-variables was added by Custom.
|
||||
;; If you edit it by hand, you could mess it up, so be careful.
|
||||
;; Your init file should contain only one such instance.
|
||||
;; If there is more than one, they won't work right.
|
||||
'(doom-big-font-mode nil)
|
||||
'(flycheck-javascript-flow-args nil)
|
||||
'(org-agenda-files
|
||||
'("/home/griffin/notes/personal.org" "/home/griffin/notes/2020-01-27-data-pipeline-deploy-mismatch.org" "/home/griffin/notes/architecture.org" "/home/griffin/notes/cooking.org" "/home/griffin/notes/culture-survey.org" "/home/griffin/notes/dir-structure.org" "/home/griffin/notes/dnd.org" "/home/griffin/notes/inbox.org" "/home/griffin/notes/misc-todo.org" "/home/griffin/notes/nix-talk.org" "/home/griffin/notes/notes.org" "/home/griffin/notes/one-on-one.org" "/home/griffin/notes/work.org" "/home/griffin/notes/xanthous.org" "/home/griffin/notes/xgboost.org"))
|
||||
'(safe-local-variable-values
|
||||
'((intero-stack-yaml . "/home/griffin/code/mlem/stack.yaml")
|
||||
(elisp-lint-indent-specs
|
||||
(if-let* . 2)
|
||||
(when-let* . 1)
|
||||
(let* . defun)
|
||||
(nrepl-dbind-response . 2)
|
||||
(cider-save-marker . 1)
|
||||
(cider-propertize-region . 1)
|
||||
(cider-map-repls . 1)
|
||||
(cider--jack-in . 1)
|
||||
(cider--make-result-overlay . 1)
|
||||
(insert-label . defun)
|
||||
(insert-align-label . defun)
|
||||
(insert-rect . defun)
|
||||
(cl-defun . 2)
|
||||
(with-parsed-tramp-file-name . 2)
|
||||
(thread-first . 1)
|
||||
(thread-last . 1))
|
||||
(checkdoc-package-keywords-flag)
|
||||
(cider-jack-in-default . "shadow-cljs")
|
||||
(projectile-project-root . "/home/griffin/code/urb/grid/backend/src")
|
||||
(python-pytest-executable . "/home/griffin/code/urb/grid/backend/src/.venv/bin/pytest"))))
|
||||
(custom-set-faces
|
||||
;; custom-set-faces was added by Custom.
|
||||
;; If you edit it by hand, you could mess it up, so be careful.
|
||||
;; Your init file should contain only one such instance.
|
||||
;; If there is more than one, they won't work right.
|
||||
'(default ((((class color) (min-colors 89)) (:foreground "#657b83" :background "#fdf6e3"))))
|
||||
'(agda2-highlight-bound-variable-face ((t nil)))
|
||||
'(agda2-highlight-coinductive-constructor-face ((t (:foreground "#b58900"))))
|
||||
'(agda2-highlight-datatype-face ((t (:foreground "#268bd2"))))
|
||||
'(agda2-highlight-dotted-face ((t nil)))
|
||||
'(agda2-highlight-error-face ((t (:foreground "#dc322f" :underline t))))
|
||||
'(agda2-highlight-field-face ((t (:foreground "#dc322f"))))
|
||||
'(agda2-highlight-function-face ((t (:foreground "#268bd2"))))
|
||||
'(agda2-highlight-incomplete-pattern-face ((t (:background "#cb4b16" :foreground "#002b36"))))
|
||||
'(agda2-highlight-inductive-constructor-face ((t (:foreground "#859900"))))
|
||||
'(agda2-highlight-keyword-face ((t (:foreground "#859900"))))
|
||||
'(agda2-highlight-module-face ((t (:foreground "#b58900"))))
|
||||
'(agda2-highlight-number-face ((t (:foreground "#6c71c4"))))
|
||||
'(agda2-highlight-operator-face ((t nil)))
|
||||
'(agda2-highlight-postulate-face ((t (:foreground "#268bd2"))))
|
||||
'(agda2-highlight-primitive-face ((t (:foreground "#268bd2"))))
|
||||
'(agda2-highlight-primitive-type-face ((t (:foreground "#268bd2"))))
|
||||
'(agda2-highlight-record-face ((t (:foreground "#268bd2"))))
|
||||
'(agda2-highlight-string-face ((t (:foreground "#2aa198"))))
|
||||
'(agda2-highlight-symbol-face ((((background "#fdf6e3")) (:foreground "#586e75"))))
|
||||
'(agda2-highlight-termination-problem-face ((t (:background "#cb4b16" :foreground "#002b36"))))
|
||||
'(agda2-highlight-typechecks-face ((t (:background "#2aa198" :foreground "#002b36"))))
|
||||
'(agda2-highlight-unsolved-constraint-face ((t (:background "#eee8d5"))))
|
||||
'(agda2-highlight-unsolved-meta-face ((t (:background "#eee8d5")))))
|
||||
125
users/grfn/emacs.d/irc.el
Normal file
125
users/grfn/emacs.d/irc.el
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
;;; -*- lexical-binding: t; -*-
|
||||
|
||||
(require 'erc)
|
||||
(require 'alert)
|
||||
|
||||
(defun irc-connect ()
|
||||
(interactive)
|
||||
(let ((pw (s-trim (shell-command-to-string "pass irccloud/freenode")))
|
||||
(gnutls-verify-error nil))
|
||||
(erc-tls :server "bnc.irccloud.com"
|
||||
:port 6697
|
||||
:nick "grfn"
|
||||
:password (concat "bnc@"
|
||||
(s-trim (shell-command-to-string "hostname"))
|
||||
":"
|
||||
pw))))
|
||||
|
||||
|
||||
(defgroup erc-alert nil
|
||||
"Alert me using alert.el for important ERC messages"
|
||||
:group 'erc)
|
||||
|
||||
(defcustom erc-noise-regexp
|
||||
"\\(Logging in:\\|Signing off\\|You're now away\\|Welcome back\\)"
|
||||
"This regexp matches unwanted noise."
|
||||
:type 'regexp
|
||||
:group 'erc)
|
||||
|
||||
(setq tvl-enabled? t)
|
||||
|
||||
(defun disable-tvl-notifications ()
|
||||
(interactive)
|
||||
(setq tvl-enabled? nil))
|
||||
|
||||
(defun enable-tvl-notifications ()
|
||||
(interactive)
|
||||
(setq tvl-enabled? t))
|
||||
|
||||
(defun erc-alert-important-p (info)
|
||||
(let ((message (plist-get info :message))
|
||||
(erc-message (-> info (plist-get :data) (plist-get :message)))
|
||||
(erc-channel (-> info (plist-get :data) (plist-get :channel))))
|
||||
(and erc-message
|
||||
(not (or (string-match "^\\** *Users on #" message)
|
||||
(string-match erc-noise-regexp
|
||||
message)))
|
||||
(or (and tvl-enabled?
|
||||
(string-equal erc-channel "##tvl"))
|
||||
(string-match "grfn" message)))))
|
||||
|
||||
(comment
|
||||
last-info
|
||||
erc-noise-regexp
|
||||
(setq tvl-enabled? nil)
|
||||
)
|
||||
|
||||
(defun my-erc-hook (&optional match-type nick message)
|
||||
"Shows a notification, when user's nick was mentioned.
|
||||
If the buffer is currently not visible, makes it sticky."
|
||||
(setq last-message message)
|
||||
(if (or (null match-type) (not (eq match-type 'fool)))
|
||||
(let (alert-log-messages)
|
||||
(alert (or message (buffer-string))
|
||||
:severity (if (string-match "grfn" (or message ""))
|
||||
'high 'low)
|
||||
:title (or nick (buffer-name))
|
||||
:data `(:message ,(or message (buffer-string))
|
||||
:channel ,(or nick (buffer-name)))))))
|
||||
|
||||
(add-hook 'erc-text-matched-hook 'my-erc-hook)
|
||||
(add-hook 'erc-insert-modify-hook 'my-erc-hook)
|
||||
|
||||
(defun my-erc-define-alerts (&rest ignore)
|
||||
;; Unless the user has recently typed in the ERC buffer, highlight the fringe
|
||||
(alert-add-rule
|
||||
:status '(buried visible idle)
|
||||
:severity '(moderate high urgent)
|
||||
:mode 'erc-mode
|
||||
:predicate
|
||||
#'(lambda (info)
|
||||
(and (not (eq (current-buffer) (plist-get info :buffer)))
|
||||
(string-match "grfn:" (plist-get info :message))))
|
||||
:persistent
|
||||
#'(lambda (info)
|
||||
;; If the buffer is buried, or the user has been idle for
|
||||
;; `alert-reveal-idle-time' seconds, make this alert
|
||||
;; persistent. Normally, alerts become persistent after
|
||||
;; `alert-persist-idle-time' seconds.
|
||||
(memq (plist-get info :status) '(buried idle)))
|
||||
:style 'message
|
||||
:continue t)
|
||||
|
||||
(alert-add-rule
|
||||
:status 'buried
|
||||
:mode 'erc-mode
|
||||
:predicate #'erc-alert-important-p
|
||||
:style 'libnotify
|
||||
:append t)
|
||||
|
||||
(alert-add-rule
|
||||
:status 'buried
|
||||
:mode 'erc-mode
|
||||
:predicate #'erc-alert-important-p
|
||||
:style 'message
|
||||
:append t)
|
||||
|
||||
(alert-add-rule
|
||||
:mode 'erc-mode
|
||||
:predicate #'erc-alert-important-p
|
||||
:style 'log
|
||||
:append t)
|
||||
|
||||
(alert-add-rule :mode 'erc-mode :style 'ignore :append t))
|
||||
|
||||
(add-hook 'erc-connect-pre-hook 'my-erc-define-alerts)
|
||||
|
||||
(defun fix-irc-message (msg)
|
||||
(let ((msg (s-trim msg)))
|
||||
(if (string-equal msg ":q") "" msg)))
|
||||
|
||||
(advice-add #'erc-user-input :filter-return #'fix-irc-message)
|
||||
|
||||
(comment
|
||||
(my-erc-define-alerts)
|
||||
)
|
||||
38
users/grfn/emacs.d/lisp.el
Normal file
38
users/grfn/emacs.d/lisp.el
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
;;; -*- lexical-binding: t; -*-
|
||||
|
||||
(defun grfn/sly-panettone ()
|
||||
(interactive)
|
||||
(sly
|
||||
(concat
|
||||
(s-trim
|
||||
(shell-command-to-string
|
||||
"nix-build -o sbcl -E 'with import ~/code/depot {}; nix.buildLisp.sbclWith [web.panettone]'"))
|
||||
"/bin/sbcl")))
|
||||
|
||||
(defun grfn/setup-lisp ()
|
||||
(interactive)
|
||||
(unless paxedit-mode (paxedit-mode 1))
|
||||
(rainbow-delimiters-mode)
|
||||
(flycheck-mode -1))
|
||||
|
||||
(add-hook 'common-lisp-lisp-mode-hook #'grfn/setup-lisp)
|
||||
|
||||
(defun sly-run-tests ()
|
||||
(interactive)
|
||||
;; TODO: handle other test frameworks
|
||||
(let ((orig-window (get-buffer-window)))
|
||||
(sly-eval '(fiveam:run!))
|
||||
(funcall-interactively #'sly-mrepl-sync)
|
||||
(select-window orig-window)))
|
||||
|
||||
(map!
|
||||
(:map sly-mode-map
|
||||
:n "g \\" #'sly-mrepl-sync
|
||||
:n "g d" #'sly-edit-definition
|
||||
:n "K" #'sly-documentation
|
||||
:n "g SPC" #'sly-compile-and-load-file
|
||||
:n "g RET" #'sly-run-tests)
|
||||
|
||||
(:map sly-mrepl-mode-map
|
||||
"C-k" #'sly-mrepl-previous-prompt
|
||||
"C-r" #'isearch-backward))
|
||||
7
users/grfn/emacs.d/nix-clangd.sh
Executable file
7
users/grfn/emacs.d/nix-clangd.sh
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
CLANGD_FLAGS=--compile-commands-dir=/home/grfn/builds/tvix \
|
||||
nix-shell /home/grfn/code/depot \
|
||||
-A third_party.nix \
|
||||
--run nix-clangd
|
||||
30
users/grfn/emacs.d/nix.el
Normal file
30
users/grfn/emacs.d/nix.el
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
;;; -*- lexical-binding: t; -*-
|
||||
|
||||
(defun nix-buffer-type ()
|
||||
"Returns:
|
||||
|
||||
'home-manager, if the current buffer is a home-manager module
|
||||
'nixos, if the current buffer is a nixos module
|
||||
nil, if none of the above are the case"
|
||||
(when buffer-file-name
|
||||
(pcase buffer-file-name
|
||||
((rx (0+ nonl) "system/home" (0+ nonl) ".nix" eos)
|
||||
'home-manager)
|
||||
((rx (0+ nonl) "system/system" (0+ nonl) ".nix" eos)
|
||||
'nixos))))
|
||||
|
||||
(defun set-nix-compile-command ()
|
||||
"Set the compile command for the current buffer based on the type of nix
|
||||
buffer it is, per `nix-buffer-type'"
|
||||
(interactive)
|
||||
(when-let ((btype (nix-buffer-type)))
|
||||
(setq-local
|
||||
compile-command
|
||||
(case btype
|
||||
('home-manager "home-manager switch")
|
||||
('nixos "sudo nixos-rebuild switch")))))
|
||||
|
||||
(add-hook 'nix-mode-hook #'set-nix-compile-command)
|
||||
|
||||
(map! (:map nix-mode-map
|
||||
(:n "g SPC" #'compile)))
|
||||
188
users/grfn/emacs.d/org-alerts.el
Normal file
188
users/grfn/emacs.d/org-alerts.el
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
;;; -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 's)
|
||||
(require 'dash)
|
||||
(require 'alert)
|
||||
(require 'org-agenda)
|
||||
|
||||
|
||||
(defvar grfn/org-alert-interval 300
|
||||
"Interval in seconds to recheck and display deadlines.")
|
||||
|
||||
|
||||
(defvar grfn/org-alert-notification-title "*org*"
|
||||
"Title to be sent with notify-send.")
|
||||
|
||||
(defvar grfn/org-alert-headline-regexp "\\(Sched.+:.+\\|Deadline:.+\\)"
|
||||
"Regexp for headlines to search in agenda buffer.")
|
||||
|
||||
(defun grfn/org-alert--strip-prefix (headline)
|
||||
"Remove the scheduled/deadline prefix from HEADLINE."
|
||||
(replace-regexp-in-string ".*:\s+" "" headline))
|
||||
|
||||
|
||||
(defun grfn/org-alert--unique-headlines (regexp agenda)
|
||||
"Return unique headlines from the results of REGEXP in AGENDA."
|
||||
(let ((matches (-distinct (-flatten (s-match-strings-all regexp agenda)))))
|
||||
(--map (grfn/org-alert--strip-prefix it) matches)))
|
||||
|
||||
|
||||
(defun grfn/org-alert--get-headlines ()
|
||||
"Return the current org agenda as text only."
|
||||
(with-temp-buffer
|
||||
(let ((org-agenda-sticky nil)
|
||||
(org-agenda-buffer-tmp-name (buffer-name)))
|
||||
(ignore-errors (org-agenda-list nil "TODAY" 1))
|
||||
(grfn/org-alert--unique-headlines
|
||||
grfn/org-alert-headline-regexp
|
||||
(buffer-substring-no-properties (point-min) (point-max))))))
|
||||
|
||||
(defun grfn/parse-range-string (str)
|
||||
(when
|
||||
(string-match (rx (group (repeat 2 (any digit))
|
||||
":"
|
||||
(repeat 2 (any digit)))
|
||||
(optional
|
||||
(and
|
||||
"-"
|
||||
(group (repeat 2 (any digit))
|
||||
":"
|
||||
(repeat 2 (any digit))))))
|
||||
str)
|
||||
(list
|
||||
(org-read-date nil t
|
||||
(match-string 1 str))
|
||||
(when-let ((et (match-string 2 str))) (org-read-date nil t et)))))
|
||||
|
||||
(defun grfn/start-time-from-range-string (str)
|
||||
(pcase-let ((`(,start-time . _) (grfn/parse-range-string str)))
|
||||
start-time))
|
||||
|
||||
(comment
|
||||
(org-agenda-list nil "TODAY" 1)
|
||||
|
||||
(grfn/org-alert--get-headlines)
|
||||
(setq --src
|
||||
(with-temp-buffer
|
||||
(let ((org-agenda-sticky nil)
|
||||
(org-agenda-buffer-tmp-name (buffer-name)))
|
||||
(ignore-errors (org-agenda-list nil "TODAY" 1))
|
||||
(buffer-substring-no-properties (point-min) (point-max)))))
|
||||
|
||||
(setq --entries
|
||||
(with-temp-buffer
|
||||
(let ((inhibit-redisplay t)
|
||||
(org-agenda-sticky nil)
|
||||
(org-agenda-buffer-tmp-name (buffer-name))
|
||||
(org-agenda-buffer-name (buffer-name))
|
||||
(org-agenda-buffer (current-buffer)))
|
||||
(org-agenda-get-day-entries
|
||||
(cadr (org-agenda-files nil 'ifmode))
|
||||
(calendar-gregorian-from-absolute
|
||||
(time-to-days (org-read-date nil t "TODAY")))))))
|
||||
|
||||
(loop for k in (text-properties-at 0 (car --entries))
|
||||
by #'cddr
|
||||
collect k)
|
||||
|
||||
(--map (substring-no-properties (get-text-property 0 'txt it)) --entries)
|
||||
(--map (get-text-property 0 'time it) --entries)
|
||||
(current-time)
|
||||
|
||||
(format-time-string "%R" (org-read-date nil t "10:00-11:00"))
|
||||
|
||||
(grfn/start-time-from-range-string "10:00")
|
||||
|
||||
(current-time-string (org-read-date nil t "10:00-11:00"))
|
||||
|
||||
(todo-state
|
||||
org-habit-p
|
||||
priority
|
||||
warntime
|
||||
ts-date
|
||||
date
|
||||
type
|
||||
org-hd-marker
|
||||
org-marker
|
||||
face
|
||||
undone-face
|
||||
help-echo
|
||||
mouse-face
|
||||
done-face
|
||||
org-complex-heading-regexp
|
||||
org-todo-regexp
|
||||
org-not-done-regexp
|
||||
dotime
|
||||
format
|
||||
extra
|
||||
time
|
||||
level
|
||||
txt
|
||||
breadcrumbs
|
||||
duration
|
||||
time-of-day
|
||||
org-lowest-priority
|
||||
org-highest-priority
|
||||
tags
|
||||
org-category)
|
||||
|
||||
(propertize)
|
||||
|
||||
--src
|
||||
)
|
||||
|
||||
|
||||
(defun grfn/org-alert--headline-complete? (headline)
|
||||
"Return whether HEADLINE has been completed."
|
||||
(--any? (s-starts-with? it headline) org-done-keywords-for-agenda))
|
||||
|
||||
|
||||
(defun grfn/org-alert--filter-active (deadlines)
|
||||
"Remove any completed headings from the provided DEADLINES."
|
||||
(-remove 'grfn/org-alert--headline-complete? deadlines))
|
||||
|
||||
|
||||
(defun grfn/org-alert--strip-states (deadlines)
|
||||
"Remove the todo states from DEADLINES."
|
||||
(--map (s-trim (s-chop-prefixes org-todo-keywords-for-agenda it)) deadlines))
|
||||
|
||||
|
||||
(defun grfn/org-alert-check ()
|
||||
"Check for active, due deadlines and initiate notifications."
|
||||
(interactive)
|
||||
;; avoid interrupting current command.
|
||||
(unless (minibufferp)
|
||||
(save-window-excursion
|
||||
(save-excursion
|
||||
(save-restriction
|
||||
(let ((active (grfn/org-alert--filter-active (grfn/org-alert--get-headlines))))
|
||||
(dolist (dl (grfn/org-alert--strip-states active))
|
||||
(alert dl :title grfn/org-alert-notification-title))))))
|
||||
(when (get-buffer org-agenda-buffer-name)
|
||||
(ignore-errors
|
||||
(with-current-buffer org-agenda-buffer-name
|
||||
(org-agenda-redo t))))))
|
||||
|
||||
|
||||
(defun grfn/org-alert-enable ()
|
||||
"Enable the notification timer. Cancels existing timer if running."
|
||||
(interactive)
|
||||
(grfn/org-alert-disable)
|
||||
(run-at-time 0 grfn/org-alert-interval 'grfn/org-alert-check))
|
||||
|
||||
|
||||
(defun grfn/org-alert-disable ()
|
||||
"Cancel the running notification timer."
|
||||
(interactive)
|
||||
(dolist (timer timer-list)
|
||||
(if (eq (elt timer 5) 'grfn/org-alert-check)
|
||||
(cancel-timer timer))))
|
||||
|
||||
|
||||
|
||||
(provide 'grfn/org-alert)
|
||||
;;; grfn/org-alert.el ends here
|
||||
179
users/grfn/emacs.d/org-config.el
Normal file
179
users/grfn/emacs.d/org-config.el
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
;;; -*- lexical-binding: t; -*-
|
||||
|
||||
(defun notes-file (f)
|
||||
(concat org-directory (if (string-prefix-p "/" f) "" "/") f))
|
||||
|
||||
(defun grfn/org-project-tag->key (tag)
|
||||
(s-replace-regexp "^project__" "" tag))
|
||||
|
||||
(defun grfn/org-project-tag->name (tag)
|
||||
(s-titleized-words
|
||||
(s-join " " (s-split "_" (grfn/org-project-tag->key tag)))))
|
||||
|
||||
(defun grfn/org-project-tag->keys (tag)
|
||||
(s-join "" (cons "p"
|
||||
(-map (lambda (s) (substring-no-properties s 0 1))
|
||||
(s-split "_" (grfn/org-project-tag->key tag))))))
|
||||
|
||||
(defun grfn/org-projects->agenda-commands (project-tags)
|
||||
(loop for tag in project-tags
|
||||
collect `(,(grfn/org-project-tag->keys tag)
|
||||
,(grfn/org-project-tag->name tag)
|
||||
tags-todo
|
||||
,tag)))
|
||||
|
||||
(defun grfn/org-projects ()
|
||||
(loop for (tag) in
|
||||
(org-global-tags-completion-table
|
||||
(directory-files-recursively "~/notes" "\\.org$"))
|
||||
when (s-starts-with-p "project__" tag)
|
||||
collect tag))
|
||||
|
||||
(comment
|
||||
(grfn/org-projects->agenda-commands (grfn/org-projects))
|
||||
)
|
||||
|
||||
(setq
|
||||
org-directory (expand-file-name "~/notes")
|
||||
+org-dir (expand-file-name "~/notes")
|
||||
org-default-notes-file (concat org-directory "/inbox.org")
|
||||
+org-default-todo-file (concat org-directory "/inbox.org")
|
||||
org-agenda-files (directory-files-recursively
|
||||
"~/notes" "\\.org$")
|
||||
org-refile-targets '((org-agenda-files :maxlevel . 3))
|
||||
org-outline-path-complete-in-steps nil
|
||||
org-refile-use-outline-path t
|
||||
org-file-apps `((auto-mode . emacs)
|
||||
(,(rx (or (and "." (optional "x") (optional "htm") (optional "l") buffer-end)
|
||||
(and buffer-start "http" (optional "s") "://")))
|
||||
. "firefox %s")
|
||||
(,(rx ".pdf" buffer-end) . "apvlv %s")
|
||||
(,(rx "." (or "png"
|
||||
"jpg"
|
||||
"jpeg"
|
||||
"gif"
|
||||
"tif"
|
||||
"tiff")
|
||||
buffer-end)
|
||||
. "feh %s"))
|
||||
org-log-done 'time
|
||||
org-archive-location "~/notes/trash::* From %s"
|
||||
org-cycle-separator-lines 2
|
||||
org-hidden-keywords '(title)
|
||||
org-tags-column -130
|
||||
org-ellipsis "⤵"
|
||||
org-imenu-depth 9
|
||||
org-capture-templates
|
||||
`(("t" "Todo" entry
|
||||
(file +org-default-todo-file)
|
||||
"* TODO %?\n%i"
|
||||
:kill-buffer t)
|
||||
|
||||
("m" "Email" entry
|
||||
(file +org-default-todo-file)
|
||||
"* TODO [%l[%:subject]] :email:\n%i")
|
||||
|
||||
("n" "Notes" entry
|
||||
(file +org-default-todo-file)
|
||||
"* %U %?\n%i"
|
||||
:prepend t
|
||||
:kill-buffer t)
|
||||
|
||||
("c" "Task note" entry
|
||||
(clock)
|
||||
"* %U %?\n%i[%l[Context]]\n"
|
||||
:kill-buffer t
|
||||
:unnarrowed t)
|
||||
|
||||
("p" "Projects")
|
||||
("px" "Xanthous" entry
|
||||
(file+headline ,(notes-file "xanthous.org") "Backlog")
|
||||
"* TODO %?\nContext %a\nIn task: %K")
|
||||
("pt" "Tvix" entry
|
||||
(file+headline ,(notes-file "tvix.org") "Tvix TODO")
|
||||
"* TODO %?\nContext %a\nIn task: %K")
|
||||
("pw" "Windtunnel" entry
|
||||
(file+headline ,(notes-file "windtunnel.org") "Tasks")
|
||||
"* TODO %i%?\nContext: %a\nIn task: %K")
|
||||
|
||||
("d" "Data recording")
|
||||
)
|
||||
|
||||
org-capture-templates-contexts
|
||||
`(("px" ((in-file . "/home/griffin/code/xanthous/.*"))))
|
||||
|
||||
org-deadline-warning-days 1
|
||||
org-agenda-skip-scheduled-if-deadline-is-shown 'todo
|
||||
org-todo-keywords '((sequence "TODO(t)" "ACTIVE(a)" "|" "DONE(d)" "RUNNING(r)")
|
||||
(sequence "NEXT(n)" "WAITING(w)" "LATER(l)" "|" "CANCELLED(c)"))
|
||||
org-agenda-custom-commands
|
||||
`(("S" "Sprint Tasks" tags-todo "sprint")
|
||||
("i" "Inbox" tags "inbox")
|
||||
("r" "Running jobs" todo "RUNNING")
|
||||
("w" "@Work" tags-todo "@work")
|
||||
("n" . "Next...")
|
||||
("np" "Next Sprint" tags-todo "next_sprint|sprint_planning")
|
||||
|
||||
("p" . "Project...")
|
||||
,@(grfn/org-projects->agenda-commands (grfn/org-projects)))
|
||||
|
||||
org-agenda-dim-blocked-tasks nil
|
||||
org-enforce-todo-dependencies nil
|
||||
|
||||
org-babel-clojure-backend 'cider)
|
||||
|
||||
(defun +grfn/org-setup ()
|
||||
(setq-local truncate-lines -1)
|
||||
(line-number-mode -1))
|
||||
|
||||
(add-hook 'org-mode-hook #'+grfn/org-setup)
|
||||
|
||||
(defun +grfn/insert-work-template ()
|
||||
(interactive)
|
||||
(goto-char (point-min))
|
||||
(forward-line)
|
||||
(insert "#+TODO: TODO(t) NEXT(n) ACTIVE(a) | DONE(d) PR(p) RUNNING(r) TESTING(D)
|
||||
#+TODO: BLOCKED(b) BACKLOG(l) PROPOSED(o) | CANCELLED(c)
|
||||
#+FILETAGS: @work
|
||||
#+FILETAGS: @work
|
||||
#+PROPERTY: Effort_ALL 0 4:00 8:00 12:00 20:00 32:00
|
||||
#+PROPERTY: ESTIMATE_ALL 0 1 2 3 5 8
|
||||
#+PROPERTY: STORY-TYPE_ALL Feature Bug Chore
|
||||
#+PROPERTY: NOBLOCKING t
|
||||
#+COLUMNS: %TODO %40ITEM(Task) %17EFFORT(Estimated){:} %CLOCKSUM(Time Spent) %17STORY-TYPE(Type) %TAGS"))
|
||||
|
||||
(defun +grfn/insert-org-template ()
|
||||
(interactive)
|
||||
(pcase (buffer-file-name)
|
||||
((s-contains "/work/") (+grfn/insert-work-template))))
|
||||
|
||||
;;; TODO: this doesn't work?
|
||||
(define-auto-insert "\\.org?$" #'grfn/insert-org-template t)
|
||||
|
||||
(defun forge--post-submit-around---link-pr-to-org-item
|
||||
(orig)
|
||||
(let ((cb (funcall orig)))
|
||||
(lambda (value headers status req)
|
||||
(prog1 (funcall cb value headers status req)
|
||||
(grfn/at-org-clocked-in-item
|
||||
(let ((url (alist-get 'html_url value))
|
||||
(number (alist-get 'number value)))
|
||||
(org-set-property
|
||||
"pull-request"
|
||||
(org-make-link-string
|
||||
url
|
||||
(format "%s/%s/%d"
|
||||
(->> value
|
||||
(alist-get 'base)
|
||||
(alist-get 'repo)
|
||||
(alist-get 'name))
|
||||
(->> value
|
||||
(alist-get 'base)
|
||||
(alist-get 'repo)
|
||||
(alist-get 'owner)
|
||||
(alist-get 'login))
|
||||
number)))))))))
|
||||
|
||||
(advice-add
|
||||
#'forge--post-submit-callback
|
||||
:around #'forge--post-submit-around---link-pr-to-org-item)
|
||||
181
users/grfn/emacs.d/org-gcal.el
Normal file
181
users/grfn/emacs.d/org-gcal.el
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
;;; -*- lexical-binding: t; -*-
|
||||
|
||||
(require 'aio)
|
||||
(require 'parse-time)
|
||||
|
||||
(setq-local lexical-binding t)
|
||||
(setq plstore-cache-passphrase-for-symmetric-encryption t)
|
||||
|
||||
(defvar gcal-client-id)
|
||||
(defvar gcal-client-secret)
|
||||
|
||||
(defvar google-calendar-readonly-scope
|
||||
"https://www.googleapis.com/auth/calendar.readonly")
|
||||
|
||||
(defvar events-file "/home/grfn/notes/events.org")
|
||||
|
||||
(defun google--get-token (scope client-id client-secret)
|
||||
(oauth2-auth-and-store
|
||||
"https://accounts.google.com/o/oauth2/v2/auth"
|
||||
"https://oauth2.googleapis.com/token"
|
||||
scope
|
||||
client-id
|
||||
client-secret))
|
||||
|
||||
(cl-defun google--request (url &key method params scope)
|
||||
(let ((p (aio-promise))
|
||||
(auth-token (google--get-token scope gcal-client-id gcal-client-secret)))
|
||||
(oauth2-refresh-access auth-token)
|
||||
(oauth2-url-retrieve
|
||||
auth-token
|
||||
url
|
||||
(lambda (&rest _)
|
||||
(goto-char (point-min))
|
||||
(re-search-forward "^$")
|
||||
(let ((resp (json-parse-buffer :object-type 'alist)))
|
||||
(aio-resolve p (lambda () resp))))
|
||||
nil
|
||||
(or method "GET")
|
||||
params)
|
||||
p))
|
||||
|
||||
(cl-defun list-events (&key min-time max-time)
|
||||
(google--request
|
||||
(concat
|
||||
"https://www.googleapis.com/calendar/v3/calendars/griffin@urbint.com/events"
|
||||
"?timeMin=" (format-time-string "%Y-%m-%dT%T%z" min-time)
|
||||
"&timeMax=" (format-time-string "%Y-%m-%dT%T%z" max-time))
|
||||
:scope google-calendar-readonly-scope))
|
||||
|
||||
|
||||
(defun last-week-events ()
|
||||
(list-events :min-time (time-subtract
|
||||
(current-time)
|
||||
(seconds-to-time
|
||||
(* 60 60 24 7)))
|
||||
:max-time (current-time)))
|
||||
|
||||
(defun next-week-events ()
|
||||
(list-events :min-time (current-time)
|
||||
:max-time (time-add
|
||||
(current-time)
|
||||
(seconds-to-time
|
||||
(* 60 60 24 7)))))
|
||||
|
||||
(defun attending-event? (event)
|
||||
(let* ((attendees (append (alist-get 'attendees event) nil))
|
||||
(self (--find (alist-get 'self it) attendees)))
|
||||
(equal "accepted" (alist-get 'responseStatus self))))
|
||||
|
||||
(defun event->org-headline (event level)
|
||||
(cl-flet ((make-time
|
||||
(key)
|
||||
(when-let ((raw-time (->> event (alist-get key) (alist-get 'dateTime))))
|
||||
(format-time-string
|
||||
(org-time-stamp-format t)
|
||||
(parse-iso8601-time-string raw-time)))))
|
||||
(if-let ((start-time (make-time 'start))
|
||||
(end-time (make-time 'end)))
|
||||
(s-format
|
||||
"${headline} [[${htmlLink}][${summary}]] :event:
|
||||
${startTime}--${endTime}
|
||||
:PROPERTIES:
|
||||
${location-prop}
|
||||
:EVENT: ${htmlLink}
|
||||
:END:
|
||||
|
||||
${description}"
|
||||
(function
|
||||
(lambda (k m)
|
||||
(or (alist-get (intern k) m)
|
||||
(format "key not found: %s" k))))
|
||||
(append
|
||||
event
|
||||
`((headline . ,(make-string level ?*))
|
||||
(startTime . ,start-time)
|
||||
(endTime . ,end-time)
|
||||
(location-prop
|
||||
. ,(if-let ((location (alist-get 'location event)))
|
||||
(s-lex-format ":LOCATION: ${location}")
|
||||
"")))))
|
||||
"")))
|
||||
|
||||
(comment
|
||||
(alist-get 'foo nil)
|
||||
)
|
||||
|
||||
(defun write-events (events)
|
||||
(with-current-buffer (find-file-noselect events-file)
|
||||
(save-mark-and-excursion
|
||||
(save-restriction
|
||||
(widen)
|
||||
(erase-buffer)
|
||||
(goto-char (point-min))
|
||||
(insert "#+TITLE: Events")
|
||||
(newline) (newline)
|
||||
(prog1
|
||||
(loop for event in (append events nil)
|
||||
when (attending-event? event)
|
||||
do
|
||||
(insert (event->org-headline event 1))
|
||||
(newline)
|
||||
sum 1)
|
||||
(org-align-tags t))))))
|
||||
|
||||
(defun +grfn/sync-events ()
|
||||
(interactive)
|
||||
(let* ((events (alist-get 'items (aio-wait-for (next-week-events))))
|
||||
(num-written (write-events events)))
|
||||
(message "Successfully wrote %d events" num-written)))
|
||||
|
||||
(comment
|
||||
((kind . "calendar#event")
|
||||
(etag . "\"3174776941020000\"")
|
||||
(id . "SNIP")
|
||||
(status . "confirmed")
|
||||
(htmlLink . "https://www.google.com/calendar/event?eid=SNIP")
|
||||
(created . "2020-04-01T13:30:09.000Z")
|
||||
(updated . "2020-04-20T13:14:30.510Z")
|
||||
(summary . "SNIP")
|
||||
(description . "SNIP")
|
||||
(location . "SNIP")
|
||||
(creator
|
||||
(email . "griffin@urbint.com")
|
||||
(self . t))
|
||||
(organizer
|
||||
(email . "griffin@urbint.com")
|
||||
(self . t))
|
||||
(start
|
||||
(dateTime . "2020-04-01T12:00:00-04:00")
|
||||
(timeZone . "America/New_York"))
|
||||
(end
|
||||
(dateTime . "2020-04-01T12:30:00-04:00")
|
||||
(timeZone . "America/New_York"))
|
||||
(recurrence .
|
||||
["RRULE:FREQ=WEEKLY;UNTIL=20200408T035959Z;BYDAY=WE"])
|
||||
(iCalUID . "SNIP")
|
||||
(sequence . 0)
|
||||
(attendees .
|
||||
[((email . "griffin@urbint.com")
|
||||
(organizer . t)
|
||||
(self . t)
|
||||
(responseStatus . "accepted"))
|
||||
((email . "SNIP")
|
||||
(displayName . "SNIP")
|
||||
(responseStatus . "needsAction"))])
|
||||
(extendedProperties
|
||||
(private
|
||||
(origRecurringId . "309q48kc1dihsvbi13pnlimb5a"))
|
||||
(shared
|
||||
(origRecurringId . "309q48kc1dihsvbi13pnlimb5a")))
|
||||
(reminders
|
||||
(useDefault . t)))
|
||||
|
||||
(require 'icalendar)
|
||||
|
||||
(icalendar--convert-recurring-to-diary
|
||||
nil
|
||||
"RRULE:FREQ=WEEKLY;UNTIL=20200408T035959Z;BYDAY=WE"
|
||||
)
|
||||
|
||||
)
|
||||
96
users/grfn/emacs.d/org-query.el
Normal file
96
users/grfn/emacs.d/org-query.el
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
;;; -*- lexical-binding: t; -*-
|
||||
|
||||
(require 'org)
|
||||
(require 'org-agenda)
|
||||
(require 'inflections)
|
||||
|
||||
(defun grfn/org-agenda-entry->element (agenda-entry)
|
||||
;; ???
|
||||
())
|
||||
|
||||
(defun org-elements-agenda-match (match &optional todo-only)
|
||||
(setq match
|
||||
(propertize match 'inherited t))
|
||||
(with-temp-buffer
|
||||
(let ((inhibit-redisplay (not debug-on-error))
|
||||
(org-agenda-sticky nil)
|
||||
(org-agenda-buffer-tmp-name (buffer-name))
|
||||
(org-agenda-buffer-name (buffer-name))
|
||||
(org-agenda-buffer (current-buffer))
|
||||
(matcher (org-make-tags-matcher match))
|
||||
result)
|
||||
(org-agenda-prepare (concat "TAGS " match))
|
||||
(setq match (car matcher)
|
||||
matcher (cdr matcher))
|
||||
(dolist (file (org-agenda-files nil 'ifmode)
|
||||
result)
|
||||
(catch 'nextfile
|
||||
(org-check-agenda-file file)
|
||||
(when-let ((buffer (if (file-exists-p file)
|
||||
(org-get-agenda-file-buffer file)
|
||||
(error "No such file %s" file))))
|
||||
(with-current-buffer buffer
|
||||
(unless (derived-mode-p 'org-mode)
|
||||
(error "Agenda file %s is not in Org mode" file))
|
||||
(save-excursion
|
||||
(save-restriction
|
||||
(if (eq buffer org-agenda-restrict)
|
||||
(narrow-to-region org-agenda-restrict-begin
|
||||
org-agenda-restrict-end)
|
||||
(widen))
|
||||
(setq result
|
||||
(append result (org-scan-tags
|
||||
'agenda
|
||||
matcher
|
||||
todo-only))))))))))))
|
||||
|
||||
(defun grfn/num-inbox-items ()
|
||||
(length (org-elements-agenda-match "inbox" t)))
|
||||
|
||||
(defun grfn/num-inbox-items-message ()
|
||||
(let ((n (grfn/num-inbox-items)))
|
||||
(unless (zerop n)
|
||||
(format "%d %s"
|
||||
n
|
||||
(if (= 1 n) "item" "items")))))
|
||||
|
||||
(defmacro grfn/at-org-clocked-in-item (&rest body)
|
||||
`(when (org-clocking-p)
|
||||
(let ((m org-clock-marker))
|
||||
(with-current-buffer (marker-buffer m)
|
||||
(save-mark-and-excursion
|
||||
(goto-char m)
|
||||
(org-back-to-heading t)
|
||||
,@body)))))
|
||||
|
||||
(defun grfn/org-element-clocked-in-task ()
|
||||
(grfn/at-org-clocked-in-item
|
||||
(org-element-at-point)))
|
||||
|
||||
(comment
|
||||
(grfn/org-element-clocked-in-task)
|
||||
(org-element-property :title (grfn/org-element-clocked-in-task))
|
||||
)
|
||||
|
||||
(defun grfn/minutes->hours:minutes (minutes)
|
||||
(format "%d:%02d"
|
||||
(floor (/ minutes 60))
|
||||
(mod minutes 60)))
|
||||
|
||||
(comment
|
||||
(grfn/minutes->hours:minutes 1) ; => "0:01"
|
||||
(grfn/minutes->hours:minutes 15) ; => "0:15"
|
||||
(grfn/minutes->hours:minutes 130) ; => "2:10"
|
||||
)
|
||||
|
||||
(defun grfn/org-current-clocked-in-task-message ()
|
||||
(if (org-clocking-p)
|
||||
(format "(%s) [%s]"
|
||||
(org-element-property :title (grfn/org-element-clocked-in-task))
|
||||
(grfn/minutes->hours:minutes
|
||||
(org-clock-get-clocked-time)))
|
||||
""))
|
||||
|
||||
(comment
|
||||
(grfn/org-current-clocked-in-task-message)
|
||||
)
|
||||
156
users/grfn/emacs.d/packages.el
Normal file
156
users/grfn/emacs.d/packages.el
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; private/grfn/packages.el
|
||||
|
||||
(package! moody)
|
||||
|
||||
;; Editor
|
||||
(package! solarized-theme)
|
||||
(package! fill-column-indicator)
|
||||
(package! flx)
|
||||
(package! general
|
||||
:recipe (:host github :repo "noctuid/general.el"))
|
||||
(package! fill-column-indicator)
|
||||
(package! writeroom-mode)
|
||||
(package! dash)
|
||||
(package! w3m)
|
||||
(package! rainbow-mode)
|
||||
(package! string-inflection)
|
||||
|
||||
;;; Org
|
||||
(package! org-clubhouse
|
||||
:recipe (:host file
|
||||
:local-repo "~/code/org-clubhouse"))
|
||||
(package! org-alert)
|
||||
(package! ob-http)
|
||||
(package! ob-ipython)
|
||||
(package! ob-async)
|
||||
(package! org-recent-headings)
|
||||
(package! org-sticky-header)
|
||||
(package! gnuplot)
|
||||
(package! gnuplot-mode)
|
||||
|
||||
;; Presentation
|
||||
(package! epresent)
|
||||
(package! org-tree-slide)
|
||||
(package! ox-reveal)
|
||||
|
||||
;; Slack etc
|
||||
(package! slack)
|
||||
(package! alert)
|
||||
|
||||
;; Git
|
||||
(package! evil-magit)
|
||||
(package! marshal)
|
||||
(package! forge)
|
||||
(package!
|
||||
github-review
|
||||
:recipe
|
||||
(:host github
|
||||
:repo "charignon/github-review"
|
||||
:files ("github-review.el")))
|
||||
|
||||
;; Elisp
|
||||
(package! dash)
|
||||
(package! dash-functional)
|
||||
(package! s)
|
||||
(package! request)
|
||||
(package! predd
|
||||
:recipe (:host github :repo "skeeto/predd"))
|
||||
(package! aio)
|
||||
|
||||
;; Haskell
|
||||
(package! lsp-haskell)
|
||||
(package! counsel-etags)
|
||||
|
||||
;;; LSP
|
||||
(package! lsp-mode)
|
||||
(package! lsp-ui :recipe (:host github :repo "emacs-lsp/lsp-ui"))
|
||||
(package! company-lsp)
|
||||
(package! lsp-treemacs)
|
||||
(package! dap-mode)
|
||||
|
||||
;; Rust
|
||||
(package! rustic :disable t)
|
||||
;; (package! racer :disable t)
|
||||
(package! cargo)
|
||||
|
||||
;; Lisp
|
||||
(package! paxedit)
|
||||
|
||||
;; Javascript
|
||||
(package! flow-minor-mode)
|
||||
(package! flycheck-flow)
|
||||
(package! company-flow)
|
||||
(package! prettier-js)
|
||||
|
||||
;; GraphQL
|
||||
(package! graphql-mode)
|
||||
|
||||
;; Haskell
|
||||
(package! lsp-mode)
|
||||
(package! lsp-ui)
|
||||
(package! lsp-haskell)
|
||||
(package! company-lsp)
|
||||
;; (package! lsp-imenu)
|
||||
|
||||
;; Clojure
|
||||
(package! flycheck-clojure)
|
||||
|
||||
;; SQL
|
||||
(package! sqlup-mode)
|
||||
(package! emacsql)
|
||||
(package! emacsql-psql)
|
||||
|
||||
;;; Python
|
||||
(package! pyimport)
|
||||
;; (package! yapfify)
|
||||
(package! blacken)
|
||||
|
||||
|
||||
;;; Desktop interaction
|
||||
(package! counsel-spotify)
|
||||
|
||||
;;; Dhall
|
||||
(package! dhall-mode)
|
||||
|
||||
;;; Kubernetes
|
||||
(package! kubernetes)
|
||||
(package! kubernetes-evil)
|
||||
(package! k8s-mode)
|
||||
|
||||
;;; Stack Exchange
|
||||
(package! sx)
|
||||
|
||||
;;; Nix
|
||||
(package! nix-update
|
||||
:recipe (:host github
|
||||
:repo "glittershark/nix-update-el"))
|
||||
(package! direnv)
|
||||
|
||||
;;; Email
|
||||
(package! mu4e)
|
||||
|
||||
;;; Sequence diagrams
|
||||
(package! wsd-mode
|
||||
:recipe (:host github
|
||||
:repo "josteink/wsd-mode"))
|
||||
|
||||
;;; logic?
|
||||
(package! metal-mercury-mode
|
||||
:recipe (:host github
|
||||
:repo "ahungry/metal-mercury-mode"))
|
||||
(package! flycheck-mercury)
|
||||
|
||||
(package! terraform-mode)
|
||||
(package! company-terraform)
|
||||
|
||||
(package! jsonnet-mode)
|
||||
|
||||
;;;
|
||||
(package! znc
|
||||
:recipe (:host github
|
||||
:repo "sshirokov/ZNC.el"))
|
||||
|
||||
;;; cpp
|
||||
(package! protobuf-mode)
|
||||
(package! clang-format+)
|
||||
37
users/grfn/emacs.d/rust.el
Normal file
37
users/grfn/emacs.d/rust.el
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
;;; -*- lexical-binding: t; -*-
|
||||
|
||||
(add-to-list 'auto-mode-alist '("\\.rs$" . rust-mode))
|
||||
|
||||
(defun grfn/rust-setup ()
|
||||
(interactive)
|
||||
|
||||
(push '(?> . ("<" . ">")) evil-surround-pairs-alist)
|
||||
(push '(?< . ("< " . " >")) evil-surround-pairs-alist)
|
||||
|
||||
(setq lsp-rust-server 'rust-analyzer)
|
||||
(setq-local whitespace-line-column 100
|
||||
fill-column 100)
|
||||
(setq rust-format-show-buffer nil)
|
||||
(setq lsp-rust-analyzer-import-merge-behaviour "last"
|
||||
lsp-rust-analyzer-cargo-watch-command "clippy"
|
||||
lsp-ui-doc-enable t)
|
||||
(rust-enable-format-on-save)
|
||||
(lsp))
|
||||
|
||||
(add-hook 'rust-mode-hook #'grfn/rust-setup)
|
||||
|
||||
(map!
|
||||
(:map rust-mode-map
|
||||
:n "g RET" #'cargo-process-current-file-tests
|
||||
:n "g R" #'lsp-find-references
|
||||
(:localleader
|
||||
"m" #'lsp-rust-analyzer-expand-macro)))
|
||||
|
||||
(comment
|
||||
(flycheck-get-next-checkers 'lsp)
|
||||
(flycheck-add-next-checker)
|
||||
(flycheck-get-next-checkers 'lsp)
|
||||
)
|
||||
|
||||
(set-company-backend! 'rust-mode
|
||||
'(:separate company-capf company-yasnippet))
|
||||
61
users/grfn/emacs.d/show-matching-paren.el
Normal file
61
users/grfn/emacs.d/show-matching-paren.el
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
;;; -*- lexical-binding: t; -*-
|
||||
|
||||
;;; https://with-emacs.com/posts/ui-hacks/show-matching-lines-when-parentheses-go-off-screen/
|
||||
|
||||
;; we will call `blink-matching-open` ourselves...
|
||||
(remove-hook 'post-self-insert-hook
|
||||
#'blink-paren-post-self-insert-function)
|
||||
;; this still needs to be set for `blink-matching-open` to work
|
||||
(setq blink-matching-paren 'show)
|
||||
|
||||
(let ((ov nil)) ; keep track of the overlay
|
||||
(advice-add
|
||||
#'show-paren-function
|
||||
:after
|
||||
(defun show-paren--off-screen+ (&rest _args)
|
||||
"Display matching line for off-screen paren."
|
||||
(when (overlayp ov)
|
||||
(delete-overlay ov))
|
||||
;; check if it's appropriate to show match info,
|
||||
;; see `blink-paren-post-self-insert-function'
|
||||
(when (and (overlay-buffer show-paren--overlay)
|
||||
(not (or cursor-in-echo-area
|
||||
executing-kbd-macro
|
||||
noninteractive
|
||||
(minibufferp)
|
||||
this-command))
|
||||
(and (not (bobp))
|
||||
(memq (char-syntax (char-before)) '(?\) ?\$)))
|
||||
(= 1 (logand 1 (- (point)
|
||||
(save-excursion
|
||||
(forward-char -1)
|
||||
(skip-syntax-backward "/\\")
|
||||
(point))))))
|
||||
;; rebind `minibuffer-message' called by
|
||||
;; `blink-matching-open' to handle the overlay display
|
||||
(cl-letf (((symbol-function #'minibuffer-message)
|
||||
(lambda (msg &rest args)
|
||||
(let ((msg (apply #'format-message msg args)))
|
||||
(setq ov (display-line-overlay+
|
||||
(window-start) msg ))))))
|
||||
(blink-matching-open))))))
|
||||
|
||||
(defun display-line-overlay+ (pos str &optional face)
|
||||
"Display line at POS as STR with FACE.
|
||||
|
||||
FACE defaults to inheriting from default and highlight."
|
||||
(let ((ol (save-excursion
|
||||
(goto-char pos)
|
||||
(make-overlay (line-beginning-position)
|
||||
(line-end-position)))))
|
||||
(overlay-put ol 'display str)
|
||||
(overlay-put ol 'face
|
||||
(or face '(:inherit default :inherit highlight)))
|
||||
ol))
|
||||
|
||||
(setq show-paren-style 'paren
|
||||
show-paren-delay 0.03
|
||||
show-paren-highlight-openparen t
|
||||
show-paren-when-point-inside-paren nil
|
||||
show-paren-when-point-in-periphery t)
|
||||
(show-paren-mode 1)
|
||||
227
users/grfn/emacs.d/slack-snippets.el
Normal file
227
users/grfn/emacs.d/slack-snippets.el
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
;;; -*- lexical-binding: t; -*-
|
||||
|
||||
(require 'dash)
|
||||
(require 'dash-functional)
|
||||
(require 'request)
|
||||
|
||||
;;;
|
||||
;;; Configuration
|
||||
;;;
|
||||
|
||||
(defvar slack/token nil
|
||||
"Legacy (https://api.slack.com/custom-integrations/legacy-tokens) access token")
|
||||
|
||||
(defvar slack/include-public-channels 't
|
||||
"Whether or not to inclue public channels in the list of conversations")
|
||||
|
||||
(defvar slack/include-private-channels 't
|
||||
"Whether or not to inclue public channels in the list of conversations")
|
||||
|
||||
(defvar slack/include-im 't
|
||||
"Whether or not to inclue IMs (private messages) in the list of conversations")
|
||||
|
||||
(defvar slack/include-mpim nil
|
||||
"Whether or not to inclue multi-person IMs (multi-person private messages) in
|
||||
the list of conversations")
|
||||
|
||||
;;;
|
||||
;;; Utilities
|
||||
;;;
|
||||
|
||||
(defmacro comment (&rest _body)
|
||||
"Comment out one or more s-expressions"
|
||||
nil)
|
||||
|
||||
(defun ->list (vec) (append vec nil))
|
||||
|
||||
(defun json-truthy? (x) (and x (not (equal :json-false x))))
|
||||
|
||||
;;;
|
||||
;;; Generic API integration
|
||||
;;;
|
||||
|
||||
(defvar slack/base-url "https://slack.com/api")
|
||||
|
||||
(defun slack/get (path params &optional callback)
|
||||
"params is an alist of query parameters"
|
||||
(let* ((params-callback (if (functionp params) `(() . ,params) (cons params callback)))
|
||||
(params (car params-callback)) (callback (cdr params-callback))
|
||||
(params (append `(("token" . ,slack/token)) params))
|
||||
(url (concat (file-name-as-directory slack/base-url) path)))
|
||||
(request url
|
||||
:type "GET"
|
||||
:params params
|
||||
:parser 'json-read
|
||||
:success (cl-function
|
||||
(lambda (&key data &allow-other-keys)
|
||||
(funcall callback data))))))
|
||||
|
||||
(defun slack/post (path params &optional callback)
|
||||
(let* ((params-callback (if (functionp params) `(() . ,params) (cons params callback)))
|
||||
(params (car params-callback)) (callback (cdr params-callback))
|
||||
(url (concat (file-name-as-directory slack/base-url) path)))
|
||||
(request url
|
||||
:type "POST"
|
||||
:data (json-encode params)
|
||||
:headers `(("Content-Type" . "application/json")
|
||||
("Authorization" . ,(format "Bearer %s" slack/token)))
|
||||
:success (cl-function
|
||||
(lambda (&key data &allow-other-keys)
|
||||
(funcall callback data))))))
|
||||
|
||||
|
||||
;;;
|
||||
;;; Specific API endpoints
|
||||
;;;
|
||||
|
||||
;; Users
|
||||
|
||||
(defun slack/users (cb)
|
||||
"Returns users as (id . name) pairs"
|
||||
(slack/get
|
||||
"users.list"
|
||||
(lambda (data)
|
||||
(->> data
|
||||
(assoc-default 'members)
|
||||
->list
|
||||
(-map (lambda (user)
|
||||
(cons (assoc-default 'id user)
|
||||
(assoc-default 'real_name user))))
|
||||
(-filter #'cdr)
|
||||
(funcall cb)))))
|
||||
|
||||
(comment
|
||||
(slack/get
|
||||
"users.list"
|
||||
(lambda (data) (setq response-data data)))
|
||||
|
||||
(slack/users (lambda (data) (setq --users data)))
|
||||
|
||||
)
|
||||
|
||||
;; Conversations
|
||||
|
||||
(defun slack/conversation-types ()
|
||||
(->>
|
||||
(list (when slack/include-public-channels "public_channel")
|
||||
(when slack/include-private-channels "private_channel")
|
||||
(when slack/include-im "im")
|
||||
(when slack/include-mpim "mpim"))
|
||||
(-filter #'identity)
|
||||
(s-join ",")))
|
||||
|
||||
(defun channel-label (chan users-alist)
|
||||
(cond
|
||||
((json-truthy? (assoc-default 'is_channel chan))
|
||||
(format "#%s" (assoc-default 'name chan)))
|
||||
((json-truthy? (assoc-default 'is_im chan))
|
||||
(let ((user-id (assoc-default 'user chan)))
|
||||
(format "Private message with %s" (assoc-default user-id users-alist))))
|
||||
((json-truthy? (assoc-default 'is_mpim chan))
|
||||
(->> chan
|
||||
(assoc-default 'purpose)
|
||||
(assoc-default 'value)))))
|
||||
|
||||
(defun slack/conversations (cb)
|
||||
"Calls `cb' with (id . '((label . \"label\") '(topic . \"topic\") '(purpose . \"purpose\"))) pairs"
|
||||
(slack/get
|
||||
"conversations.list"
|
||||
`(("types" . ,(slack/conversation-types))
|
||||
("exclude-archived" . "true"))
|
||||
(lambda (data)
|
||||
(setq --data data)
|
||||
(slack/users
|
||||
(lambda (users)
|
||||
(->> data
|
||||
(assoc-default 'channels)
|
||||
->list
|
||||
(-map
|
||||
(lambda (chan)
|
||||
(cons (assoc-default 'id chan)
|
||||
`((label . ,(channel-label chan users))
|
||||
(topic . ,(->> chan
|
||||
(assoc-default 'topic)
|
||||
(assoc-default 'value)))
|
||||
(purpose . ,(->> chan
|
||||
(assoc-default 'purpose)
|
||||
(assoc-default 'value)))))))
|
||||
(funcall cb)))))))
|
||||
|
||||
(comment
|
||||
(slack/get
|
||||
"conversations.list"
|
||||
'(("types" . "public_channel,private_channel,im,mpim"))
|
||||
(lambda (data) (setq response-data data)))
|
||||
|
||||
(slack/get
|
||||
"conversations.list"
|
||||
'(("types" . "im"))
|
||||
(lambda (data) (setq response-data data)))
|
||||
|
||||
(slack/conversations
|
||||
(lambda (convos) (setq --conversations convos)))
|
||||
|
||||
)
|
||||
|
||||
;; Messages
|
||||
|
||||
(cl-defun slack/post-message
|
||||
(&key text channel-id (on-success #'identity))
|
||||
(slack/post "chat.postMessage"
|
||||
`((text . ,text)
|
||||
(channel . ,channel-id)
|
||||
(as_user . t))
|
||||
on-success))
|
||||
|
||||
(comment
|
||||
|
||||
(slack/post-message
|
||||
:text "hi slackbot"
|
||||
:channel-id slackbot-channel-id
|
||||
:on-success (lambda (data) (setq resp data)))
|
||||
|
||||
)
|
||||
|
||||
;;;
|
||||
;;; Posting code snippets to slack
|
||||
;;;
|
||||
|
||||
(defun prompt-for-channel (cb)
|
||||
(slack/conversations
|
||||
(lambda (conversations)
|
||||
(ivy-read
|
||||
"Select channel: "
|
||||
;; TODO want to potentially use purpose / topic stuff here
|
||||
(->> conversations
|
||||
(-filter (lambda (c) (assoc-default 'label (cdr c))))
|
||||
(-map (lambda (chan) (let ((label (assoc-default 'label (cdr chan)))
|
||||
(id (car chan)))
|
||||
(propertize label 'channel-id id)))))
|
||||
:history 'slack/channel-history
|
||||
:action (lambda (selected)
|
||||
(let ((channel-id (get-text-property 0 'channel-id selected)))
|
||||
(funcall cb channel-id)
|
||||
(message "Sent message to %s" selected))))))
|
||||
nil)
|
||||
|
||||
(comment
|
||||
(prompt-for-channel #'message)
|
||||
(->> --convos
|
||||
(-filter (lambda (c) (assoc-default 'label (cdr c))))
|
||||
(-map (lambda (chan) (let ((label (assoc-default 'label (cdr chan)))
|
||||
(id (car chan)))
|
||||
(propertize label 'channel-id id)))))
|
||||
|
||||
(->> --convos (car) (cdr) (assoc-default 'label))
|
||||
)
|
||||
|
||||
(defun slack-send-code-snippet (&optional snippet-text)
|
||||
(interactive
|
||||
(list (buffer-substring-no-properties (mark) (point))))
|
||||
(prompt-for-channel
|
||||
(lambda (channel-id)
|
||||
(slack/post-message
|
||||
:text (format "```\n%s```" snippet-text)
|
||||
:channel-id channel-id))))
|
||||
|
||||
(provide 'slack-snippets)
|
||||
24
users/grfn/emacs.d/slack.el
Normal file
24
users/grfn/emacs.d/slack.el
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
;;; -*- lexical-binding: t; -*-
|
||||
|
||||
(after! slack
|
||||
(set-face-foreground 'slack-message-output-header +solarized-s-base01)
|
||||
(set-face-attribute 'slack-message-output-header nil :underline nil)
|
||||
(set-face-attribute 'slack-message-output-text nil :height 1.0))
|
||||
|
||||
(require 'slack)
|
||||
(setq slack-buffer-emojify 't
|
||||
slack-prefer-current-team 't
|
||||
slack-thread-also-send-to-room nil)
|
||||
|
||||
(set-popup-rule! "^\\*Slack"
|
||||
:quit nil
|
||||
:select t
|
||||
:side 'bottom
|
||||
:ttl nil
|
||||
:size 0.5)
|
||||
|
||||
(add-hook #'slack-message-buffer-mode-hook
|
||||
(lambda () (toggle-truncate-lines -1)))
|
||||
|
||||
(map! (:map slack-message-buffer-mode-map
|
||||
:n "q" #'delete-window))
|
||||
5
users/grfn/emacs.d/snippets/haskell-mode/annotation
Normal file
5
users/grfn/emacs.d/snippets/haskell-mode/annotation
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# key: ann
|
||||
# name: annotation
|
||||
# expand-env: ((yas-indent-line 'fixed))
|
||||
# --
|
||||
{-# ANN ${1:module} ("${2:HLint: ignore ${3:Reduce duplication}}" :: String) #-}
|
||||
26
users/grfn/emacs.d/snippets/haskell-mode/benchmark-module
Normal file
26
users/grfn/emacs.d/snippets/haskell-mode/benchmark-module
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# key: bench
|
||||
# name: benchmark-module
|
||||
# expand-env: ((yas-indent-line (quote fixed)))
|
||||
# --
|
||||
--------------------------------------------------------------------------------
|
||||
module ${1:`(if (not buffer-file-name) "Module"
|
||||
(let ((name (file-name-sans-extension (buffer-file-name)))
|
||||
(case-fold-search nil))
|
||||
(if (cl-search "bench/" name)
|
||||
(replace-regexp-in-string "/" "."
|
||||
(replace-regexp-in-string "^\/[^A-Z]*" ""
|
||||
(car (last (split-string name "src")))))
|
||||
(file-name-nondirectory name))))`} ( benchmark, main ) where
|
||||
--------------------------------------------------------------------------------
|
||||
import Bench.Prelude
|
||||
--------------------------------------------------------------------------------
|
||||
import ${1:$(s-chop-suffix "Bench" yas-text)}
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
main :: IO ()
|
||||
main = defaultMain [benchmark]
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
benchmark :: Benchmark
|
||||
benchmark = bgroup "${1:$(->> yas-text (s-chop-suffix "Bench") (s-split ".") -last-item)}" [bench "something dumb" $ nf (1 +) (1 :: Int)]
|
||||
5
users/grfn/emacs.d/snippets/haskell-mode/header
Normal file
5
users/grfn/emacs.d/snippets/haskell-mode/header
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# key: hh
|
||||
# name: header
|
||||
# expand-env: ((yas-indent-line 'fixed))
|
||||
# --
|
||||
--------------------------------------------------------------------------------$2
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# key: gen
|
||||
# name: Hedgehog Generator
|
||||
# expand-env: ((yas-indent-line (quote fixed)))
|
||||
# --
|
||||
gen${1:Foo} :: Gen $1
|
||||
gen$1 = do
|
||||
$2
|
||||
pure $1{..}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# -*- mode: snippet -*-
|
||||
# name: Hedgehog Property
|
||||
# key: hprop
|
||||
# expand-env: ((yas-indent-line 'fixed))
|
||||
# --
|
||||
hprop_${1:somethingIsAlwaysTrue} :: Property
|
||||
hprop_$1 = property $ do
|
||||
${2:x} <- forAll ${3:Gen.int $ Range.linear 1 100}
|
||||
${4:x === x}
|
||||
8
users/grfn/emacs.d/snippets/haskell-mode/hlint
Normal file
8
users/grfn/emacs.d/snippets/haskell-mode/hlint
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# -*- mode: snippet -*-
|
||||
# name: hlint
|
||||
# uuid:
|
||||
# expand-env: ((yas-indent-line 'fixed))
|
||||
# key: hlint
|
||||
# condition: t
|
||||
# --
|
||||
{-# ANN module ("Hlint: ignore $1" :: String) #- }
|
||||
4
users/grfn/emacs.d/snippets/haskell-mode/import-i
Normal file
4
users/grfn/emacs.d/snippets/haskell-mode/import-i
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# key: i
|
||||
# name: import-i
|
||||
# --
|
||||
import ${1:Prelude}
|
||||
6
users/grfn/emacs.d/snippets/haskell-mode/inl
Normal file
6
users/grfn/emacs.d/snippets/haskell-mode/inl
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# -*- mode: snippet -*-
|
||||
# name: inl
|
||||
# key: inl
|
||||
# expand-env: ((yas-indent-line 'fixed))
|
||||
# --
|
||||
{-# INLINE $1 #-}
|
||||
5
users/grfn/emacs.d/snippets/haskell-mode/inline
Normal file
5
users/grfn/emacs.d/snippets/haskell-mode/inline
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# key: inline
|
||||
# name: inline
|
||||
# expand-env: ((yas-indent-line 'fixed))
|
||||
# --
|
||||
{-# INLINE $1 #-}
|
||||
6
users/grfn/emacs.d/snippets/haskell-mode/language pragma
Normal file
6
users/grfn/emacs.d/snippets/haskell-mode/language pragma
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# -*- mode: snippet -*-
|
||||
# name: language pragma
|
||||
# key: lang
|
||||
# expand-env: ((yas-indent-line 'fixed))
|
||||
# --
|
||||
{-# LANGUAGE $1 #-}
|
||||
7
users/grfn/emacs.d/snippets/haskell-mode/lens.field
Normal file
7
users/grfn/emacs.d/snippets/haskell-mode/lens.field
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# -*- mode: snippet -*-
|
||||
# name: lens.field
|
||||
# key: lens
|
||||
# expand-env: ((yas-indent-line 'fixed))
|
||||
# --
|
||||
${1:field} :: Lens' ${2:Source} ${3:Target}
|
||||
$1 = lens _${4:sourceField} $ \\${2:$(-> yas-text s-word-initials s-downcase)} ${4:$(-> yas-text s-word-initials s-downcase)} -> ${2:$(-> yas-text s-word-initials s-downcase)} { _$4 = ${4:$(-> yas-text s-word-initials s-downcase)} }
|
||||
32
users/grfn/emacs.d/snippets/haskell-mode/module
Normal file
32
users/grfn/emacs.d/snippets/haskell-mode/module
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# -*- mode: snippet -*-
|
||||
# key: module
|
||||
# name: module
|
||||
# condition: (= (length "module") (current-column))
|
||||
# expand-env: ((yas-indent-line 'fixed))
|
||||
# contributor: Luke Hoersten <luke@hoersten.org>
|
||||
# --
|
||||
--------------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : $1
|
||||
-- Description : $2
|
||||
-- Maintainer : Griffin Smith <grfn@urbint.com>
|
||||
-- Maturity : ${3:Draft, Usable, Maintained, OR MatureAF}
|
||||
--
|
||||
-- $4
|
||||
--------------------------------------------------------------------------------
|
||||
module ${1:`(if (not buffer-file-name) "Module"
|
||||
(let ((name (file-name-sans-extension (buffer-file-name)))
|
||||
(case-fold-search nil))
|
||||
(if (or (cl-search "src/" name)
|
||||
(cl-search "test/" name))
|
||||
(replace-regexp-in-string "/" "."
|
||||
(replace-regexp-in-string "^\/[^A-Z]*" ""
|
||||
(car (last (split-string name "src")))))
|
||||
(file-name-nondirectory name))))`}
|
||||
(
|
||||
) where
|
||||
--------------------------------------------------------------------------------
|
||||
import Prelude
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
$0
|
||||
6
users/grfn/emacs.d/snippets/haskell-mode/shut up, hlint
Normal file
6
users/grfn/emacs.d/snippets/haskell-mode/shut up, hlint
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# -*- mode: snippet -*-
|
||||
# name: shut up, hlint
|
||||
# key: dupl
|
||||
# expand-env: ((yas-indent-line 'fixed))
|
||||
# --
|
||||
{-# ANN module ("HLint: ignore Reduce duplication" :: String) #-}
|
||||
22
users/grfn/emacs.d/snippets/haskell-mode/test-module
Normal file
22
users/grfn/emacs.d/snippets/haskell-mode/test-module
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# -*- mode: snippet -*-
|
||||
# name: test-module
|
||||
# key: test
|
||||
# expand-env: ((yas-indent-line 'fixed))
|
||||
# --
|
||||
{-# LANGUAGE ApplicativeDo #-}
|
||||
--------------------------------------------------------------------------------
|
||||
module ${1:`(if (not buffer-file-name) "Module"
|
||||
(let ((name (file-name-sans-extension (buffer-file-name)))
|
||||
(case-fold-search nil))
|
||||
(if (cl-search "test/" name)
|
||||
(replace-regexp-in-string "/" "."
|
||||
(replace-regexp-in-string "^\/[^A-Z]*" ""
|
||||
(car (last (split-string name "src")))))
|
||||
(file-name-nondirectory name))))`} where
|
||||
--------------------------------------------------------------------------------
|
||||
import Test.Prelude
|
||||
import qualified Hedgehog.Gen as Gen
|
||||
import qualified Hedgehog.Range as Range
|
||||
--------------------------------------------------------------------------------
|
||||
import ${1:$(s-chop-suffix "Test" yas-text)}
|
||||
--------------------------------------------------------------------------------
|
||||
6
users/grfn/emacs.d/snippets/haskell-mode/undefined
Normal file
6
users/grfn/emacs.d/snippets/haskell-mode/undefined
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# -*- mode: snippet -*-
|
||||
# name: undefined
|
||||
# key: u
|
||||
# expand-env: ((yas-indent-line 'fixed) (yas-wrap-around-region 'nil))
|
||||
# --
|
||||
undefined$1
|
||||
4
users/grfn/emacs.d/snippets/js2-mode/action-type
Normal file
4
users/grfn/emacs.d/snippets/js2-mode/action-type
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# key: at
|
||||
# name: action-type
|
||||
# --
|
||||
export const ${1:FOO_BAR$(->> yas-text s-upcase (s-replace-all '(("-" . "_") (" " . "_"))))}: '${3:ns}/${1:$(-> yas-text s-dashed-words)}' = '$3/${1:$(-> yas-text s-dashed-words)}'$5
|
||||
7
users/grfn/emacs.d/snippets/js2-mode/before
Normal file
7
users/grfn/emacs.d/snippets/js2-mode/before
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# -*- mode: snippet -*-
|
||||
# name: before
|
||||
# key: bef
|
||||
# --
|
||||
before(function() {
|
||||
$1
|
||||
})
|
||||
7
users/grfn/emacs.d/snippets/js2-mode/context
Normal file
7
users/grfn/emacs.d/snippets/js2-mode/context
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# -*- mode: snippet -*-
|
||||
# name: context
|
||||
# key: context
|
||||
# --
|
||||
context('$1', function() {
|
||||
$2
|
||||
})
|
||||
6
users/grfn/emacs.d/snippets/js2-mode/describe
Normal file
6
users/grfn/emacs.d/snippets/js2-mode/describe
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# key: desc
|
||||
# name: describe
|
||||
# --
|
||||
describe('$1', () => {
|
||||
$2
|
||||
})
|
||||
5
users/grfn/emacs.d/snippets/js2-mode/expect
Normal file
5
users/grfn/emacs.d/snippets/js2-mode/expect
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# -*- mode: snippet -*-
|
||||
# name: expect
|
||||
# key: ex
|
||||
# --
|
||||
expect($1).$2
|
||||
6
users/grfn/emacs.d/snippets/js2-mode/function
Normal file
6
users/grfn/emacs.d/snippets/js2-mode/function
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# key: f
|
||||
# name: function
|
||||
# --
|
||||
function $1($2) {
|
||||
$3
|
||||
}
|
||||
6
users/grfn/emacs.d/snippets/js2-mode/header
Normal file
6
users/grfn/emacs.d/snippets/js2-mode/header
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# -*- mode: snippet -*-
|
||||
# name: header
|
||||
# key: hh
|
||||
# expand-env: ((yas-indent-line 'fixed))
|
||||
# --
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
7
users/grfn/emacs.d/snippets/js2-mode/it
Normal file
7
users/grfn/emacs.d/snippets/js2-mode/it
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# -*- mode: snippet -*-
|
||||
# name: it
|
||||
# key: it
|
||||
# --
|
||||
it('$1', () => {
|
||||
$2
|
||||
})
|
||||
5
users/grfn/emacs.d/snippets/js2-mode/it-pending
Normal file
5
users/grfn/emacs.d/snippets/js2-mode/it-pending
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# -*- mode: snippet -*-
|
||||
# name: it-pending
|
||||
# key: xi
|
||||
# --
|
||||
it('$1')$0
|
||||
12
users/grfn/emacs.d/snippets/js2-mode/module
Normal file
12
users/grfn/emacs.d/snippets/js2-mode/module
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# key: module
|
||||
# name: module
|
||||
# expand-env: ((yas-indent-line (quote fixed)))
|
||||
# condition: (= (length "module") (current-column))
|
||||
# --
|
||||
/**
|
||||
* @fileOverview $1
|
||||
* @name ${2:`(file-name-nondirectory (buffer-file-name))`}
|
||||
* @author Griffin Smith
|
||||
* @license Proprietary
|
||||
*/
|
||||
$3
|
||||
7
users/grfn/emacs.d/snippets/js2-mode/record
Normal file
7
users/grfn/emacs.d/snippets/js2-mode/record
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# -*- mode: snippet -*-
|
||||
# name: record
|
||||
# key: rec
|
||||
# --
|
||||
export default class $1 extends Record({
|
||||
$2
|
||||
}) {}
|
||||
7
users/grfn/emacs.d/snippets/js2-mode/test
Normal file
7
users/grfn/emacs.d/snippets/js2-mode/test
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# -*- mode: snippet -*-
|
||||
# name: test
|
||||
# key: test
|
||||
# --
|
||||
test('$1', () => {
|
||||
$2
|
||||
})
|
||||
12
users/grfn/emacs.d/snippets/nix-mode/fetchFromGitHub
Normal file
12
users/grfn/emacs.d/snippets/nix-mode/fetchFromGitHub
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# -*- mode: snippet -*-
|
||||
# name: fetchFromGitHub
|
||||
# uuid:
|
||||
# key: fetchFromGitHub
|
||||
# condition: t
|
||||
# --
|
||||
fetchFromGitHub {
|
||||
owner = "$1";
|
||||
repo = "$2";
|
||||
rev = "$3";
|
||||
sha256 = "0000000000000000000000000000000000000000000000000000";
|
||||
}
|
||||
16
users/grfn/emacs.d/snippets/nix-mode/pythonPackage
Normal file
16
users/grfn/emacs.d/snippets/nix-mode/pythonPackage
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# key: pypkg
|
||||
# name: pythonPackage
|
||||
# condition: t
|
||||
# --
|
||||
${1:pname} = buildPythonPackage rec {
|
||||
name = "\${pname}-\${version}";
|
||||
pname = "$1";
|
||||
version = "${2:1.0.0}";
|
||||
src = fetchPypi {
|
||||
inherit pname version;
|
||||
sha256 = "0000000000000000000000000000000000000000000000000000";
|
||||
};
|
||||
propagatedBuildInputs = with pythonSelf; [
|
||||
$3
|
||||
];
|
||||
};
|
||||
7
users/grfn/emacs.d/snippets/nix-mode/sha256
Normal file
7
users/grfn/emacs.d/snippets/nix-mode/sha256
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# -*- mode: snippet -*-
|
||||
# name: sha256
|
||||
# uuid:
|
||||
# key: sha256
|
||||
# condition: t
|
||||
# --
|
||||
sha256 = "0000000000000000000000000000000000000000000000000000";
|
||||
6
users/grfn/emacs.d/snippets/org-mode/SQL source block
Normal file
6
users/grfn/emacs.d/snippets/org-mode/SQL source block
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# key: sql
|
||||
# name: SQL source block
|
||||
# --
|
||||
#+BEGIN_SRC sql ${1::async}
|
||||
$2
|
||||
#+END_SRC
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue