Proper quasiquote expansion rules, unquote-splice
This commit is contained in:
@@ -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)))
|
||||
@@ -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")
|
||||
)
|
||||
|
||||
|
||||
@@ -382,6 +382,11 @@ impl FunctionBlock {
|
||||
.add_local(optional.clone(), Some(-100))
|
||||
.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
|
||||
}
|
||||
|
||||
@@ -489,17 +494,15 @@ impl FunctionBlock {
|
||||
instructions[position] = branch_offset as u8;
|
||||
}
|
||||
|
||||
let min_arity = self.signature.min_arity();
|
||||
let max_arity = self.signature.max_arity();
|
||||
|
||||
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(),
|
||||
min_arity,
|
||||
max_arity,
|
||||
required_count: self.signature.required_arguments.len(),
|
||||
optional_count: self.signature.optional_arguments.len(),
|
||||
has_rest: self.signature.rest_argument.is_some(),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -173,8 +173,9 @@ mod tests {
|
||||
Instruction::Return.into(),
|
||||
]
|
||||
);
|
||||
assert_eq!(lambda_function.min_arity, 1);
|
||||
assert_eq!(lambda_function.max_arity, 1);
|
||||
assert_eq!(lambda_function.required_count, 1);
|
||||
assert_eq!(lambda_function.optional_count, 0);
|
||||
assert!(!lambda_function.has_rest);
|
||||
assert_eq!(
|
||||
lambda_function.upvalues.as_ref(),
|
||||
&[UpvalueDef {
|
||||
@@ -283,8 +284,9 @@ mod tests {
|
||||
);
|
||||
// inner function
|
||||
assert!(defun_function.constants.is_empty());
|
||||
assert_eq!(defun_function.min_arity, 1);
|
||||
assert_eq!(defun_function.max_arity, 1);
|
||||
assert_eq!(defun_function.required_count, 1);
|
||||
assert_eq!(defun_function.optional_count, 0);
|
||||
assert!(!defun_function.has_rest);
|
||||
assert_eq!(
|
||||
defun_function.instructions.as_ref(),
|
||||
&[
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
pub fn try_collect_extraneous<E, I: IntoIterator<Item = Result<Value, E>>>(
|
||||
iter: I,
|
||||
) -> Result<Self, E> {
|
||||
) -> Result<Self, E>
|
||||
where
|
||||
I::IntoIter: ExactSizeIterator + DoubleEndedIterator,
|
||||
{
|
||||
Ok(Self::extraneous(&Value::try_list_or_nil(iter)?))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ impl Expression {
|
||||
Value::String(value) => Rc::new(Self::StringLiteral(value.clone())),
|
||||
Value::Quasi(_value) => todo!("{value}"),
|
||||
Value::Unquote(_value) => todo!("Unquote {_value}"),
|
||||
Value::UnquoteSplice(_value) => todo!("UnquoteSplice {_value}"),
|
||||
Value::Quote(value) => Rc::new(Self::Quote(value.clone())),
|
||||
|
||||
Value::Nil => Rc::new(Self::Nil),
|
||||
|
||||
+30
-15
@@ -1,4 +1,4 @@
|
||||
use std::{fmt, io, ops::RangeInclusive, rc::Rc};
|
||||
use std::{fmt, io, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
compile::CompileError,
|
||||
@@ -40,7 +40,6 @@ pub enum ReadError {
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ArgumentCountError {
|
||||
pub function: Rc<BytecodeFunction>,
|
||||
pub expected_range: RangeInclusive<usize>,
|
||||
pub actual: usize,
|
||||
}
|
||||
|
||||
@@ -143,11 +142,7 @@ impl PartialEq for ReadError {
|
||||
|
||||
impl fmt::Display for ArgumentCountError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let min_arity = *self.expected_range.start();
|
||||
let max_arity = *self.expected_range.end();
|
||||
let one_argument = min_arity == max_arity;
|
||||
let too_few = self.actual < min_arity;
|
||||
|
||||
let too_few = self.actual < self.function.required_count;
|
||||
write!(
|
||||
f,
|
||||
"too {} arguments for function {}: expected ",
|
||||
@@ -155,15 +150,35 @@ impl fmt::Display for ArgumentCountError {
|
||||
self.function
|
||||
)?;
|
||||
|
||||
if one_argument {
|
||||
write!(f, "{min_arity}")?;
|
||||
write!(f, " argument")?;
|
||||
if min_arity != 1 {
|
||||
write!(f, "s")?;
|
||||
}
|
||||
if self.function.has_rest {
|
||||
let singular = self.function.required_count == 1;
|
||||
|
||||
write!(
|
||||
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 {
|
||||
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
@@ -37,6 +37,7 @@ pub enum Trace {
|
||||
Call,
|
||||
Return,
|
||||
Stack,
|
||||
Macro,
|
||||
}
|
||||
|
||||
impl FromStr for Trace {
|
||||
@@ -49,6 +50,7 @@ impl FromStr for Trace {
|
||||
"call" => Ok(Self::Call),
|
||||
"return" => Ok(Self::Return),
|
||||
"stack" => Ok(Self::Stack),
|
||||
"macro" => Ok(Self::Macro),
|
||||
_ => Err(format!("Unknown trace flag: {s:?}")),
|
||||
}
|
||||
}
|
||||
@@ -178,7 +180,7 @@ fn run_module<P: AsRef<Path>>(
|
||||
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_reader = ModuleReader::new(reader, vm.trace_macros);
|
||||
let function = match module_reader.compile(Some(name.into()), compile_options, env) {
|
||||
Ok(function) => function,
|
||||
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_returns = args.trace.contains(&Trace::Return);
|
||||
vm.trace_stack = args.trace.contains(&Trace::Stack);
|
||||
vm.trace_macros = args.trace.contains(&Trace::Macro);
|
||||
let env = Rc::new(Environment::default());
|
||||
prelude::load(&env);
|
||||
let mut arguments = vec![];
|
||||
|
||||
@@ -305,6 +305,12 @@ fn parse_unquote(input: &str) -> IResult<&str, Value> {
|
||||
.map(Value::Unquote)
|
||||
.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> {
|
||||
preceded(char('\''), parse_value)
|
||||
@@ -330,6 +336,7 @@ pub fn parse_value(input: &str) -> IResult<&str, Value> {
|
||||
parse_list_or_nil,
|
||||
parse_vector,
|
||||
parse_boolean,
|
||||
parse_unquote_splice,
|
||||
parse_quote,
|
||||
parse_quasi,
|
||||
parse_unquote,
|
||||
|
||||
@@ -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
@@ -16,7 +16,6 @@ use crate::{
|
||||
env::Environment,
|
||||
instruction::Instruction,
|
||||
machine::Machine,
|
||||
macros::MacroExpand,
|
||||
value::{BytecodeFunction, IdentifierValue, Value},
|
||||
},
|
||||
};
|
||||
@@ -94,10 +93,12 @@ impl<R: BufRead> Reader for FileReader<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 {
|
||||
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 {
|
||||
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))
|
||||
}
|
||||
|
||||
+125
-42
@@ -1,4 +1,7 @@
|
||||
use std::rc::Rc;
|
||||
use std::{
|
||||
io::{self, BufReader, Read},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
compile::{CompileContext, CompileOptions},
|
||||
@@ -6,6 +9,7 @@ use crate::{
|
||||
ArgumentCountError, MachineError, MachineErrorAt, MachineErrorLocation,
|
||||
ValueConversionError,
|
||||
},
|
||||
read::{self, FileReader},
|
||||
vm::{
|
||||
Value,
|
||||
env::Environment,
|
||||
@@ -15,7 +19,7 @@ use crate::{
|
||||
macros::MacroExpand,
|
||||
prelude,
|
||||
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_stack: bool,
|
||||
pub trace_calls: bool,
|
||||
pub trace_macros: bool,
|
||||
}
|
||||
|
||||
impl Default for Machine {
|
||||
@@ -50,6 +55,7 @@ impl Default for Machine {
|
||||
trace_stack: false,
|
||||
trace_returns: false,
|
||||
trace_instructions: false,
|
||||
trace_macros: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,6 +192,48 @@ impl Machine {
|
||||
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(
|
||||
&mut self,
|
||||
env: &Rc<Environment>,
|
||||
@@ -246,22 +294,7 @@ impl Machine {
|
||||
}
|
||||
};
|
||||
|
||||
if !(closure.function.min_arity..=closure.function.max_arity).contains(&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")
|
||||
// }
|
||||
self.collect_call_arguments(&closure.function, argument_count)?;
|
||||
|
||||
if self.trace_calls {
|
||||
eprintln!("TRACE: Call closure");
|
||||
@@ -599,26 +632,11 @@ impl Machine {
|
||||
closure: ClosureValue,
|
||||
args: &[Value],
|
||||
) -> 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))
|
||||
.map_err(MachineErrorAt::at_unknown)?;
|
||||
if max_arity == usize::MAX {
|
||||
todo!("VM support for &rest argument")
|
||||
}
|
||||
for arg in args.iter() {
|
||||
for arg in args {
|
||||
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();
|
||||
self.execute_call(env, args.len())
|
||||
.map_err(MachineErrorAt::at_unknown)?;
|
||||
@@ -633,6 +651,33 @@ impl Machine {
|
||||
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(
|
||||
&mut self,
|
||||
compile_options: CompileOptions,
|
||||
@@ -640,8 +685,8 @@ impl Machine {
|
||||
env: &Rc<Environment>,
|
||||
value: Value,
|
||||
) -> Result<Value, MachineErrorAt> {
|
||||
let value = value.macro_expand(self, env, false)?;
|
||||
let function = CompileContext::compile_value(compile_options, chunk_name, &value)
|
||||
let value_expanded = self.macro_expand(env, &value)?;
|
||||
let function = CompileContext::compile_value(compile_options, chunk_name, &value_expanded)
|
||||
.map_err(MachineError::Compile)
|
||||
.map_err(MachineErrorAt::at_unknown)?;
|
||||
|
||||
@@ -653,6 +698,41 @@ impl Machine {
|
||||
let value = self.evaluate_closure(env, closure, 0)?;
|
||||
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)]
|
||||
@@ -684,8 +764,9 @@ mod tests {
|
||||
instructions: instructions.into(),
|
||||
constants: constants.into(),
|
||||
upvalues: [].into(),
|
||||
min_arity: 0,
|
||||
max_arity: 0,
|
||||
required_count: 0,
|
||||
optional_count: 0,
|
||||
has_rest: false,
|
||||
}),
|
||||
};
|
||||
let mut machine = Machine::default();
|
||||
@@ -758,8 +839,9 @@ mod tests {
|
||||
.into(),
|
||||
constants: [].into(),
|
||||
upvalues: [].into(),
|
||||
min_arity: 1,
|
||||
max_arity: 1,
|
||||
required_count: 1,
|
||||
optional_count: 0,
|
||||
has_rest: false,
|
||||
});
|
||||
let (m, r) = eval0(
|
||||
&env,
|
||||
@@ -799,8 +881,9 @@ mod tests {
|
||||
.into(),
|
||||
constants: [].into(),
|
||||
upvalues: [].into(),
|
||||
min_arity: 2,
|
||||
max_arity: 2,
|
||||
required_count: 2,
|
||||
optional_count: 0,
|
||||
has_rest: false,
|
||||
});
|
||||
let (m, r) = eval0(
|
||||
&env,
|
||||
|
||||
+32
-22
@@ -5,7 +5,7 @@ use crate::{
|
||||
vm::{
|
||||
env::{Environment, Macro},
|
||||
machine::Machine,
|
||||
value::{ClosureValue, ConsCell, Keyword, Value},
|
||||
value::{ClosureValue, ConsCell, Value},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -40,6 +40,7 @@ impl MacroExpand for Value {
|
||||
| Self::NativeFunction(_)
|
||||
| Self::NativeValue(_)
|
||||
| Self::Vector(_)
|
||||
| Self::UnquoteSplice(_)
|
||||
| Self::Unquote(_) => Ok(self.clone()),
|
||||
// | Self::NativeFunction(_) => Ok(self.clone()),
|
||||
Self::Cons(cons) => {
|
||||
@@ -86,28 +87,37 @@ impl MacroExpand for Value {
|
||||
fn expand_quasiquote(value: &Value) -> Value {
|
||||
match value {
|
||||
Value::Nil => Value::Nil,
|
||||
// Toplevel-only
|
||||
Value::Unquote(inner) => inner.as_ref().clone(),
|
||||
Value::Cons(cons) => {
|
||||
// x . y -> (cons <exp-car> <exp-cdr>)
|
||||
let ConsCell(car, cdr) = cons.as_ref();
|
||||
let exp_car = expand_quasiquote(car);
|
||||
let exp_cdr = expand_quasiquote(cdr);
|
||||
Value::Identifier("cons".into()).cons(exp_car.cons(exp_cdr.cons(Value::Nil)))
|
||||
Value::UnquoteSplice(inner) => inner.as_ref().clone(),
|
||||
Value::Cons(_) => {
|
||||
let mut elements = vec![];
|
||||
let mut current = value;
|
||||
|
||||
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) => {
|
||||
// (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())),
|
||||
_ => value.clone().quote(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
error::MachineError,
|
||||
error::{MachineError, ValueConversionError},
|
||||
vm::{
|
||||
Value,
|
||||
env::Environment,
|
||||
@@ -43,6 +43,31 @@ pub fn load(env: &Rc<Environment>) {
|
||||
// });
|
||||
|
||||
// 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(
|
||||
"car",
|
||||
"Returns the CAR value of a cons-cell",
|
||||
|
||||
@@ -20,7 +20,8 @@ pub fn load(env: &Rc<Environment>) {
|
||||
Value::Boolean(_) => "a boolean value".into(),
|
||||
Value::Quasi(_) => "a quasi-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::Vector(_) => "a vector".into(),
|
||||
Value::String(_) => "a string".into(),
|
||||
|
||||
@@ -84,7 +84,11 @@ pub fn load(env: &Rc<Environment>) {
|
||||
if i != 0 {
|
||||
print!(" ");
|
||||
}
|
||||
print!("{arg}");
|
||||
if let Value::String(string) = arg {
|
||||
print!("{}", &**string);
|
||||
} else {
|
||||
print!("{arg}");
|
||||
}
|
||||
}
|
||||
println!();
|
||||
Ok(Value::Nil)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::vm::env::Environment;
|
||||
use crate::vm::{env::Environment, machine::Machine};
|
||||
|
||||
mod collections;
|
||||
mod convert;
|
||||
@@ -12,7 +12,11 @@ mod math;
|
||||
|
||||
pub(crate) use math::*;
|
||||
|
||||
const PRELUDE_SOURCE: &str = include_str!("../../prelude.lysp");
|
||||
|
||||
pub fn load(env: &Rc<Environment>) {
|
||||
let mut vm = Machine::default();
|
||||
|
||||
math::load(env);
|
||||
eval::load(env);
|
||||
functional::load(env);
|
||||
@@ -20,4 +24,8 @@ pub fn load(env: &Rc<Environment>) {
|
||||
convert::load(env);
|
||||
debug::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");
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@ pub struct BytecodeFunction {
|
||||
pub instructions: Box<[u8]>,
|
||||
pub constants: Box<[Value]>,
|
||||
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 {
|
||||
@@ -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> {
|
||||
let Some(b0) = self.instructions.get(address).copied() else {
|
||||
eprint!(" <??> <??>");
|
||||
|
||||
+28
-21
@@ -49,6 +49,7 @@ pub enum Value {
|
||||
Quasi(Rc<Value>),
|
||||
Quote(Rc<Value>),
|
||||
Unquote(Rc<Value>),
|
||||
UnquoteSplice(Rc<Value>),
|
||||
// Semantic
|
||||
Closure(ClosureValue),
|
||||
Function(Rc<BytecodeFunction>),
|
||||
@@ -98,6 +99,9 @@ impl Value {
|
||||
Self::Unquote(value) => {
|
||||
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::Keyword(_) => Self::Identifier("keyword".into()),
|
||||
Self::String(_) => Self::Identifier("string".into()),
|
||||
@@ -121,6 +125,7 @@ impl Value {
|
||||
| Self::Quasi(_)
|
||||
| Self::Quote(_)
|
||||
| Self::Unquote(_)
|
||||
| Self::UnquoteSplice(_)
|
||||
| Self::Closure(_)
|
||||
| Self::NativeFunction(_)
|
||||
| Self::NativeValue(_)
|
||||
@@ -132,30 +137,28 @@ impl Value {
|
||||
Self::Cons(Rc::new(ConsCell(self, cdr)))
|
||||
}
|
||||
|
||||
pub fn try_list_or_nil<E, I: IntoIterator<Item = Result<Self, E>>>(
|
||||
items: I,
|
||||
) -> Result<Self, E> {
|
||||
Self::try_list_or_nil_inner(&mut items.into_iter())
|
||||
}
|
||||
|
||||
pub fn list_or_nil<I: IntoIterator<Item = Self>>(items: I) -> Self {
|
||||
Self::list_or_nil_inner(&mut items.into_iter())
|
||||
}
|
||||
|
||||
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),
|
||||
pub fn try_list_or_nil<E, I: IntoIterator<Item = Result<Self, E>>>(items: I) -> Result<Self, E>
|
||||
where
|
||||
I::IntoIter: ExactSizeIterator + DoubleEndedIterator,
|
||||
{
|
||||
let iter = items.into_iter();
|
||||
let mut result = Value::Nil;
|
||||
for element in iter.rev() {
|
||||
result = element?.cons(result);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn list_or_nil_inner<I: Iterator<Item = Self>>(items: &mut I) -> Self {
|
||||
match items.next() {
|
||||
Some(value) => value.cons(Self::list_or_nil_inner(items)),
|
||||
None => Self::Nil,
|
||||
pub fn list_or_nil<I: IntoIterator<Item = Self>>(items: I) -> Self
|
||||
where
|
||||
I::IntoIter: ExactSizeIterator + DoubleEndedIterator,
|
||||
{
|
||||
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> {
|
||||
@@ -214,7 +217,7 @@ impl fmt::Display for Value {
|
||||
fmt::Display::fmt(vector, f)?;
|
||||
f.write_char(']')
|
||||
}
|
||||
Self::Keyword(keyword) => write!(f, ":{keyword}"),
|
||||
Self::Keyword(keyword) => write!(f, "{keyword}"),
|
||||
Self::Identifier(identifier) => write!(f, "{identifier}"),
|
||||
Self::String(value) => fmt::Display::fmt(value, f),
|
||||
Self::Quote(value) => {
|
||||
@@ -229,6 +232,10 @@ impl fmt::Display for Value {
|
||||
f.write_char(',')?;
|
||||
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::Boolean(value) => fmt::Display::fmt(value, f),
|
||||
Self::Closure(value) => fmt::Display::fmt(value, f),
|
||||
|
||||
@@ -25,6 +25,6 @@ impl Deref for StringValue {
|
||||
|
||||
impl fmt::Display for StringValue {
|
||||
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
@@ -1,57 +1,56 @@
|
||||
use std::{
|
||||
io::{self, BufReader, Read},
|
||||
rc::Rc,
|
||||
};
|
||||
use std::{fs, path::Path, rc::Rc};
|
||||
|
||||
use lysp::{
|
||||
error::MachineErrorAt,
|
||||
read::{FileReader, read},
|
||||
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]
|
||||
fn eval_str_in(code: &str, env: &Rc<Environment>) -> Result<Value, MachineErrorAt> {
|
||||
let mut machine = Machine::default();
|
||||
let reader = BufReader::new(SliceReader(code.as_bytes()));
|
||||
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")
|
||||
machine.evaluate_str(Default::default(), None, env, code)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn eval_str(code: &str) -> Value {
|
||||
let env = Rc::new(Environment::default());
|
||||
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]
|
||||
fn eval_str_err(code: &str) -> MachineErrorAt {
|
||||
let env = Rc::new(Environment::default());
|
||||
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]
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user