mulch
Code repository: https://codeberg.org/wr7/mulch
Mulch is a lazily evaluated declarative programming language. It was originally intended to replace the language used in vcat, but I now have grander visions for it.
Unlike the language in vcat, Mulch has its own custom garbage collector, it's lazily evaluated, has support for infinite-precision rational numbers, and it supports lambdas. Some of its syntax is borrowed from the "Nix" programming language, and some is borrowed from other languages.
🚧 Work in progress 🚧
Architecture
- Custom two-space garbage collector
- In order to support large input programs, the AST that is emitted by the parser is garbage collected. This is because sections of the AST are stored during evaluation for lazy evaluation and lambdas.
- We have a safe garbage collection API that utilizes borrow checking to ensure at compile time that direct references to garbage collected data are not accessed after a garbage collection cycle is performed.
- Infinite precision rational numbers
- On 64-bit platforms, rational numbers are represented as either unsigned 63-bit integers or as a fraction of two bignums. These bignums are implemented using low-level functions provided by GMP, but I plan on reimplementing them myself so that all of our math code can be tested with MIRI (which doesn't support calling C functions).
- Procedurally generated recursive descent parser
- A custom parser generator is used because our AST is garbage collected. Syntax is defined using structs or enums, and parsing code is generated using Rust's derive macros:
// The `#[derive(Parse)]` will automatically generate the parsing code based off of our struct
// definition. The `#[error_if_not_found]` attributes are to facilitate better parse error messages.
// The `#[parse_until_next]` attribute is required to resolve ambiguity in parse code generation.
// The code wouldn't compile if it was omitted.
#[derive(GCPtr, Parse, Clone, Copy)]
#[mulch_parse_error(<keyword!["let"]>::EXPECTED_ERROR_FUNCTION)]
pub struct LetIn {
pub let_: keyword!["let"],
#[parse_until_next]
#[error_if_not_found]
pub variables: SeparatedList<NamedValue, punct![";"]>,
pub in_: keyword!["in"],
#[error_if_not_found]
pub val: OldGCBox<PartialSpanned<Expression>>,
}Currently implemented features
Language features
- Lazy evaluation
- Lists (represented with square brackets)
- String literals and number literals
- Attribute sets (represented with curly brackets)
- Lambdas (which can support list arguments, set arguments, or a single argument)
let inexpressions
Examples
let
join3_regular = (a, b, c) -> [a, b, c];
join3_attrs = {a, b, c = "default_c_value"} -> [a, b, c];
join3_complex = {args: [a, b, c]} -> [a, b, c];
nop = a -> a;
in
[
join3_regular(1, 2, 3),
join3_attrs{a = "a"; b = "b"},
join3_complex{args = [3, 1, 4]},
nop([4, 5, 6]),
]
Output:
List [
List [1, 2, 3],
List ["a", "b", "default_c_value"],
List [3, 1, 4],
List [4, 5, 6],
]
Note that because this is a declarative language, the order in which variables are defined is not relevant, and we can define values that depend on eachother:
let
val0 = {
next = val1;
value = 42;
};
val1 = {
next = val0;
value = 72;
};
in
[
val0.next.next.next.value,
val0.next.next.next.next.value,
]
Output:
List [
72,
42,
]
In this case, we're sort of describing a cyclic linked list with two nodes.
Miscellaneous features
- Extensive error message system