Compare commits
4 Commits
28d1c7ab0c
...
bb683b09b4
Author | SHA1 | Date | |
---|---|---|---|
|
bb683b09b4 | ||
|
32ab5f7944 | ||
|
fd55604608 | ||
|
7819472048 |
|
@ -43,7 +43,7 @@ mod vm;
|
||||||
use crate::vm::*;
|
use crate::vm::*;
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
let src = "let (x, 2) = (1, 2); x";
|
let src = "when { 0 -> :false; true -> :true}";
|
||||||
let (tokens, lex_errs) = lexer().parse(src).into_output_errors();
|
let (tokens, lex_errs) = lexer().parse(src).into_output_errors();
|
||||||
if lex_errs.len() > 0 {
|
if lex_errs.len() > 0 {
|
||||||
println!("{:?}", lex_errs);
|
println!("{:?}", lex_errs);
|
||||||
|
|
|
@ -3,6 +3,35 @@ use crate::spans::*;
|
||||||
use chumsky::{input::ValueInput, prelude::*, recursive::Recursive};
|
use chumsky::{input::ValueInput, prelude::*, recursive::Recursive};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct WhenClause<'src> {
|
||||||
|
pub cond: Spanned<Ast<'src>>,
|
||||||
|
pub body: Spanned<Ast<'src>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'src> fmt::Display for WhenClause<'src> {
|
||||||
|
fn fmt(self: &WhenClause<'src>, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "cond: {}, body: {}", self.cond.0, self.body.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct MatchClause<'src> {
|
||||||
|
pub patt: Spanned<Pattern<'src>>,
|
||||||
|
pub guard: Option<Spanned<Ast<'src>>>,
|
||||||
|
pub body: Spanned<Ast<'src>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'src> fmt::Display for MatchClause<'src> {
|
||||||
|
fn fmt(self: &MatchClause<'src>, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"pattern: {}, guard: {:?} body: {}",
|
||||||
|
self.patt.0, self.guard, self.body.0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Ast<'src> {
|
pub enum Ast<'src> {
|
||||||
Error,
|
Error,
|
||||||
|
@ -23,12 +52,9 @@ pub enum Ast<'src> {
|
||||||
Let(Box<Spanned<Pattern<'src>>>, Box<Spanned<Self>>),
|
Let(Box<Spanned<Pattern<'src>>>, Box<Spanned<Self>>),
|
||||||
Box(&'src str, Box<Spanned<Self>>),
|
Box(&'src str, Box<Spanned<Self>>),
|
||||||
Synthetic(Box<Spanned<Self>>, Box<Spanned<Self>>, Vec<Spanned<Self>>),
|
Synthetic(Box<Spanned<Self>>, Box<Spanned<Self>>, Vec<Spanned<Self>>),
|
||||||
WhenClause(Box<Spanned<Self>>, Box<Spanned<Self>>),
|
When(Vec<Spanned<WhenClause<'src>>>),
|
||||||
When(Vec<Spanned<Self>>),
|
Match(Box<Spanned<Self>>, Vec<Spanned<MatchClause<'src>>>),
|
||||||
MatchClause(Box<Spanned<Pattern<'src>>>, Box<Spanned<Self>>),
|
Fn(&'src str, Vec<Spanned<MatchClause<'src>>>),
|
||||||
Match(Box<Spanned<Self>>, Vec<Spanned<Self>>),
|
|
||||||
FnClause(Box<Spanned<Pattern<'src>>>, Box<Spanned<Self>>),
|
|
||||||
Fn(&'src str, Vec<Spanned<Self>>),
|
|
||||||
FnDeclaration(&'src str),
|
FnDeclaration(&'src str),
|
||||||
Panic(Box<Spanned<Self>>),
|
Panic(Box<Spanned<Self>>),
|
||||||
Do(Vec<Spanned<Self>>),
|
Do(Vec<Spanned<Self>>),
|
||||||
|
@ -99,6 +125,15 @@ impl<'src> fmt::Display for Ast<'src> {
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n")
|
.join("\n")
|
||||||
),
|
),
|
||||||
|
Ast::When(clauses) => write!(
|
||||||
|
f,
|
||||||
|
"When: [{}]",
|
||||||
|
clauses
|
||||||
|
.iter()
|
||||||
|
.map(|clause| clause.0.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
),
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,6 +147,7 @@ pub enum Pattern<'src> {
|
||||||
String(&'src str),
|
String(&'src str),
|
||||||
Keyword(&'src str),
|
Keyword(&'src str),
|
||||||
Word(&'src str),
|
Word(&'src str),
|
||||||
|
As(&'src str, &'src str),
|
||||||
Placeholder,
|
Placeholder,
|
||||||
Tuple(Vec<Spanned<Self>>),
|
Tuple(Vec<Spanned<Self>>),
|
||||||
List(Vec<Spanned<Self>>),
|
List(Vec<Spanned<Self>>),
|
||||||
|
@ -130,6 +166,7 @@ impl<'src> fmt::Display for Pattern<'src> {
|
||||||
Pattern::String(s) => write!(f, "{}", s),
|
Pattern::String(s) => write!(f, "{}", s),
|
||||||
Pattern::Keyword(k) => write!(f, ":{}", k),
|
Pattern::Keyword(k) => write!(f, ":{}", k),
|
||||||
Pattern::Word(w) => write!(f, "{}", w),
|
Pattern::Word(w) => write!(f, "{}", w),
|
||||||
|
Pattern::As(w, t) => write!(f, "{} as {}", w, t),
|
||||||
Pattern::Placeholder => write!(f, "_"),
|
Pattern::Placeholder => write!(f, "_"),
|
||||||
Pattern::Tuple(t) => write!(
|
Pattern::Tuple(t) => write!(
|
||||||
f,
|
f,
|
||||||
|
@ -244,8 +281,16 @@ where
|
||||||
)
|
)
|
||||||
.map_with(|dict, e| (Pattern::Dict(dict), e.span()));
|
.map_with(|dict, e| (Pattern::Dict(dict), e.span()));
|
||||||
|
|
||||||
|
let keyword = select! {Token::Keyword(k) => Ast::Keyword(k),}.map_with(|k, e| (k, e.span()));
|
||||||
|
|
||||||
|
let as_pattern = select! {Token::Word(w) => w}
|
||||||
|
.then_ignore(just(Token::Reserved("as")))
|
||||||
|
.then(select! {Token::Keyword(k) => k})
|
||||||
|
.map_with(|(w, t), e| (Pattern::As(w, t), e.span()));
|
||||||
|
|
||||||
pattern.define(
|
pattern.define(
|
||||||
atom_pattern
|
atom_pattern
|
||||||
|
.or(as_pattern)
|
||||||
.or(word_pattern)
|
.or(word_pattern)
|
||||||
.or(placeholder_pattern)
|
.or(placeholder_pattern)
|
||||||
.or(tuple_pattern.clone())
|
.or(tuple_pattern.clone())
|
||||||
|
@ -269,8 +314,6 @@ where
|
||||||
}
|
}
|
||||||
.map_with(|v, e| (v, e.span()));
|
.map_with(|v, e| (v, e.span()));
|
||||||
|
|
||||||
let keyword = select! {Token::Keyword(k) => Ast::Keyword(k),}.map_with(|k, e| (k, e.span()));
|
|
||||||
|
|
||||||
let tuple = simple
|
let tuple = simple
|
||||||
.clone()
|
.clone()
|
||||||
.separated_by(separators.clone())
|
.separated_by(separators.clone())
|
||||||
|
@ -382,7 +425,7 @@ where
|
||||||
.clone()
|
.clone()
|
||||||
.then_ignore(just(Token::Punctuation("->")))
|
.then_ignore(just(Token::Punctuation("->")))
|
||||||
.then(expr.clone())
|
.then(expr.clone())
|
||||||
.map_with(|(cond, body), e| (Ast::WhenClause(Box::new(cond), Box::new(body)), e.span()));
|
.map_with(|(cond, body), e| (WhenClause { cond, body }, e.span()));
|
||||||
|
|
||||||
let when = just(Token::Reserved("when"))
|
let when = just(Token::Reserved("when"))
|
||||||
.ignore_then(
|
.ignore_then(
|
||||||
|
@ -399,7 +442,16 @@ where
|
||||||
.clone()
|
.clone()
|
||||||
.then_ignore(just(Token::Punctuation("->")))
|
.then_ignore(just(Token::Punctuation("->")))
|
||||||
.then(expr.clone())
|
.then(expr.clone())
|
||||||
.map_with(|(patt, body), e| (Ast::MatchClause(Box::new(patt), Box::new(body)), e.span()));
|
.map_with(|(patt, body), e| {
|
||||||
|
(
|
||||||
|
MatchClause {
|
||||||
|
patt,
|
||||||
|
guard: None,
|
||||||
|
body,
|
||||||
|
},
|
||||||
|
e.span(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
let match_ = just(Token::Reserved("match"))
|
let match_ = just(Token::Reserved("match"))
|
||||||
.ignore_then(simple.clone())
|
.ignore_then(simple.clone())
|
||||||
|
@ -420,7 +472,7 @@ where
|
||||||
//todo:
|
//todo:
|
||||||
// * [x] do
|
// * [x] do
|
||||||
// * [ ] loop
|
// * [ ] loop
|
||||||
// * [ ] repeat
|
// * [x] repeat
|
||||||
// * [x] panic!
|
// * [x] panic!
|
||||||
|
|
||||||
let panic = just(Token::Reserved("panic!"))
|
let panic = just(Token::Reserved("panic!"))
|
||||||
|
@ -447,7 +499,16 @@ where
|
||||||
.clone()
|
.clone()
|
||||||
.then_ignore(just(Token::Punctuation("->")))
|
.then_ignore(just(Token::Punctuation("->")))
|
||||||
.then(nonbinding.clone())
|
.then(nonbinding.clone())
|
||||||
.map_with(|(pattern, body), e| (Ast::FnClause(Box::new(pattern), Box::new(body)), e.span()))
|
.map_with(|(patt, body), e| {
|
||||||
|
(
|
||||||
|
MatchClause {
|
||||||
|
patt,
|
||||||
|
body,
|
||||||
|
guard: None,
|
||||||
|
},
|
||||||
|
e.span(),
|
||||||
|
)
|
||||||
|
})
|
||||||
.labelled("function clause");
|
.labelled("function clause");
|
||||||
|
|
||||||
let lambda = just(Token::Reserved("fn"))
|
let lambda = just(Token::Reserved("fn"))
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::parser::*;
|
use crate::parser::*;
|
||||||
use crate::spans::*;
|
use crate::spans::*;
|
||||||
use imbl::*;
|
use imbl::*;
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
@ -28,9 +29,10 @@ pub enum Value<'src> {
|
||||||
// on the heap for now
|
// on the heap for now
|
||||||
Tuple(Rc<Vec<Self>>),
|
Tuple(Rc<Vec<Self>>),
|
||||||
// ref-counted, immutable, persistent
|
// ref-counted, immutable, persistent
|
||||||
List(Rc<Vector<Self>>),
|
List(Vector<Self>),
|
||||||
// ref-counted, immutable, persistent
|
// ref-counted, immutable, persistent
|
||||||
Dict(Rc<HashMap<&'src str, Self>>),
|
Dict(HashMap<&'src str, Self>),
|
||||||
|
Box(&'src str, Rc<RefCell<Self>>),
|
||||||
// Fn(Rc<Fn<'src>>),
|
// Fn(Rc<Fn<'src>>),
|
||||||
// Set(HashSet<Self>),
|
// Set(HashSet<Self>),
|
||||||
// Sets are hard
|
// Sets are hard
|
||||||
|
@ -55,6 +57,7 @@ impl<'src> Clone for Value<'src> {
|
||||||
Value::Tuple(t) => Value::Tuple(t.clone()),
|
Value::Tuple(t) => Value::Tuple(t.clone()),
|
||||||
Value::List(l) => Value::List(l.clone()),
|
Value::List(l) => Value::List(l.clone()),
|
||||||
Value::Dict(d) => Value::Dict(d.clone()),
|
Value::Dict(d) => Value::Dict(d.clone()),
|
||||||
|
Value::Box(name, b) => Value::Box(name, b.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,6 +88,7 @@ impl<'src> fmt::Display for Value<'src> {
|
||||||
.join(", ")
|
.join(", ")
|
||||||
),
|
),
|
||||||
Value::Dict(d) => write!(f, "#{{{:?}}}", d),
|
Value::Dict(d) => write!(f, "#{{{:?}}}", d),
|
||||||
|
Value::Box(name, b) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
90
src/vm.rs
90
src/vm.rs
|
@ -5,7 +5,7 @@ use std::rc::Rc;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct LudusError {
|
pub struct LudusError {
|
||||||
msg: &'static str,
|
msg: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// oy
|
// oy
|
||||||
|
@ -17,25 +17,25 @@ pub struct LudusError {
|
||||||
// I guess the question is how to get
|
// I guess the question is how to get
|
||||||
// the branches for Ast::Block and Ast::If
|
// the branches for Ast::Block and Ast::If
|
||||||
// to work with a mutable borrow of ctx
|
// to work with a mutable borrow of ctx
|
||||||
pub struct Ctx<'src> {
|
// pub struct Ctx<'src> {
|
||||||
pub locals: Vec<(&'src str, Value<'src>)>,
|
// pub locals: Vec<(&'src str, Value<'src>)>,
|
||||||
// pub names: Vec<&'src str>,
|
// // pub names: Vec<&'src str>,
|
||||||
// pub values: Vec<Value<'src>>,
|
// // pub values: Vec<Value<'src>>,
|
||||||
}
|
// }
|
||||||
|
|
||||||
impl<'src> Ctx<'src> {
|
// impl<'src> Ctx<'src> {
|
||||||
pub fn resolve(&self, name: &'src str) -> Value {
|
// pub fn resolve(&self, name: &'src str) -> Value {
|
||||||
if let Some((_, val)) = self.locals.iter().rev().find(|(bound, _)| *bound == name) {
|
// if let Some((_, val)) = self.locals.iter().rev().find(|(bound, _)| *bound == name) {
|
||||||
val.clone()
|
// val.clone()
|
||||||
} else {
|
// } else {
|
||||||
unreachable!()
|
// unreachable!()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn store(&mut self, name: &'src str, value: Value<'src>) {
|
// pub fn store(&mut self, name: &'src str, value: Value<'src>) {
|
||||||
self.locals.push((name, value));
|
// self.locals.push((name, value));
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn match_eq<T, U>(x: T, y: T, z: U) -> Option<U>
|
pub fn match_eq<T, U>(x: T, y: T, z: U) -> Option<U>
|
||||||
where
|
where
|
||||||
|
@ -135,7 +135,7 @@ pub fn eval<'src, 'a>(
|
||||||
for member in members {
|
for member in members {
|
||||||
vect.push_back(eval(&member.0, ctx)?)
|
vect.push_back(eval(&member.0, ctx)?)
|
||||||
}
|
}
|
||||||
Ok(Value::List(Rc::new(vect)))
|
Ok(Value::List(vect))
|
||||||
}
|
}
|
||||||
Ast::Tuple(members) => {
|
Ast::Tuple(members) => {
|
||||||
let mut vect = Vec::new();
|
let mut vect = Vec::new();
|
||||||
|
@ -156,9 +156,57 @@ pub fn eval<'src, 'a>(
|
||||||
let val = eval(&expr.0, ctx)?;
|
let val = eval(&expr.0, ctx)?;
|
||||||
match matchh(&patt.0, &val, ctx) {
|
match matchh(&patt.0, &val, ctx) {
|
||||||
Some(_) => Ok(val),
|
Some(_) => Ok(val),
|
||||||
None => Err(LudusError { msg: "No match" }),
|
None => Err(LudusError {
|
||||||
|
msg: "No match".to_string(),
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => todo!(),
|
Ast::Placeholder => todo!(),
|
||||||
|
Ast::Error => todo!(),
|
||||||
|
Ast::Dict(_) => todo!(),
|
||||||
|
Ast::Arguments(_) => todo!(),
|
||||||
|
Ast::Pair(_, _) => todo!(),
|
||||||
|
Ast::Box(_, _) => todo!(),
|
||||||
|
Ast::Synthetic(_, _, _) => todo!(),
|
||||||
|
Ast::When(clauses) => {
|
||||||
|
for clause in clauses.iter() {
|
||||||
|
let WhenClause { cond, body } = &clause.0;
|
||||||
|
if eval(&cond.0, ctx)?.bool() {
|
||||||
|
return eval(&body.0, ctx);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return Err(LudusError {
|
||||||
|
msg: "no match".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ast::Match(_, _) => todo!(),
|
||||||
|
Ast::Fn(_, _) => todo!(),
|
||||||
|
Ast::FnDeclaration(_) => todo!(),
|
||||||
|
Ast::Panic(msg) => {
|
||||||
|
let msg = eval(&msg.0, ctx)?;
|
||||||
|
Err(LudusError {
|
||||||
|
msg: msg.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Ast::Repeat(times, body) => {
|
||||||
|
let mut times_num = 0;
|
||||||
|
match eval(×.0, ctx) {
|
||||||
|
Ok(Value::Number(n)) => {
|
||||||
|
times_num = n as usize;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(LudusError {
|
||||||
|
msg: "repeat may only take numbers".to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _ in 0..times_num {
|
||||||
|
let _ = eval(&body.0, ctx)?;
|
||||||
|
}
|
||||||
|
Ok(Value::Nil)
|
||||||
|
}
|
||||||
|
Ast::Do(_) => todo!(),
|
||||||
|
Ast::Loop(_, _) => todo!(),
|
||||||
|
Ast::Recur(_) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
43
thoughts.md
43
thoughts.md
|
@ -1,5 +1,6 @@
|
||||||
# VM thoughts
|
# VM thoughts
|
||||||
|
|
||||||
|
### Initial thoughts
|
||||||
We want numbers and bools as unboxed as possible.
|
We want numbers and bools as unboxed as possible.
|
||||||
|
|
||||||
Nil is a singleton, and should be static.
|
Nil is a singleton, and should be static.
|
||||||
|
@ -12,4 +13,46 @@ Keywords are static/interned.
|
||||||
|
|
||||||
Tuples should be refcounted for now.
|
Tuples should be refcounted for now.
|
||||||
|
|
||||||
|
### Optimization and other thoughts
|
||||||
|
2024-11-09
|
||||||
|
* To put tuples on the stack, we need to know both how long they are (number of members) and how big they are (amount of memory), since tuples can contain other tuples.
|
||||||
|
- All other values must be one stack cell:
|
||||||
|
* `nil` is its own thing
|
||||||
|
* numbers are a wrapped `f64` (at least until we get to NaN boxed values)
|
||||||
|
* booleans are a wrapped `bool`
|
||||||
|
* keywords are a wrapped `u16` or `u32`, which is an index into a vec of `&str`s, which can be read back into a string when printed
|
||||||
|
* strings are a `&str` or an `Rc<String>` (with two possible wrappers: `Value::Str` or `Value::String`)
|
||||||
|
* dicts are `imbl::HashMap<u16, Value>`, with the hash generated on the index of the keyword
|
||||||
|
* sets are `imbl::HashSet<Value>`, with the caveat that `f64` isn't `Eq`, which means that we can't use it for a hash key. The way around this, I think, is to implement `Eq` for `Value`, with a panic if we try to put NaN in a set
|
||||||
|
* functions are `Rc<LFn>`
|
||||||
|
* boxes are `Rc<RefCell>`
|
||||||
|
* That means everything is either a wrapped `Copy` (`:nil`, `:number`, `:bool`), an interned reference (`:keyword`, `:string`), `Rc` reference types (`:string`, `:box`, `:fn`), or persistent reference types that have their own `clone` (`:list`, `:dict`, `:set`)
|
||||||
|
* This doesn't cover everything, yet. But other reference types will be `Rc`ed structs: to wit, processes and packages.
|
||||||
|
- Tuples, meanwhile, have a special representation on the stack.
|
||||||
|
* They start with a `Value::TupleStart(len: u8, size: u8)`.
|
||||||
|
* They then have a number of members.
|
||||||
|
* They end with a `Value::TupleEnd(len: u8, size: u8)`.
|
||||||
|
* `len` indicates the number of members in the tuple; `size` indicates the size of the tuple on the stack, including the `TupleStart` and `TupleEnd` cells. For `()`, `len` is `0`, and `size` is `2`. Nesting tuples will lead to larger divergences, and will increase `size` but not `len`.
|
||||||
|
* If sombody tries to stuff more than 255 members in a tuple, nested or not, we get a validation error to tell them to use a list.
|
||||||
|
- Or promote it to be a reference type? The natural encoding of a list in Ludus is using a `(car, cdr)` encoding (or `(data, next)`). I believe the way to get this out of a scope (block or function) is to expand the tuple fully, which could lead very quickly to very large tuples.
|
||||||
|
- But we can easily distinguish between argument tuples and value tuples, and promote value tuples with a size larger than 255 to a `Value::BigTuple(Rc<Vec<Value>>)`.
|
||||||
|
- But in no case should we allow arguments to get bigger than 255.
|
||||||
|
- Keeping small value tuples on the stack is worthwhile, especially given the importance of result tuples, which should stay on the stack.
|
||||||
|
* This naturally leads to questions about pattern matching, especially when we get to a stack-based bytecode VM.
|
||||||
|
- A pattern, like a tuple, is a series of cells.
|
||||||
|
- The goal is to keep pattern sizes and lengths identical to the tuple data representation.
|
||||||
|
- That means that, like data representations, a pattern has to include both a set of bytecode instructions and a data representation on the stack.
|
||||||
|
- In fact, I suspect that the fastest way to encode this will be to push the data representation of the scrutinee on the stack, and then to push the pattern, and to then compare within the stack, at different offsets.
|
||||||
|
|
||||||
|
### Let's not reinvent the wheel
|
||||||
|
#### Or, crates we will use
|
||||||
|
* `chumsky` for parsing
|
||||||
|
* `ariadne` for parsing errors
|
||||||
|
* `imbl` for persistent data structures
|
||||||
|
* `boxing` for NaN boxing
|
||||||
|
* `tailcall` for tail recursion
|
||||||
|
|
||||||
|
We additionally might want crates for:
|
||||||
|
* processes/actors, although given that Ludus will be single-threaded for the forseeable future, it may be lighter weight to just write my own `process` abstraction
|
||||||
|
* in that case, we will need a ringbuffer, `ringbuf`
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user