Add parser, vm, objects

Big ol thing. You should check it out sometime

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2022-01-07 20:30:55 -08:00
parent 946a927b09
commit 9e20dcf59c
17 changed files with 712 additions and 84 deletions

1
examples/hello.sy Normal file
View File

@@ -0,0 +1 @@
"Hell world" .

0
src/compile.rs Normal file
View File

View File

@@ -1,15 +1,24 @@
mod compile;
mod object; mod object;
mod syn; mod syn;
mod vm;
use std::io::Read; use std::io::Read;
use std::path::PathBuf; use std::path::PathBuf;
use structopt::StructOpt; use structopt::StructOpt;
use syn::lexer::Lexer; use syn::parser::Parser;
use vm::machine::MachineBuilder;
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
struct Opt { struct Opt {
#[structopt(name = "PATH", parse(from_os_str))] #[structopt(name = "PATH", parse(from_os_str))]
path: Option<PathBuf>, path: Option<PathBuf>,
#[structopt(long)]
max_stack_size: Option<usize>,
#[structopt(long)]
max_arena_objects: Option<usize>,
} }
type Result<T = (), E = Box<dyn std::error::Error>> = std::result::Result<T, E>; type Result<T = (), E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
@@ -25,10 +34,16 @@ fn main() -> Result {
input input
}; };
let mut lexer = Lexer::new(&text); let mut parser = Parser::from(text.as_str());
while let Some(token) = lexer.next()? { let mut exprs = Vec::new();
println!("{:?}", token); while !parser.is_eof() {
exprs.extend(parser.next_expr_list()?);
} }
let machine = MachineBuilder::default()
.max_stack_size(opt.max_stack_size)
.max_arena_objects(opt.max_arena_objects)
.finish();
Ok(()) Ok(())
} }

View File

