Compare commits

...

2 Commits

25 changed files with 649 additions and 161 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)))
-1
View File
@@ -1 +0,0 @@
(print (explain explain))
+55
View File
@@ -0,0 +1,55 @@
; 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)))))
; those are prelude, but defined in lysp itself:
(print "The previously printed expression is AFTER this one in the code")
(compile-debug
(when #t
(print "a")
(print "b")
)
)
(runtime-debug
(when #t
(print "a")
(print "b")
)
)
(when 1
(print "a")
(print "b")
)
(unless nil
(print "c")
(print "d")
)
; catch macro
(print "Failing in a catch block...")
(catch
(print a)
e (print "Caught an error:" e)
)
(print "... doesn't fail the execution")
+31 -5
View File
@@ -267,6 +267,29 @@ impl CompileContext {
}
}
pub fn guard_argument(&mut self) {
let depth = self.scope_depth as isize;
self.locals.push(Local {
name: "".into(),
depth,
is_captured: false,
});
}
pub fn unguard_argument(&mut self) {
let guard = self
.locals
.pop()
.expect("unguard_argument() called with an empty locals stack");
let stack_index = self.locals.len();
if guard.name.as_ref() != "" {
panic!(
"unguard_argument(): the local is not a guard: {:?} at stack index {}",
guard.name, stack_index,
);
}
}
pub fn push_scope(&mut self) {
self.scope_depth += 1;
if self.options.trace_compile {
@@ -382,6 +405,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 +517,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(),
}))
}
+15 -4
View File
@@ -53,13 +53,22 @@ impl Compile for CallExpression {
if instruction == Instruction::Call {
let callee = self.callee.compile(cx)?;
cx.push(callee)?;
cx.guard_argument();
}
for expression in self.arguments.iter() {
let value = expression.compile(cx)?;
cx.push(value)?;
cx.guard_argument();
}
cx.emit(instruction);
cx.emit(argument_count);
let mut unguard_count = usize::from(argument_count);
if instruction == Instruction::Call {
unguard_count += 1;
}
for _ in 0..unguard_count {
cx.unguard_argument();
}
Ok(CompileValue::Stack)
}
}
@@ -173,8 +182,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 +293,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(),
&[
+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))
}
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)?))
}
}
+2 -1
View File
@@ -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),
@@ -120,7 +121,7 @@ impl Expression {
_ => Self::map_or(CallExpression::parse(cons, value), Expression::Call),
}
}
Value::Keyword(_) => todo!("Error here"),
Value::Keyword(keyword) => todo!("Error here: keyword {keyword}"),
Value::Closure(_) => todo!("Error here"),
Value::Function(_) => todo!("Error here"),
Value::NativeFunction(_) => todo!("Error here"),
+30 -15
View File
@@ -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
View File
@@ -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![];
+7
View File
@@ -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,
+70
View File
@@ -0,0 +1,70 @@
; Convenience flow control macros
(defmacro when (condition body-head &rest body)
"If condition is true, evaluates the expressions in body, otherwise returns nil"
`(if ,condition (progn ,body-head ,@body))
)
(defmacro unless (condition body-head &rest body)
"If condition is false, evaluates the expressions in body, otherwise returns nil"
`(if (not ,condition) (progn ,body-head ,@body))
)
; Result handling functions
(defun result/ok? (x)
"Returns #t if x is an ok"
(= 'ok (car x))
)
(defun result/err? (x)
"Returns #t if x is an error"
(= 'err (car x))
)
(defun result/map (f x)
"If x is an ok, applies f to its value, otherwise does nothing"
(if (result/ok? x)
`(ok ,(f (cadr x)))
x
)
)
(defun result/map-err (f x)
"If x is an error, applies f to its value, otherwise does nothing"
(if (result/err? x)
`(err ,(f (cadr x)))
x
)
)
; Convenience error handling macros
(defmacro catch (expression error-symbol handler)
"Evaluates expression, running the handler if evaluation fails, introducing error-symbol as error"
(let (eval-result-symbol (gensym))
`(let (,eval-result-symbol (eval (quote ,expression)))
(cond
((result/ok? ,eval-result-symbol) (cadr ,eval-result-symbol))
(&otherwise
(let (,error-symbol (cadr ,eval-result-symbol))
,handler
)
)
)
)
)
)
(defmacro compile-debug (expression)
"Prints the input expression during macro expansion/compile time and evaluates the expression in runtime"
(print expression)
expression
)
(defmacro runtime-debug (expression)
"Prints the input expression and evaluates it in runtime"
`(progn
(print (quote ,expression))
,expression
)
)
; Convenience list functions
(defun cadr (x) "Alias for (car (cdr x))" (car (cdr x)))
(defun cdar (x) "Alias for (cdr (car x))" (cdr (car x)))
(defun caddr (x) "Alias for (car (cdr (cdr x)))" (car (cdr (cdr x))))
(defun cadar (x) "Alias for (car (cdr (car x)))" (car (cdr (car x))))
+5 -4
View File
@@ -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))
}
+16 -1
View File
@@ -1,4 +1,12 @@
use std::{borrow::Borrow, cell::RefCell, collections::HashMap, fmt, hash::Hash, rc::Rc};
use std::{
borrow::Borrow,
cell::RefCell,
collections::HashMap,
fmt,
hash::Hash,
rc::Rc,
sync::atomic::{AtomicU32, Ordering},
};
use crate::{
error::MachineError,
@@ -22,6 +30,7 @@ pub struct Environment {
globals: RefCell<HashMap<IdentifierValue, Value>>,
macros: RefCell<HashMap<IdentifierValue, Macro>>,
parent: Option<Rc<Environment>>,
gensym_index: AtomicU32,
}
impl Environment {
@@ -32,6 +41,12 @@ impl Environment {
}
}
pub fn gensym(&self) -> IdentifierValue {
// TODO do this in a root environment?
let index = self.gensym_index.fetch_add(1, Ordering::SeqCst);
format!("___gensym{index}").into()
}
pub fn defun_native<S, D, F>(&self, identifier: S, docstring: D, function: F) -> Value
where
S: Into<IdentifierValue>,
+125 -42
View File
@@ -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
View File
@@ -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(),
}
}
+26 -1
View File
@@ -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",
+5 -1
View File
@@ -6,6 +6,9 @@ use crate::{
};
pub fn load(env: &Rc<Environment>) {
env.defun_native("gensym", "Provides an unique symbol name", |_, env, _| {
Ok(env.gensym().into())
});
env.defun_native(
"explain",
"Provides an explanation for a given value",
@@ -20,7 +23,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(),
+5 -1
View File
@@ -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)
+9 -1
View File
@@ -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");
}
+8 -2
View File
@@ -7,8 +7,8 @@ use crate::{
env::Environment,
machine::Machine,
value::{
BooleanValue, BytecodeFunction, ClosureValue, ConsCell, NativeFunction, NativeValue,
NumberValue, StringValue, Vector,
BooleanValue, BytecodeFunction, ClosureValue, ConsCell, IdentifierValue,
NativeFunction, NativeValue, NumberValue, StringValue, Vector,
},
},
};
@@ -126,6 +126,12 @@ impl From<Vec<u8>> for Value {
}
}
impl From<IdentifierValue> for Value {
fn from(value: IdentifierValue) -> Self {
Value::Identifier(value)
}
}
impl TryFromValue<'_> for AnyFunction {
fn try_from_value(value: &'_ Value) -> Result<Self, ValueConversionError> {
match value {
+21 -2
View File
@@ -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!(" <??> <??>");
+1
View File
@@ -52,6 +52,7 @@ impl_keyword! {
Break => "break",
Continue => "continue",
Declare => "declare",
Error => "&error",
// Cons => "cons",
}
}
+28 -21
View File
@@ -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),
+1 -1
View File
@@ -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
View File
@@ -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());
}
}