work on prelude; update tailcall to deal properly with base fns

This commit is contained in:
Scott Richmond 2025-06-20 14:29:31 -04:00
parent c73c7e0d6a
commit 1e3fcde57a
3 changed files with 106 additions and 11 deletions

View File

@ -24,6 +24,7 @@ pub enum Op {
Pop,
PopN,
PushBinding,
PushGlobal,
Store,
StoreAt,
Stash,
@ -151,6 +152,7 @@ impl std::fmt::Display for Op {
Pop => "pop",
PopN => "pop_n",
PushBinding => "push_binding",
PushGlobal => "push_global",
Store => "store",
StoreAt => "store_at",
Stash => "stash",
@ -253,6 +255,7 @@ pub struct Chunk {
pub bytecode: Vec<u8>,
pub keywords: Vec<&'static str>,
pub string_patterns: Vec<StrPattern>,
pub env: imbl::HashMap<&'static str, Value>,
}
impl Chunk {
@ -265,7 +268,7 @@ impl Chunk {
| Duplicate | Decrement | Truncate | Noop | LoadTuple | LoadList | Eq | Add | Sub
| Mult | Div | Unbox | BoxStore | Assert | Get | At | Not | Panic | EmptyString
| ConcatStrings | Stringify | MatchType | Return | Match | Print | AppendList
| ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing => {
| ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing | PushGlobal => {
println!("{i:04}: {op}")
}
Constant | MatchConstant => {
@ -383,12 +386,14 @@ impl<'a> Compiler<'a> {
name: &'static str,
src: &'static str,
enclosing: Option<&'a Compiler>,
env: imbl::HashMap<&'static str, Value>,
) -> Compiler<'a> {
let chunk = Chunk {
constants: vec![],
bytecode: vec![],
keywords: vec![],
string_patterns: vec![],
env,
};
Compiler {
chunk,
@ -530,6 +535,31 @@ impl<'a> Compiler<'a> {
}
fn resolve_binding(&mut self, name: &'static str) {
if let Some(pos) = self.resolve_local(name) {
self.emit_op(Op::PushBinding);
self.emit_byte(pos);
self.stack_depth += 1;
return;
}
if let Some(pos) = self.resolve_upvalue(name) {
self.emit_op(Op::GetUpvalue);
self.emit_byte(pos);
self.stack_depth += 1;
return;
}
if self.chunk.env.contains_key(name) {
self.emit_constant(Value::Keyword(name));
self.emit_op(Op::PushGlobal);
return;
}
let upvalue = self.get_upvalue(name);
self.emit_op(Op::GetUpvalue);
self.emit_byte(self.upvalues.len());
self.upvalues.push(upvalue);
self.stack_depth += 1;
}
fn resolve_binding_old(&mut self, name: &'static str) {
match self.resolve_local(name) {
Some(position) => {
self.emit_op(Op::PushBinding);
@ -1250,8 +1280,13 @@ impl<'a> Compiler<'a> {
let compiler = match compilers.get_mut(&arity) {
Some(compiler) => compiler,
None => {
let mut compiler =
Compiler::new(clause, self.name, self.src, Some(self));
let mut compiler = Compiler::new(
clause,
self.name,
self.src,
Some(self),
self.chunk.env.clone(),
);
compiler.emit_op(Op::ResetMatch);
compilers.insert(arity, compiler);
compilers.get_mut(&arity).unwrap()

View File

@ -1,4 +1,5 @@
use chumsky::{input::Stream, prelude::*};
use imbl::HashMap;
use std::env;
const DEBUG_COMPILE: bool = true;
@ -21,10 +22,43 @@ mod compiler;
use crate::compiler::Compiler;
mod value;
use value::Value;
mod vm;
use vm::Vm;
const PRELUDE: &str = "
fn print! (...args) -> base :print! (args)
fn inc (x) -> base :inc (x)
fn dec (x) -> base :dec (x)
fn eq? (x, y) -> base :eq? (x, y)
#{print!, inc, dec, eq?}
";
pub fn prelude() -> HashMap<&'static str, Value> {
let tokens = lexer().parse(PRELUDE).into_output_errors().0.unwrap();
let parsed = parser()
.parse(Stream::from_iter(tokens).map((0..PRELUDE.len()).into(), |(t, s)| (t, s)))
.into_output_errors()
.0
.unwrap();
let parsed: &'static Spanned<Ast> = Box::leak(Box::new(parsed));
let mut compiler = Compiler::new(parsed, "prelude", PRELUDE, None, HashMap::new());
let base = base::make_base();
compiler.emit_constant(base);
compiler.bind("base");
compiler.compile();
let chunk = compiler.chunk;
let mut vm = Vm::new(chunk);
let prelude = vm.run().clone().unwrap();
match prelude {
Value::Dict(hashmap) => *hashmap,
_ => unreachable!(),
}
}
pub fn run(src: &'static str) {
let (tokens, lex_errs) = lexer().parse(src).into_output_errors();
if !lex_errs.is_empty() {
@ -47,11 +81,9 @@ pub fn run(src: &'static str) {
// in any event, the AST should live forever
let parsed: &'static Spanned<Ast> = Box::leak(Box::new(parse_result.unwrap()));
let base = base::make_base();
let prelude = prelude();
let mut compiler = Compiler::new(parsed, "test", src, None);
compiler.emit_constant(base);
compiler.bind("base");
let mut compiler = Compiler::new(parsed, "test", src, None, prelude);
compiler.compile();
if DEBUG_COMPILE {
@ -80,8 +112,11 @@ pub fn run(src: &'static str) {
pub fn main() {
env::set_var("RUST_BACKTRACE", "1");
let src = "
fn print! (...args) -> base :print! (args)
print! (:foo, :bar, :baz)
[
eq? (1, 2)
eq? (:foo, :foo)
inc (3)
]
";
run(src);
}

View File

@ -301,6 +301,15 @@ impl Vm {
self.push(binding_value);
self.ip += 2;
}
PushGlobal => {
let key = self.pop();
let Value::Keyword(name) = key else {
unreachable!("internal Ludus error: expected key for global resolution")
};
let value = self.chunk().env.get(name).unwrap();
self.push(value.clone());
self.ip += 1;
}
Store => {
self.return_register[0] = self.pop();
self.push(Value::Nothing);
@ -919,10 +928,26 @@ impl Vm {
let value = match (arity, base_fn) {
(0, BaseFn::Nullary(f)) => f(),
(1, BaseFn::Unary(f)) => f(&self.pop()),
(2, BaseFn::Binary(f)) => f(&self.pop(), &self.pop()),
(3, BaseFn::Ternary(f)) => f(&self.pop(), &self.pop(), &self.pop()),
(2, BaseFn::Binary(f)) => {
let y = &self.pop();
let x = &self.pop();
f(x, y)
}
(3, BaseFn::Ternary(f)) => {
let z = &self.pop();
let y = &self.pop();
let x = &self.pop();
f(x, y, z)
}
_ => return self.panic("internal ludus error"),
};
// algo:
// clear the stack
self.stack.truncate(self.frame.stack_base);
// then pop back out to the enclosing stack frame
self.frame = self.call_stack.pop().unwrap();
self.ip = self.frame.ip;
// finally, throw the value on the stack
self.push(value);
}
Value::Partial(partial) => {