@@ -1,36 +1,58 @@
// TODO - remove this at some point.
// I haven't gotten around to testing or working with this API yet and I don't
// want it to clog the warnings yet.
#![allow(dead_code)]
use crate::syn::ast::SpExpr;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::{BTreeMap, HashMap, VecDeque}; use std::collections::{BTreeMap, HashMap};
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
pub type Str = String; pub type Str = String;
pub type Int = i64; pub type Int = i64;
pub type Float = f64; pub type Float = f64;
pub type VTable = HashMap<String, Value>;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Value { pub enum Value {
Array(Vec<Value>), Array(Vec<Value>),
Float(Float), Float(Float),
Int(Int), Int(Int),
Str(String), Str(Str),
Obj(ObjPtr), Quote(Vec<SpExpr>),
ObjPtr(ObjPtr),
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ObjPtr { pub struct ObjPtr {
arena: Weak<Arena>, arena: Weak<RefCell<Arena>>,
slot: usize, slot: usize,
} }
#[derive(Debug)] #[derive(Debug, Default)]
pub struct Obj { pub struct Obj {
vtable: HashMap<String, Value>, vtable: VTable,
}
impl Obj {
pub fn new(vtable: VTable) -> Self {
Self { vtable }
}
pub fn vtable(&self) -> &VTable {
&self.vtable
}
pub fn vtable_mut(&mut self) -> &mut VTable {
&mut self.vtable
}
} }
#[derive(Debug)] #[derive(Debug)]
pub struct Arena { pub struct Arena {
slots: Vec<SlotRange>, slots: Vec<SlotRange>,
slots_dirty: bool, slots_dirty: bool,
objects: BTreeMap<usize, RefCell<Obj>>, objects: BTreeMap<usize, Obj>,
max_size: Option<usize>, max_size: Option<usize>,
} }
@@ -76,19 +98,26 @@ impl Arena {
} }
} }
self.slots = slots; self.slots = slots;
// Make sure to indicate that slots available are not dirty.
self.slots_dirty = false;
} }
pub fn obj_new(self: &mut Rc<Self>, obj: Obj) -> Option<ObjPtr> { /// Creates a shared pointer for an Obj value, using the next available slot.
///
/// If no slot is available, None is returned.
pub fn alloc_obj(self_rc: &Rc<RefCell<Self>>, obj: Obj) -> Option<ObjPtr> {
use SlotRange::*; use SlotRange::*;
let mut self_mut = self_rc
.try_borrow_mut()
.expect("could not get arena mutably from Rc pointer");
// Compress if necessary // Compress if necessary
if !self.slots_dirty { if self_mut.slots_dirty {
let self_mut = Rc::get_mut(self).expect("could not get arena mutably from Rc pointer");
self_mut.compress_slots(); self_mut.compress_slots();
} }
if self if self_mut
.max_size .max_size
.map(|max_size| self.objects.len() >= max_size) .map(|max_size| self_mut.objects.len() >= max_size)
.unwrap_or(false) .unwrap_or(false)
{ {
// TODO : return err instead of option // TODO : return err instead of option
@@ -97,7 +126,6 @@ impl Arena {
} }
// Get the next slot // Get the next slot
let self_mut = Rc::get_mut(self).expect("could not get arena mutably from Rc pointer");
let slots = &mut self_mut.slots; let slots = &mut self_mut.slots;
let slot = match slots.first().copied().unwrap() { let slot = match slots.first().copied().unwrap() {
Range(start, end) => { Range(start, end) => {
@@ -113,68 +141,42 @@ impl Arena {
index index
} }
}; };
let previous = self_mut.objects.insert(slot, obj);
assert!(
previous.is_none(),
"slot {} was allocated but is already in use",
slot
);
Some(ObjPtr { Some(ObjPtr {
arena: Rc::downgrade(self), arena: Rc::downgrade(self_rc),
slot, slot,
}) })
} }
pub fn free_obj(&mut self, obj_ptr: ObjPtr) { pub fn free_obj(&mut self, obj_ptr: ObjPtr) {
// Compress if necessary // Compress if necessary
if !self.slots_dirty { if self.slots_dirty {
self.compress_slots(); self.compress_slots();
} }
let value = self.objects.remove(&obj_ptr.slot);
assert!(
value.is_some(),
"attempted to free object that was not tracked (slot {})",
obj_ptr.slot
);
self.slots self.slots
.push(SlotRange::Range(obj_ptr.slot, obj_ptr.slot)); .push(SlotRange::Range(obj_ptr.slot, obj_ptr.slot));
self.slots_dirty = true; // not my problem self.slots_dirty = true; // not my problem
} }
} }
#[test]
fn test_arena_compress_slots() {
use SlotRange::*;
let tests = [
(vec![Range(0, 4), Range(2, 6), Open(0)], vec![Open(0)]),
(vec![Open(7), Range(0, 4), Range(2, 6)], vec![Open(0)]),
(
vec![Open(8), Range(0, 4), Range(2, 6)],
vec![Range(0, 6), Open(8)],
),
(vec![Range(0, 4), Range(2, 6)], vec![Range(0, 6)]),
(vec![Range(0, 4), Range(2, 6)], vec![Range(0, 6)]),
(
vec![Range(0, 1), Range(2, 2), Range(3, 4)],
vec![Range(0, 4)],
),
(
vec![Range(0, 4), Range(3, 4), Range(2, 2)],
vec![Range(0, 4)],
),
(
vec![Range(3, 6), Range(0, 4), Range(2, 2)],
vec![Range(0, 6)],
),
(vec![Range(0, 6), Range(6, 6)], vec![Range(0, 6)]),
(vec![Range(7, 6), Range(6, 6)], vec![Range(6, 6)]),
];
for (slots, expected) in tests {
let mut arena = Arena {
slots,
slots_dirty: true,
objects: Default::default(),
max_size: None,
};
arena.compress_slots();
assert_eq!(arena.slots, expected);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SlotRange { pub enum SlotRange {
/// A list of slots that are in a range, inclusive. /// A list of slots that are in a range, exclusive.
///
/// If the range start and end are equal, then it is a single opening.
Range(usize, usize), Range(usize, usize),
/// Everything from here and onward is open /// Everything from here and onward is open.
Open(usize), Open(usize),
} }
@@ -243,3 +245,85 @@ fn test_slot_range_merge() {
); );
} }
} }
#[test]
fn test_arena_compress_slots() {
use SlotRange::*;
let tests = [
(vec![Range(0, 4), Range(2, 6), Open(0)], vec![Open(0)]),
(vec![Open(7), Range(0, 4), Range(2, 6)], vec![Open(0)]),
(
vec![Open(8), Range(0, 4), Range(2, 6)],
vec![Range(0, 6), Open(8)],
),
(vec![Range(0, 4), Range(2, 6)], vec![Range(0, 6)]),
(vec![Range(0, 4), Range(2, 6)], vec![Range(0, 6)]),
(
vec![Range(0, 1), Range(2, 2), Range(3, 4)],
vec![Range(0, 4)],
),
(
vec![Range(0, 4), Range(3, 4), Range(2, 2)],
vec![Range(0, 4)],
),
(
vec![Range(3, 6), Range(0, 4), Range(2, 2)],
vec![Range(0, 6)],
),
(vec![Range(0, 6), Range(6, 6)], vec![Range(0, 6)]),
(vec![Range(7, 6), Range(6, 6)], vec![Range(6, 6)]),
(
vec![Range(0, 0), Range(1, 1), Range(2, 2)],
vec![Range(0, 2)],
),
(
vec![Range(0, 0), Range(1, 1), Range(2, 2), Open(3)],
vec![Open(0)],
),
];
for (slots, expected) in tests {
let mut arena = Arena {
slots,
slots_dirty: true,
objects: Default::default(),
max_size: None,
};
arena.compress_slots();
assert_eq!(arena.slots, expected);
}
}
#[test]
fn test_arena_obj_lifetime() {
let arena = Rc::new(RefCell::new(Arena::new(None)));
let p1 = Arena::alloc_obj(&arena, Obj::default()).unwrap();
assert_eq!(arena.borrow().slots, vec![SlotRange::Open(1)]);
let p2 = Arena::alloc_obj(&arena, Obj::default()).unwrap();
assert_eq!(arena.borrow().slots, vec![SlotRange::Open(2)]);
let p3 = Arena::alloc_obj(&arena, Obj::default()).unwrap();
assert_eq!(arena.borrow().slots, vec![SlotRange::Open(3)]);
{
let mut arena_mut = arena.borrow_mut();
arena_mut.free_obj(p2);
arena_mut.compress_slots();
assert_eq!(
arena_mut.slots,
vec![SlotRange::Range(1, 1), SlotRange::Open(3)]
);
}
let p4 = Arena::alloc_obj(&arena, Obj::default()).unwrap();
assert_eq!(arena.borrow().slots, vec![SlotRange::Open(3)]);
{
let mut arena_mut = arena.borrow_mut();
arena_mut.free_obj(p1);
arena_mut.free_obj(p3);
arena_mut.free_obj(p4);
arena_mut.compress_slots();
assert_eq!(arena_mut.slots, vec![SlotRange::Open(0)],);
}
}

