From 43ff442d89c79e1b6416ac72a9527a2660e678fe Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Thu, 21 May 2026 09:26:55 +0300 Subject: [PATCH] Add function/macro docstrings --- src/compile/block.rs | 11 ++- src/compile/codegen/function.rs | 21 +++++- src/compile/syntax/function.rs | 112 ++++++++++++++++++++++++++++- src/compile/syntax/lambda.rs | 121 -------------------------------- src/compile/syntax/macros.rs | 6 +- src/compile/syntax/mod.rs | 10 ++- src/main.rs | 6 +- src/vm/machine.rs | 4 ++ src/vm/value/function.rs | 8 ++- src/vm/value/mod.rs | 7 ++ 10 files changed, 171 insertions(+), 135 deletions(-) delete mode 100644 src/compile/syntax/lambda.rs diff --git a/src/compile/block.rs b/src/compile/block.rs index cd1651d..9b08f42 100644 --- a/src/compile/block.rs +++ b/src/compile/block.rs @@ -26,6 +26,7 @@ use crate::{ pub struct FunctionBlock { parent: Option, identifier: Option, + docstring: Option, // Data pub(crate) constants: Vec, @@ -107,11 +108,12 @@ impl CompileContext { pub fn compile_function( &mut self, identifier: Option, + docstring: Option, signature: &FunctionSignature, body: &FunctionBody, ) -> Result { // TODO signature - let index = self.push_lambda_context(identifier, signature)?; + let index = self.push_lambda_context(identifier, docstring, signature)?; for expression in body.head.iter() { self.compile_statement(expression)?; } @@ -130,12 +132,13 @@ impl CompileContext { pub fn push_lambda_context( &mut self, identifier: Option, + docstring: Option, signature: &FunctionSignature, ) -> Result { if self.options.trace_compile { eprintln!("COMPILE: push_lambda_context({identifier:?})"); } - let block = FunctionBlock::new(Some(self.current), identifier, signature); + let block = FunctionBlock::new(Some(self.current), identifier, docstring, signature); let index = self.function_blocks.len(); self.function_blocks.push(block); self.current = index; @@ -353,11 +356,13 @@ impl FunctionBlock { fn new( parent: Option, identifier: Option, + docstring: Option, signature: &FunctionSignature, ) -> Self { let mut block = Self { parent, identifier, + docstring, constants: vec![], locals: vec![], upvalues: vec![], @@ -379,6 +384,7 @@ impl FunctionBlock { Self::new( None, identifier, + None, &FunctionSignature { required_arguments: vec![], optional_arguments: vec![], @@ -481,6 +487,7 @@ impl FunctionBlock { Ok(Rc::new(BytecodeFunction { identifier: self.identifier.clone(), instructions: instructions.into(), + docstring: self.docstring.clone(), constants: self.constants.iter().cloned().collect(), upvalues: self.upvalues.iter().copied().collect(), arity: self.arity, diff --git a/src/compile/codegen/function.rs b/src/compile/codegen/function.rs index 0bf4bd7..02f3365 100644 --- a/src/compile/codegen/function.rs +++ b/src/compile/codegen/function.rs @@ -66,14 +66,24 @@ impl Compile for CallExpression { impl Compile for LambdaExpression { fn compile(&self, cx: &mut CompileContext) -> Result { - let function = cx.compile_function(Some("lambda".into()), &self.signature, &self.body)?; + let function = cx.compile_function( + Some("lambda".into()), + self.docstring.clone(), + &self.signature, + &self.body, + )?; Ok(CompileValue::LocalFunction(function)) } } impl Compile for DefunExpression { fn compile(&self, cx: &mut CompileContext) -> Result { - let function = cx.compile_function(Some(self.name.clone()), &self.signature, &self.body)?; + let function = cx.compile_function( + Some(self.name.clone()), + self.docstring.clone(), + &self.signature, + &self.body, + )?; cx.compile_set_global(self.name.clone(), CompileValue::LocalFunction(function))?; Ok(CompileValue::Nil) } @@ -81,7 +91,12 @@ impl Compile for DefunExpression { impl Compile for DefmacroExpression { fn compile(&self, cx: &mut CompileContext) -> Result { - let function = cx.compile_function(Some(self.name.clone()), &self.signature, &self.body)?; + let function = cx.compile_function( + Some(self.name.clone()), + self.docstring.clone(), + &self.signature, + &self.body, + )?; cx.compile_declare_macro(self.name.clone(), CompileValue::LocalFunction(function))?; Ok(CompileValue::Nil) } diff --git a/src/compile/syntax/function.rs b/src/compile/syntax/function.rs index 9a7b2db..fc5fe6c 100644 --- a/src/compile/syntax/function.rs +++ b/src/compile/syntax/function.rs @@ -5,9 +5,10 @@ use crate::{ function::FunctionSignature, syntax::{ CollectErrors, ExpectedWhat, ExpectedWhere, Expression, ParseError, ParseErrorKind, + maybe_docstring, }, }, - vm::value::{ConsCell, IdentifierValue, Keyword, Value}, + vm::value::{ConsCell, IdentifierValue, Keyword, StringValue, Value}, }; #[derive(Debug, PartialEq)] @@ -16,9 +17,17 @@ pub struct FunctionBody { pub tail: Rc, } +#[derive(Debug, PartialEq)] +pub struct LambdaExpression { + pub docstring: Option, + pub signature: FunctionSignature, + pub body: FunctionBody, +} + #[derive(Debug, PartialEq)] pub struct DefunExpression { pub name: IdentifierValue, + pub docstring: Option, pub signature: FunctionSignature, pub body: FunctionBody, } @@ -185,6 +194,29 @@ impl PrognExpression { } } +impl LambdaExpression { + pub(super) fn parse(value: &Value, input: &Value) -> Result { + let Value::Cons(value) = value else { + return Err(ParseError { + input: input.clone(), + error: ParseErrorKind::Expected( + ExpectedWhat::ArgumentList, + ExpectedWhere::AfterKeyword(Keyword::Lambda), + ), + }); + }; + let ConsCell(car, cdr) = value.as_ref(); + let signature = FunctionSignature::parse(car, input)?; + let (cdr, docstring) = maybe_docstring(cdr); + let body = FunctionBody::parse(cdr, input, Keyword::Lambda)?; + Ok(Self { + docstring, + signature, + body, + }) + } +} + impl DefunExpression { pub(super) fn parse(value: &Value, input: &Value) -> Result { let Value::Cons(value) = value else { @@ -218,11 +250,12 @@ impl DefunExpression { }); }; let ConsCell(car, cdr) = cdr.as_ref(); - let signature = FunctionSignature::parse(car, input)?; + let (cdr, docstring) = maybe_docstring(cdr); let body = FunctionBody::parse(cdr, input, Keyword::Lambda)?; Ok(Self { name: identifier.clone(), + docstring, signature, body, }) @@ -240,6 +273,12 @@ impl CollectErrors for FunctionBody { } } +impl CollectErrors for LambdaExpression { + fn collect_errors(&self, errors: &mut Vec) -> bool { + self.body.collect_errors(errors) + } +} + impl CollectErrors for DefunExpression { fn collect_errors(&self, errors: &mut Vec) -> bool { self.body.collect_errors(errors) @@ -260,12 +299,79 @@ mod tests { compile::{ function::FunctionSignature, syntax::{ - ExpectedWhat, ExpectedWhere, Expression, FunctionBody, ParseError, ParseErrorKind, + CallExpression, ExpectedWhat, ExpectedWhere, Expression, FunctionBody, + LambdaExpression, ParseError, ParseErrorKind, }, }, vm::value::{Keyword, Value}, }; + #[test] + fn test_parse_lambda() { + let args = Value::list_or_nil([ + Value::Identifier("a".into()), + Value::Keyword(Keyword::Optional), + Value::Identifier("b".into()), + Value::Keyword(Keyword::Rest), + Value::Identifier("c".into()), + ]); + let body = Value::list_or_nil([ + Value::Identifier("+".into()), + Value::Identifier("a".into()), + Value::Number(1.into()), + ]); + let lambda = Value::Keyword(Keyword::Lambda).cons(args.cons(body.cons(Value::Nil))); + let expr = Expression::parse(&lambda).unwrap(); + + assert_eq!( + expr.as_ref(), + &Expression::Lambda(LambdaExpression { + signature: FunctionSignature { + required_arguments: vec!["a".into()], + optional_arguments: vec!["b".into()], + rest_argument: Some("c".into()) + }, + body: FunctionBody { + head: vec![], + tail: Expression::Call(CallExpression { + callee: Expression::Identifier("+".into()).into(), + arguments: vec![ + Rc::new(Expression::Identifier("a".into())), + Rc::new(Expression::IntegerLiteral(1.into())) + ] + }) + .into() + } + }) + ); + + // syntax errors + let lambda = Value::list_or_nil([Value::Keyword(Keyword::Lambda)]); + let e = Expression::parse(&lambda).unwrap_err(); + assert_eq!( + e, + vec![ParseError { + input: lambda, + error: ParseErrorKind::Expected( + ExpectedWhat::ArgumentList, + ExpectedWhere::AfterKeyword(Keyword::Lambda) + ) + }] + ); + let lambda = Value::list_or_nil([Value::Keyword(Keyword::Lambda), Value::Nil]); + let e = Expression::parse(&lambda).unwrap_err(); + assert_eq!( + e, + vec![ParseError { + input: lambda, + error: ParseErrorKind::Expected( + ExpectedWhat::Expression, + ExpectedWhere::AfterArgumentList(Keyword::Lambda) + ) + }] + ); + } + #[test] fn test_parse_function_body() { let v = Value::list_or_nil([Value::Number(1.into())]); diff --git a/src/compile/syntax/lambda.rs b/src/compile/syntax/lambda.rs deleted file mode 100644 index e3186df..0000000 --- a/src/compile/syntax/lambda.rs +++ /dev/null @@ -1,121 +0,0 @@ -use crate::{ - compile::{ - function::FunctionSignature, - syntax::{ - CollectErrors, ExpectedWhat, ExpectedWhere, FunctionBody, ParseError, ParseErrorKind, - }, - }, - vm::value::{ConsCell, Keyword, Value}, -}; - -#[derive(Debug, PartialEq)] -pub struct LambdaExpression { - pub signature: FunctionSignature, - pub body: FunctionBody, -} - -impl LambdaExpression { - pub(super) fn parse(value: &Value, input: &Value) -> Result { - let Value::Cons(value) = value else { - return Err(ParseError { - input: input.clone(), - error: ParseErrorKind::Expected( - ExpectedWhat::ArgumentList, - ExpectedWhere::AfterKeyword(Keyword::Lambda), - ), - }); - }; - let ConsCell(car, cdr) = value.as_ref(); - let signature = FunctionSignature::parse(car, input)?; - let body = FunctionBody::parse(cdr, input, Keyword::Lambda)?; - Ok(Self { signature, body }) - } -} - -impl CollectErrors for LambdaExpression { - fn collect_errors(&self, errors: &mut Vec) -> bool { - self.body.collect_errors(errors) - } -} - -#[cfg(test)] -mod tests { - use std::rc::Rc; - - use crate::{ - compile::{ - function::FunctionSignature, - syntax::{ - CallExpression, ExpectedWhat, ExpectedWhere, Expression, FunctionBody, - LambdaExpression, ParseError, ParseErrorKind, - }, - }, - vm::value::{Keyword, Value}, - }; - - #[test] - fn test_parse_lambda() { - let args = Value::list_or_nil([ - Value::Identifier("a".into()), - Value::Keyword(Keyword::Optional), - Value::Identifier("b".into()), - Value::Keyword(Keyword::Rest), - Value::Identifier("c".into()), - ]); - let body = Value::list_or_nil([ - Value::Identifier("+".into()), - Value::Identifier("a".into()), - Value::Number(1.into()), - ]); - let lambda = Value::Keyword(Keyword::Lambda).cons(args.cons(body.cons(Value::Nil))); - let expr = Expression::parse(&lambda).unwrap(); - - assert_eq!( - expr.as_ref(), - &Expression::Lambda(LambdaExpression { - signature: FunctionSignature { - required_arguments: vec!["a".into()], - optional_arguments: vec!["b".into()], - rest_argument: Some("c".into()) - }, - body: FunctionBody { - head: vec![], - tail: Expression::Call(CallExpression { - callee: Expression::Identifier("+".into()).into(), - arguments: vec![ - Rc::new(Expression::Identifier("a".into())), - Rc::new(Expression::IntegerLiteral(1.into())) - ] - }) - .into() - } - }) - ); - - // syntax errors - let lambda = Value::list_or_nil([Value::Keyword(Keyword::Lambda)]); - let e = Expression::parse(&lambda).unwrap_err(); - assert_eq!( - e, - vec![ParseError { - input: lambda, - error: ParseErrorKind::Expected( - ExpectedWhat::ArgumentList, - ExpectedWhere::AfterKeyword(Keyword::Lambda) - ) - }] - ); - let lambda = Value::list_or_nil([Value::Keyword(Keyword::Lambda), Value::Nil]); - let e = Expression::parse(&lambda).unwrap_err(); - assert_eq!( - e, - vec![ParseError { - input: lambda, - error: ParseErrorKind::Expected( - ExpectedWhat::Expression, - ExpectedWhere::AfterArgumentList(Keyword::Lambda) - ) - }] - ); - } -} diff --git a/src/compile/syntax/macros.rs b/src/compile/syntax/macros.rs index b1180b1..38de185 100644 --- a/src/compile/syntax/macros.rs +++ b/src/compile/syntax/macros.rs @@ -4,14 +4,16 @@ use crate::{ syntax::{ CollectErrors, FunctionBody, error::{ExpectedWhat, ExpectedWhere, ParseError, ParseErrorKind}, + maybe_docstring, }, }, - vm::value::{ConsCell, IdentifierValue, Keyword, Value}, + vm::value::{ConsCell, IdentifierValue, Keyword, StringValue, Value}, }; #[derive(Debug, PartialEq)] pub struct DefmacroExpression { pub name: IdentifierValue, + pub docstring: Option, pub signature: FunctionSignature, pub body: FunctionBody, } @@ -51,9 +53,11 @@ impl DefmacroExpression { let ConsCell(car, cdr) = cdr.as_ref(); let signature = FunctionSignature::parse(car, input)?; + let (cdr, docstring) = maybe_docstring(cdr); let body = FunctionBody::parse(cdr, input, Keyword::Lambda)?; Ok(Self { name: identifier.clone(), + docstring, signature, body, }) diff --git a/src/compile/syntax/mod.rs b/src/compile/syntax/mod.rs index d1c460d..a8261b3 100644 --- a/src/compile/syntax/mod.rs +++ b/src/compile/syntax/mod.rs @@ -9,7 +9,6 @@ mod call; mod condition; mod error; mod function; -mod lambda; mod loops; mod macros; @@ -18,7 +17,6 @@ pub use call::*; pub use condition::*; pub use error::*; pub use function::*; -pub use lambda::*; pub use loops::*; pub use macros::*; @@ -161,6 +159,14 @@ impl CollectErrors for Expression { } } +fn maybe_docstring(input: &Value) -> (&Value, Option) { + if let Some((Value::String(docstring), cdr)) = input.uncons_ref() { + (cdr, Some(docstring.clone())) + } else { + (input, None) + } +} + #[cfg(test)] mod tests { use crate::{ diff --git a/src/main.rs b/src/main.rs index b82bbf4..0b9b408 100644 --- a/src/main.rs +++ b/src/main.rs @@ -88,7 +88,11 @@ fn handle_eval_error(value: Option<&Value>, input: MachineErrorAt) -> Error { eprintln!(); } match input.error { - MachineError::Compile(_) => todo!(), + MachineError::Compile(error) => { + eprintln!("Compilation error:"); + eprintln!(); + eprintln!(":: {error}"); + } MachineError::Read(error) => { eprintln!("Syntax error:"); eprintln!(); diff --git a/src/vm/machine.rs b/src/vm/machine.rs index 15461da..6b47794 100644 --- a/src/vm/machine.rs +++ b/src/vm/machine.rs @@ -240,6 +240,10 @@ impl Machine { } }; + if argument_count != closure.function.arity { + todo!("TODO error here") + } + if self.trace_calls { eprintln!("TRACE: Call closure"); eprintln!("TRACE: {closure}"); diff --git a/src/vm/value/function.rs b/src/vm/value/function.rs index c6e40b0..833ada4 100644 --- a/src/vm/value/function.rs +++ b/src/vm/value/function.rs @@ -12,6 +12,7 @@ use crate::{ #[derive(Debug, PartialEq)] pub struct BytecodeFunction { pub identifier: Option, + pub docstring: Option, pub instructions: Box<[u8]>, pub constants: Box<[Value]>, pub upvalues: Box<[UpvalueDef]>, @@ -40,8 +41,11 @@ impl BytecodeFunction { } } - pub fn docstring(&self) -> &StringValue { - todo!() + pub fn docstring(&self) -> &str { + match self.docstring.as_ref() { + Some(docstring) => docstring.as_ref(), + None => "", + } } fn trace_immediate_integer_at(&self, address: usize) -> Option { diff --git a/src/vm/value/mod.rs b/src/vm/value/mod.rs index f00b52f..cce54e7 100644 --- a/src/vm/value/mod.rs +++ b/src/vm/value/mod.rs @@ -71,6 +71,13 @@ impl Value { } } + pub fn uncons_ref(&self) -> Option<(&Value, &Value)> { + match self { + Self::Cons(cons) => Some((&cons.0, &cons.1)), + _ => None, + } + } + pub fn quote(self) -> Value { Value::Quote(self.into()) }