From aa0026fa451a621b2d7a2c430973747ba94ea321 Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Fri, 8 May 2026 17:16:32 +0300 Subject: [PATCH] Tracing options, while loop --- examples/factorial.lysp | 32 +++++++------- examples/scopes.lysp | 25 +++++++++++ src/compile/block.rs | 78 ++++++++++++++++++++++++++------- src/compile/mod.rs | 5 +++ src/compile/module.rs | 11 ++++- src/compile/syntax/condition.rs | 6 +-- src/compile/syntax/loops.rs | 41 +++++++++++++++++ src/compile/syntax/mod.rs | 7 +++ src/main.rs | 55 +++++++++++++++++++---- src/read.rs | 12 +++-- src/vm/instruction.rs | 10 ++++- src/vm/machine.rs | 4 +- src/vm/module.rs | 22 ++++++---- src/vm/prelude/mod.rs | 2 +- tests/integration.rs | 2 +- 15 files changed, 253 insertions(+), 59 deletions(-) create mode 100644 examples/scopes.lysp create mode 100644 src/compile/syntax/loops.rs diff --git a/examples/factorial.lysp b/examples/factorial.lysp index 183d33a..12c9d10 100644 --- a/examples/factorial.lysp +++ b/examples/factorial.lysp @@ -7,18 +7,20 @@ ) ) -(print 1 "\t" (factorial 1)) -(print 2 "\t" (factorial 2)) -(print 3 "\t" (factorial 3)) -(print 4 "\t" (factorial 4)) -(print 5 "\t" (factorial 5)) -(print 6 "\t" (factorial 6)) -(print 7 "\t" (factorial 7)) -(print 8 "\t" (factorial 8)) -(print 9 "\t" (factorial 9)) -(print 10 "\t" (factorial 10)) -(print 11 "\t" (factorial 11)) -(print 12 "\t" (factorial 12)) -(print 13 "\t" (factorial 13)) -(print 14 "\t" (factorial 14)) -(print 15 "\t" (factorial 15)) +(defun loop-factorial (x) + (let (i 0 acc 1) + (while (< i x) + (setq i (+ i 1)) + (setq acc (* acc i)) + ) + acc + ) + ) + +;; TODO for loops? +(let (i 0) + (while (<= i 15) + (print i "\t" (factorial i) "\t" (loop-factorial i)) + (setq i (+ i 1)) + ) + ) diff --git a/examples/scopes.lysp b/examples/scopes.lysp new file mode 100644 index 0000000..5537d98 --- /dev/null +++ b/examples/scopes.lysp @@ -0,0 +1,25 @@ +;; vi:ft=lisp:sw=2:ts=2 + +(defun replace-argument (x) (let (x 1) x)) + +(defun replace-let () + (let (x 1) + (setq x 2) ;; mutate local x + x + ) + ) + +(defun reassignment-in-a-loop (iterations) + (let (x 0 y 1) + (while (< x iterations) + (setq y (* y 2)) + (setq x (+ x 1)) + ) + y + ) + ) + +(assert (= (replace-argument 2) 1)) +(assert (= (replace-argument 3) 1)) +(assert (= (replace-let) 2)) +(assert (= (reassignment-in-a-loop 4) 16)) diff --git a/src/compile/block.rs b/src/compile/block.rs index 96d850c..368b74a 100644 --- a/src/compile/block.rs +++ b/src/compile/block.rs @@ -9,6 +9,7 @@ use crate::{ syntax::{ CallExpression, CondExpression, DefmacroExpression, DefunExpression, Expression, FunctionBody, IfExpression, LambdaExpression, LetExpression, SetqExpression, + WhileExpression, }, value::{BuiltinFunction, CompileConstant, CompileValue}, }, @@ -136,13 +137,19 @@ impl FunctionBlock { self.labels.insert(label, self.instructions.len()); } - pub fn resolve_labels(self) -> CompiledFunction { + pub fn resolve_labels(self, trace: bool) -> CompiledFunction { let mut instructions = vec![]; - // eprintln!("RESOLVE LABELS"); + + if trace { + eprintln!("Instruction resolution:"); + } + for (function_offset, emitted) in self.instructions.into_iter().enumerate() { match emitted { Emitted::Instruction(instruction) => { - // eprintln!("{function_offset}: {instruction:?}"); + if trace { + eprintln!("{function_offset}: {instruction:?}"); + } instructions.push(instruction); } Emitted::Branch(label) => { @@ -151,9 +158,11 @@ impl FunctionBlock { todo!() } let diff = address.checked_signed_diff(function_offset).expect("TODO"); - // eprintln!( - // "{function_offset}: branch to label {label} (@ {address}) -> {diff:+}" - // ); + if trace { + eprintln!( + "{function_offset}: Branch: label {label} (@ {address}) -> {diff:+}" + ); + } let offset = U::from_signed(diff as i64).expect("TODO"); instructions.push(Instruction::Branch(offset)); } @@ -163,7 +172,11 @@ impl FunctionBlock { todo!() } let diff = address.checked_signed_diff(function_offset).expect("TODO"); - // eprintln!("{function_offset}: jump to label {label} (@ {address}) -> {diff:+}"); + if trace { + eprintln!( + "{function_offset}: jump to label {label} (@ {address}) -> {diff:+}" + ); + } let offset = U::from_signed(diff as i64).expect("TODO"); instructions.push(Instruction::Jump(offset)); } @@ -464,6 +477,30 @@ impl<'a> LocalBlock<'a> { Ok(CompileValue::Stack) } + fn compile_while(&mut self, cloop: &WhileExpression) -> Result { + let label_start = self.function.new_label(); + let label_end = self.function.new_label(); + + self.function.adjust_label(label_start); + + // Condition + let condition_value = self.compile_expression(&cloop.condition)?; + self.compile_push(condition_value)?; + self.function.emit(Emitted::Branch(label_end)); + + // Body + for expression in &cloop.body.head { + self.compile_statement(expression)?; + } + self.compile_statement(&cloop.body.tail)?; + + // Jump back + self.function.emit(Emitted::Jump(label_start)); + + self.function.adjust_label(label_end); + Ok(CompileValue::Nil) + } + fn compile_let(&mut self, binding: &LetExpression) -> Result { self.function.push_local_scope(); let mut indices = vec![]; @@ -496,7 +533,16 @@ impl<'a> LocalBlock<'a> { fn compile_setq(&mut self, setq: &SetqExpression) -> Result { for pair in setq.pairs.iter() { let value = self.compile_expression(&pair.value)?; - self.compile_set_global(&pair.identifier, value)?; + if let Some(index) = self + .function + .local_scope_ref() + .and_then(|scope| scope.get(&pair.identifier)) + { + self.compile_push(value)?; + self.function.emit(Instruction::SetLocal(index)); + } else { + self.compile_set_global(&pair.identifier, value)?; + } } Ok(CompileValue::Nil) } @@ -524,6 +570,7 @@ impl<'a> LocalBlock<'a> { Expression::Setq(setq) => self.compile_setq(setq), Expression::Defmacro(defmacro) => self.compile_defmacro(defmacro), Expression::Quote(quote) => self.compile_quote(quote), + Expression::While(cloop) => self.compile_while(cloop), Expression::SyntaxError(_) => unreachable!(), } @@ -600,7 +647,8 @@ mod tests { }; let mut local = LocalBlock::root(&mut function, &mut module); let value = local.compile_expression(&Rc::new(expression)).unwrap(); - (module, function.resolve_labels(), value) + let trace = module.options.trace_compile; + (module, function.resolve_labels(trace), value) } #[test] @@ -647,9 +695,9 @@ mod tests { &f.instructions, &[ Instruction::PushBool(false), // 0 - Instruction::Branch(U::truncate(4)), // 1 + Instruction::Branch(U::truncate(3)), // 1 Instruction::PushInteger(U::truncate(1)), // 2 - Instruction::Jump(U::truncate(5)), // 3 + Instruction::Jump(U::truncate(2)), // 3 Instruction::PushInteger(U::truncate(2)) // 4 // 5 ] @@ -676,13 +724,13 @@ mod tests { &f.instructions, &[ Instruction::PushBool(false), // 0 - Instruction::Branch(U::truncate(4)), // 1 + Instruction::Branch(U::truncate(3)), // 1 Instruction::PushInteger(U::truncate(1)), // 2 - Instruction::Jump(U::truncate(9)), // 3 + Instruction::Jump(U::truncate(6)), // 3 Instruction::PushBool(true), // 4 - Instruction::Branch(U::truncate(8)), // 5 + Instruction::Branch(U::truncate(3)), // 5 Instruction::PushInteger(U::truncate(2)), // 6 - Instruction::Jump(U::truncate(9)), // 7 + Instruction::Jump(U::truncate(2)), // 7 Instruction::PushInteger(U::truncate(3)), // 8 // 9 ] diff --git a/src/compile/mod.rs b/src/compile/mod.rs index 42dcbb9..60415e1 100644 --- a/src/compile/mod.rs +++ b/src/compile/mod.rs @@ -13,3 +13,8 @@ pub use syntax::{ CallExpression, ExpectedWhat, ExpectedWhere, Expression, FunctionBody, LambdaExpression, ParseError, ParseErrorKind, }; + +#[derive(Default, Clone)] +pub struct CompileOptions { + pub trace_compile: bool, +} diff --git a/src/compile/module.rs b/src/compile/module.rs index 4676445..aa95af7 100644 --- a/src/compile/module.rs +++ b/src/compile/module.rs @@ -2,6 +2,7 @@ use std::{collections::HashMap, rc::Rc}; use crate::{ compile::{ + CompileOptions, block::{CompiledFunction, FunctionBlock}, error::CompileError, function::FunctionSignature, @@ -19,12 +20,20 @@ use crate::{ pub struct CompilationModule { pub(crate) constant_pool: Pool, pub(crate) local_functions: HashMap, + pub(crate) options: CompileOptions, macros: HashMap, DefmacroExpression>, local_function_index: u32, root: Option, } impl CompilationModule { + pub fn new(options: CompileOptions) -> Self { + Self { + options, + ..Default::default() + } + } + pub fn define_macro(&mut self, defmacro: DefmacroExpression) { self.macros.insert(defmacro.name.clone(), defmacro); } @@ -49,7 +58,7 @@ impl CompilationModule { self.local_function_index += 1; let mut function = FunctionBlock::new(signature); function.compile_body(self, body)?; - let function = function.resolve_labels(); + let function = function.resolve_labels(self.options.trace_compile); self.local_functions.insert(index, function); if root { self.root = Some(index); diff --git a/src/compile/syntax/condition.rs b/src/compile/syntax/condition.rs index 7acba7f..f98bc3c 100644 --- a/src/compile/syntax/condition.rs +++ b/src/compile/syntax/condition.rs @@ -64,9 +64,9 @@ impl IfExpression { Some(Expression::parse_inner(if_false)) }; Ok(Self { - condition: condition.into(), - if_true: if_true.into(), - if_false: if_false.map(Into::into), + condition, + if_true, + if_false, }) } } diff --git a/src/compile/syntax/loops.rs b/src/compile/syntax/loops.rs new file mode 100644 index 0000000..f1a2a14 --- /dev/null +++ b/src/compile/syntax/loops.rs @@ -0,0 +1,41 @@ +use std::rc::Rc; + +use crate::{ + compile::{ + ExpectedWhat, ExpectedWhere, Expression, FunctionBody, ParseError, ParseErrorKind, + syntax::CollectErrors, + }, + vm::value::{ConsCell, Keyword, Value}, +}; + +#[derive(Debug, PartialEq)] +pub struct WhileExpression { + pub condition: Rc, + pub body: FunctionBody, +} + +impl WhileExpression { + pub(super) fn parse(value: &Value, input: &Value) -> Result { + let Value::Cons(cons) = value else { + return Err(ParseError { + input: input.clone(), + error: ParseErrorKind::Expected( + ExpectedWhat::ProperList, + ExpectedWhere::AfterKeyword(Keyword::While), + ), + }); + }; + let ConsCell(car, cdr) = cons.as_ref(); + let condition = Expression::parse_inner(car); + let body = FunctionBody::parse(cdr, input, Keyword::While)?; + Ok(Self { condition, body }) + } +} + +impl CollectErrors for WhileExpression { + fn collect_errors(&self, errors: &mut Vec) -> bool { + let a = self.condition.collect_errors(errors); + let b = self.body.collect_errors(errors); + a | b + } +} diff --git a/src/compile/syntax/mod.rs b/src/compile/syntax/mod.rs index c00a278..6737527 100644 --- a/src/compile/syntax/mod.rs +++ b/src/compile/syntax/mod.rs @@ -8,6 +8,7 @@ mod condition; mod error; mod function; mod lambda; +mod loops; mod macros; pub use binding::*; @@ -16,6 +17,7 @@ pub use condition::*; pub use error::*; pub use function::*; pub use lambda::*; +pub use loops::*; pub use macros::*; #[derive(Debug, PartialEq)] @@ -35,6 +37,7 @@ pub enum Expression { Defmacro(DefmacroExpression), SyntaxError(ParseError), Quote(Rc), + While(WhileExpression), } impl Expression { @@ -97,6 +100,9 @@ impl Expression { }; Rc::new(Self::Quote(value.into())) } + Value::Keyword(Keyword::While) => { + Self::map_or(WhileExpression::parse(cdr, value), Expression::While) + } _ => Self::map_or(CallExpression::parse(cons, value), Expression::Call), } } @@ -123,6 +129,7 @@ impl CollectErrors for Expression { Self::Let(let_) => let_.collect_errors(errors), Self::Setq(setq) => setq.collect_errors(errors), Self::Defmacro(defmacro) => defmacro.collect_errors(errors), + Self::While(cloop) => cloop.collect_errors(errors), Self::Nil | Self::IntegerLiteral(_) | Self::Identifier(_) diff --git a/src/main.rs b/src/main.rs index e0d6ced..9796ce5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,11 +3,12 @@ use std::{ io::{self, BufReader}, path::{Path, PathBuf}, process::ExitCode, + str::FromStr, }; use clap::Parser; use lysp::{ - compile::{CompileError, ParseError}, + compile::{CompileError, CompileOptions, ParseError}, error::{EvalError, MachineErrorKind}, read::{InteractiveReader, ModuleReader, read}, util::Either, @@ -25,8 +26,32 @@ enum Error { Printed, } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Trace { + Compile, + Execute, +} + +impl FromStr for Trace { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "compile" => Ok(Self::Compile), + "execute" => Ok(Self::Execute), + _ => Err(format!("Unknown trace flag: {s:?}")), + } + } +} + #[derive(Debug, Parser)] struct Args { + #[clap( + short = 'T', + long = "trace", + help = "Enable tracing of execution/compilation steps" + )] + trace: Vec, module: Option, } @@ -88,8 +113,13 @@ fn handle_module_error(input: Either>) -> Error { } } -fn eval(vm: &mut Machine, env: &mut Environment, value: Value) -> Option { - let result = vm.eval_value(env, value.clone()); +fn eval( + options: &CompileOptions, + vm: &mut Machine, + env: &mut Environment, + value: Value, +) -> Option { + let result = vm.eval_value(options.clone(), env, value.clone()); match result { Ok(r) => Some(r), Err(error) => { @@ -99,7 +129,11 @@ fn eval(vm: &mut Machine, env: &mut Environment, value: Value) -> Option } } -fn run_interactive(vm: &mut Machine, env: &mut Environment) -> Result<(), Error> { +fn run_interactive( + compile_options: &CompileOptions, + vm: &mut Machine, + env: &mut Environment, +) -> Result<(), Error> { let mut reader = InteractiveReader::new("> ", ">> "); loop { @@ -111,7 +145,7 @@ fn run_interactive(vm: &mut Machine, env: &mut Environment) -> Result<(), Error> continue; } }; - if let Some(value) = eval(vm, env, value) { + if let Some(value) = eval(compile_options, vm, env, value) { println!("== {value}"); } else { reader.reset(); @@ -122,6 +156,7 @@ fn run_interactive(vm: &mut Machine, env: &mut Environment) -> Result<(), Error> } fn run_module>( + compile_options: &CompileOptions, vm: &mut Machine, env: &mut Environment, path: P, @@ -129,7 +164,7 @@ fn run_module>( let path = path.as_ref(); let reader = BufReader::new(File::open(path)?); let module_reader = ModuleReader::new(reader); - let module = match module_reader.compile(env) { + let module = match module_reader.compile(compile_options, env) { Ok(module) => module, Err(error) => return Err(handle_module_error(error)), }; @@ -143,11 +178,15 @@ fn run_module>( fn main() -> ExitCode { let args = Args::parse(); let mut vm = Machine::default(); + let compile_options = CompileOptions { + trace_compile: args.trace.contains(&Trace::Compile), + }; + vm.trace_instructions = args.trace.contains(&Trace::Execute); let mut env = Environment::default(); prelude::load(&mut env); let result = match args.module.as_ref() { - Some(module) => run_module(&mut vm, &mut env, module), - None => run_interactive(&mut vm, &mut env), + Some(module) => run_module(&compile_options, &mut vm, &mut env, module), + None => run_interactive(&compile_options, &mut vm, &mut env), }; match result { Ok(()) => ExitCode::SUCCESS, diff --git a/src/read.rs b/src/read.rs index 246e04d..347b196 100644 --- a/src/read.rs +++ b/src/read.rs @@ -5,7 +5,9 @@ use std::{ }; use crate::{ - compile::{CompilationModule, Expression, FunctionBody, FunctionSignature, ParseError}, + compile::{ + CompilationModule, CompileOptions, Expression, FunctionBody, FunctionSignature, ParseError, + }, error::EvalError, parse::{self, parse_value}, util::Either, @@ -106,6 +108,7 @@ impl ModuleReader { pub fn read_expression( &mut self, + options: &CompileOptions, env: &mut Environment, ) -> Result>, Either>> { loop { @@ -117,7 +120,7 @@ impl ModuleReader { let expression = Expression::parse(&value).map_err(Either::Right)?; if let Expression::Defmacro(_) = expression.as_ref() { self.macro_machine - .eval_value(env, value) + .eval_value(options.clone(), env, value) .map_err(Either::Left)?; continue; } @@ -127,9 +130,10 @@ impl ModuleReader { pub fn compile( mut self, + options: &CompileOptions, env: &mut Environment, ) -> Result>> { - let mut module = CompilationModule::default(); + let mut module = CompilationModule::new(options.clone()); let mut body = FunctionBody { head: vec![], tail: Rc::new(Expression::Nil), @@ -138,7 +142,7 @@ impl ModuleReader { let mut syntax_errors = vec![]; loop { - let expression = match self.read_expression(env) { + let expression = match self.read_expression(options, env) { Ok(Some(expression)) => expression, Ok(None) => break, Err(Either::Left(error)) => return Err(Either::Left(error)), diff --git a/src/vm/instruction.rs b/src/vm/instruction.rs index 45b038a..c28d83e 100644 --- a/src/vm/instruction.rs +++ b/src/vm/instruction.rs @@ -91,7 +91,7 @@ pub enum Instruction { } pub type ConstantId = U<12>; -pub type FunctionOffset = U<12>; +pub type FunctionOffset = U<10>; pub type LocalId = U<8>; pub type ArgumentId = U<6>; pub type ArgumentCount = U<6>; @@ -222,7 +222,7 @@ impl TryFrom for Instruction { #[cfg(test)] mod tests { - use crate::vm::instruction::U; + use crate::vm::instruction::{FunctionOffset, U}; #[test] fn test_u_convert() { @@ -238,4 +238,10 @@ mod tests { let t = T::from_signed(-0x1000001); assert!(t.is_none()); } + + #[test] + fn test_branch_target_extend() { + let target = FunctionOffset::truncate(1022); + assert_eq!(target.sign_extend_i64(), -2); + } } diff --git a/src/vm/machine.rs b/src/vm/machine.rs index 18f4b8b..fab48f5 100644 --- a/src/vm/machine.rs +++ b/src/vm/machine.rs @@ -1,6 +1,7 @@ use std::{collections::HashMap, fmt}; use crate::{ + compile::CompileOptions, error::{EvalError, MachineError, MachineErrorKind}, vm::{ env::Environment, @@ -516,11 +517,12 @@ impl Machine { pub fn eval_value( &mut self, + compile_options: CompileOptions, environment: &mut Environment, value: Value, ) -> Result { let value = value.macro_expand(self, environment, false)?; - let module = Module::compile_value(&value)?; + let module = Module::compile_value(compile_options, &value)?; let module = ModuleRef::from(module); self.eval_module(environment, module) } diff --git a/src/vm/module.rs b/src/vm/module.rs index 98760d8..051adc4 100644 --- a/src/vm/module.rs +++ b/src/vm/module.rs @@ -7,7 +7,10 @@ use std::{ }; use crate::{ - compile::{CompilationModule, CompileError, Expression, FunctionBody, FunctionSignature}, + compile::{ + CompilationModule, CompileError, CompileOptions, Expression, FunctionBody, + FunctionSignature, + }, vm::{ instruction::{ConstantId, Instruction}, pool::Pool, @@ -122,9 +125,9 @@ impl Module { self.entry } - pub fn compile_value(value: &Value) -> Result { + pub fn compile_value(options: CompileOptions, value: &Value) -> Result { let expression = Expression::parse(value).map_err(CompileError::Parse)?; - let mut module = CompilationModule::default(); + let mut module = CompilationModule::new(options); module.compile_function( FunctionSignature::EMPTY, &FunctionBody { @@ -215,10 +218,13 @@ impl fmt::Display for ModuleConstant { #[cfg(test)] mod tests { - use crate::vm::{ - instruction::{Instruction, MathInstruction, U}, - module::Module, - value::Value, + use crate::{ + compile::CompileOptions, + vm::{ + instruction::{Instruction, MathInstruction, U}, + module::Module, + value::Value, + }, }; #[test] @@ -228,7 +234,7 @@ mod tests { Value::Integer(1), Value::Integer(2), ]); - let m = Module::compile_value(&v).unwrap(); + let m = Module::compile_value(CompileOptions::default(), &v).unwrap(); assert!(m.constants.is_empty()); let is = [ Instruction::PushInteger(U::truncate(2)), diff --git a/src/vm/prelude/mod.rs b/src/vm/prelude/mod.rs index 79dac2a..ca98916 100644 --- a/src/vm/prelude/mod.rs +++ b/src/vm/prelude/mod.rs @@ -102,7 +102,7 @@ pub fn load(env: &mut Environment) { [_, _] => todo!(), _ => todo!(), }; - let value = match vm.eval_value(env, value.clone()) { + let value = match vm.eval_value(Default::default(), env, value.clone()) { Ok(result) => result, _ => todo!(), }; diff --git a/tests/integration.rs b/tests/integration.rs index 9fd6346..b88abdc 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -30,7 +30,7 @@ fn eval_str_in(code: &str, env: &mut Environment) -> Result { Err(error) => panic!("{error}"), }; - last_value = Some(machine.eval_value(env, value)); + last_value = Some(machine.eval_value(Default::default(), env, value)); } last_value.expect("no expressions evaluated")