Proper quasiquote expansion rules, unquote-splice

This commit is contained in:
2026-05-22 10:10:57 +03:00
parent d21f927a24
commit 6936966455
21 changed files with 516 additions and 156 deletions
+94
View File
@@ -0,0 +1,94 @@
(defun err? (v) (= 'err (car v)))
(defun no-arguments () 1234)
(defun only-required (a b c) (+ a b c))
(defun only-optional (&optional a b c) (list a b c))
(defun only-rest (&rest r) (length r))
(defun required-and-optional (a b &optional c d) (list a b c d))
(defun required-and-rest (a b &rest r) (list a b r))
(defun optional-and-rest (&optional a b &rest r) (list a b r))
(defun required-and-optional-and-rest (a b &optional c d &rest r) (list a b c d r))
; no-arguments
(assert (= '(ok 1234) (eval '(no-arguments))))
(assert (err? (eval '(no-arguments 1))))
; only-required
(assert (= '(ok 6) (eval '(only-required 1 2 3))))
(assert (err? (eval '(only-required 1 2))))
; only-optional
(assert
(=
'(ok (nil nil nil))
(eval '(only-optional))
)
)
(assert
(=
'(ok (1 nil nil))
(eval '(only-optional 1))
)
)
(assert
(=
'(ok (1 2 nil))
(eval '(only-optional 1 2))
)
)
(assert
(=
'(ok (1 2 3))
(eval '(only-optional 1 2 3))
)
)
(assert (err? (eval '(only-optional 1 2 3 4))))
; only-rest
(assert (= 0 (only-rest)))
(assert (= 1 (only-rest 1)))
(assert (= 2 (only-rest 1 2)))
(assert (= 3 (only-rest 1 2 3)))
; required-and-optional
(assert (err? (eval '(required-and-optional))))
(assert (err? (eval '(required-and-optional 1))))
(assert
(=
'(1 2 nil nil)
(required-and-optional 1 2)
)
)
(assert
(=
'(1 2 3 nil)
(required-and-optional 1 2 3)
)
)
(assert
(=
'(1 2 3 4)
(required-and-optional 1 2 3 4)
)
)
(assert (err? (eval '(required-and-optional 1 2 3 4 5))))
; required-and-rest
(assert (err? (eval '(required-and-rest))))
(assert (err? (eval '(required-and-rest 1))))
(assert (= '(1 2 nil) (required-and-rest 1 2)))
(assert (= '(1 2 (3)) (required-and-rest 1 2 3)))
(assert (= '(1 2 (3 4 5)) (required-and-rest 1 2 3 4 5)))
; optional-and-rest
(assert (= '(nil nil nil) (optional-and-rest)))
(assert (= '(1 nil nil) (optional-and-rest 1)))
(assert (= '(1 2 nil) (optional-and-rest 1 2)))
(assert (= '(1 2 (3)) (optional-and-rest 1 2 3)))
(assert (= '(1 2 (3 4 5)) (optional-and-rest 1 2 3 4 5)))
; required-and-optional-and-rest
(assert (err? (eval '(required-and-optional-and-rest))))
(assert (err? (eval '(required-and-optional-and-rest 1))))
(assert (= '(1 2 nil nil nil) (required-and-optional-and-rest 1 2)))
(assert (= '(1 2 3 nil nil) (required-and-optional-and-rest 1 2 3)))
(assert (= '(1 2 3 4 nil) (required-and-optional-and-rest 1 2 3 4)))
(assert (= '(1 2 3 4 (5)) (required-and-optional-and-rest 1 2 3 4 5)))
(assert (= '(1 2 3 4 (5 6 7 8)) (required-and-optional-and-rest 1 2 3 4 5 6 7 8)))
+45
View File
@@ -0,0 +1,45 @@
; quoting rules
(setq glob0 123)
(setq glob1 '(2 3 4))
(assert (= (list 1 2 3) '(1 2 3) `(1 2 3)))
(assert (= '(1 glob0 3) `(1 glob0 3)))
(assert (= '(1 123 3) `(1 ,glob0 3)))
(assert (= '(1 glob1 5) `(1 glob1 5)))
(assert (= '(1 (2 3 4) 5) `(1 ,glob1 5)))
(assert (= '(1 2 3 4 5) `(1 ,@glob1 5)))
(assert (= '(2 3 4 5) `(,@glob1 5)))
(assert (= '(1 2 3 4) `(1 ,@glob1)))
(assert (= '(2 3 4) `(,@glob1)))
(assert (= '((2 3 4)) `(,glob1)))
; Nested
(assert (= '((123 123) (123 123)) `((,glob0 ,glob0) (,glob0 ,glob0))))
(assert (= '(2 3 4 2 3 4) `(,@glob1 ,@glob1)))
(assert (= '((((2 3 4)))) `(((,glob1)))))
(defmacro debug (expression)
(print "DEBUG:" expression)
expression
)
; those are prelude, but defined in lysp itself:
(debug
(when #t
(print "a")
(print "b")
)
)
(when 1
(print "a")
(print "b")
)
(unless nil
(print "c")
(print "d")
)
+8 -5
View File
@@ -382,6 +382,11 @@ impl FunctionBlock {
.add_local(optional.clone(), Some(-100)) .add_local(optional.clone(), Some(-100))
.expect("couldn't add an argument"); .expect("couldn't add an argument");
} }
if let Some(rest) = signature.rest_argument.as_ref() {
block
.add_local(rest.clone(), Some(-100))
.expect("couldn't add an argument");
}
block block
} }
@@ -489,17 +494,15 @@ impl FunctionBlock {
instructions[position] = branch_offset as u8; instructions[position] = branch_offset as u8;
} }
let min_arity = self.signature.min_arity();
let max_arity = self.signature.max_arity();
Ok(Rc::new(BytecodeFunction { Ok(Rc::new(BytecodeFunction {
identifier: self.identifier.clone(), identifier: self.identifier.clone(),
instructions: instructions.into(), instructions: instructions.into(),
docstring: self.docstring.clone(), docstring: self.docstring.clone(),
constants: self.constants.iter().cloned().collect(), constants: self.constants.iter().cloned().collect(),
upvalues: self.upvalues.iter().copied().collect(), upvalues: self.upvalues.iter().copied().collect(),
min_arity, required_count: self.signature.required_arguments.len(),
max_arity, optional_count: self.signature.optional_arguments.len(),
has_rest: self.signature.rest_argument.is_some(),
})) }))
} }
+6 -4
View File
@@ -173,8 +173,9 @@ mod tests {
Instruction::Return.into(), Instruction::Return.into(),
] ]
); );
assert_eq!(lambda_function.min_arity, 1); assert_eq!(lambda_function.required_count, 1);
assert_eq!(lambda_function.max_arity, 1); assert_eq!(lambda_function.optional_count, 0);
assert!(!lambda_function.has_rest);
assert_eq!( assert_eq!(
lambda_function.upvalues.as_ref(), lambda_function.upvalues.as_ref(),
&[UpvalueDef { &[UpvalueDef {
@@ -283,8 +284,9 @@ mod tests {
); );
// inner function // inner function
assert!(defun_function.constants.is_empty()); assert!(defun_function.constants.is_empty());
assert_eq!(defun_function.min_arity, 1); assert_eq!(defun_function.required_count, 1);
assert_eq!(defun_function.max_arity, 1); assert_eq!(defun_function.optional_count, 0);
assert!(!defun_function.has_rest);
assert_eq!( assert_eq!(
defun_function.instructions.as_ref(), defun_function.instructions.as_ref(),
&[ &[
+8 -2
View File
@@ -93,13 +93,19 @@ impl ParseErrorKind {
} }
} }
pub fn collect_extraneous<I: IntoIterator<Item = Value>>(iter: I) -> Self { pub fn collect_extraneous<I: IntoIterator<Item = Value>>(iter: I) -> Self
where
I::IntoIter: ExactSizeIterator + DoubleEndedIterator,
{
Self::extraneous(&Value::list_or_nil(iter)) Self::extraneous(&Value::list_or_nil(iter))
} }
pub fn try_collect_extraneous<E, I: IntoIterator<Item = Result<Value, E>>>( pub fn try_collect_extraneous<E, I: IntoIterator<Item = Result<Value, E>>>(
iter: I, iter: I,
) -> Result<Self, E> { ) -> Result<Self, E>
where
I::IntoIter: ExactSizeIterator + DoubleEndedIterator,
{
Ok(Self::extraneous(&Value::try_list_or_nil(iter)?)) Ok(Self::extraneous(&Value::try_list_or_nil(iter)?))
} }
} }
+1
View File
@@ -69,6 +69,7 @@ impl Expression {
Value::String(value) => Rc::new(Self::StringLiteral(value.clone())), Value::String(value) => Rc::new(Self::StringLiteral(value.clone())),
Value::Quasi(_value) => todo!("{value}"), Value::Quasi(_value) => todo!("{value}"),
Value::Unquote(_value) => todo!("Unquote {_value}"), Value::Unquote(_value) => todo!("Unquote {_value}"),
Value::UnquoteSplice(_value) => todo!("UnquoteSplice {_value}"),
Value::Quote(value) => Rc::new(Self::Quote(value.clone())), Value::Quote(value) => Rc::new(Self::Quote(value.clone())),
Value::Nil => Rc::new(Self::Nil), Value::Nil => Rc::new(Self::Nil),
+30 -15
View File
@@ -1,4 +1,4 @@
use std::{fmt, io, ops::RangeInclusive, rc::Rc}; use std::{fmt, io, rc::Rc};
use crate::{ use crate::{
compile::CompileError, compile::CompileError,
@@ -40,7 +40,6 @@ pub enum ReadError {
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct ArgumentCountError { pub struct ArgumentCountError {
pub function: Rc<BytecodeFunction>, pub function: Rc<BytecodeFunction>,
pub expected_range: RangeInclusive<usize>,
pub actual: usize, pub actual: usize,
} }
@@ -143,11 +142,7 @@ impl PartialEq for ReadError {
impl fmt::Display for ArgumentCountError { impl fmt::Display for ArgumentCountError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let min_arity = *self.expected_range.start(); let too_few = self.actual < self.function.required_count;
let max_arity = *self.expected_range.end();
let one_argument = min_arity == max_arity;
let too_few = self.actual < min_arity;
write!( write!(
f, f,
"too {} arguments for function {}: expected ", "too {} arguments for function {}: expected ",
@@ -155,15 +150,35 @@ impl fmt::Display for ArgumentCountError {
self.function self.function
)?; )?;
if one_argument { if self.function.has_rest {
write!(f, "{min_arity}")?; let singular = self.function.required_count == 1;
write!(f, " argument")?;
if min_arity != 1 { write!(
write!(f, "s")?; f,
} "at least {} argument{}",
self.function.required_count,
if singular { "" } else { "s" }
)?;
} else if self.function.optional_count > 0 {
write!(
f,
"{}-{} arguments",
self.function.required_count,
self.function.required_count + self.function.optional_count
)?;
} else { } else {
write!(f, "{min_arity}-{max_arity} arguments")?; let singular = self.function.required_count == 1;
write!(
f,
"{} argument{}",
self.function.required_count,
if singular { "" } else { "s" }
)?;
} }
write!(f, ", got {}", self.actual)
write!(f, ", got {}", self.actual)?;
Ok(())
} }
} }
+4 -1
View File
@@ -37,6 +37,7 @@ pub enum Trace {
Call, Call,
Return, Return,
Stack, Stack,
Macro,
} }
impl FromStr for Trace { impl FromStr for Trace {
@@ -49,6 +50,7 @@ impl FromStr for Trace {
"call" => Ok(Self::Call), "call" => Ok(Self::Call),
"return" => Ok(Self::Return), "return" => Ok(Self::Return),
"stack" => Ok(Self::Stack), "stack" => Ok(Self::Stack),
"macro" => Ok(Self::Macro),
_ => Err(format!("Unknown trace flag: {s:?}")), _ => Err(format!("Unknown trace flag: {s:?}")),
} }
} }
@@ -178,7 +180,7 @@ fn run_module<P: AsRef<Path>>(
let path = path.as_ref(); let path = path.as_ref();
let name = format!("{}", path.display()); let name = format!("{}", path.display());
let reader = BufReader::new(File::open(path)?); let reader = BufReader::new(File::open(path)?);
let module_reader = ModuleReader::new(reader); let module_reader = ModuleReader::new(reader, vm.trace_macros);
let function = match module_reader.compile(Some(name.into()), compile_options, env) { let function = match module_reader.compile(Some(name.into()), compile_options, env) {
Ok(function) => function, Ok(function) => function,
Err(error) => return Err(handle_module_error(error)), Err(error) => return Err(handle_module_error(error)),
@@ -203,6 +205,7 @@ fn main() -> ExitCode {
vm.trace_calls = args.trace.contains(&Trace::Call); vm.trace_calls = args.trace.contains(&Trace::Call);
vm.trace_returns = args.trace.contains(&Trace::Return); vm.trace_returns = args.trace.contains(&Trace::Return);
vm.trace_stack = args.trace.contains(&Trace::Stack); vm.trace_stack = args.trace.contains(&Trace::Stack);
vm.trace_macros = args.trace.contains(&Trace::Macro);
let env = Rc::new(Environment::default()); let env = Rc::new(Environment::default());
prelude::load(&env); prelude::load(&env);
let mut arguments = vec![]; let mut arguments = vec![];
+7
View File
@@ -305,6 +305,12 @@ fn parse_unquote(input: &str) -> IResult<&str, Value> {
.map(Value::Unquote) .map(Value::Unquote)
.parse(input) .parse(input)
} }
fn parse_unquote_splice(input: &str) -> IResult<&str, Value> {
preceded(tag(",@"), parse_value)
.map(Rc::new)
.map(Value::UnquoteSplice)
.parse(input)
}
fn parse_quote(input: &str) -> IResult<&str, Value> { fn parse_quote(input: &str) -> IResult<&str, Value> {
preceded(char('\''), parse_value) preceded(char('\''), parse_value)
@@ -330,6 +336,7 @@ pub fn parse_value(input: &str) -> IResult<&str, Value> {
parse_list_or_nil, parse_list_or_nil,
parse_vector, parse_vector,
parse_boolean, parse_boolean,
parse_unquote_splice,
parse_quote, parse_quote,
parse_quasi, parse_quasi,
parse_unquote, parse_unquote,
+8
View File
@@ -0,0 +1,8 @@
(defmacro when (condition &rest body)
`(if ,condition (progn ,@body))
)
(defmacro unless (condition &rest body)
`(if (not ,condition) (progn ,@body))
)
+5 -4
View File
@@ -16,7 +16,6 @@ use crate::{
env::Environment, env::Environment,
instruction::Instruction, instruction::Instruction,
machine::Machine, machine::Machine,
macros::MacroExpand,
value::{BytecodeFunction, IdentifierValue, Value}, value::{BytecodeFunction, IdentifierValue, Value},
}, },
}; };
@@ -94,10 +93,12 @@ impl<R: BufRead> Reader for FileReader<R> {
} }
impl<R: BufRead> ModuleReader<R> { impl<R: BufRead> ModuleReader<R> {
pub fn new(reader: R) -> Self { pub fn new(reader: R, trace_macros: bool) -> Self {
let mut macro_machine = Machine::default();
macro_machine.trace_macros = trace_macros;
Self { Self {
reader: FileReader::new(reader), reader: FileReader::new(reader),
macro_machine: Machine::default(), macro_machine,
} }
} }
@@ -255,6 +256,6 @@ pub fn read<R: Reader>(
let Some(raw_value) = raw_value else { let Some(raw_value) = raw_value else {
return Ok(None); return Ok(None);
}; };
let exp_value = raw_value.macro_expand(vm, env, false)?; let exp_value = vm.macro_expand(env, &raw_value)?;
Ok(Some(exp_value)) Ok(Some(exp_value))
} }
+125 -42
View File
@@ -1,4 +1,7 @@
use std::rc::Rc; use std::{
io::{self, BufReader, Read},
rc::Rc,
};
use crate::{ use crate::{
compile::{CompileContext, CompileOptions}, compile::{CompileContext, CompileOptions},
@@ -6,6 +9,7 @@ use crate::{
ArgumentCountError, MachineError, MachineErrorAt, MachineErrorLocation, ArgumentCountError, MachineError, MachineErrorAt, MachineErrorLocation,
ValueConversionError, ValueConversionError,
}, },
read::{self, FileReader},
vm::{ vm::{
Value, Value,
env::Environment, env::Environment,
@@ -15,7 +19,7 @@ use crate::{
macros::MacroExpand, macros::MacroExpand,
prelude, prelude,
stack::Stack, stack::Stack,
value::{ClosureValue, IdentifierValue, NumberValue, UpvalueValue}, value::{BytecodeFunction, ClosureValue, IdentifierValue, NumberValue, UpvalueValue},
}, },
}; };
@@ -36,6 +40,7 @@ pub struct Machine {
pub trace_returns: bool, pub trace_returns: bool,
pub trace_stack: bool, pub trace_stack: bool,
pub trace_calls: bool, pub trace_calls: bool,
pub trace_macros: bool,
} }
impl Default for Machine { impl Default for Machine {
@@ -50,6 +55,7 @@ impl Default for Machine {
trace_stack: false, trace_stack: false,
trace_returns: false, trace_returns: false,
trace_instructions: false, trace_instructions: false,
trace_macros: false,
} }
} }
} }
@@ -186,6 +192,48 @@ impl Machine {
Ok(()) Ok(())
} }
fn collect_rest_argument(&mut self, count: usize) -> Result<Value, MachineError> {
let mut rest = Value::Nil;
for _ in 0..count {
let value = self.pop()?;
rest = value.cons(rest);
}
Ok(rest)
}
fn collect_call_arguments(
&mut self,
function: &Rc<BytecodeFunction>,
argument_count: usize,
) -> Result<(), MachineError> {
if !(function.min_arity()..=function.max_arity()).contains(&argument_count) {
return Err(MachineError::ArgumentCount(ArgumentCountError {
function: function.clone(),
actual: argument_count,
}));
}
// Fill out missing optionals
let mut remaining = argument_count - function.required_count;
if remaining > function.optional_count {
// Collect into &rest X
remaining -= function.optional_count;
} else {
// Pad missing optionals
for _ in remaining..function.optional_count {
self.push(Value::Nil)?;
}
remaining = 0;
}
if function.has_rest {
let rest = self.collect_rest_argument(remaining)?;
self.push(rest)?;
}
Ok(())
}
fn execute_call( fn execute_call(
&mut self, &mut self,
env: &Rc<Environment>, env: &Rc<Environment>,
@@ -246,22 +294,7 @@ impl Machine {
} }
}; };
if !(closure.function.min_arity..=closure.function.max_arity).contains(&argument_count) { self.collect_call_arguments(&closure.function, argument_count)?;
return Err(MachineError::ArgumentCount(ArgumentCountError {
function: closure.function.clone(),
expected_range: closure.function.min_arity..=closure.function.max_arity,
actual: argument_count,
}));
}
if closure.function.max_arity == usize::MAX {
todo!("VM support for &rest argument")
}
for _ in argument_count..closure.function.max_arity {
self.push(Value::Nil)?;
}
// if argument_count != closure.function.arity {
// todo!("TODO error here")
// }
if self.trace_calls { if self.trace_calls {
eprintln!("TRACE: Call closure"); eprintln!("TRACE: Call closure");
@@ -599,26 +632,11 @@ impl Machine {
closure: ClosureValue, closure: ClosureValue,
args: &[Value], args: &[Value],
) -> Result<Value, MachineErrorAt> { ) -> Result<Value, MachineErrorAt> {
let max_arity = closure.function.max_arity;
if !(closure.function.min_arity..=closure.function.max_arity).contains(&args.len()) {
return Err(MachineError::ArgumentCount(ArgumentCountError {
function: closure.function.clone(),
expected_range: closure.function.min_arity..=closure.function.max_arity,
actual: args.len(),
})
.at_unknown());
}
self.push(Value::Closure(closure)) self.push(Value::Closure(closure))
.map_err(MachineErrorAt::at_unknown)?; .map_err(MachineErrorAt::at_unknown)?;
if max_arity == usize::MAX { for arg in args {
todo!("VM support for &rest argument")
}
for arg in args.iter() {
self.push(arg.clone()).map_err(MachineErrorAt::at_unknown)?; self.push(arg.clone()).map_err(MachineErrorAt::at_unknown)?;
} }
for _ in args.len()..max_arity {
self.push(Value::Nil).map_err(MachineErrorAt::at_unknown)?;
}
let unwind_target = self.call_stack.pointer(); let unwind_target = self.call_stack.pointer();
self.execute_call(env, args.len()) self.execute_call(env, args.len())
.map_err(MachineErrorAt::at_unknown)?; .map_err(MachineErrorAt::at_unknown)?;
@@ -633,6 +651,33 @@ impl Machine {
self.pop().map_err(MachineErrorAt::at_unknown) self.pop().map_err(MachineErrorAt::at_unknown)
} }
pub fn macro_expand(
&mut self,
env: &Rc<Environment>,
value: &Value,
) -> Result<Value, MachineErrorAt> {
match value.macro_expand(self, env, false) {
Ok(result) => {
if self.trace_macros && *value != result {
eprintln!("TRACE: Macro expansion:");
eprintln!("TRACE: {value}");
eprintln!("TRACE: VVVVV");
eprintln!("TRACE: {result}");
}
Ok(result)
}
Err(error) => {
if self.trace_macros {
eprintln!("TRACE: Macro expansion:");
eprintln!("TRACE: {value}");
eprintln!("TRACE: VVVVV");
eprintln!("TRACE: {error}");
}
Err(error)
}
}
}
pub fn evaluate_value( pub fn evaluate_value(
&mut self, &mut self,
compile_options: CompileOptions, compile_options: CompileOptions,
@@ -640,8 +685,8 @@ impl Machine {
env: &Rc<Environment>, env: &Rc<Environment>,
value: Value, value: Value,
) -> Result<Value, MachineErrorAt> { ) -> Result<Value, MachineErrorAt> {
let value = value.macro_expand(self, env, false)?; let value_expanded = self.macro_expand(env, &value)?;
let function = CompileContext::compile_value(compile_options, chunk_name, &value) let function = CompileContext::compile_value(compile_options, chunk_name, &value_expanded)
.map_err(MachineError::Compile) .map_err(MachineError::Compile)
.map_err(MachineErrorAt::at_unknown)?; .map_err(MachineErrorAt::at_unknown)?;
@@ -653,6 +698,41 @@ impl Machine {
let value = self.evaluate_closure(env, closure, 0)?; let value = self.evaluate_closure(env, closure, 0)?;
Ok(value) Ok(value)
} }
pub fn evaluate_str(
&mut self,
compile_options: CompileOptions,
chunk_name: Option<IdentifierValue>,
env: &Rc<Environment>,
text: &str,
) -> Result<Value, MachineErrorAt> {
struct SliceReader<'a>(&'a [u8]);
impl Read for SliceReader<'_> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let count = self.0.len().min(buf.len());
buf[..count].copy_from_slice(&self.0[..count]);
self.0 = &self.0[count..];
Ok(count)
}
}
let reader = BufReader::new(SliceReader(text.as_bytes()));
let mut reader = FileReader::new(reader);
let mut last_value = Value::Nil;
loop {
let value = match read::read(&mut reader, self, env)? {
Some(value) => value,
None => break,
};
last_value =
self.evaluate_value(compile_options.clone(), chunk_name.clone(), env, value)?;
}
Ok(last_value)
}
} }
#[cfg(test)] #[cfg(test)]
@@ -684,8 +764,9 @@ mod tests {
instructions: instructions.into(), instructions: instructions.into(),
constants: constants.into(), constants: constants.into(),
upvalues: [].into(), upvalues: [].into(),
min_arity: 0, required_count: 0,
max_arity: 0, optional_count: 0,
has_rest: false,
}), }),
}; };
let mut machine = Machine::default(); let mut machine = Machine::default();
@@ -758,8 +839,9 @@ mod tests {
.into(), .into(),
constants: [].into(), constants: [].into(),
upvalues: [].into(), upvalues: [].into(),
min_arity: 1, required_count: 1,
max_arity: 1, optional_count: 0,
has_rest: false,
}); });
let (m, r) = eval0( let (m, r) = eval0(
&env, &env,
@@ -799,8 +881,9 @@ mod tests {
.into(), .into(),
constants: [].into(), constants: [].into(),
upvalues: [].into(), upvalues: [].into(),
min_arity: 2, required_count: 2,
max_arity: 2, optional_count: 0,
has_rest: false,
}); });
let (m, r) = eval0( let (m, r) = eval0(
&env, &env,
+32 -22
View File
@@ -5,7 +5,7 @@ use crate::{
vm::{ vm::{
env::{Environment, Macro}, env::{Environment, Macro},
machine::Machine, machine::Machine,
value::{ClosureValue, ConsCell, Keyword, Value}, value::{ClosureValue, ConsCell, Value},
}, },
}; };
@@ -40,6 +40,7 @@ impl MacroExpand for Value {
| Self::NativeFunction(_) | Self::NativeFunction(_)
| Self::NativeValue(_) | Self::NativeValue(_)
| Self::Vector(_) | Self::Vector(_)
| Self::UnquoteSplice(_)
| Self::Unquote(_) => Ok(self.clone()), | Self::Unquote(_) => Ok(self.clone()),
// | Self::NativeFunction(_) => Ok(self.clone()), // | Self::NativeFunction(_) => Ok(self.clone()),
Self::Cons(cons) => { Self::Cons(cons) => {
@@ -86,28 +87,37 @@ impl MacroExpand for Value {
fn expand_quasiquote(value: &Value) -> Value { fn expand_quasiquote(value: &Value) -> Value {
match value { match value {
Value::Nil => Value::Nil, Value::Nil => Value::Nil,
// Toplevel-only
Value::Unquote(inner) => inner.as_ref().clone(), Value::Unquote(inner) => inner.as_ref().clone(),
Value::Cons(cons) => { Value::UnquoteSplice(inner) => inner.as_ref().clone(),
// x . y -> (cons <exp-car> <exp-cdr>) Value::Cons(_) => {
let ConsCell(car, cdr) = cons.as_ref(); let mut elements = vec![];
let exp_car = expand_quasiquote(car); let mut current = value;
let exp_cdr = expand_quasiquote(cdr);
Value::Identifier("cons".into()).cons(exp_car.cons(exp_cdr.cons(Value::Nil))) elements.push(Value::Identifier("append".into()));
while !current.is_nil() {
let Value::Cons(cons) = current else { todo!() };
let ConsCell(car, cdr) = cons.as_ref();
match car {
Value::UnquoteSplice(splice) => {
elements.push(splice.as_ref().clone());
}
_ => {
let exp_car = expand_quasiquote(car);
elements.push(Value::list_or_nil([
Value::Identifier("list".into()),
exp_car,
]));
}
}
current = cdr;
}
Value::list_or_nil(elements)
} }
Value::Quote(value) => { _ => value.clone().quote(),
// (cons 'quote inner)
let cons = Value::Identifier("cons".into());
let quote_kw = Value::Quote(Rc::new(Value::Keyword(Keyword::Quote)));
let quote_nil = Value::Quote(Rc::new(Value::Nil));
let exp_inner = expand_quasiquote(value);
let cons_inner = cons
.clone()
.cons(exp_inner.cons(quote_nil.clone().cons(Value::Nil)));
cons.cons(quote_kw.cons(cons_inner.cons(Value::Nil)))
}
_ => Value::Quote(Rc::new(value.clone())),
} }
} }
+26 -1
View File
@@ -1,7 +1,7 @@
use std::rc::Rc; use std::rc::Rc;
use crate::{ use crate::{
error::MachineError, error::{MachineError, ValueConversionError},
vm::{ vm::{
Value, Value,
env::Environment, env::Environment,
@@ -43,6 +43,31 @@ pub fn load(env: &Rc<Environment>) {
// }); // });
// lists // lists
env.defun_native(
"append",
"Concatenates the lists into one list",
|_, _, args| match args {
[] => Ok(Value::Nil),
[xs] => Ok(xs.clone()),
[head @ .., tail] => {
let mut elements = vec![];
for arg in head {
let iter = arg.proper_iter(ValueConversionError {
expected: "proper list".into(),
got: arg.clone(),
});
for element in iter {
elements.push(element?.clone());
}
}
let mut output = tail.clone();
for element in elements.into_iter().rev() {
output = element.cons(output);
}
Ok(output)
}
},
);
env.defun_native( env.defun_native(
"car", "car",
"Returns the CAR value of a cons-cell", "Returns the CAR value of a cons-cell",
+2 -1
View File
@@ -20,7 +20,8 @@ pub fn load(env: &Rc<Environment>) {
Value::Boolean(_) => "a boolean value".into(), Value::Boolean(_) => "a boolean value".into(),
Value::Quasi(_) => "a quasi-quoted value".into(), Value::Quasi(_) => "a quasi-quoted value".into(),
Value::Quote(_) => "a quoted value".into(), Value::Quote(_) => "a quoted value".into(),
Value::Unquote(_) => "an un-quoted value".into(), Value::Unquote(_) => "an unquoted value".into(),
Value::UnquoteSplice(_) => "an unquote-spliced value".into(),
Value::Identifier(identifier) => format!("an identifier {:?}", identifier.as_ref()), Value::Identifier(identifier) => format!("an identifier {:?}", identifier.as_ref()),
Value::Vector(_) => "a vector".into(), Value::Vector(_) => "a vector".into(),
Value::String(_) => "a string".into(), Value::String(_) => "a string".into(),
+5 -1
View File
@@ -84,7 +84,11 @@ pub fn load(env: &Rc<Environment>) {
if i != 0 { if i != 0 {
print!(" "); print!(" ");
} }
print!("{arg}"); if let Value::String(string) = arg {
print!("{}", &**string);
} else {
print!("{arg}");
}
} }
println!(); println!();
Ok(Value::Nil) Ok(Value::Nil)
+9 -1
View File
@@ -1,6 +1,6 @@
use std::rc::Rc; use std::rc::Rc;
use crate::vm::env::Environment; use crate::vm::{env::Environment, machine::Machine};
mod collections; mod collections;
mod convert; mod convert;
@@ -12,7 +12,11 @@ mod math;
pub(crate) use math::*; pub(crate) use math::*;
const PRELUDE_SOURCE: &str = include_str!("../../prelude.lysp");
pub fn load(env: &Rc<Environment>) { pub fn load(env: &Rc<Environment>) {
let mut vm = Machine::default();
math::load(env); math::load(env);
eval::load(env); eval::load(env);
functional::load(env); functional::load(env);
@@ -20,4 +24,8 @@ pub fn load(env: &Rc<Environment>) {
convert::load(env); convert::load(env);
debug::load(env); debug::load(env);
io::load(env); io::load(env);
// Load the lysp part of the prelude
vm.evaluate_str(Default::default(), None, env, PRELUDE_SOURCE)
.expect("Couldn't evaluate prelude lysp part");
} }
+21 -2
View File
@@ -16,8 +16,10 @@ pub struct BytecodeFunction {
pub instructions: Box<[u8]>, pub instructions: Box<[u8]>,
pub constants: Box<[Value]>, pub constants: Box<[Value]>,
pub upvalues: Box<[UpvalueDef]>, pub upvalues: Box<[UpvalueDef]>,
pub min_arity: usize,
pub max_arity: usize, pub required_count: usize,
pub optional_count: usize,
pub has_rest: bool,
} }
enum TraceArgument { enum TraceArgument {
@@ -49,6 +51,23 @@ impl BytecodeFunction {
} }
} }
pub fn min_arity(&self) -> usize {
self.required_count
}
pub fn rest_argument_start(&self) -> Option<usize> {
self.has_rest
.then_some(self.required_count + self.optional_count)
}
pub fn max_arity(&self) -> usize {
if self.has_rest {
usize::MAX
} else {
self.required_count + self.optional_count
}
}
fn trace_immediate_integer_at(&self, address: usize) -> Option<ImmediateInteger> { fn trace_immediate_integer_at(&self, address: usize) -> Option<ImmediateInteger> {
let Some(b0) = self.instructions.get(address).copied() else { let Some(b0) = self.instructions.get(address).copied() else {
eprint!(" <??> <??>"); eprint!(" <??> <??>");
+28 -21
View File
@@ -49,6 +49,7 @@ pub enum Value {
Quasi(Rc<Value>), Quasi(Rc<Value>),
Quote(Rc<Value>), Quote(Rc<Value>),
Unquote(Rc<Value>), Unquote(Rc<Value>),
UnquoteSplice(Rc<Value>),
// Semantic // Semantic
Closure(ClosureValue), Closure(ClosureValue),
Function(Rc<BytecodeFunction>), Function(Rc<BytecodeFunction>),
@@ -98,6 +99,9 @@ impl Value {
Self::Unquote(value) => { Self::Unquote(value) => {
Value::list_or_nil([Self::Identifier("unquote".into()), value.type_id()]) Value::list_or_nil([Self::Identifier("unquote".into()), value.type_id()])
} }
Self::UnquoteSplice(value) => {
Value::list_or_nil([Self::Identifier("unquote-splice".into()), value.type_id()])
}
Self::Vector(_) => Self::Identifier("vector".into()), Self::Vector(_) => Self::Identifier("vector".into()),
Self::Keyword(_) => Self::Identifier("keyword".into()), Self::Keyword(_) => Self::Identifier("keyword".into()),
Self::String(_) => Self::Identifier("string".into()), Self::String(_) => Self::Identifier("string".into()),
@@ -121,6 +125,7 @@ impl Value {
| Self::Quasi(_) | Self::Quasi(_)
| Self::Quote(_) | Self::Quote(_)
| Self::Unquote(_) | Self::Unquote(_)
| Self::UnquoteSplice(_)
| Self::Closure(_) | Self::Closure(_)
| Self::NativeFunction(_) | Self::NativeFunction(_)
| Self::NativeValue(_) | Self::NativeValue(_)
@@ -132,30 +137,28 @@ impl Value {
Self::Cons(Rc::new(ConsCell(self, cdr))) Self::Cons(Rc::new(ConsCell(self, cdr)))
} }
pub fn try_list_or_nil<E, I: IntoIterator<Item = Result<Self, E>>>( pub fn try_list_or_nil<E, I: IntoIterator<Item = Result<Self, E>>>(items: I) -> Result<Self, E>
items: I, where
) -> Result<Self, E> { I::IntoIter: ExactSizeIterator + DoubleEndedIterator,
Self::try_list_or_nil_inner(&mut items.into_iter()) {
} let iter = items.into_iter();
let mut result = Value::Nil;
pub fn list_or_nil<I: IntoIterator<Item = Self>>(items: I) -> Self { for element in iter.rev() {
Self::list_or_nil_inner(&mut items.into_iter()) result = element?.cons(result);
}
fn try_list_or_nil_inner<E, I: Iterator<Item = Result<Self, E>>>(
items: &mut I,
) -> Result<Self, E> {
match items.next() {
Some(value) => Ok(value?.cons(Self::try_list_or_nil_inner(items)?)),
None => Ok(Self::Nil),
} }
Ok(result)
} }
fn list_or_nil_inner<I: Iterator<Item = Self>>(items: &mut I) -> Self { pub fn list_or_nil<I: IntoIterator<Item = Self>>(items: I) -> Self
match items.next() { where
Some(value) => value.cons(Self::list_or_nil_inner(items)), I::IntoIter: ExactSizeIterator + DoubleEndedIterator,
None => Self::Nil, {
let iter = items.into_iter();
let mut result = Value::Nil;
for element in iter.rev() {
result = element.cons(result);
} }
result
} }
pub fn proper_iter<E>(&self, error: E) -> ProperListIter<'_, E> { pub fn proper_iter<E>(&self, error: E) -> ProperListIter<'_, E> {
@@ -214,7 +217,7 @@ impl fmt::Display for Value {
fmt::Display::fmt(vector, f)?; fmt::Display::fmt(vector, f)?;
f.write_char(']') f.write_char(']')
} }
Self::Keyword(keyword) => write!(f, ":{keyword}"), Self::Keyword(keyword) => write!(f, "{keyword}"),
Self::Identifier(identifier) => write!(f, "{identifier}"), Self::Identifier(identifier) => write!(f, "{identifier}"),
Self::String(value) => fmt::Display::fmt(value, f), Self::String(value) => fmt::Display::fmt(value, f),
Self::Quote(value) => { Self::Quote(value) => {
@@ -229,6 +232,10 @@ impl fmt::Display for Value {
f.write_char(',')?; f.write_char(',')?;
fmt::Display::fmt(value, f) fmt::Display::fmt(value, f)
} }
Self::UnquoteSplice(value) => {
f.write_str(",@")?;
fmt::Display::fmt(value, f)
}
Self::Number(value) => fmt::Display::fmt(value, f), Self::Number(value) => fmt::Display::fmt(value, f),
Self::Boolean(value) => fmt::Display::fmt(value, f), Self::Boolean(value) => fmt::Display::fmt(value, f),
Self::Closure(value) => fmt::Display::fmt(value, f), Self::Closure(value) => fmt::Display::fmt(value, f),
+1 -1
View File
@@ -25,6 +25,6 @@ impl Deref for StringValue {
impl fmt::Display for StringValue { impl fmt::Display for StringValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.0.as_ref(), f) fmt::Debug::fmt(self.0.as_ref(), f)
} }
} }
+51 -33
View File
@@ -1,57 +1,56 @@
use std::{ use std::{fs, path::Path, rc::Rc};
io::{self, BufReader, Read},
rc::Rc,
};
use lysp::{ use lysp::{
error::MachineErrorAt, error::MachineErrorAt,
read::{FileReader, read},
vm::{env::Environment, machine::Machine, prelude, value::Value}, vm::{env::Environment, machine::Machine, prelude, value::Value},
}; };
struct SliceReader<'a>(&'a [u8]);
impl Read for SliceReader<'_> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let count = self.0.len().min(buf.len());
buf[..count].copy_from_slice(&self.0[..count]);
self.0 = &self.0[count..];
Ok(count)
}
}
#[track_caller] #[track_caller]
fn eval_str_in(code: &str, env: &Rc<Environment>) -> Result<Value, MachineErrorAt> { fn eval_str_in(code: &str, env: &Rc<Environment>) -> Result<Value, MachineErrorAt> {
let mut machine = Machine::default(); let mut machine = Machine::default();
let reader = BufReader::new(SliceReader(code.as_bytes())); machine.evaluate_str(Default::default(), None, env, code)
let mut reader = FileReader::new(reader);
let mut last_value = None;
loop {
let value = match read(&mut reader, &mut machine, env) {
Ok(Some(value)) => value,
Ok(None) => break,
Err(error) => panic!("{error}"),
};
last_value = Some(machine.evaluate_value(Default::default(), None, env, value));
}
last_value.expect("no expressions evaluated")
} }
#[track_caller] #[track_caller]
fn eval_str(code: &str) -> Value { fn eval_str(code: &str) -> Value {
let env = Rc::new(Environment::default()); let env = Rc::new(Environment::default());
prelude::load(&env); prelude::load(&env);
eval_str_in(code, &env).expect("expression evaluation failed") match eval_str_in(code, &env) {
Ok(value) => value,
Err(error) => {
eprintln!("Couldn't evaluate expression:");
eprintln!();
eprintln!(" {code}");
eprintln!();
eprintln!(":: {error}");
panic!("TEST FAILED");
}
}
} }
#[track_caller] #[track_caller]
fn eval_str_err(code: &str) -> MachineErrorAt { fn eval_str_err(code: &str) -> MachineErrorAt {
let env = Rc::new(Environment::default()); let env = Rc::new(Environment::default());
prelude::load(&env); prelude::load(&env);
eval_str_in(code, &env).expect_err("expression was expected to fail") match eval_str_in(code, &env) {
Ok(value) => {
eprintln!("Expected the code to fail to evaluate, but it returned success:");
eprintln!();
eprintln!(" {code}");
eprintln!();
eprintln!("Returned");
eprintln!();
eprintln!(":: {value}");
panic!("TEST FAILED");
}
Err(error) => error,
}
}
#[track_caller]
fn eval_file<P: AsRef<Path>>(path: P) -> Value {
let code = fs::read_to_string(path).expect("file read failed");
eval_str(&code)
} }
#[test] #[test]
@@ -186,3 +185,22 @@ fn test_macro() {
]) ])
); );
} }
#[test]
fn test_examples_work() {
const EXCLUDE: &[&str] = &["repl.lysp", "echo.lysp", "io.lysp"];
// None of them should crash at least
for file in fs::read_dir("examples").unwrap() {
let entry = file.unwrap();
let filename = entry.file_name();
let filename = filename.to_str().unwrap();
if !filename.ends_with(".lysp") || EXCLUDE.contains(&filename) {
continue;
}
eprintln!("Eval {}", entry.path().display());
eval_file(entry.path());
}
}