~remexre/stahl

b23e537b53389f21cbe429cd3472385ba0862518 — Nathan Ringo 6 days ago a3465fa main
Adds push-enter machine.
M lang/compiler/main.stahl => lang/compiler/main.stahl +1 -1
@@ 5,6 5,6 @@

(def main unit
  (fn (a b : c)
    (tuple a b c <>)))
    <>))

; vi: set ft=lisp :

M lang/prelude.stahl => lang/prelude.stahl +1 -1
@@ 18,6 18,6 @@
(def the (pi ((a type) (x a)) a)
     (fn (a x) x))

(check ($ the unit <>))
(check (the unit <>))

; vi: set ft=lisp :

M langbs/driver/src/compiler/mod.rs => langbs/driver/src/compiler/mod.rs +2 -1
@@ 12,6 12,7 @@ pub fn process_body_form(module: &'static Module, form: Sexpr) -> Result<()> {
    let arena = Bump::new();
    eprintln!("{}\n  {}", module.name(), form);

    eprintln!("  {}", DeclForm::parse(&arena, form)?);
    let decl = DeclForm::parse(&arena, form)?;
    eprintln!("  {}", decl);
    Ok(())
}

M langbs/driver/src/lib.rs => langbs/driver/src/lib.rs +1 -0
@@ 4,6 4,7 @@ mod error;
mod pkg;
mod sexpr;
mod util;
pub mod value;

pub use crate::{
    core::{ClosedTerm, Core, Decl, Expr, Globals, IsImplicit, Level, M},

A langbs/driver/src/value.rs => langbs/driver/src/value.rs +226 -0
@@ 0,0 1,226 @@
use crate::sexpr::{InternStr, Symbol};
use bumpalo::Bump;

#[derive(Clone, Copy, Debug)]
pub enum Value<'a> {
    Clo(&'a Code<'a>, &'a CloEnv<'a>),
    Con(Symbol, &'a [Value<'a>]),
    Int(isize),
    Kwd(InternStr),
    Str(&'a str),
    Sym(Symbol),
}

#[derive(Clone, Copy, Debug)]
pub enum Code<'a> {
    Halt,
    Access(usize, &'a Code<'a>),
    Apply(usize),
    Grab(&'a Code<'a>),
    Push(&'a Value<'a>, &'a Code<'a>),
    PushClosure(&'a Code<'a>, &'a Code<'a>),
    PushRetAddr(&'a Code<'a>, &'a Code<'a>),
    Return,
}

#[derive(Clone, Copy, Debug)]
pub struct CloEnv<'a> {
    frame: &'a [Value<'a>],
    parent: Option<&'a CloEnv<'a>>,
}

impl<'a> CloEnv<'a> {
    pub fn get(&self, i: usize) -> Value<'a> {
        if i < self.frame.len() {
            self.frame[i]
        } else if let Some(p) = self.parent {
            p.get(i - self.frame.len())
        } else {
            panic!("env lookup out of bounds")
        }
    }

    pub fn is_empty(&self) -> bool {
        self.frame.is_empty() && self.parent.map(|p| p.is_empty()).unwrap_or(true)
    }
}

#[derive(Clone, Debug, Default)]
pub struct Env<'a> {
    frame: Vec<Value<'a>>,
    parent: Option<&'a CloEnv<'a>>,
    clo: Option<&'a CloEnv<'a>>,
}

impl<'a> Env<'a> {
    pub fn clo(&mut self, arena: &'a Bump) -> &'a CloEnv<'a> {
        if let Some(clo) = self.clo {
            clo
        } else if self.frame.is_empty() && self.parent.is_some() {
            self.parent.unwrap()
        } else {
            let clo = arena.alloc(CloEnv {
                frame: arena.alloc_slice_fill_iter(self.frame.iter().rev().copied()),
                parent: self.parent,
            });
            self.clo = Some(clo);
            clo
        }
    }

    pub fn get(&self, i: usize) -> Value<'a> {
        if i < self.frame.len() {
            self.frame[i]
        } else if let Some(p) = self.parent {
            p.get(i - self.frame.len())
        } else {
            panic!("env lookup out of bounds")
        }
    }

    pub fn is_empty(&self) -> bool {
        self.frame.is_empty() && self.parent.map(|p| p.is_empty()).unwrap_or(true)
    }

    pub fn push(&mut self, value: Value<'a>) {
        self.frame.push(value);
        self.clo = None;
    }
}

