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))
|
.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(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(),
|
||||||
&[
|
&[
|
||||||
|
|||||||
@@ -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)?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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![];
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
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
@@ -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
@@ -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())),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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),
|
||||||
|
|||||||
@@ -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
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user