19
src/syn/ast.rs Normal file
View File

@@ -0,0 +1,19 @@
use crate::object::{Float, Int, Str, Value};
use crate::syn::span::*;
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
Atom(SpAtom),
Quote(Vec<SpExpr>),
}
#[derive(Debug, Clone, PartialEq)]
pub enum Atom {
Word(Str),
Float(Float),
Int(Int),
Str(Str),
}
pub type SpAtom = Spanned<Atom>;
pub type SpExpr = Spanned<Expr>;

View File

@@ -1,10 +1,9 @@
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug)] #[derive(Error, Debug, Clone)]
pub enum SyntaxError { pub enum SyntaxError {
#[error("unexpected {0}")] //#[error("unexpected {0}")]
Unexpected(String), //Unexpected(String),
#[error("expected {expected}, but got {got}")] #[error("expected {expected}, but got {got}")]
ExpectedGot { expected: String, got: String }, ExpectedGot { expected: String, got: String },
} }

View File

@@ -1,4 +1,4 @@
use crate::syn::{error::*, span::*, token::*, util::*}; use crate::syn::{error::*, span::*, token::*};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use regex::{Regex, RegexBuilder}; use regex::{Regex, RegexBuilder};
@@ -11,6 +11,7 @@ lazy_static! {
| (?P<lquote>\[) | (?P<lquote>\[)
| (?P<rquote>\]) | (?P<rquote>\])
| (?P<colon>:) | (?P<colon>:)
| (?P<str>"([^"\\]|\\["'\\ntrb])*")
)"# )"#
) )
.ignore_whitespace(true) .ignore_whitespace(true)
@@ -19,6 +20,7 @@ lazy_static! {
} }
/// Lexes things. /// Lexes things.
#[derive(Debug, Clone)]
pub struct Lexer<'t> { pub struct Lexer<'t> {
text: &'t str, text: &'t str,
start: Pos, start: Pos,
@@ -113,6 +115,10 @@ impl<'t> Lexer<'t> {
} }
} }
fn expected_got_char(c: char) -> String {
format!("character {}", c.escape_debug())
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
@@ -200,7 +206,7 @@ mod test {
#[test] #[test]
fn test_colon() { fn test_colon() {
let mut lexer = Lexer::new(": :: ::: ::::"); let mut lexer = Lexer::new(": :: ::: :::: some-name:");
assert_token!(lexer, Token::Colon); assert_token!(lexer, Token::Colon);
assert_token!(lexer, Token::Colon); assert_token!(lexer, Token::Colon);
assert_token!(lexer, Token::Colon); assert_token!(lexer, Token::Colon);
@@ -211,6 +217,8 @@ mod test {
assert_token!(lexer, Token::Colon); assert_token!(lexer, Token::Colon);
assert_token!(lexer, Token::Colon); assert_token!(lexer, Token::Colon);
assert_token!(lexer, Token::Colon); assert_token!(lexer, Token::Colon);
assert_token!(lexer, Token::Word);
assert_token!(lexer, Token::Colon);
assert!(lexer.is_eof()); assert!(lexer.is_eof());
} }
} }

View File

@@ -1,6 +1,6 @@
pub mod ast;
pub mod error; pub mod error;
pub mod lexer; pub mod lexer;
pub mod parser; pub mod parser;
pub mod span; pub mod span;
pub mod token; pub mod token;
pub mod util;

View File

@@ -1,5 +1,307 @@
use crate::syn::lexer::*; use crate::syn::{ast::*, error::*, lexer::*, token::*};
#[derive(Debug)]
pub struct Parser<'t> { pub struct Parser<'t> {
lexer: Lexer<'t>, lexer: Lexer<'t>,
token: Result<Option<SpToken>>,
}
impl<'t> From<&'t str> for Parser<'t> {
fn from(text: &'t str) -> Self {
Parser::new(Lexer::new(text))
}
}
impl<'t> Parser<'t> {
pub fn new(mut lexer: Lexer<'t>) -> Self {
let token = lexer.next();
Self { lexer, token }
}
pub fn is_eof(&self) -> bool {
self.lexer.is_eof()
}
fn peek(&self) -> Result<Option<SpToken>> {
self.token.clone()
}
fn adv(&mut self) -> Result<Option<SpToken>> {
let next = self.lexer.next();
std::mem::replace(&mut self.token, next)
}
/// Checks if the next token is part of the list, returning it if so.
fn expect_any_token(&mut self, expected: &[Token]) -> Result<SpToken> {
let token = self.peek()?;
match (token, expected) {
// Token matches
(Some(token), expected) if expected.contains(token.inner()) => {
self.adv()?;
Ok(token)
}
// Token does not match, only one token expected
(token, &[expected]) => {
// get the string version of whether this is a token or EOF
let got = token
.map(|t| format!("{} token", t.inner().name()))
.unwrap_or_else(|| "EOF".to_string());
Err(SyntaxError::ExpectedGot {
expected: format!("{} token", expected.name()),
got,
})
}
// Token does not match, any of N tokens expected
(token, expected) => {
// make the comma-separated list of everything except for the last item
let expected_str = expected
.iter()
.take(expected.len() - 1)
.map(Token::name)
.collect::<Vec<_>>()
.join(", ");
// get the string version of whether this is a token or EOF
let got = token
.map(|t| format!("{} token", t.inner().name()))
.unwrap_or_else(|| "EOF".to_string());
Err(SyntaxError::ExpectedGot {
expected: format!(
"{} or {} token",
expected_str,
expected.last().unwrap().name()
),
got,
})
}
}
}
/// Gets all expressions until EOF is reached, or until a quote end is reached.
pub fn next_expr_list(&mut self) -> Result<Vec<SpExpr>> {
let mut exprs = Vec::new();
while let Some(peek) = self.peek()? {
match peek.inner() {
Token::RQuote => break,
_ => exprs.push(self.next_expr()?),
}
}
Ok(exprs)
}
pub fn next_expr(&mut self) -> Result<SpExpr> {
// peek ahead and see if we need to handle a quote
match self.peek()? {
Some(peek) if *peek.inner() == Token::LQuote => self.next_quote(),
_ => {
let atom = self.next_atom()?;
let span = atom.span();
Ok(SpExpr::new(span, Expr::Atom(atom)))
}
}
}
pub fn next_quote(&mut self) -> Result<SpExpr> {
let start = self.expect_any_token(&[Token::LQuote])?;
let exprs = self.next_expr_list()?;
let end = self.expect_any_token(&[Token::RQuote])?;
let span = start.span().union(end.span());
Ok(SpExpr::new(span, Expr::Quote(exprs)))
}
pub fn next_atom(&mut self) -> Result<SpAtom> {
use Token::*;
let token = self.expect_any_token(&[Word, Float, Int, Str])?;
Ok(self.token_to_atom(token))
}
fn token_to_atom(&self, token: SpToken) -> SpAtom {
// NOTE - self is required for this because we get the text
let (span, token) = token.into_split();
let text = span.text_at(self.lexer.text());
let atom = match token {
Token::Word => Atom::Word(text.to_string()),
Token::Float => Atom::Float(text.parse().unwrap()),
Token::Int => Atom::Int(text.parse().unwrap()),
Token::Str => Atom::Str(unescape_string(text)),
_ => panic!("invalid token specified for token_to_atom, it should be an atom"),
};
SpAtom::new(span, atom)
}
}
fn unescape_string(text: &str) -> String {
let mut string = String::with_capacity(text.len() - 2);
let mut chars = text.chars().skip(1).take(text.len() - 2);
while let Some(c) = chars.next() {
if c == '\\' {
let c = match chars
.next()
.expect("reached end of string literal before escape")
{
'"' => '"',
'\'' => '\'',
'\\' => '\\',
'n' => '\n',
'r' => '\r',
't' => '\t',
u => panic!(
"unexpected character escape that made it through the lexer: {:?}",
u
),
};
string.push(c);
} else {
string.push(c);
}
}
string
}
#[cfg(test)]
macro_rules! expect_atom {
($parser:expr, $expected:expr) => {{
let expr_result = $parser.next_expr();
assert!(
expr_result.is_ok(),
"expected {:?} but got {:?} instead",
$expected,
expr_result
);
let expr = expr_result.unwrap();
let (span, expr) = expr.into_split();
assert_eq!(expr, Expr::Atom(SpAtom::new(span, $expected)));
}};
}
#[cfg(test)]
macro_rules! expect_expr {
($parser:expr, $expected:expr) => {{
let expr_result = $parser.next_expr();
assert!(
expr_result.is_ok(),
"expected {:?} but got {:?} instead",
$expected,
expr_result
);
let expr = expr_result.unwrap();
assert_eq!(expr, $expected);
}};
}
/// Makes an SpExpr Quote value using the given SpExpr values
#[cfg(test)]
macro_rules! make_quote {
($($expr:expr),+ $(,)?) => {{
SpExpr::new(
Default::default(),
Expr::Quote(make_quote_vec!($($expr),+))
)
}};
}
/// Makes a vec appropriate for an Expr::Quote
#[cfg(test)]
macro_rules! make_quote_vec {
($($expr:expr),+ $(,)?) => {{
vec![$(
SpExpr::new(Default::default(), $expr)
),+]
}};
}
/// Makes an SpAtom from an Atom type.
#[cfg(test)]
macro_rules! make_atom {
($atom:expr) => {{
SpAtom::new(Default::default(), $atom)
}};
}
#[test]
fn test_parser_atoms() {
let mut parser = Parser::from(
r#"
a ab bcd dcefg foo bar baz
1 2 3 4 5
1.2 3.4 5.6 7.8 9.10
"this is a string"
"this\nis\na\nstring\nwith\nnewlines"
"this\tis\ta\tstring\twith\ttabs"
"#,
);
expect_atom!(parser, Atom::Word("a".to_string()));
expect_atom!(parser, Atom::Word("ab".to_string()));
expect_atom!(parser, Atom::Word("bcd".to_string()));
expect_atom!(parser, Atom::Word("dcefg".to_string()));
expect_atom!(parser, Atom::Word("foo".to_string()));
expect_atom!(parser, Atom::Word("bar".to_string()));
expect_atom!(parser, Atom::Word("baz".to_string()));
expect_atom!(parser, Atom::Int(1));
expect_atom!(parser, Atom::Int(2));
expect_atom!(parser, Atom::Int(3));
expect_atom!(parser, Atom::Int(4));
expect_atom!(parser, Atom::Int(5));
expect_atom!(parser, Atom::Float(1.2));
expect_atom!(parser, Atom::Float(3.4));
expect_atom!(parser, Atom::Float(5.6));
expect_atom!(parser, Atom::Float(7.8));
expect_atom!(parser, Atom::Float(9.1));
expect_atom!(parser, Atom::Str("this is a string".to_string()));
expect_atom!(
parser,
Atom::Str("this\nis\na\nstring\nwith\nnewlines".to_string())
);
expect_atom!(
parser,
Atom::Str("this\tis\ta\tstring\twith\ttabs".to_string())
);
assert!(parser.is_eof());
}
#[test]
fn test_parser_quotes() {
let mut parser = Parser::from(
r#"
[
a ab bcd dcefg foo bar baz
]
[1 2 3 4 5
[1.2 3.4 5.6 7.8 9.10]
]
"#,
);
expect_expr!(
parser,
make_quote![
Expr::Atom(make_atom!(Atom::Word("a".to_string()))),
Expr::Atom(make_atom!(Atom::Word("ab".to_string()))),
Expr::Atom(make_atom!(Atom::Word("bcd".to_string()))),
Expr::Atom(make_atom!(Atom::Word("dcefg".to_string()))),
Expr::Atom(make_atom!(Atom::Word("foo".to_string()))),
Expr::Atom(make_atom!(Atom::Word("bar".to_string()))),
Expr::Atom(make_atom!(Atom::Word("baz".to_string()))),
]
);
expect_expr!(
parser,
make_quote![
Expr::Atom(make_atom!(Atom::Int(1))),
Expr::Atom(make_atom!(Atom::Int(2))),
Expr::Atom(make_atom!(Atom::Int(3))),
Expr::Atom(make_atom!(Atom::Int(4))),
Expr::Atom(make_atom!(Atom::Int(5))),
Expr::Quote(make_quote_vec![
Expr::Atom(make_atom!(Atom::Float(1.2))),
Expr::Atom(make_atom!(Atom::Float(3.4))),
Expr::Atom(make_atom!(Atom::Float(5.6))),
Expr::Atom(make_atom!(Atom::Float(7.8))),
Expr::Atom(make_atom!(Atom::Float(9.10))),
]),
]
);
} }