#[derive(Clone, Debug)]
pub enum StackItem<'a> {
    /// A value.
    V(Value<'a>),
    /// A stack frame.
    R(&'a Code<'a>, Env<'a>, usize),
}

pub fn run<'a>(
    arena: &'a Bump,
    mut code: &'a Code<'a>,
    env: &mut Env<'a>,
    stack: &mut Vec<StackItem<'a>>,
    argn: &mut usize,
) {
    loop {
        println!("\n");
        println!("code = {:?}", code);
        println!("env = {:#?}", env);
        println!("stack = {:#?}", stack);
        println!("argn = {}", argn);

        match *code {
            Code::Halt => break,
            Code::Access(i, k) => {
                stack.push(StackItem::V(env.get(i)));
                code = k;
            }
            Code::Apply(i) => {
                let func = stack.pop();
                if let Some(StackItem::V(Value::Clo(clo_code, clo_env))) = func {
                    code = clo_code;
                    env.frame.clear();
                    env.parent = Some(clo_env);
                    *argn = i;
                } else {
                    panic!("apply on non-function {:?}", func)
                }
            }
            Code::Grab(k) => {
                let top = stack.pop();
                if *argn == 0 {
                    if let Some(StackItem::R(saved_code, saved_env, saved_argn)) = top {
                        stack.push(StackItem::V(Value::Clo(code, env.clo(arena))));
                        code = saved_code;
                        *env = saved_env;
                        *argn = saved_argn;
                    } else {
                        panic!("grab on non-return-frame {:?}", top);
                    }
                } else {
                    if let Some(StackItem::V(val)) = top {
                        env.push(val);
                        *argn -= 1;
                        code = k;
                    } else {
                        panic!("grab on non-function {:?}", top);
                    }
                }
            }
            Code::Push(v, k) => {
                stack.push(StackItem::V(*v));
                code = k;
            }
            Code::PushClosure(c, k) => {
                stack.push(StackItem::V(Value::Clo(c, env.clo(arena))));
                code = k;
            }
            Code::PushRetAddr(c, k) => {
                stack.push(StackItem::R(c, env.clone(), *argn));
                code = k;
            }
            Code::Return => {
                let top = stack.pop();
                if *argn == 0 {
                    let snd = stack.pop();
                    if let Some(StackItem::R(saved_code, saved_env, saved_argn)) = snd {
                        stack.push(top.unwrap());
                        code = saved_code;
                        *env = saved_env;
                        *argn = saved_argn;
                    } else {
                        panic!("grab on non-return-frame {:?}", top);
                    }
                } else {
                    if let Some(StackItem::V(Value::Clo(clo_code, clo_env))) = top {
                        code = clo_code;
                        env.frame.clear();
                        env.parent = Some(clo_env);
                    } else {
                        panic!("tail-call on non-function {:?}", top)
                    }
                }
            }
        }
    }
}

#[test]
fn const_const5_id_6() {
    let arena = Bump::new();

    let k = arena.alloc(Code::Grab(arena.alloc(Code::Grab(
        arena.alloc(Code::Access(1, arena.alloc(Code::Return))),
    ))));
    let i = arena.alloc(Code::Grab(
        arena.alloc(Code::Access(0, arena.alloc(Code::Return))),
    ));

    let code = arena.alloc(Code::PushRetAddr(
        arena.alloc(Code::Halt),
        arena.alloc(Code::Push(
            arena.alloc(Value::Int(6)),
            arena.alloc(Code::PushClosure(
                i,
                arena.alloc(Code::PushRetAddr(
                    arena.alloc(Code::PushClosure(k, arena.alloc(Code::Apply(3)))),
                    arena.alloc(Code::Push(
                        arena.alloc(Value::Int(5)),
                        arena.alloc(Code::PushClosure(k, arena.alloc(Code::Apply(1)))),
                    )),
                )),
            )),
        )),
    ));

    let mut env = Env::default();
    let mut stack = Vec::new();
    let mut argn = 0;

    run(&arena, code, &mut env, &mut stack, &mut argn);

    assert!(env.is_empty());
    assert_eq!(argn, 0);
    assert!(matches!(&stack as &[_], &[StackItem::V(Value::Int(6))]));
}