Compare commits

...

19 Commits

Author SHA1 Message Date
ondra05 a0cd6879eb rename 2022-09-19 20:27:16 +02:00
ondra05 966f610040 basic functions support 2022-08-06 23:30:37 +02:00
ondra05 f1f01ad2a5 added basic VM 2022-08-06 23:20:51 +02:00
ondra05 4a28a9c5ea changes 2022-08-06 22:23:57 +02:00
ondra05 a2d32970fb We do a little... whatever... value printing. 2022-08-06 21:58:15 +02:00
ondra05 bfa685c3af various stuff 2022-08-06 21:47:06 +02:00
ondra05 5d51383775 simplified syntax rules 2022-08-06 21:28:12 +02:00
ondra05 b51b60fdb7 dot-pair pretty-print 2022-08-06 21:25:47 +02:00
ondra05 3c86118f6e nil 2022-08-06 01:01:23 +02:00
ondra05 3b03b783bd basic formatting 2022-08-06 00:49:25 +02:00
ondra05 5ee68efcb4 nothing beter to do, added docs for string type 2022-08-06 00:21:47 +02:00
ondra05 6ae38b1d63 added some conversions 2022-08-06 00:17:47 +02:00
ondra05 d432356215 thiserror 2022-08-06 00:03:52 +02:00
ondra05 a6269c454c Added dot-pair type 2022-08-05 23:59:13 +02:00
ondra05 1bd2fc3f93 fmt 2022-08-05 23:53:13 +02:00
ondra05 ed4bd6a26b todo 2022-08-05 23:52:56 +02:00
ondra05 24a701dba5 added basic datatypes 2022-08-05 23:41:09 +02:00
ondra05 6b3368069a why not? 2022-08-05 21:31:22 +02:00
ondra05 0c6424a663 added interpreter mod 2022-08-05 21:20:37 +02:00
14 changed files with 464 additions and 31 deletions

9
Cargo.lock generated
View File

@ -1416,18 +1416,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.31"
version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.31"
version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21"
dependencies = [
"proc-macro2",
"quote",
@ -1738,6 +1738,7 @@ dependencies = [
"logos",
"ordered-float",
"rustyline",
"thiserror",
]
[[package]]

View File

@ -12,3 +12,4 @@ eframe = "*"
logos = "*"
ordered-float = "3.0"
rustyline = "10.0"
thiserror = "1.0"

View File

@ -1,10 +1,11 @@
use crate::syntax::lexer::Token;
use ariadne::{Color, Fmt, Label, Report, ReportKind, Source};
use chumsky::error::{Simple, SimpleReason};
use std::fmt::Display;
use thiserror::Error;
#[derive(Debug, Clone)]
#[derive(Debug, Error, Clone)]
pub enum Error<'a> {
#[error("parse error: {0:?}")]
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> {}

View 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
View 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
View 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;

View 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> {}

View 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)
}

View 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;

View 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)
}
}

View File

@ -1,3 +1,4 @@
pub mod error;
pub mod interpreter;
pub mod repl;
pub mod syntax;

View File

@ -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};
pub fn repl() -> rustyline::Result<()> {
@ -7,7 +7,9 @@ pub fn repl() -> rustyline::Result<()> {
loop {
match rl.readline(prompt) {
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| {
if let Err(e) = e.report(&line) {
eprintln!("Failed to generate error report\n{e:?}")

View File

@ -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> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.item.hash(state);
@ -38,7 +40,7 @@ impl<T: Hash> Hash for Spanned<T> {
}
/// A Wisp AST
#[derive(Debug, Clone, Hash, PartialEq)]
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum Expr<'s> {
List(Vec<Spanned<Self>>),
Vector(Vec<Spanned<Self>>),

View File

@ -33,24 +33,22 @@ fn parser<'s>() -> impl Parser<Token<'s>, Vec<Spanned<Expr<'s>>>, Error = Simple
let list = list.map(Expr::List);
let quote = just(Token::Quote)
.ignore_then(expr)
.ignore_then(expr.clone())
.map(Box::new)
.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(vector)
.or(quote)
.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)
.map_with_span(Spanned::new)
})
.repeated()
.then_ignore(end())