View File

@@ -1,6 +1,13 @@
use std::cmp::{Ord, Ordering, PartialOrd}; // TODO - remove this at some point.
// I'm happy with this API design and I don't think that it should be clogging
// up the warning lists because I'm not using a logical part of the API *at the moment*.
#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)] use std::cmp::{Ord, Ordering, PartialOrd};
use std::fmt::{self, Debug};
#[cfg_attr(not(test), derive(PartialEq))]
#[derive(Debug, Default, Clone, Copy, Eq)]
pub struct Pos { pub struct Pos {
pub source: usize, pub source: usize,
pub line: usize, pub line: usize,
@@ -9,6 +16,14 @@ pub struct Pos {
pub c: char, pub c: char,
} }
// when testing, don't actually compare positions
#[cfg(test)]
impl PartialEq for Pos {
fn eq(&self, _other: &Pos) -> bool {
true
}
}
impl Pos { impl Pos {
pub fn new(c: char) -> Self { pub fn new(c: char) -> Self {
Pos { Pos {
@@ -56,7 +71,7 @@ impl Ord for Pos {
} }
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct Span { pub struct Span {
pub start: Pos, pub start: Pos,
pub end: Pos, pub end: Pos,
@@ -66,9 +81,15 @@ impl Span {
pub fn text_at<'t>(&self, text: &'t str) -> &'t str { pub fn text_at<'t>(&self, text: &'t str) -> &'t str {
&text[self.start.byte..self.end.byte] &text[self.start.byte..self.end.byte]
} }
pub fn union(&self, other: Span) -> Self {
let start = self.start.min(other.start);
let end = self.end.max(other.end);
Span { start, end }
}
} }
#[derive(Debug, Clone)] #[derive(Clone, PartialEq, Eq)]
pub struct Spanned<T> { pub struct Spanned<T> {
span: Span, span: Span,
inner: T, inner: T,
@@ -107,3 +128,9 @@ impl<T> Spanned<T> {
self.span().text_at(text) self.span().text_at(text)
} }
} }
impl<T: Debug> Debug for Spanned<T> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
Debug::fmt(self.inner(), fmt)
}
}

View File

@@ -25,5 +25,20 @@ pub enum Token {
Colon, Colon,
} }
impl Token {
pub fn name(&self) -> &'static str {
use Token::*;
match self {
Word => "word",
Float => "float",
Int => "int",
Str => "str",
LQuote => "quote begin",
RQuote => "quote end",
Colon => "colon",
}
}
}
/// Spanned token. /// Spanned token.
pub type SpToken = Spanned<Token>; pub type SpToken = Spanned<Token>;

View File

@@ -1,11 +0,0 @@
pub fn got_char_or_eof(got: Option<char>) -> String {
if let Some(got) = got {
expected_got_char(got)
} else {
"EOF".to_string()
}
}
pub fn expected_got_char(c: char) -> String {
format!("character {}", c.escape_debug())
}

36
src/vm/error.rs Normal file
View File

@@ -0,0 +1,36 @@
use thiserror::Error;
#[derive(Error, Debug, Clone)]
pub enum RuntimeError {
#[error("stack overflow")]
StackOverflow,
#[error("stack underflow")]
StackUnderflow,
//#[error("unexpected {0}")]
//Unexpected(String),
//#[error("expected {expected}, but got {got}")]
//ExpectedGot { expected: String, got: String },
}
pub type Result<T, E = RuntimeError> = std::result::Result<T, E>;
// TODO
// Error building idea:
// pass RuntimeError values upward, but also keep a list of spans/locations
// added by functions adding errors upward.
// e.g.
/*
fn do_thing() -> Result<()> {
...
let result = do_fallible_thing();
if let Err(e) = result {
e.add_location(context.file, context.span);
return Err(e);
}
...
}
*/

49
src/vm/eval.rs Normal file
View File

@@ -0,0 +1,49 @@
use crate::object::*;
use crate::syn::ast::*;
use crate::vm::{error::*, inst::Inst, machine::Machine};
/// An evaluation context for the VM.
pub struct Eval<'m> {
machine: &'m mut Machine,
}
impl<'m> Eval<'m> {
pub fn new(machine: &'m mut Machine) -> Self {
Self { machine }
}
pub fn eval_expr(&mut self, expr: &SpExpr) -> Result<()> {
match expr.inner() {
Expr::Atom(atom) => match atom.inner() {
Atom::Float(f) => self.machine.stack_push(Value::Float(*f))?,
Atom::Int(i) => self.machine.stack_push(Value::Int(*i))?,
Atom::Str(s) => self.machine.stack_push(Value::Str(s.clone()))?,
Atom::Word(_) => todo!("TODO - word lookup"),
},
Expr::Quote(quote) => {
self.machine.stack_push(Value::Quote(quote.clone()))?;
}
}
Ok(())
}
pub fn eval_inst(&mut self, inst: Inst) -> Result<()> {
match inst {
Inst::Push(v) => self.machine.stack_push(v)?,
Inst::Pop => {
self.machine
.stack_pop()
.ok_or(RuntimeError::StackUnderflow)?;
}
Inst::When => todo!(),
Inst::If => todo!(),
Inst::Eval => todo!(),
Inst::Print => todo!(),
}
Ok(())
}
}
// IDEA: Eval chain
// chain of eval-able things, including macros. An eval can add something to the
// top of the eval chain and immediately jump to it, such as an include

