Compare commits
8 Commits
3ce75cfed8
...
bcee1cf160
Author | SHA1 | Date |
---|---|---|
mat ess | bcee1cf160 | |
mat ess | e84b351daa | |
mat ess | 501dfff220 | |
mat ess | a1e5aef811 | |
mat ess | 8af9855a27 | |
mat ess | 2ccc3e8ef1 | |
mat ess | 056f6d58bf | |
mat ess | d3a8c822fb |
|
@ -13,9 +13,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.71"
|
version = "1.0.72"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ascii-canvas"
|
name = "ascii-canvas"
|
||||||
|
@ -207,9 +207,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lalrpop"
|
name = "lalrpop"
|
||||||
version = "0.19.9"
|
version = "0.20.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f34313ec00c2eb5c3c87ca6732ea02dcf3af99c3ff7a8fb622ffb99c9d860a87"
|
checksum = "da4081d44f4611b66c6dd725e6de3169f9f63905421e8626fcb86b6a898998b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ascii-canvas",
|
"ascii-canvas",
|
||||||
"bit-set",
|
"bit-set",
|
||||||
|
@ -221,7 +221,7 @@ dependencies = [
|
||||||
"petgraph",
|
"petgraph",
|
||||||
"pico-args",
|
"pico-args",
|
||||||
"regex",
|
"regex",
|
||||||
"regex-syntax 0.6.29",
|
"regex-syntax",
|
||||||
"string_cache",
|
"string_cache",
|
||||||
"term",
|
"term",
|
||||||
"tiny-keccak",
|
"tiny-keccak",
|
||||||
|
@ -230,9 +230,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lalrpop-util"
|
name = "lalrpop-util"
|
||||||
version = "0.19.9"
|
version = "0.20.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e5c1f7869c94d214466c5fd432dfed12c379fd87786768d36455892d46b18edd"
|
checksum = "3f35c735096c0293d313e8f2a641627472b83d01b937177fe76e5e2708d31e0d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
@ -281,7 +281,9 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"lalrpop",
|
"lalrpop",
|
||||||
"lalrpop-util",
|
"lalrpop-util",
|
||||||
|
"pico-args",
|
||||||
"regex",
|
"regex",
|
||||||
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -340,9 +342,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pico-args"
|
name = "pico-args"
|
||||||
version = "0.4.2"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
|
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "precomputed-hash"
|
name = "precomputed-hash"
|
||||||
|
@ -352,18 +354,18 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.56"
|
version = "1.0.66"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
|
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.26"
|
version = "1.0.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
|
checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
@ -396,15 +398,9 @@ checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-syntax 0.7.1",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-syntax"
|
|
||||||
version = "0.6.29"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
@ -464,9 +460,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.15"
|
version = "2.0.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
|
checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -486,18 +482,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.40"
|
version = "1.0.43"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
|
checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.40"
|
version = "1.0.43"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
|
@ -4,9 +4,11 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
lalrpop = "0.19.9"
|
lalrpop = "0.20.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.71"
|
anyhow = "1.0.72"
|
||||||
lalrpop-util = { version = "0.19.9", features = ["lexer"] }
|
lalrpop-util = { version = "0.20.0", features = ["lexer"] }
|
||||||
|
pico-args = { version = "0.5.0", features = ["eq-separator", "short-space-opt", "combined-flags"] }
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
thiserror = "1.0.43"
|
||||||
|
|
36
README.md
36
README.md
|
@ -1,37 +1,9 @@
|
||||||
# mul
|
# mul
|
||||||
|
|
||||||
mat's untitled language
|
## mat's untitled language
|
||||||
|
|
||||||
## goals
|
see [`example.mul`](/example.mul) for a syntax sample.
|
||||||
|
|
||||||
### start simple and grow
|
see [`GOALS.md`](/docs/GOALS.md) for guiding principles and inspirations.
|
||||||
|
|
||||||
while mul's implementation is still getting started, i want to implement features atomically, one at a time, in an end to end manner.
|
see [`ROADMAP.md`](/docs/ROADMAP.md) for specific implementation plans and progress.
|
||||||
|
|
||||||
for example, we'll start with a simple system-f/bidirectional system, implement a parser, typechecker, interpreter, and code generator, then gradually layer features onto that, supporting them at every "level" of the compiler.
|
|
||||||
|
|
||||||
### orthogonal and discoverable
|
|
||||||
|
|
||||||
avoid overly redundant or unrelated functionality. make the core set of functionality well documented and easy to inspect. taking cues here from [fish shell](https://fishshell.com/docs/current/design.html).
|
|
||||||
|
|
||||||
### robust static type system
|
|
||||||
|
|
||||||
without going to the extent of dependent types, mul's type system should be expressive and flexible.
|
|
||||||
|
|
||||||
### application-friendly ergonomics without sacrificing performance
|
|
||||||
|
|
||||||
mul will take cues from visions for a "smaller" rust that is able to focus on a different set of problems by not specializing in systems programming.
|
|
||||||
|
|
||||||
[Notes on a smaller Rust](https://without.boats/blog/notes-on-a-smaller-rust/)
|
|
||||||
|
|
||||||
[Revisiting a 'smaller Rust'](https://without.boats/blog/revisiting-a-smaller-rust/)
|
|
||||||
|
|
||||||
[Rust's Ugly Syntax](https://matklad.github.io/2023/01/26/rusts-ugly-syntax.html)
|
|
||||||
|
|
||||||
in short, we want to take parts of rust's syntax and semantics that work well and are amenable to both machine and human analysis, while leaving behind the components that are designed around control of memory layout + representation, explicit mentions of ownership and borrowing, and other more complicated concepts.
|
|
||||||
|
|
||||||
while not prioritizing it in the early implementation, mul will try to learn from rust (and its progeny) to sacrifice as little performance as possible while still offering a substantially simpler interface.
|
|
||||||
|
|
||||||
### incremental, interactive, intelligent
|
|
||||||
|
|
||||||
parsing, typechecking, evaluating, compiling, etc should be usable across a batch compiler, interactive REPL, and IDE tooling. program information should be preserved where possible.
|
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
# goals
|
||||||
|
|
||||||
|
## learning!
|
||||||
|
|
||||||
|
the primary goal with mul is for me to work through implementing a compiler, something i have long wanted to do, but never actually succeeded in.
|
||||||
|
|
||||||
|
## start simple and grow
|
||||||
|
|
||||||
|
while mul's implementation is still getting started, i want to implement features atomically, one at a time, in an end to end manner.
|
||||||
|
|
||||||
|
for example, we'll start with a simple system-f/bidirectional system, implement a parser, typechecker, interpreter, and code generator, then gradually layer features onto that, supporting them at every "level" of the compiler.
|
||||||
|
|
||||||
|
## orthogonal and discoverable
|
||||||
|
|
||||||
|
avoid overly redundant or unrelated functionality. make the core set of functionality well documented and easy to inspect.
|
||||||
|
|
||||||
|
inspiration:
|
||||||
|
- [fish shell design](https://fishshell.com/docs/current/design.html)
|
||||||
|
|
||||||
|
## robust static type system
|
||||||
|
|
||||||
|
without going to the extent of dependent types, mul's type system should be expressive and flexible.
|
||||||
|
|
||||||
|
### experiment with capabilities and/or effects
|
||||||
|
|
||||||
|
one specific feature of the type system will be a focus on safety and security through control of side effects. two interesting avenues to explore for these aspects will be capabilities and effect handling.
|
||||||
|
|
||||||
|
capabilities:
|
||||||
|
- [Lambda Capabilities](https://roscidus.com/blog/blog/2023/04/26/lambda-capabilities)
|
||||||
|
- [What Are Capabilities?](http://habitatchronicles.com/2017/05/what-are-capabilities/)
|
||||||
|
- [Spritely - What is CapTP, and what does it enable?](https://spritelyproject.org/news/what-is-captp.html)
|
||||||
|
- [Cap'n Proto](https://capnproto.org/)
|
||||||
|
|
||||||
|
effects:
|
||||||
|
- [Unison's Introduction to Abilities: A Mental Model](https://www.unison-lang.org/learn/fundamentals/abilities/)
|
||||||
|
- [Algebraic Effects for the Rest of Us](https://overreacted.io/algebraic-effects-for-the-rest-of-us/)
|
||||||
|
- [The Koka Programming Language](https://koka-lang.github.io/koka/doc/book.html)
|
||||||
|
- [Eff's An Introduction to Algebraic Effects and Handlers](https://www.eff-lang.org/handlers-tutorial.pdf)
|
||||||
|
- [Exotic Programming Ideas: Effect Systems](https://www.stephendiehl.com/posts/exotic03.html)
|
||||||
|
|
||||||
|
open question: how should this design interact with a trait system? can/should effects subsume traits?
|
||||||
|
|
||||||
|
## application-friendly ergonomics without sacrificing performance
|
||||||
|
|
||||||
|
mul will take cues from visions for a "smaller" rust that is able to focus on a different set of problems by not specializing in systems programming.
|
||||||
|
|
||||||
|
inspiration:
|
||||||
|
- [Notes on a smaller Rust](https://without.boats/blog/notes-on-a-smaller-rust/)
|
||||||
|
- [Revisiting a 'smaller Rust'](https://without.boats/blog/revisiting-a-smaller-rust/)
|
||||||
|
- [Rust's Ugly Syntax](https://matklad.github.io/2023/01/26/rusts-ugly-syntax.html)
|
||||||
|
|
||||||
|
in short, we want to take parts of rust's syntax and semantics that work well and are amenable to both machine and human analysis, while leaving behind the components that are designed around control of memory layout + representation, explicit mentions of ownership and borrowing, and other more complicated concepts.
|
||||||
|
|
||||||
|
while not prioritizing it in the early implementation, mul will try to learn from rust (and its progeny) to sacrifice as little performance as possible while still offering a substantially simpler interface.
|
||||||
|
|
||||||
|
## incremental, interactive, intelligent
|
||||||
|
|
||||||
|
parsing, typechecking, evaluating, compiling, etc should be usable across a batch compiler, interactive REPL, and IDE tooling. program information should be preserved where possible.
|
||||||
|
|
||||||
|
inspiration:
|
||||||
|
- [Query-based compiler architectures](https://ollef.github.io/blog/posts/query-based-compilers.html)
|
|
@ -14,6 +14,9 @@
|
||||||
- [x] integers
|
- [x] integers
|
||||||
- [ ] floating point numbers
|
- [ ] floating point numbers
|
||||||
- [ ] text
|
- [ ] text
|
||||||
|
- [ ] items
|
||||||
|
- [x] function definitions
|
||||||
|
- [ ] type definitions
|
||||||
- [ ] functions
|
- [ ] functions
|
||||||
- [x] lambdas / closures
|
- [x] lambdas / closures
|
||||||
- [ ] generic functions
|
- [ ] generic functions
|
||||||
|
@ -24,5 +27,6 @@
|
||||||
- [ ] sum types
|
- [ ] sum types
|
||||||
- [ ] variant types
|
- [ ] variant types
|
||||||
- [ ] pattern matching
|
- [ ] pattern matching
|
||||||
- [ ] traits
|
- [ ] traits (?)
|
||||||
- [ ] operator overloading
|
- [ ] operator overloading
|
||||||
|
- [ ] capabilities/effects
|
||||||
|
|
15
example.mul
15
example.mul
|
@ -1,9 +1,8 @@
|
||||||
let main = || {
|
fn main() {
|
||||||
|
let sum_equals = |x, y, expected| {
|
||||||
|
let sum = _add(x, y);
|
||||||
|
_equals(sum, expected)
|
||||||
|
};
|
||||||
let result = sum_equals(1, 2, 3);
|
let result = sum_equals(1, 2, 3);
|
||||||
print(result)
|
_print(result)
|
||||||
};
|
}
|
||||||
|
|
||||||
let sum_equals = |x, y, expected| {
|
|
||||||
let sum = add(x, y);
|
|
||||||
equals(sum, expected)
|
|
||||||
};
|
|
||||||
|
|
60
flake.lock
60
flake.lock
|
@ -10,11 +10,11 @@
|
||||||
"rust-overlay": "rust-overlay"
|
"rust-overlay": "rust-overlay"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1681680516,
|
"lastModified": 1684468982,
|
||||||
"narHash": "sha256-EB8Adaeg4zgcYDJn9sR6UMjN/OHdIiMMK19+3LmmXQY=",
|
"narHash": "sha256-EoC1N5sFdmjuAP3UOkyQujSOT6EdcXTnRw8hPjJkEgc=",
|
||||||
"owner": "ipetkov",
|
"owner": "ipetkov",
|
||||||
"repo": "crane",
|
"repo": "crane",
|
||||||
"rev": "54b63c8eae4c50172cb50b612946ff1d2bc1c75c",
|
"rev": "99de890b6ef4b4aab031582125b6056b792a4a30",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -60,11 +60,11 @@
|
||||||
"nixpkgs-lib": "nixpkgs-lib"
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1680392223,
|
"lastModified": 1683560683,
|
||||||
"narHash": "sha256-n3g7QFr85lDODKt250rkZj2IFS3i4/8HBU2yKHO3tqw=",
|
"narHash": "sha256-XAygPMN5Xnk/W2c1aW0jyEa6lfMDZWlQgiNtmHXytPc=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "dcc36e45d054d7bb554c9cdab69093debd91a0b5",
|
"rev": "006c75898cf814ef9497252b022e91c946ba8e17",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -74,12 +74,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1678901627,
|
"lastModified": 1681202837,
|
||||||
"narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=",
|
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6",
|
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -126,11 +129,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1681920287,
|
"lastModified": 1684570954,
|
||||||
"narHash": "sha256-+/d6XQQfhhXVfqfLROJoqj3TuG38CAeoT6jO1g9r1k0=",
|
"narHash": "sha256-FX5y4Sm87RWwfu9PI71XFvuRpZLowh00FQpIJ1WfXqE=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "645bc49f34fa8eff95479f0345ff57e55b53437e",
|
"rev": "3005f20ce0aaa58169cdee57c8aa12e5f1b6e1b3",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -143,11 +146,11 @@
|
||||||
"nixpkgs-lib": {
|
"nixpkgs-lib": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"dir": "lib",
|
"dir": "lib",
|
||||||
"lastModified": 1680213900,
|
"lastModified": 1682879489,
|
||||||
"narHash": "sha256-cIDr5WZIj3EkKyCgj/6j3HBH4Jj1W296z7HTcWj1aMA=",
|
"narHash": "sha256-sASwo8gBt7JDnOOstnps90K1wxmVfyhsTPPNTGBPjjg=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "e3652e0735fbec227f342712f180f4f21f0594f2",
|
"rev": "da45bf6ec7bbcc5d1e14d3795c025199f28e0de0",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -185,11 +188,11 @@
|
||||||
"nixpkgs-stable": "nixpkgs-stable"
|
"nixpkgs-stable": "nixpkgs-stable"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1681831107,
|
"lastModified": 1684195081,
|
||||||
"narHash": "sha256-pXl3DPhhul9NztSetUJw2fcN+RI3sGOYgKu29xpgnqw=",
|
"narHash": "sha256-IKnQUSBhQTChFERxW2AzuauVpY1HRgeVzAjNMAA4B6I=",
|
||||||
"owner": "cachix",
|
"owner": "cachix",
|
||||||
"repo": "pre-commit-hooks.nix",
|
"repo": "pre-commit-hooks.nix",
|
||||||
"rev": "b7ca8f6fff42f6af75c17f9438fed1686b7d855d",
|
"rev": "96eabec58248ed8f4b0ad59e7ce9398018684fdc",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -218,11 +221,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1680488274,
|
"lastModified": 1683080331,
|
||||||
"narHash": "sha256-0vYMrZDdokVmPQQXtFpnqA2wEgCCUXf5a3dDuDVshn0=",
|
"narHash": "sha256-nGDvJ1DAxZIwdn6ww8IFwzoHb2rqBP4wv/65Wt5vflk=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "7ec2ff598a172c6e8584457167575b3a1a5d80d8",
|
"rev": "d59c3fa0cba8336e115b376c2d9e91053aa59e56",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -230,6 +233,21 @@
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
|
|
@ -59,6 +59,11 @@
|
||||||
clippy
|
clippy
|
||||||
# profiling
|
# profiling
|
||||||
cargo-flamegraph
|
cargo-flamegraph
|
||||||
|
# other tooling
|
||||||
|
cargo-audit
|
||||||
|
cargo-deny
|
||||||
|
cargo-edit
|
||||||
|
cargo-license
|
||||||
;
|
;
|
||||||
rustfmt = pkgs.rustfmt.overrideAttrs (old: {
|
rustfmt = pkgs.rustfmt.overrideAttrs (old: {
|
||||||
preFixup = pkgs.lib.optionalString pkgs.stdenv.isDarwin ''
|
preFixup = pkgs.lib.optionalString pkgs.stdenv.isDarwin ''
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::evaluate::{Dictionary, Value};
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub enum Builtin {
|
||||||
|
Print,
|
||||||
|
Add,
|
||||||
|
Sub,
|
||||||
|
Mul,
|
||||||
|
Div,
|
||||||
|
Equals,
|
||||||
|
GreaterThan,
|
||||||
|
LessThan,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("Tried to {0} a non-integer: {1}")]
|
||||||
|
NonInteger(&'static str, Value),
|
||||||
|
}
|
||||||
|
|
||||||
|
type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
fn unwrap_int_value(op_name: &'static str) -> impl Fn(Value) -> Result<i64> {
|
||||||
|
move |value| match value {
|
||||||
|
Value::Integer(i) => Ok(i),
|
||||||
|
_ => Err(Error::NonInteger(op_name, value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! int_op {
|
||||||
|
($arguments:expr, $op:tt, $op_name:expr) => {
|
||||||
|
Value::Integer(
|
||||||
|
$arguments
|
||||||
|
.into_iter()
|
||||||
|
.map(unwrap_int_value($op_name))
|
||||||
|
.reduce(|acc, v| Ok(acc? $op v?))
|
||||||
|
.unwrap()?,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! int_cmp {
|
||||||
|
($arguments:expr, $op:tt) => {{
|
||||||
|
let mut arguments = $arguments;
|
||||||
|
let unwrap = unwrap_int_value("compare");
|
||||||
|
let y = unwrap(arguments.pop().unwrap())?;
|
||||||
|
let x = unwrap(arguments.pop().unwrap())?;
|
||||||
|
Value::Boolean(x $op y)
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Builtin {
|
||||||
|
pub fn dictionary() -> Dictionary<Value> {
|
||||||
|
[
|
||||||
|
("_print", Builtin::Print),
|
||||||
|
("_add", Builtin::Add),
|
||||||
|
("_sub", Builtin::Sub),
|
||||||
|
("_mul", Builtin::Mul),
|
||||||
|
("_div", Builtin::Div),
|
||||||
|
("_equals", Builtin::Equals),
|
||||||
|
("_greaterthan", Builtin::GreaterThan),
|
||||||
|
("_lessthan", Builtin::LessThan),
|
||||||
|
]
|
||||||
|
.map(|(name, builtin)| (name.to_string(), Value::Builtin(builtin)))
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn min_parameters(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
Builtin::Print => 0,
|
||||||
|
Builtin::Add
|
||||||
|
| Builtin::Sub
|
||||||
|
| Builtin::Mul
|
||||||
|
| Builtin::Div
|
||||||
|
| Builtin::Equals
|
||||||
|
| Builtin::GreaterThan
|
||||||
|
| Builtin::LessThan => 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn max_parameters(&self) -> Option<usize> {
|
||||||
|
match self {
|
||||||
|
Builtin::Print
|
||||||
|
| Builtin::Add
|
||||||
|
| Builtin::Sub
|
||||||
|
| Builtin::Mul
|
||||||
|
| Builtin::Div
|
||||||
|
| Builtin::Equals => None,
|
||||||
|
Builtin::GreaterThan | Builtin::LessThan => Some(2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn call(&self, arguments: Vec<Value>) -> Result<Value> {
|
||||||
|
let result = match self {
|
||||||
|
Builtin::Print => {
|
||||||
|
let mut output = String::new();
|
||||||
|
let mut arguments = arguments.into_iter().peekable();
|
||||||
|
while let Some(term) = arguments.next() {
|
||||||
|
if arguments.peek().is_some() {
|
||||||
|
write!(output, "{term} ").expect("Error during printing");
|
||||||
|
} else {
|
||||||
|
write!(output, "{term}").expect("Error during printing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("{output}");
|
||||||
|
Value::Unit
|
||||||
|
}
|
||||||
|
Builtin::Add => int_op!(arguments, +, "add"),
|
||||||
|
Builtin::Sub => int_op!(arguments, -, "subtract"),
|
||||||
|
Builtin::Mul => int_op!(arguments, *, "multiply"),
|
||||||
|
Builtin::Div => int_op!(arguments, /, "divide"),
|
||||||
|
Builtin::Equals => {
|
||||||
|
let mut arguments = arguments;
|
||||||
|
let y = arguments.pop().unwrap();
|
||||||
|
let x = arguments.pop().unwrap();
|
||||||
|
let b = match (x, y) {
|
||||||
|
(Value::Integer(x), Value::Integer(y)) => x == y,
|
||||||
|
(Value::Boolean(x), Value::Boolean(y)) => x == y,
|
||||||
|
(Value::Unit, Value::Unit) => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
Value::Boolean(b)
|
||||||
|
}
|
||||||
|
Builtin::GreaterThan => int_cmp!(arguments, >),
|
||||||
|
Builtin::LessThan => int_cmp!(arguments, <),
|
||||||
|
};
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
use std::{ffi::OsString, path::PathBuf};
|
||||||
|
|
||||||
|
use pico_args::Arguments;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub const USAGE: &str = "
|
||||||
|
USAGE:
|
||||||
|
mul [-h|--help] (parse <FILE> | evaluate <FILE> | repl)
|
||||||
|
|
||||||
|
FLAGS:
|
||||||
|
-h, --help Prints help information (this text)
|
||||||
|
|
||||||
|
COMMANDS:
|
||||||
|
parse <FILE> Parse the given file and print a serialized AST to stdout
|
||||||
|
evaluate, eval <FILE> Evaluate the given file
|
||||||
|
repl Start the Read-Evaluate-Print-Loop
|
||||||
|
";
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("Problem while parsing arguments, {0}")]
|
||||||
|
ArgsError(#[from] pico_args::Error),
|
||||||
|
#[error("Invalid command '{0}'\n{USAGE}")]
|
||||||
|
InvalidCommand(String),
|
||||||
|
#[error("Unrecognized arguments {0:?}\n{USAGE}")]
|
||||||
|
UnrecognizedArguments(Vec<OsString>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Command {
|
||||||
|
Parse { path: PathBuf },
|
||||||
|
Run { path: PathBuf },
|
||||||
|
Repl,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command {
|
||||||
|
pub fn from_environment() -> Result<Option<Self>, Error> {
|
||||||
|
let mut args = Arguments::from_env();
|
||||||
|
if args.contains(["-h", "--help"]) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let cmd = match args.subcommand()?.as_deref() {
|
||||||
|
Some("parse") => {
|
||||||
|
let path = args.free_from_str()?;
|
||||||
|
Command::Parse { path }
|
||||||
|
}
|
||||||
|
Some("run") => {
|
||||||
|
let path = args.free_from_str()?;
|
||||||
|
Command::Run { path }
|
||||||
|
}
|
||||||
|
Some("repl") | None => Command::Repl,
|
||||||
|
Some(cmd) => return Err(Error::InvalidCommand(cmd.to_string())),
|
||||||
|
};
|
||||||
|
let remaining = args.finish();
|
||||||
|
if remaining.is_empty() {
|
||||||
|
Ok(Some(cmd))
|
||||||
|
} else {
|
||||||
|
Err(Error::UnrecognizedArguments(remaining))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,197 @@
|
||||||
|
use std::{collections::HashMap, fmt::Display};
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::builtins::{self, Builtin};
|
||||||
|
use crate::syntax::{Block, Expression, InteractiveEntry, Item, Name, Program};
|
||||||
|
|
||||||
|
pub type Dictionary<T> = HashMap<Name, T>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Value {
|
||||||
|
Builtin(Builtin),
|
||||||
|
Closure {
|
||||||
|
environment: Dictionary<Value>,
|
||||||
|
parameters: Vec<Name>,
|
||||||
|
result: Expression,
|
||||||
|
},
|
||||||
|
Boolean(bool),
|
||||||
|
Integer(i64),
|
||||||
|
Unit,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Value {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Value::Builtin(b) => write!(f, "<builtin:{b:?}>"),
|
||||||
|
Value::Closure { parameters, .. } => write!(f, "<closure({})>", parameters.join(", ")),
|
||||||
|
Value::Boolean(b) => write!(f, "{b}"),
|
||||||
|
Value::Integer(i) => write!(f, "{i}"),
|
||||||
|
Value::Unit => write!(f, "()"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("The name '{0} was not bound to any value in this context")]
|
||||||
|
UnboundName(Name),
|
||||||
|
#[error("Tried to call a term which was not a function")]
|
||||||
|
NotAFunction,
|
||||||
|
#[error("Mismatched number of function parameters and arguments: expected {0}, got {1}")]
|
||||||
|
ArgumentsMismatch(usize, usize),
|
||||||
|
#[error("No function called main to run")]
|
||||||
|
MissingMainFunction,
|
||||||
|
#[error("Problem with builtin call: {0}")]
|
||||||
|
BuiltinError(#[from] builtins::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Interpreter {
|
||||||
|
environment: Dictionary<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Interpreter {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Interpreter {
|
||||||
|
environment: Builtin::dictionary(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nested(environment: Dictionary<Value>) -> Self {
|
||||||
|
Interpreter { environment }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self, program: &Program) -> Result<Value> {
|
||||||
|
for item in program {
|
||||||
|
self.interpret_item(item)?;
|
||||||
|
}
|
||||||
|
if let Some(main) = self.environment.get("main").cloned() {
|
||||||
|
self.apply_call(&main, &vec![])
|
||||||
|
} else {
|
||||||
|
Err(Error::MissingMainFunction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interpret_item(&mut self, item: &Item) -> Result<Value> {
|
||||||
|
match item {
|
||||||
|
Item::Fn {
|
||||||
|
name,
|
||||||
|
parameters,
|
||||||
|
body,
|
||||||
|
} => {
|
||||||
|
let closure = Value::Closure {
|
||||||
|
environment: self.environment.clone(),
|
||||||
|
parameters: parameters.clone(),
|
||||||
|
result: Expression::Block(Box::new(body.clone())),
|
||||||
|
};
|
||||||
|
self.environment.insert(name.clone(), closure.clone());
|
||||||
|
Ok(closure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn evaluate_interactive_entry(&mut self, entry: &InteractiveEntry) -> Result<Value> {
|
||||||
|
match entry {
|
||||||
|
InteractiveEntry::Item(item) => self.interpret_item(item),
|
||||||
|
InteractiveEntry::Binding(binding) => {
|
||||||
|
self.bind(binding.name.clone(), &binding.expression)
|
||||||
|
}
|
||||||
|
InteractiveEntry::Expression(term) => self.evaluate(term),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn evaluate(&mut self, term: &Expression) -> Result<Value> {
|
||||||
|
match term {
|
||||||
|
Expression::Variable(name) => self
|
||||||
|
.environment
|
||||||
|
.get(name)
|
||||||
|
.ok_or(Error::UnboundName(name.clone()))
|
||||||
|
.cloned(),
|
||||||
|
Expression::Block(b) => self.evaluate_block(b.as_ref()),
|
||||||
|
Expression::Lambda { parameters, result } => Ok(Value::Closure {
|
||||||
|
environment: self.environment.clone(),
|
||||||
|
parameters: parameters.clone(),
|
||||||
|
result: *result.clone(),
|
||||||
|
}),
|
||||||
|
Expression::Call { callee, arguments } => {
|
||||||
|
let callee = self.evaluate(callee.as_ref())?;
|
||||||
|
self.apply_call(&callee, arguments)
|
||||||
|
}
|
||||||
|
Expression::Boolean(b) => Ok(Value::Boolean(*b)),
|
||||||
|
Expression::Integer(i) => Ok(Value::Integer(*i)),
|
||||||
|
Expression::Unit => Ok(Value::Unit),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_call(&mut self, callee: &Value, arguments: &Vec<Expression>) -> Result<Value> {
|
||||||
|
match callee {
|
||||||
|
Value::Closure {
|
||||||
|
environment,
|
||||||
|
parameters,
|
||||||
|
result,
|
||||||
|
} => {
|
||||||
|
if parameters.len() != arguments.len() {
|
||||||
|
return Err(Error::ArgumentsMismatch(parameters.len(), arguments.len()));
|
||||||
|
}
|
||||||
|
let mut nested = Interpreter::nested(environment.clone());
|
||||||
|
for (name, argument) in parameters.iter().zip(arguments) {
|
||||||
|
// we don't want arguments to refer to each other, so use the parent interpreter
|
||||||
|
self.bind_nested(name.clone(), argument, &mut nested)?;
|
||||||
|
}
|
||||||
|
nested.evaluate(result)
|
||||||
|
}
|
||||||
|
Value::Builtin(b) => {
|
||||||
|
if arguments.len() < b.min_parameters() {
|
||||||
|
return Err(Error::ArgumentsMismatch(
|
||||||
|
b.min_parameters(),
|
||||||
|
arguments.len(),
|
||||||
|
));
|
||||||
|
} else if let Some(max) = b.max_parameters() {
|
||||||
|
if arguments.len() > max {
|
||||||
|
return Err(Error::ArgumentsMismatch(max, arguments.len()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let arguments = arguments
|
||||||
|
.iter()
|
||||||
|
.map(|term| self.evaluate(term))
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
b.call(arguments).map_err(Error::BuiltinError)
|
||||||
|
}
|
||||||
|
_ => Err(Error::NotAFunction),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evaluate_block(&mut self, block: &Block) -> Result<Value> {
|
||||||
|
let mut nested = self.clone();
|
||||||
|
for binding in &block.bindings {
|
||||||
|
nested.bind(binding.name.clone(), &binding.expression)?;
|
||||||
|
}
|
||||||
|
nested.evaluate(&block.result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bind(&mut self, name: Name, term: &Expression) -> Result<Value> {
|
||||||
|
let value = self.evaluate(term)?;
|
||||||
|
self.environment.insert(name, value.clone());
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bind_nested(
|
||||||
|
&mut self,
|
||||||
|
name: Name,
|
||||||
|
term: &Expression,
|
||||||
|
nested: &mut Interpreter,
|
||||||
|
) -> Result<Value> {
|
||||||
|
let value = self.evaluate(term)?;
|
||||||
|
nested.environment.insert(name, value.clone());
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Interpreter {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,150 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use crate::syntax::{Expression, Name, Program, Statement};
|
|
||||||
|
|
||||||
pub type Environment = HashMap<Name, Value>;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Value {
|
|
||||||
Boolean(bool),
|
|
||||||
Integer(i64),
|
|
||||||
Closure(Vec<Name>, Expression, Environment),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Interpreter {
|
|
||||||
environment: Environment,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum InterpreterError {
|
|
||||||
VariableNotFound(Name),
|
|
||||||
IncorrectArgumentCount(usize, usize),
|
|
||||||
NonFunctionCall(Value),
|
|
||||||
EmptyProgram,
|
|
||||||
ExecutionError(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for InterpreterError {
|
|
||||||
fn from(s: String) -> Self {
|
|
||||||
InterpreterError::ExecutionError(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for InterpreterError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
InterpreterError::VariableNotFound(name) => write!(f, "Variable '{}' not found", name),
|
|
||||||
InterpreterError::IncorrectArgumentCount(expected, actual) => {
|
|
||||||
write!(f, "Expected {} arguments, but got {}", expected, actual)
|
|
||||||
}
|
|
||||||
InterpreterError::NonFunctionCall(value) => {
|
|
||||||
write!(f, "Function call on non-function value: {:?}", value)
|
|
||||||
}
|
|
||||||
InterpreterError::EmptyProgram => write!(f, "Program is empty"),
|
|
||||||
InterpreterError::ExecutionError(s) => write!(f, "Execution error: {}", s),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, InterpreterError>;
|
|
||||||
|
|
||||||
impl Interpreter {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
environment: Environment::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn interpret_statement(&mut self, statement: &Statement) -> Result<Value> {
|
|
||||||
match statement {
|
|
||||||
Statement::Binding(name, expression) => {
|
|
||||||
let value = self.evaluate(expression)?;
|
|
||||||
self.environment.insert(name.clone(), value.clone());
|
|
||||||
Ok(value)
|
|
||||||
}
|
|
||||||
Statement::Expression(expression) => self.evaluate(expression),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn interpret(&mut self, program: &Program) -> Result<Value> {
|
|
||||||
let mut result = None;
|
|
||||||
|
|
||||||
for statement in program {
|
|
||||||
result = Some(self.interpret_statement(statement)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Some(value) => Ok(value),
|
|
||||||
None => Err(InterpreterError::EmptyProgram),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn evaluate(&mut self, expression: &Expression) -> Result<Value> {
|
|
||||||
match expression {
|
|
||||||
Expression::Variable(name) => self
|
|
||||||
.environment
|
|
||||||
.get(name)
|
|
||||||
.cloned()
|
|
||||||
.ok_or_else(|| InterpreterError::VariableNotFound(name.clone())),
|
|
||||||
|
|
||||||
Expression::Boolean(value) => Ok(Value::Boolean(*value)),
|
|
||||||
|
|
||||||
Expression::Integer(value) => Ok(Value::Integer(*value)),
|
|
||||||
|
|
||||||
Expression::Lambda(params, body) => Ok(Value::Closure(
|
|
||||||
params.clone(),
|
|
||||||
*body.clone(),
|
|
||||||
self.environment.clone(),
|
|
||||||
)),
|
|
||||||
|
|
||||||
Expression::Call(function, args) => {
|
|
||||||
let function_value = self.evaluate(function)?;
|
|
||||||
|
|
||||||
match function_value {
|
|
||||||
Value::Closure(params, body, mut environment) => {
|
|
||||||
if params.len() != args.len() {
|
|
||||||
return Err(InterpreterError::IncorrectArgumentCount(
|
|
||||||
params.len(),
|
|
||||||
args.len(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut arg_values = Vec::new();
|
|
||||||
for arg in args {
|
|
||||||
arg_values.push(self.evaluate(arg)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind argument values to parameter names in a new environment
|
|
||||||
for (param, arg_value) in params.iter().zip(arg_values.into_iter()) {
|
|
||||||
environment.insert(param.clone(), arg_value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate the body expression in the new environment
|
|
||||||
let mut interpreter = Interpreter { environment };
|
|
||||||
interpreter.evaluate(&body)
|
|
||||||
}
|
|
||||||
_ => Err(InterpreterError::NonFunctionCall(function_value)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Expression::Block(statements, expression) => {
|
|
||||||
let new_environment = self.environment.clone();
|
|
||||||
|
|
||||||
for statement in statements {
|
|
||||||
self.interpret_statement(statement)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut interpreter = Interpreter {
|
|
||||||
environment: new_environment,
|
|
||||||
};
|
|
||||||
interpreter.evaluate(expression)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Interpreter {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
use lalrpop_util::lalrpop_mod;
|
pub mod builtins;
|
||||||
|
pub mod cli;
|
||||||
pub mod interpreter;
|
pub mod evaluate;
|
||||||
lalrpop_mod!(pub parser);
|
pub mod parse;
|
||||||
pub mod syntax;
|
pub mod syntax;
|
||||||
|
|
89
src/main.rs
89
src/main.rs
|
@ -1,68 +1,69 @@
|
||||||
use std::env::args;
|
|
||||||
use std::fs::read_to_string;
|
use std::fs::read_to_string;
|
||||||
use std::io::{stdin, stdout, BufRead, Write};
|
use std::io::{stdin, stdout, BufRead, Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::exit;
|
|
||||||
|
|
||||||
use mul::interpreter::Interpreter;
|
use anyhow::{anyhow, Context, Result};
|
||||||
use mul::parser::{ProgramParser, StatementParser};
|
|
||||||
|
|
||||||
fn repl() {
|
use mul::cli::{Command, USAGE};
|
||||||
|
use mul::evaluate::Interpreter;
|
||||||
|
use mul::parse::{parse_interactive_entry, parse_program};
|
||||||
|
use mul::syntax::Program;
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let Some(command) = Command::from_environment()? else {
|
||||||
|
eprintln!("mul\n{USAGE}");
|
||||||
|
return Ok(())
|
||||||
|
};
|
||||||
|
match command {
|
||||||
|
Command::Parse { path } => {
|
||||||
|
let program = parse(path.as_path())?;
|
||||||
|
println!("{program:#?}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Command::Run { path } => {
|
||||||
|
let program = parse(path.as_path())?;
|
||||||
|
let mut interpreter = Interpreter::new();
|
||||||
|
let _result = interpreter.run(&program)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Command::Repl => repl(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(path: &Path) -> Result<Program> {
|
||||||
|
let source =
|
||||||
|
read_to_string(path).with_context(|| format!("failed to open {}", path.display()))?;
|
||||||
|
parse_program(source.as_str())
|
||||||
|
.map_err(|e| anyhow!("{e}"))
|
||||||
|
.with_context(|| format!("failed to parse {}", path.display()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn repl() -> Result<()> {
|
||||||
let mut stdin = stdin().lock();
|
let mut stdin = stdin().lock();
|
||||||
let mut stdout = stdout().lock();
|
let mut stdout = stdout().lock();
|
||||||
let parser = StatementParser::new();
|
let mut interpreter = Interpreter::new();
|
||||||
let mut intepreter = Interpreter::new();
|
println!("welcome to M U L");
|
||||||
loop {
|
loop {
|
||||||
let mut line = String::new();
|
let mut line = String::new();
|
||||||
print!("> ");
|
print!("> ");
|
||||||
stdout.flush().unwrap();
|
stdout.flush()?;
|
||||||
stdin
|
stdin
|
||||||
.read_line(&mut line)
|
.read_line(&mut line)
|
||||||
.expect("Failed to read from stdin");
|
.context("Failed to read from stdin")?;
|
||||||
if line.trim().is_empty() {
|
if line.trim().is_empty() {
|
||||||
println!();
|
println!();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if !line.trim_end().ends_with(';') {
|
let parsed = match parse_interactive_entry(&line) {
|
||||||
line.push(';');
|
|
||||||
}
|
|
||||||
let parsed = match parser.parse(&line) {
|
|
||||||
Ok(parsed) => parsed,
|
Ok(parsed) => parsed,
|
||||||
Err(lalrpop_util::ParseError::UnrecognizedEOF { .. }) => break,
|
Err(lalrpop_util::ParseError::UnrecognizedEof { .. }) => break,
|
||||||
Err(e) => {
|
|
||||||
eprintln!("{e}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
match intepreter.interpret_statement(&parsed) {
|
|
||||||
Ok(value) => println!("{value:?}"),
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("{e}");
|
eprintln!("{e}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let value = interpreter.evaluate_interactive_entry(&parsed)?;
|
||||||
|
println!("{value}");
|
||||||
}
|
}
|
||||||
}
|
Ok(())
|
||||||
|
|
||||||
fn build(file: &Path) {
|
|
||||||
let content = read_to_string(file).expect("Failed to read file");
|
|
||||||
let parser = ProgramParser::new();
|
|
||||||
let mut interpreter = Interpreter::new();
|
|
||||||
let program = parser.parse(content.as_str()).unwrap_or_else(|e| {
|
|
||||||
eprintln!("{e}");
|
|
||||||
exit(1);
|
|
||||||
});
|
|
||||||
let v = interpreter.interpret(&program).unwrap_or_else(|e| {
|
|
||||||
eprintln!("{e}");
|
|
||||||
exit(1);
|
|
||||||
});
|
|
||||||
println!("{v:?}")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
if let Some(file) = args().nth(1) {
|
|
||||||
build(file.as_ref())
|
|
||||||
} else {
|
|
||||||
repl()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
use lalrpop_util::{lalrpop_mod, lexer::Token, ParseError};
|
||||||
|
|
||||||
|
use crate::syntax::{InteractiveEntry, Program};
|
||||||
|
|
||||||
|
lalrpop_mod!(parser, "/parse/parser.rs");
|
||||||
|
|
||||||
|
type Result<'input, T> = std::result::Result<T, ParseError<usize, Token<'input>, &'static str>>;
|
||||||
|
|
||||||
|
pub fn parse_program(input: &str) -> Result<Program> {
|
||||||
|
parser::ProgramParser::new().parse(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_interactive_entry(input: &str) -> Result<InteractiveEntry> {
|
||||||
|
parser::InteractiveEntryParser::new().parse(input)
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
use crate::syntax::{
|
||||||
|
Binding,
|
||||||
|
Block,
|
||||||
|
Expression,
|
||||||
|
Item,
|
||||||
|
InteractiveEntry,
|
||||||
|
Name,
|
||||||
|
Program,
|
||||||
|
};
|
||||||
|
|
||||||
|
grammar;
|
||||||
|
|
||||||
|
pub Program: Program = Item*;
|
||||||
|
|
||||||
|
pub InteractiveEntry: InteractiveEntry = {
|
||||||
|
Item => InteractiveEntry::Item(<>),
|
||||||
|
<Binding> ";"? => InteractiveEntry::Binding(<>),
|
||||||
|
<Expression> ";"? => InteractiveEntry::Expression(<>),
|
||||||
|
}
|
||||||
|
|
||||||
|
Item: Item = {
|
||||||
|
"fn" <name:Name> "(" <parameters:Comma<Name>> ")" <body:Block> ";"? => Item::Fn { <> },
|
||||||
|
}
|
||||||
|
|
||||||
|
Binding: Binding =
|
||||||
|
"let" <name:Name> "=" <expression:Expression> => Binding { <> };
|
||||||
|
|
||||||
|
Expression: Expression = {
|
||||||
|
Simple,
|
||||||
|
Boxed<Block> => Expression::Block(<>),
|
||||||
|
};
|
||||||
|
|
||||||
|
Simple: Expression = {
|
||||||
|
"|" <parameters:Comma<Name>> "|" <result:Boxed<Expression>> => Expression::Lambda { <> },
|
||||||
|
Atom,
|
||||||
|
};
|
||||||
|
|
||||||
|
Atom: Expression = {
|
||||||
|
Name => Expression::Variable(<>),
|
||||||
|
<callee:Boxed<Atom>> "(" <arguments:Comma<Expression>> ")" => Expression::Call { <> },
|
||||||
|
Integer => Expression::Integer(<>),
|
||||||
|
Boolean => Expression::Boolean(<>),
|
||||||
|
"(" <Expression> ")",
|
||||||
|
"(" ")" => Expression::Unit,
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockBinding = <Binding> ";";
|
||||||
|
|
||||||
|
Block: Block =
|
||||||
|
"{" <bindings:BlockBinding*> <result:Expression> "}" => Block { <> };
|
||||||
|
|
||||||
|
// TODO: decide on identifier syntax
|
||||||
|
Name: Name = r"[a-zA-Z_]+" => <>.to_string();
|
||||||
|
|
||||||
|
Integer: i64 = r"[0-9]+" => <>.parse::<i64>().unwrap();
|
||||||
|
|
||||||
|
Boolean: bool = {
|
||||||
|
"true" => true,
|
||||||
|
"false" => false,
|
||||||
|
}
|
||||||
|
|
||||||
|
Boxed<T>: Box<T> = T => Box::new(<>);
|
||||||
|
|
||||||
|
Comma<T>: Vec<T> = {
|
||||||
|
<mut v:(<T> ",")*> <e:T?> => match e {
|
||||||
|
None => v,
|
||||||
|
Some(e) => {
|
||||||
|
v.push(e);
|
||||||
|
v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,52 +0,0 @@
|
||||||
use crate::syntax::{Expression, Name, Program, Statement};
|
|
||||||
|
|
||||||
grammar;
|
|
||||||
|
|
||||||
pub Program: Program = Statement*;
|
|
||||||
|
|
||||||
pub Statement: Statement = {
|
|
||||||
"let" <Name> "=" <Expression> ";" => Statement::Binding(<>),
|
|
||||||
<Block> ";"? => Statement::Expression(<>),
|
|
||||||
<Simple> ";" => Statement::Expression(<>),
|
|
||||||
};
|
|
||||||
|
|
||||||
pub Expression = {
|
|
||||||
Simple,
|
|
||||||
Block,
|
|
||||||
};
|
|
||||||
|
|
||||||
Simple: Expression = {
|
|
||||||
"|" <parameters:Comma<Name>> "|" <body:Expression> => Expression::Lambda(parameters, Box::new(body)),
|
|
||||||
Atom,
|
|
||||||
};
|
|
||||||
|
|
||||||
Atom: Expression = {
|
|
||||||
Name => Expression::Variable(<>),
|
|
||||||
<callee:Atom> "(" <arguments:Comma<Expression>> ")" => Expression::Call(Box::new(callee), arguments),
|
|
||||||
Integer => Expression::Integer(<>),
|
|
||||||
Boolean => Expression::Boolean(<>),
|
|
||||||
"(" <Expression> ")",
|
|
||||||
};
|
|
||||||
|
|
||||||
Block: Expression =
|
|
||||||
"{" <statements:Statement*> <result:Expression> "}" => Expression::Block(statements, Box::new(result));
|
|
||||||
|
|
||||||
// TODO: decide on identifier syntax
|
|
||||||
Name: Name = r"[a-zA-Z_]+" => <>.to_string();
|
|
||||||
|
|
||||||
Integer: i64 = r"[0-9]+" => <>.parse::<i64>().unwrap();
|
|
||||||
|
|
||||||
Boolean: bool = {
|
|
||||||
"true" => true,
|
|
||||||
"false" => false,
|
|
||||||
}
|
|
||||||
|
|
||||||
Comma<T>: Vec<T> = {
|
|
||||||
<mut v:(<T> ",")*> <e:T?> => match e {
|
|
||||||
None => v,
|
|
||||||
Some(e) => {
|
|
||||||
v.push(e);
|
|
||||||
v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,19 +1,57 @@
|
||||||
|
//! Syntax elements.
|
||||||
|
|
||||||
|
/// Identifiers.
|
||||||
pub type Name = String;
|
pub type Name = String;
|
||||||
|
|
||||||
pub type Program = Vec<Statement>;
|
/// The rough equivalent of a module.
|
||||||
|
pub type Program = Vec<Item>;
|
||||||
|
|
||||||
|
/// A fragment of code entered in, e.g., the REPL.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Statement {
|
pub enum InteractiveEntry {
|
||||||
Binding(Name, Expression),
|
Item(Item),
|
||||||
|
Binding(Binding),
|
||||||
Expression(Expression),
|
Expression(Expression),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A top level definition.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Item {
|
||||||
|
Fn {
|
||||||
|
name: Name,
|
||||||
|
parameters: Vec<Name>,
|
||||||
|
body: Block,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Syntactic mapping from an identifier to an expression.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Binding {
|
||||||
|
pub name: Name,
|
||||||
|
pub expression: Expression,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Some individually addressable syntax node, either atomic or compound.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Expression {
|
pub enum Expression {
|
||||||
Variable(Name),
|
Variable(Name),
|
||||||
Block(Vec<Statement>, Box<Expression>),
|
Block(Box<Block>),
|
||||||
Lambda(Vec<Name>, Box<Expression>),
|
Lambda {
|
||||||
Call(Box<Expression>, Vec<Expression>),
|
parameters: Vec<Name>,
|
||||||
|
result: Box<Expression>,
|
||||||
|
},
|
||||||
|
Call {
|
||||||
|
callee: Box<Expression>,
|
||||||
|
arguments: Vec<Expression>,
|
||||||
|
},
|
||||||
Boolean(bool),
|
Boolean(bool),
|
||||||
Integer(i64),
|
Integer(i64),
|
||||||
|
Unit,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A sequence of bindings with a final result.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Block {
|
||||||
|
pub bindings: Vec<Binding>,
|
||||||
|
pub result: Expression,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue