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:
1
examples/hello.sy
Normal file
1
examples/hello.sy
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"Hell world" .
|
||||||
0
src/compile.rs
Normal file
0
src/compile.rs
Normal file
23
src/main.rs
23
src/main.rs
@@ -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(())
|
||||||
}
|
}
|
||||||
|
|||||||
198
src/object.rs
198
src/object.rs
@@ -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
19
src/syn/ast.rs
Normal 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>;
|
||||||
@@ -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 },
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
|
|||||||
@@ -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))),
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
|||||||
@@ -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
36
src/vm/error.rs
Normal 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
49
src/vm/eval.rs
Normal 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
11
src/vm/inst.rs
Normal 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
69
src/vm/machine.rs
Normal 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
4
src/vm/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
pub mod error;
|
||||||
|
pub mod eval;
|
||||||
|
pub mod inst;
|
||||||
|
pub mod machine;
|
||||||
Reference in New Issue
Block a user