11
src/vm/inst.rs Normal file
View File

@@ -0,0 +1,11 @@
use crate::object::Value;
#[derive(Debug, Clone)]
pub enum Inst {
Push(Value),
Pop,
When,
If,
Eval,
Print,
}

69
src/vm/machine.rs Normal file
View File

@@ -0,0 +1,69 @@
use crate::object::*;
use crate::vm::error::*;
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Default)]
pub struct MachineBuilder {
max_stack_size: Option<usize>,
max_arena_objects: Option<usize>,
}
impl MachineBuilder {
pub fn max_stack_size(mut self, max_stack_size: Option<usize>) -> Self {
self.max_stack_size = max_stack_size;
self
}
pub fn max_arena_objects(mut self, max_arena_objects: Option<usize>) -> Self {
self.max_arena_objects = max_arena_objects;
self
}
pub fn finish(self) -> Machine {
Machine::new(self.max_stack_size, Arena::new(self.max_arena_objects))
}
}
/// The current state of a VM.
#[derive(Debug)]
pub struct Machine {
stack: Vec<Value>,
max_stack_size: Option<usize>,
arena: Rc<RefCell<Arena>>,
}
impl Machine {
pub fn new(max_stack_size: Option<usize>, arena: Arena) -> Self {
Machine {
stack: Default::default(),
max_stack_size,
arena: Rc::new(RefCell::new(arena)),
}
}
pub fn max_stack_size(&self) -> Option<usize> {
self.max_stack_size
}
pub fn stack(&self) -> &Vec<Value> {
&self.stack
}
pub fn stack_mut(&mut self) -> &mut Vec<Value> {
&mut self.stack
}
pub fn stack_push(&mut self, value: Value) -> Result<()> {
if let Some(max) = self.max_stack_size() {
if self.stack.len() >= max {
return Err(RuntimeError::StackOverflow);
}
}
self.stack_mut().push(value);
Ok(())
}
pub fn stack_pop(&mut self) -> Option<Value> {
self.stack_mut().pop()
}
}

4
src/vm/mod.rs Normal file
View File

@@ -0,0 +1,4 @@
pub mod error;
pub mod eval;
pub mod inst;
pub mod machine;