Implement unwinding mechanism, repl example

This commit is contained in:
2026-05-08 23:19:17 +03:00
parent aa7e371747
commit 01012ae211
10 changed files with 249 additions and 32 deletions
+4
View File
@@ -0,0 +1,4 @@
;; vi:ft=lisp:sw=2:ts=2
(print (length *args*))
(print *args*)
+36
View File
@@ -0,0 +1,36 @@
;; vi:ft=lisp:sw=2:ts=2
(defun cadr (x) (car (cdr x)))
(defun map-ok-err (f-ok f-err result)
(if (= (car result) 'ok)
`(ok ,(f-ok (cadr result)))
`(err ,(f-err (cadr result)))
)
)
(defun map-ok (f-ok result) (map-ok-err f-ok identity result))
(defun map-err (f-err result) (map-ok-err identity f-err result))
(defun repl-print (value)
(print "==>" value)
)
(defun repl-eval-print (expression)
(map-ok-err repl-print repl-eval-error (unquote (eval expression)))
)
(defun repl-eval-error (error)
(print "Evaluation error:")
(print error)
)
(defun repl-read-error (error)
(print "Parse error:")
(print error)
)
(loop
(let (expression (read))
(if expression NIL (return))
(setq expression (unquote expression))
(map-ok-err repl-eval-print repl-read-error expression)
)
)
+5 -1
View File
@@ -18,6 +18,7 @@ use crate::{
#[derive(Default)]
pub struct CompilationModule {
pub(crate) name: Option<Rc<str>>,
pub(crate) constant_pool: Pool<CompileConstant, { ConstantId::BITS }>,
pub(crate) local_functions: HashMap<u32, CompiledFunction>,
pub(crate) options: CompileOptions,
@@ -27,8 +28,9 @@ pub struct CompilationModule {
}
impl CompilationModule {
pub fn new(options: CompileOptions) -> Self {
pub fn new(name: Option<Rc<str>>, options: CompileOptions) -> Self {
Self {
name,
options,
..Default::default()
}
@@ -70,6 +72,7 @@ impl CompilationModule {
// Emit all function code first
let mut function_offsets = HashMap::new();
let mut instructions = vec![];
let name = self.name;
let root = self.root.unwrap();
for (index, function) in self.local_functions.into_iter() {
function_offsets.insert(index, instructions.len());
@@ -99,6 +102,7 @@ impl CompilationModule {
.collect();
Ok(Module {
name,
constants,
instructions,
entry,
+21 -4
View File
@@ -30,6 +30,8 @@ enum Error {
pub enum Trace {
Compile,
Execute,
Call,
Return,
}
impl FromStr for Trace {
@@ -39,6 +41,8 @@ impl FromStr for Trace {
match s {
"compile" => Ok(Self::Compile),
"execute" => Ok(Self::Execute),
"call" => Ok(Self::Call),
"return" => Ok(Self::Return),
_ => Err(format!("Unknown trace flag: {s:?}")),
}
}
@@ -53,6 +57,7 @@ struct Args {
)]
trace: Vec<Trace>,
module: Option<PathBuf>,
arguments: Vec<String>,
}
fn print_syntax_errors(errors: &[ParseError]) {
@@ -84,7 +89,7 @@ fn handle_eval_error(value: Option<&Value>, input: EvalError) -> Error {
eprintln!(":: {}", error.error);
eprintln!();
if let Some(ip) = error.ip.as_ref() {
ip.module.dump(Some(ip.address), 8);
ip.module.dump(Some(ip.address), 8, 2);
}
}
EvalError::Compile(CompileError::Parse(errors)) => {
@@ -119,7 +124,7 @@ fn eval(
env: &mut Environment,
value: Value,
) -> Option<Value> {
let result = vm.eval_value(options.clone(), env, value.clone());
let result = vm.eval_value(options.clone(), env, value.clone(), false);
match result {
Ok(r) => Some(r),
Err(error) => {
@@ -162,14 +167,15 @@ fn run_module<P: AsRef<Path>>(
path: P,
) -> Result<(), Error> {
let path = path.as_ref();
let name = format!("{}", path.display());
let reader = BufReader::new(File::open(path)?);
let module_reader = ModuleReader::new(reader);
let module = match module_reader.compile(compile_options, env) {
let module = match module_reader.compile(Some(name.into()), compile_options, env) {
Ok(module) => module,
Err(error) => return Err(handle_module_error(error)),
};
match vm.eval_module(env, module) {
match vm.eval_module(env, module, false) {
Ok(_) => Ok(()),
Err(error) => Err(handle_eval_error(None, error)),
}
@@ -182,8 +188,19 @@ fn main() -> ExitCode {
trace_compile: args.trace.contains(&Trace::Compile),
};
vm.trace_instructions = args.trace.contains(&Trace::Execute);
vm.trace_calls = args.trace.contains(&Trace::Call);
vm.trace_returns = args.trace.contains(&Trace::Return);
let mut env = Environment::default();
prelude::load(&mut env);
let mut arguments = vec![];
if let Some(script) = args.module.as_ref() {
arguments.push(format!("{}", script.display()));
}
arguments.extend(args.arguments);
env.set_global_value(
"*args*",
Value::list_or_nil(arguments.into_iter().map(|arg| Value::String(arg.into()))),
);
let result = match args.module.as_ref() {
Some(module) => run_module(&compile_options, &mut vm, &mut env, module),
None => run_interactive(&compile_options, &mut vm, &mut env),
+3 -2
View File
@@ -120,7 +120,7 @@ impl<R: BufRead> ModuleReader<R> {
let expression = Expression::parse(&value).map_err(Either::Right)?;
if let Expression::Defmacro(_) = expression.as_ref() {
self.macro_machine
.eval_value(options.clone(), env, value)
.eval_value(options.clone(), env, value, false)
.map_err(Either::Left)?;
continue;
}
@@ -130,10 +130,11 @@ impl<R: BufRead> ModuleReader<R> {
pub fn compile(
mut self,
module_name: Option<Rc<str>>,
options: &CompileOptions,
env: &mut Environment,
) -> Result<ModuleRef, Either<EvalError, Vec<ParseError>>> {
let mut module = CompilationModule::new(options.clone());
let mut module = CompilationModule::new(module_name, options.clone());
let mut body = FunctionBody {
head: vec![],
tail: Rc::new(Expression::Nil),
+102 -18
View File
@@ -33,6 +33,8 @@ pub struct Machine {
value_stack: Stack<Value>,
pub call_stack: Stack<CallFrame>,
pub trace_instructions: bool,
pub trace_calls: bool,
pub trace_returns: bool,
// Top-level locals
locals: HashMap<u32, Value>,
}
@@ -53,6 +55,8 @@ impl Default for Machine {
locals: HashMap::new(),
trace_instructions: false,
trace_calls: false,
trace_returns: false,
}
}
}
@@ -123,10 +127,20 @@ impl Machine {
}),
locals: HashMap::new(),
};
let entry_ip = InstructionPointer { module, address };
if self.trace_calls {
eprintln!("TRACE: Call bytecode function");
if let Some(source_ip) = self.ip.as_ref() {
eprintln!("TRACE: From {source_ip}");
} else {
eprintln!("TRACE: From <undefined>");
}
eprintln!("TRACE: To {entry_ip}");
}
if self.call_stack.push(frame).is_err() {
return Err(self.error_at_ip(MachineErrorKind::CallStackOverflow));
}
self.ip = Some(InstructionPointer { module, address });
self.ip = Some(entry_ip);
Ok(())
}
@@ -187,10 +201,26 @@ impl Machine {
fn execute_return(&mut self) -> Result<ExecutionEvent, MachineError> {
let ip = self.ip.clone().unwrap();
if self.trace_returns {
eprintln!("TRACE: Return");
eprintln!("TRACE: From {ip}");
ip.module.dump(Some(ip.address), 4, 0);
}
if let Some(frame) = self.call_stack.pop() {
if self.trace_returns {
if let Some(target_ip) = frame.return_address.as_ref() {
eprintln!("TRACE: To {target_ip}");
} else {
eprintln!("TRACE: To <undefined>");
}
}
self.ip = frame.return_address;
Ok(frame.event)
} else {
if self.trace_returns {
eprintln!("TRACE: To <undefined>");
}
self.ip = None;
Ok(ExecutionEvent::ModuleExit(ip.module))
}
@@ -370,6 +400,36 @@ impl Machine {
eprintln!();
}
fn unwind(&mut self, until: ExecutionEvent) {
if self.trace_returns {
eprintln!("TRACE: Begin unwind");
if let Some(ip) = self.ip.as_ref() {
eprintln!("TRACE: <- {ip}");
} else {
eprintln!("TRACE: <- <undefined>");
}
}
let mut ip = self.ip.clone();
while let Some(frame) = self.call_stack.pop() {
if self.trace_returns {
eprintln!("TRACE: Unwind frame:");
if let Some(ip) = frame.return_address.as_ref() {
eprintln!("TRACE: -> {ip}");
} else {
eprintln!("TRACE: -> <undefined>");
}
}
ip = frame.return_address;
if frame.event == until {
break;
}
}
self.ip = ip;
if self.trace_returns {
eprintln!("TRACE: Finished unwind");
}
}
pub fn execute_next(
&mut self,
environment: &mut Environment,
@@ -469,23 +529,38 @@ impl Machine {
Ok(value)
}
pub fn load_module(&mut self, module: ModuleRef) -> Result<ModuleRef, MachineError> {
pub fn load_module(
&mut self,
module: ModuleRef,
advance_on_return: bool,
) -> Result<ModuleRef, MachineError> {
let entry = module.entry();
let entry_ip = InstructionPointer {
module: module.clone(),
address: entry,
};
if let Some(ip) = self.ip.clone()
&& self
.call_stack
.push(CallFrame {
arguments: vec![],
return_address: Some(ip),
event: ExecutionEvent::ModuleExit(module.clone()),
locals: HashMap::new(),
})
.is_err()
{
let entry_frame = CallFrame {
arguments: vec![],
event: ExecutionEvent::ModuleExit(module.clone()),
locals: HashMap::new(),
return_address: self.ip.clone().map(|ip| InstructionPointer {
module: ip.module,
address: ip.address + advance_on_return as usize,
}),
};
if self.trace_calls {
eprintln!("TRACE: Enter module");
if let Some(source_ip) = self.ip.as_ref() {
eprintln!("TRACE: From {source_ip}");
} else {
eprintln!("TRACE: From <undefined>");
}
eprintln!("TRACE: To {entry_ip}");
}
if self.call_stack.push(entry_frame).is_err() {
return Err(self.error_at_ip(MachineErrorKind::CallStackOverflow));
}
self.ip = Some(entry_ip);
@@ -496,8 +571,9 @@ impl Machine {
&mut self,
environment: &mut Environment,
module: ModuleRef,
advance_on_return: bool,
) -> Result<Value, EvalError> {
let module = match self.load_module(module) {
let module = match self.load_module(module, advance_on_return) {
Ok(module) => module,
Err(error) => return Err(EvalError::Machine(error)),
};
@@ -505,7 +581,10 @@ impl Machine {
loop {
let event = match self.execute_next(environment) {
Ok(event) => event,
Err(error) => return Err(EvalError::Machine(error)),
Err(error) => {
self.unwind(expect);
return Err(EvalError::Machine(error));
}
};
if event == expect {
break;
@@ -520,17 +599,22 @@ impl Machine {
compile_options: CompileOptions,
environment: &mut Environment,
value: Value,
advance_on_return: bool,
) -> Result<Value, EvalError> {
let value = value.macro_expand(self, environment, false)?;
let module = Module::compile_value(compile_options, &value)?;
let module = ModuleRef::from(module);
self.eval_module(environment, module)
self.eval_module(environment, module, advance_on_return)
}
}
impl fmt::Display for InstructionPointer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:p}:{}", self.module, self.address)
if let Some(name) = self.module.name.as_ref() {
write!(f, "<{} {:p}>:{}", name, self.module, self.address)
} else {
write!(f, "<unnamed {:p}>:{}", self.module, self.address)
}
}
}
@@ -568,7 +652,7 @@ mod tests {
build(i as u32, &mut builder);
builder.add(Instruction::Return);
let module = builder.build();
values.push(machine.eval_module(&mut env, module.into()).unwrap());
values.push(machine.eval_module(&mut env, module.into(), false).unwrap());
}
(machine, values)
}
+6 -3
View File
@@ -41,6 +41,7 @@ impl Hash for ModuleRef {
impl Eq for ModuleRef {}
pub struct Module {
pub name: Option<Rc<str>>,
pub constants: HashMap<ConstantId, ModuleConstant>,
pub instructions: Vec<u32>,
pub entry: usize,
@@ -97,6 +98,7 @@ impl Deref for ModuleRef {
impl Module {
pub fn dummy() -> Self {
Self {
name: None,
constants: HashMap::new(),
instructions: vec![0],
entry: 0,
@@ -127,7 +129,7 @@ impl Module {
pub fn compile_value(options: CompileOptions, value: &Value) -> Result<Self, CompileError> {
let expression = Expression::parse(value).map_err(CompileError::Parse)?;
let mut module = CompilationModule::new(options);
let mut module = CompilationModule::new(None, options);
module.compile_function(
FunctionSignature::EMPTY,
&FunctionBody {
@@ -139,10 +141,10 @@ impl Module {
module.compile_module()
}
pub fn dump(&self, highlight: Option<usize>, context: usize) {
pub fn dump(&self, highlight: Option<usize>, context_backward: usize, context_forward: usize) {
let window = highlight
.map(|end| (end + 1).min(self.instructions.len()))
.map(|end| end.saturating_sub(context)..end)
.map(|end| end.saturating_sub(context_backward)..end.saturating_add(context_forward))
.unwrap_or(0..self.instructions.len());
let start = window.start;
@@ -178,6 +180,7 @@ impl ModuleBuilder {
pub fn build(self) -> Module {
Module {
name: None,
constants: self.constants.into_map(),
instructions: self.instructions,
entry: self.entry.unwrap(),
+1
View File
@@ -200,6 +200,7 @@ pub(crate) fn builtin_cmp(
(Value::Integer(a), Value::Integer(b)) => Ord::cmp(a, b),
(Value::Boolean(a), Value::Boolean(b)) => Ord::cmp(a, b),
(Value::String(a), Value::String(b)) => Ord::cmp(a, b),
(Value::Identifier(a), Value::Identifier(b)) => Ord::cmp(a, b),
_ => Ordering::Less,
}
}
+70 -3
View File
@@ -2,6 +2,7 @@ use std::{rc::Rc, slice};
use crate::{
error::MachineErrorKind,
read::{self, InteractiveReader},
util::IteratorExt,
vm::{
env::Environment,
@@ -52,6 +53,24 @@ pub fn load(env: &mut Environment) {
});
// lists
env.defun_native("car", |vm, _env, args| {
let [x] = args else {
return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
};
let Value::Cons(cons) = x else {
return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
};
Ok(cons.0.clone())
});
env.defun_native("cdr", |vm, _env, args| {
let [x] = args else {
return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
};
let Value::Cons(cons) = x else {
return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
};
Ok(cons.1.clone())
});
env.defun_native("cons", |vm, _env, args| {
let [car, cdr] = args else {
return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
@@ -85,6 +104,23 @@ pub fn load(env: &mut Environment) {
let out = Value::list_or_nil(args.iter().cloned());
Ok(out)
});
env.defun_native("length", |vm, _, args| {
let [xs] = args else {
return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
};
let mut xs = xs;
let mut count = 0;
while !xs.is_nil() {
let Value::Cons(cons) = xs else {
break;
};
let ConsCell(_, cdr) = cons.as_ref();
count += 1;
xs = cdr;
}
Ok(Value::Integer(count))
});
// functional
env.defun_native("identity", |vm, _, args| {
@@ -102,9 +138,15 @@ pub fn load(env: &mut Environment) {
[_, _] => todo!(),
_ => todo!(),
};
let value = match vm.eval_value(Default::default(), env, value.clone()) {
Ok(result) => result,
_ => todo!(),
let value = match vm.eval_value(Default::default(), env, value.clone(), true) {
Ok(result) => Value::Quote(Rc::new(Value::list_or_nil([
Value::Identifier("ok".into()),
result,
]))),
Err(error) => Value::Quote(Rc::new(Value::list_or_nil([
Value::Identifier("err".into()),
Value::String(format!("{error}").into()),
]))),
};
Ok(value)
});
@@ -121,6 +163,31 @@ pub fn load(env: &mut Environment) {
});
// io
env.defun_native("read", |vm, env, _args| {
let mut reader = InteractiveReader::new("> ", ">> ");
let value = read::read(&mut reader, vm, env);
let value = match value {
Ok(Some(value)) => Value::Quote(Rc::new(Value::list_or_nil([
Value::Identifier("ok".into()),
value,
]))),
Ok(None) => Value::Nil,
Err(error) => Value::Quote(Rc::new(Value::list_or_nil([
Value::Identifier("err".into()),
Value::String(format!("{error}").into()),
]))),
};
Ok(value)
});
env.defun_native("unquote", |vm, _env, args| {
let [x] = args else {
return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
};
let Value::Quote(value) = x else {
return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
};
Ok(value.as_ref().clone())
});
env.defun_native("print", |_, _, args| {
for (i, arg) in args.iter().enumerate() {
if i != 0 {
+1 -1
View File
@@ -30,7 +30,7 @@ fn eval_str_in(code: &str, env: &mut Environment) -> Result<Value, EvalError> {
Err(error) => panic!("{error}"),
};
last_value = Some(machine.eval_value(Default::default(), env, value));
last_value = Some(machine.eval_value(Default::default(), env, value, false));
}
last_value.expect("no expressions evaluated")