Compare commits
19 commits
main
...
basic-inte
Author | SHA1 | Date | |
---|---|---|---|
Erin | dd2c58bfcf | ||
Erin | 33e1904e7f | ||
Erin | 26a0cce990 | ||
Erin | 18fd6a35d5 | ||
Erin | 9bf1a6b633 | ||
Erin | 1c4b04dc6d | ||
Erin | cb20383f08 | ||
Erin | a592cf80f2 | ||
Erin | d1d7ab4067 | ||
Erin | a547dc711c | ||
Erin | bc445db1c9 | ||
Erin | 3496fec48e | ||
Erin | f23266f63c | ||
Erin | e92e1e3f7c | ||
Erin | 35676865b0 | ||
Erin | cbf7fde8e1 | ||
Erin | c3c8c23956 | ||
Erin | 5664e1309a | ||
Erin | f5013943fe |
9
Cargo.lock
generated
9
Cargo.lock
generated
|
@ -1416,18 +1416,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.31"
|
version = "1.0.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
|
checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.31"
|
version = "1.0.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
|
checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -1738,6 +1738,7 @@ dependencies = [
|
||||||
"logos",
|
"logos",
|
||||||
"ordered-float",
|
"ordered-float",
|
||||||
"rustyline",
|
"rustyline",
|
||||||
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -12,3 +12,4 @@ eframe = "*"
|
||||||
logos = "*"
|
logos = "*"
|
||||||
ordered-float = "3.0"
|
ordered-float = "3.0"
|
||||||
rustyline = "10.0"
|
rustyline = "10.0"
|
||||||
|
thiserror = "1.0"
|
||||||
|
|
15
src/error.rs
15
src/error.rs
|
@ -1,10 +1,11 @@
|
||||||
use crate::syntax::lexer::Token;
|
use crate::syntax::lexer::Token;
|
||||||
use ariadne::{Color, Fmt, Label, Report, ReportKind, Source};
|
use ariadne::{Color, Fmt, Label, Report, ReportKind, Source};
|
||||||
use chumsky::error::{Simple, SimpleReason};
|
use chumsky::error::{Simple, SimpleReason};
|
||||||
use std::fmt::Display;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Error, Clone)]
|
||||||
pub enum Error<'a> {
|
pub enum Error<'a> {
|
||||||
|
#[error("parse error: {0:?}")]
|
||||||
Parse(Simple<Token<'a>>),
|
Parse(Simple<Token<'a>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,13 +74,3 @@ impl<'a> Error<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Display for Error<'a> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Error::Parse(e) => write!(f, "{e:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> std::error::Error for Error<'a> {}
|
|
||||||
|
|
28
src/interpreter/bytecode.rs
Normal file
28
src/interpreter/bytecode.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum Instruction {
|
||||||
|
/// Call a function on top of the stack
|
||||||
|
/// with n arguments
|
||||||
|
Call(usize),
|
||||||
|
|
||||||
|
/// Unconditional jump by offset
|
||||||
|
Jump(isize),
|
||||||
|
|
||||||
|
/// Jump by offset if value on
|
||||||
|
/// top of the stack is falsey (nil)
|
||||||
|
JumpN(isize),
|
||||||
|
|
||||||
|
/// Load a value from constants table
|
||||||
|
LdConst(usize),
|
||||||
|
|
||||||
|
/// Load a value from locals table
|
||||||
|
LdLocal(usize),
|
||||||
|
|
||||||
|
/// Store a value to locals table
|
||||||
|
StLocal(usize),
|
||||||
|
|
||||||
|
/// Drop a value from stack
|
||||||
|
Drop,
|
||||||
|
|
||||||
|
/// Duplicate value on stack
|
||||||
|
Dup,
|
||||||
|
}
|
159
src/interpreter/context.rs
Normal file
159
src/interpreter/context.rs
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
//! Evaluation context for Wisp
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
bytecode::Instruction,
|
||||||
|
value::{Function, Value},
|
||||||
|
};
|
||||||
|
use crate::interpreter::value::NativeFun;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
|
|
||||||
|
pub struct Context<'s> {
|
||||||
|
fun: Rc<Function<'s>>,
|
||||||
|
stack: Vec<Value<'s>>,
|
||||||
|
locals: Vec<Value<'s>>,
|
||||||
|
pc: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> Context<'s> {
|
||||||
|
/// Create a new evaluation context
|
||||||
|
pub fn new(fun: Rc<Function<'s>>) -> Self {
|
||||||
|
Self {
|
||||||
|
stack: vec![],
|
||||||
|
locals: vec![Value::Nil; fun.locals_len],
|
||||||
|
pc: 0,
|
||||||
|
fun,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute whole function and return last value on stack (or nil)
|
||||||
|
pub fn execute(&mut self) -> Result<Value<'s>> {
|
||||||
|
let len = self.fun.bytecode.len();
|
||||||
|
while self.pc < len {
|
||||||
|
self.tick()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self.stack.pop().unwrap_or(Value::Nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute next instruction
|
||||||
|
pub fn tick(&mut self) -> Result<()> {
|
||||||
|
use Instruction::*;
|
||||||
|
match self
|
||||||
|
.fun
|
||||||
|
.bytecode
|
||||||
|
.get(self.pc)
|
||||||
|
.copied()
|
||||||
|
.ok_or(IndexOutOfBoundsError::Bytecode)?
|
||||||
|
{
|
||||||
|
Call(n) if n >= self.stack.len() => return Err(IndexOutOfBoundsError::Stack.into()),
|
||||||
|
Call(n) => {
|
||||||
|
let fun = self.stack_pop()?;
|
||||||
|
let mut args = self.stack.split_off(self.stack.len() - n);
|
||||||
|
match fun {
|
||||||
|
Value::Function(fun) => {
|
||||||
|
if fun.locals_len > args.len() {
|
||||||
|
args.resize(fun.locals_len, Value::Nil);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.stack.push(
|
||||||
|
Context {
|
||||||
|
stack: vec![],
|
||||||
|
locals: args,
|
||||||
|
pc: 0,
|
||||||
|
fun,
|
||||||
|
}
|
||||||
|
.execute()?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Value::NativeFun(NativeFun(f)) => {
|
||||||
|
let v = f(&args);
|
||||||
|
self.stack.push(v);
|
||||||
|
}
|
||||||
|
_ => unimplemented!("invalid function type"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Jump(o) => {
|
||||||
|
self.jump(o);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
JumpN(o) => {
|
||||||
|
if self.stack_pop()? == Value::Nil {
|
||||||
|
self.jump(o);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LdConst(i) => {
|
||||||
|
self.stack.push(
|
||||||
|
self.fun
|
||||||
|
.consts
|
||||||
|
.get(i)
|
||||||
|
.cloned()
|
||||||
|
.ok_or(IndexOutOfBoundsError::Const)?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
LdLocal(i) => {
|
||||||
|
self.stack.push(
|
||||||
|
self.locals
|
||||||
|
.get(i)
|
||||||
|
.cloned()
|
||||||
|
.ok_or(IndexOutOfBoundsError::Local)?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
StLocal(i) => {
|
||||||
|
*self.locals.get_mut(i).ok_or(IndexOutOfBoundsError::Local)? = self.stack_pop()?;
|
||||||
|
}
|
||||||
|
Drop => {
|
||||||
|
self.stack_pop()?;
|
||||||
|
}
|
||||||
|
Dup => {
|
||||||
|
self.stack.push(
|
||||||
|
self.stack
|
||||||
|
.last()
|
||||||
|
.cloned()
|
||||||
|
.ok_or(IndexOutOfBoundsError::Stack)?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.pc += 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Jump by offset
|
||||||
|
fn jump(&mut self, offset: isize) {
|
||||||
|
(if offset.is_negative() {
|
||||||
|
std::ops::SubAssign::sub_assign
|
||||||
|
} else {
|
||||||
|
std::ops::AddAssign::add_assign
|
||||||
|
})(&mut self.pc, offset.unsigned_abs());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pop a value from a stack
|
||||||
|
fn stack_pop(&mut self) -> Result<Value<'s>, IndexOutOfBoundsError> {
|
||||||
|
self.stack.pop().ok_or(IndexOutOfBoundsError::Stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error(transparent)]
|
||||||
|
IndexOutOfBounds(#[from] IndexOutOfBoundsError),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Error)]
|
||||||
|
pub enum IndexOutOfBoundsError {
|
||||||
|
#[error("bytecode index out of bounds")]
|
||||||
|
Bytecode,
|
||||||
|
|
||||||
|
#[error("const index out of bounds")]
|
||||||
|
Const,
|
||||||
|
|
||||||
|
#[error("stack index out of bounds")]
|
||||||
|
Stack,
|
||||||
|
|
||||||
|
#[error("locals index out of bounds")]
|
||||||
|
Local,
|
||||||
|
}
|
12
src/interpreter/mod.rs
Normal file
12
src/interpreter/mod.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
//! **bodge**
|
||||||
|
//! (third-person singular simple present bodges, present participle **bodging**,
|
||||||
|
//! simple past and past participle **bodged**)
|
||||||
|
//! 1. (Britain) To do a clumsy or inelegant job, usually as a temporary repair; mend, patch up, repair.
|
||||||
|
//! 2. To work green wood using traditional country methods; to perform the craft of a bodger.
|
||||||
|
//!
|
||||||
|
//! A simple silly compiler + VM intended for testing purposes.
|
||||||
|
//! To be replaced by something better soon™️ :ferrisClueless:
|
||||||
|
|
||||||
|
pub mod bytecode;
|
||||||
|
pub mod context;
|
||||||
|
pub mod value;
|
29
src/interpreter/value/function.rs
Normal file
29
src/interpreter/value/function.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
use super::Value;
|
||||||
|
use crate::interpreter::bytecode::Instruction;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Function<'s> {
|
||||||
|
pub bytecode: Box<[Instruction]>,
|
||||||
|
pub consts: Box<[Value<'s>]>,
|
||||||
|
pub locals_len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct NativeFun<'s>(pub fn(&[Value<'s>]) -> Value<'s>);
|
||||||
|
impl<'s> Debug for NativeFun<'s> {
|
||||||
|
fn fmt<'a>(&'a self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_tuple("NativeFun")
|
||||||
|
.field(&(self.0 as fn(&'a _) -> _))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> PartialEq for NativeFun<'s> {
|
||||||
|
fn eq<'a>(&'a self, other: &Self) -> bool {
|
||||||
|
(self.0 as fn(&'a _) -> _) == other.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> Eq for NativeFun<'s> {}
|
92
src/interpreter/value/mod.rs
Normal file
92
src/interpreter/value/mod.rs
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
mod function;
|
||||||
|
mod pair;
|
||||||
|
mod string;
|
||||||
|
|
||||||
|
pub use function::{Function, NativeFun};
|
||||||
|
pub use pair::DotPair;
|
||||||
|
pub use string::Str;
|
||||||
|
|
||||||
|
use crate::syntax::ast::{Expr, Spanned};
|
||||||
|
use pair::list;
|
||||||
|
use std::{collections::BTreeMap, fmt::Display, rc::Rc};
|
||||||
|
|
||||||
|
pub type OrderedF64 = ordered_float::OrderedFloat<f64>;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||||
|
pub enum Value<'s> {
|
||||||
|
#[default]
|
||||||
|
Nil,
|
||||||
|
DotPair(DotPair<'s>),
|
||||||
|
Vector(Rc<Vec<Self>>),
|
||||||
|
Map(Rc<BTreeMap<Self, Self>>),
|
||||||
|
Symbol(Str<'s>),
|
||||||
|
Keyword(Str<'s>),
|
||||||
|
Number(OrderedF64),
|
||||||
|
String(Str<'s>),
|
||||||
|
Function(Rc<Function<'s>>),
|
||||||
|
NativeFun(NativeFun<'s>),
|
||||||
|
Macro(Rc<Function<'s>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> From<Spanned<Expr<'s>>> for Value<'s> {
|
||||||
|
fn from(e: Spanned<Expr<'s>>) -> Self {
|
||||||
|
e.item.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> From<Expr<'s>> for Value<'s> {
|
||||||
|
fn from(e: Expr<'s>) -> Self {
|
||||||
|
match e {
|
||||||
|
Expr::List(v) => v.into_iter().rev().fold(Self::Nil, |acc, x| {
|
||||||
|
Value::DotPair(DotPair::new(x.into(), acc))
|
||||||
|
}),
|
||||||
|
Expr::Vector(v) => Self::Vector(Rc::new(v.into_iter().map(Into::into).collect())),
|
||||||
|
Expr::Pair((l, r)) => Self::DotPair(DotPair::new((*l).into(), (*r).into())),
|
||||||
|
Expr::Quote(e) => list!(Self::Symbol("quote".into()), (*e).into()),
|
||||||
|
Expr::Symbol("nil") => Self::Nil,
|
||||||
|
Expr::Symbol(s) => Self::Symbol(s.into()),
|
||||||
|
Expr::Keyword(s) => Self::Keyword(s.into()),
|
||||||
|
Expr::Number(n) => Self::Number(n),
|
||||||
|
Expr::String(s) => Self::String(s.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> Display for Value<'s> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Value::Nil => "nil".fmt(f),
|
||||||
|
Value::DotPair(p) => p.fmt(f),
|
||||||
|
Value::Vector(v) => fmt_sequence(f, v.as_ref(), "#(", ")", |i, f| i.fmt(f)),
|
||||||
|
Value::Map(m) => {
|
||||||
|
fmt_sequence(f, m.as_ref(), "(", ")", |(k, v), f| write!(f, "{k} -> {v}"))
|
||||||
|
}
|
||||||
|
Value::Symbol(s) => s.fmt(f),
|
||||||
|
Value::Keyword(k) => write!(f, ":{k}"),
|
||||||
|
Value::Number(n) => n.fmt(f),
|
||||||
|
Value::String(s) => write!(f, "\"{s}\""),
|
||||||
|
Value::Function(_) => "#fun#".fmt(f),
|
||||||
|
Value::NativeFun(fp) => write!(f, "#native-fun({fp:p})#"),
|
||||||
|
Value::Macro(_) => "#macro#".fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fmt_sequence<T>(
|
||||||
|
f: &mut std::fmt::Formatter<'_>,
|
||||||
|
s: impl IntoIterator<Item = T>,
|
||||||
|
start: &str,
|
||||||
|
end: &str,
|
||||||
|
it_fmt: impl Fn(T, &mut std::fmt::Formatter<'_>) -> std::fmt::Result,
|
||||||
|
) -> std::fmt::Result {
|
||||||
|
let mut it = s.into_iter();
|
||||||
|
start.fmt(f)?;
|
||||||
|
if let Some(i) = it.next() {
|
||||||
|
it_fmt(i, f)?;
|
||||||
|
}
|
||||||
|
for i in it {
|
||||||
|
" ".fmt(f)?;
|
||||||
|
it_fmt(i, f)?;
|
||||||
|
}
|
||||||
|
end.fmt(f)
|
||||||
|
}
|
60
src/interpreter/value/pair.rs
Normal file
60
src/interpreter/value/pair.rs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
use super::Value;
|
||||||
|
use std::{fmt::Display, rc::Rc};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct DotPair<'s>(pub Rc<(Value<'s>, Value<'s>)>);
|
||||||
|
impl<'s> DotPair<'s> {
|
||||||
|
pub fn new(first: Value<'s>, second: Value<'s>) -> Self {
|
||||||
|
Self(Rc::new((first, second)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn first(&self) -> &Value<'s> {
|
||||||
|
&self.0 .0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn second(&self) -> &Value<'s> {
|
||||||
|
&self.0 .1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> Display for DotPair<'s> {
|
||||||
|
fn fmt(mut self: &Self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
"(".fmt(f)?;
|
||||||
|
loop {
|
||||||
|
self.first().fmt(f)?;
|
||||||
|
match self.second() {
|
||||||
|
Value::Nil => break,
|
||||||
|
Value::DotPair(p) => {
|
||||||
|
" ".fmt(f)?;
|
||||||
|
self = p;
|
||||||
|
}
|
||||||
|
val => break write!(f, " . {val}")?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
")".fmt(f)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! list {
|
||||||
|
() => {
|
||||||
|
$crate::interpreter::value::Value::Nil
|
||||||
|
};
|
||||||
|
|
||||||
|
($item: expr $(,)?) => {
|
||||||
|
Value::DotPair(
|
||||||
|
$crate::interpreter::value::DotPair::new($item, $crate::interpreter::value::Value::Nil)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
($first: expr, $($rest: expr),* $(,)?) => {
|
||||||
|
Value::DotPair(
|
||||||
|
$crate::interpreter::value::DotPair::new(
|
||||||
|
$first,
|
||||||
|
$crate::interpreter::value::pair::list!($($rest),*)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use list;
|
57
src/interpreter/value/string.rs
Normal file
57
src/interpreter/value/string.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
use std::{fmt::Display, hash::Hash, ops::Deref, rc::Rc};
|
||||||
|
|
||||||
|
/// A Wisp string type which can hold two variants:
|
||||||
|
/// - Borrowed (usually a reference to source code or built-in literal)
|
||||||
|
/// - Shared (Ref-counted pointer to [`str`])
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Str<'a> {
|
||||||
|
Borrowed(&'a str),
|
||||||
|
Shared(Rc<str>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a str> for Str<'a> {
|
||||||
|
fn from(x: &'a str) -> Self {
|
||||||
|
Self::Borrowed(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<Rc<str>> for Str<'a> {
|
||||||
|
fn from(x: Rc<str>) -> Self {
|
||||||
|
Self::Shared(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Deref for Str<'a> {
|
||||||
|
type Target = str;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
match self {
|
||||||
|
Self::Borrowed(b) => b,
|
||||||
|
Self::Shared(s) => s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PartialEq for Str<'a> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
**self == **other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Eq for Str<'a> {}
|
||||||
|
|
||||||
|
impl<'a> Hash for Str<'a> {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
(**self).hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Display for Str<'a> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Str::Borrowed(b) => *b,
|
||||||
|
Str::Shared(s) => s,
|
||||||
|
}
|
||||||
|
.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod interpreter;
|
||||||
pub mod repl;
|
pub mod repl;
|
||||||
pub mod syntax;
|
pub mod syntax;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{error::Error, syntax::parser::parse};
|
use crate::{error::Error, interpreter::value::Value, syntax::parser::parse};
|
||||||
use rustyline::{error::ReadlineError, Editor};
|
use rustyline::{error::ReadlineError, Editor};
|
||||||
|
|
||||||
pub fn repl() -> rustyline::Result<()> {
|
pub fn repl() -> rustyline::Result<()> {
|
||||||
|
@ -7,7 +7,9 @@ pub fn repl() -> rustyline::Result<()> {
|
||||||
loop {
|
loop {
|
||||||
match rl.readline(prompt) {
|
match rl.readline(prompt) {
|
||||||
Ok(line) => match parse(&line) {
|
Ok(line) => match parse(&line) {
|
||||||
Ok(values) => values.iter().for_each(|e| println!("{e}")),
|
Ok(values) => values
|
||||||
|
.into_iter()
|
||||||
|
.for_each(|e| println!("{}", Into::<Value>::into(e))),
|
||||||
Err(e) => e.into_iter().map(Error::Parse).for_each(|e| {
|
Err(e) => e.into_iter().map(Error::Parse).for_each(|e| {
|
||||||
if let Err(e) = e.report(&line) {
|
if let Err(e) = e.report(&line) {
|
||||||
eprintln!("Failed to generate error report\n{e:?}")
|
eprintln!("Failed to generate error report\n{e:?}")
|
||||||
|
|
|
@ -31,6 +31,8 @@ impl<T: PartialEq> PartialEq for Spanned<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Eq> Eq for Spanned<T> {}
|
||||||
|
|
||||||
impl<T: Hash> Hash for Spanned<T> {
|
impl<T: Hash> Hash for Spanned<T> {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.item.hash(state);
|
self.item.hash(state);
|
||||||
|
@ -38,7 +40,7 @@ impl<T: Hash> Hash for Spanned<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A Wisp AST
|
/// A Wisp AST
|
||||||
#[derive(Debug, Clone, Hash, PartialEq)]
|
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||||
pub enum Expr<'s> {
|
pub enum Expr<'s> {
|
||||||
List(Vec<Spanned<Self>>),
|
List(Vec<Spanned<Self>>),
|
||||||
Vector(Vec<Spanned<Self>>),
|
Vector(Vec<Spanned<Self>>),
|
||||||
|
|
|
@ -33,24 +33,22 @@ fn parser<'s>() -> impl Parser<Token<'s>, Vec<Spanned<Expr<'s>>>, Error = Simple
|
||||||
let list = list.map(Expr::List);
|
let list = list.map(Expr::List);
|
||||||
|
|
||||||
let quote = just(Token::Quote)
|
let quote = just(Token::Quote)
|
||||||
.ignore_then(expr)
|
.ignore_then(expr.clone())
|
||||||
.map(Box::new)
|
.map(Box::new)
|
||||||
.map(Expr::Quote);
|
.map(Expr::Quote);
|
||||||
|
|
||||||
let without_pair = atom
|
let pair = expr
|
||||||
|
.clone()
|
||||||
|
.then_ignore(just(Token::Dot))
|
||||||
|
.then(expr)
|
||||||
|
.delimited_by(just(Token::LeftParen), just(Token::RightParen))
|
||||||
|
.map(|(l, r)| Expr::Pair((Box::new(l), Box::new(r))));
|
||||||
|
|
||||||
|
atom.or(pair)
|
||||||
.or(list)
|
.or(list)
|
||||||
.or(vector)
|
.or(vector)
|
||||||
.or(quote)
|
.or(quote)
|
||||||
.map_with_span(Spanned::new);
|
.map_with_span(Spanned::new)
|
||||||
|
|
||||||
let pair = without_pair
|
|
||||||
.clone()
|
|
||||||
.then_ignore(just(Token::Dot))
|
|
||||||
.then(without_pair.clone())
|
|
||||||
.map(|(l, r)| Expr::Pair((Box::new(l), Box::new(r))))
|
|
||||||
.map_with_span(Spanned::new);
|
|
||||||
|
|
||||||
pair.or(without_pair)
|
|
||||||
})
|
})
|
||||||
.repeated()
|
.repeated()
|
||||||
.then_ignore(end())
|
.then_ignore(end())
|
||||||
|
|
Loading…
Reference in a new issue