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

View File

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

View File

@ -301,6 +301,15 @@ impl Vm {
self.push(binding_value); self.push(binding_value);
self.ip += 2; 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 => { Store => {
self.return_register[0] = self.pop(); self.return_register[0] = self.pop();
self.push(Value::Nothing); self.push(Value::Nothing);
@ -919,10 +928,26 @@ impl Vm {
let value = match (arity, base_fn) { let value = match (arity, base_fn) {
(0, BaseFn::Nullary(f)) => f(), (0, BaseFn::Nullary(f)) => f(),
(1, BaseFn::Unary(f)) => f(&self.pop()), (1, BaseFn::Unary(f)) => f(&self.pop()),
(2, BaseFn::Binary(f)) => f(&self.pop(), &self.pop()), (2, BaseFn::Binary(f)) => {
(3, BaseFn::Ternary(f)) => f(&self.pop(), &self.pop(), &self.pop()), 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"), _ => 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); self.push(value);
} }
Value::Partial(partial) => { Value::Partial(partial) => {