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:
Griffin Smith 2021-04-11 17:53:27 -04:00 committed by glittershark
parent 968effb5dc
commit 6266c5d32f
362 changed files with 52 additions and 56 deletions

3
users/grfn/OWNERS Normal file
View file

@ -0,0 +1,3 @@
inherited: false
owners:
- grfn

View file

@ -0,0 +1 @@
eval "$(lorri direnv)"

1
users/grfn/achilles/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

868
users/grfn/achilles/Cargo.lock generated Normal file
View 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"

View 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
View file

@ -0,0 +1,7 @@
*.ll
*.o
functions
simple
externs
units

View 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

View file

@ -0,0 +1,5 @@
extern puts : fn cstring -> int
fn main =
let _ = puts "foobar"
in 0

View 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

View file

@ -0,0 +1 @@
fn main = let x = 2; y = 3 in x + y

View 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

View 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;
}

View 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
];
}

View 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)?,
}),
}
}
}

View 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")),
})))
}
}
}

View 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);
}
}

View 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())
}
}

View 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(())
}
}

View 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)
}
}

View 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(())
}
}

View 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;

View 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
}
}

View 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>;

View 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};

View 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);
}
}

View 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));
}
}

View 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>;

View 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);
}
}

View 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
}
}

View 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()?),
}
}

View 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")
}
)
}
}
}

View 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
}};
}

View 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),
},
}
)
}
}

View 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())),
})
)
}
}

View 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(())
}
}

View 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!(),
}
}
}

View 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);
}
}

View file

@ -0,0 +1 @@
pub(crate) mod hir;

View 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");
}
}

View 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");
}
}

File diff suppressed because it is too large Load diff

View 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))

Binary file not shown.

2
users/grfn/emacs.d/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
.authinfo.gpg
+private.el

View 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))

View 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)

View 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)))))))

View 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)

View 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

File diff suppressed because it is too large Load diff

39
users/grfn/emacs.d/cpp.el Normal file
View 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)
)

View 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)

View 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)))

View 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
View 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
View 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
View 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)
)

View 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))

View 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
View 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)))

View 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

View 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)

View 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"
)
)

View 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)
)

View 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+)

View 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))

View 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)

View 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)

View 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))

View 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) #-}

View 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)]

View file

@ -0,0 +1,5 @@
# key: hh
# name: header
# expand-env: ((yas-indent-line 'fixed))
# --
--------------------------------------------------------------------------------$2

View file

@ -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{..}

View file

@ -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}

View 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) #- }

View file

@ -0,0 +1,4 @@
# key: i
# name: import-i
# --
import ${1:Prelude}

View file

@ -0,0 +1,6 @@
# -*- mode: snippet -*-
# name: inl
# key: inl
# expand-env: ((yas-indent-line 'fixed))
# --
{-# INLINE $1 #-}

View file

@ -0,0 +1,5 @@
# key: inline
# name: inline
# expand-env: ((yas-indent-line 'fixed))
# --
{-# INLINE $1 #-}

View file

@ -0,0 +1,6 @@
# -*- mode: snippet -*-
# name: language pragma
# key: lang
# expand-env: ((yas-indent-line 'fixed))
# --
{-# LANGUAGE $1 #-}

View 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)} }

View 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

View 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) #-}

View 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)}
--------------------------------------------------------------------------------

View file

@ -0,0 +1,6 @@
# -*- mode: snippet -*-
# name: undefined
# key: u
# expand-env: ((yas-indent-line 'fixed) (yas-wrap-around-region 'nil))
# --
undefined$1

View 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

View file

@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: before
# key: bef
# --
before(function() {
$1
})

View file

@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: context
# key: context
# --
context('$1', function() {
$2
})

View file

@ -0,0 +1,6 @@
# key: desc
# name: describe
# --
describe('$1', () => {
$2
})

View file

@ -0,0 +1,5 @@
# -*- mode: snippet -*-
# name: expect
# key: ex
# --
expect($1).$2

View file

@ -0,0 +1,6 @@
# key: f
# name: function
# --
function $1($2) {
$3
}

View file

@ -0,0 +1,6 @@
# -*- mode: snippet -*-
# name: header
# key: hh
# expand-env: ((yas-indent-line 'fixed))
# --
////////////////////////////////////////////////////////////////////////////////

View file

@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: it
# key: it
# --
it('$1', () => {
$2
})

View file

@ -0,0 +1,5 @@
# -*- mode: snippet -*-
# name: it-pending
# key: xi
# --
it('$1')$0

View 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

View file

@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: record
# key: rec
# --
export default class $1 extends Record({
$2
}) {}

View file

@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: test
# key: test
# --
test('$1', () => {
$2
})

View file

@ -0,0 +1,12 @@
# -*- mode: snippet -*-
# name: fetchFromGitHub
# uuid:
# key: fetchFromGitHub
# condition: t
# --
fetchFromGitHub {
owner = "$1";
repo = "$2";
rev = "$3";
sha256 = "0000000000000000000000000000000000000000000000000000";
}

View 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
];
};

View file

@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: sha256
# uuid:
# key: sha256
# condition: t
# --
sha256 = "0000000000000000000000000000000000000000000000000000";

View 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