[rlox] More error handling, less panicking
This commit is contained in:
460
rlox/src/lc.rs
460
rlox/src/lc.rs
@@ -1,11 +1,16 @@
|
|||||||
|
use std::fmt;
|
||||||
use std::iter::Peekable;
|
use std::iter::Peekable;
|
||||||
use std::ops::Range;
|
|
||||||
use std::str::CharIndices;
|
use std::str::CharIndices;
|
||||||
use std::{collections::HashMap, convert::identity};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::bc::{Chunk, Op};
|
use crate::bc::{Chunk, Op};
|
||||||
use crate::gc::allocate_string;
|
use crate::gc::allocate_string;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
enum ScanErrorKind {
|
||||||
|
UndelimitedString,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
enum TokenType {
|
enum TokenType {
|
||||||
LeftParen,
|
LeftParen,
|
||||||
@@ -50,74 +55,74 @@ enum TokenType {
|
|||||||
Var,
|
Var,
|
||||||
While,
|
While,
|
||||||
|
|
||||||
Error,
|
Error(ScanErrorKind),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Token<'src> {
|
pub struct Token<'src> {
|
||||||
ttype: TokenType,
|
ttype: TokenType,
|
||||||
span: &'src str,
|
span: &'src str,
|
||||||
|
line: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Source<'src> {
|
||||||
|
line: usize,
|
||||||
|
iter: CharIndices<'src>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'src> Source<'src> {
|
||||||
|
fn new(str: &'src str) -> Self {
|
||||||
|
Source {
|
||||||
|
line: 1,
|
||||||
|
iter: str.char_indices(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'src> Iterator for Source<'src> {
|
||||||
|
type Item = (usize, usize, char);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
match self.iter.next() {
|
||||||
|
Some((p, ch)) => {
|
||||||
|
let old_line = self.line;
|
||||||
|
if ch == '\n' {
|
||||||
|
self.line += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((old_line, p, ch))
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Scanner<'src> {
|
pub struct Scanner<'src> {
|
||||||
source: &'src str,
|
source: &'src str,
|
||||||
iter: Peekable<CharIndices<'src>>,
|
iter: Peekable<Source<'src>>,
|
||||||
line_map: LineMap,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct LineMap {
|
|
||||||
line_breaks: Vec<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LineMap {
|
|
||||||
fn new(source: &str) -> LineMap {
|
|
||||||
LineMap {
|
|
||||||
line_breaks: source
|
|
||||||
.bytes()
|
|
||||||
.enumerate()
|
|
||||||
.filter_map(|(pos, c)| if c == b'\n' { Some(pos) } else { None })
|
|
||||||
.collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pos_to_line(&self, pos: usize) -> usize {
|
|
||||||
let (Ok(index) | Err(index)) = self.line_breaks.binary_search(&pos);
|
|
||||||
index + 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'src> Scanner<'src> {
|
impl<'src> Scanner<'src> {
|
||||||
pub fn new(source: &'src str) -> Self {
|
pub fn new(source: &'src str) -> Self {
|
||||||
Scanner {
|
Scanner {
|
||||||
source,
|
source,
|
||||||
iter: source.char_indices().peekable(),
|
iter: Source::new(source).peekable(),
|
||||||
line_map: LineMap::new(source),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_token(&self, ttype: TokenType, start: usize, end: usize) -> Token<'src> {
|
fn make_token(&self, ttype: TokenType, line: usize, start: usize, end: usize) -> Token<'src> {
|
||||||
Token {
|
Token {
|
||||||
ttype,
|
ttype,
|
||||||
span: &self.source[start..=end],
|
span: &self.source[start..=end],
|
||||||
|
line,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_range(&self, token: Token) -> Option<Range<usize>> {
|
|
||||||
let [source_addr, span_addr]: [usize; 2] =
|
|
||||||
[self.source, token.span].map(|s| s.as_ptr() as usize);
|
|
||||||
if span_addr < source_addr || span_addr + token.span.len() > source_addr + self.source.len()
|
|
||||||
{
|
|
||||||
return None; // out of bounds
|
|
||||||
}
|
|
||||||
let start_index = span_addr - source_addr;
|
|
||||||
Some(start_index..start_index + token.span.len())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn consume_if<P>(&mut self, p: P) -> Option<usize>
|
fn consume_if<P>(&mut self, p: P) -> Option<usize>
|
||||||
where
|
where
|
||||||
P: Fn(char) -> bool,
|
P: Fn(char) -> bool,
|
||||||
{
|
{
|
||||||
self.iter.next_if(|&(_, c)| p(c)).map(|(p, _c)| p)
|
self.iter.next_if(|&(_, _, c)| p(c)).map(|(_l, p, _c)| p)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn consume_if_eq(&mut self, expected: char) -> Option<usize> {
|
fn consume_if_eq(&mut self, expected: char) -> Option<usize> {
|
||||||
@@ -137,24 +142,21 @@ impl<'src> Scanner<'src> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn consume_until_eq(&mut self, limit: char) -> Option<usize> {
|
fn consume_until_eq(&mut self, limit: char) -> std::result::Result<usize, usize> {
|
||||||
for (p, c) in self.iter.by_ref() {
|
for (_line, p, c) in self.iter.by_ref() {
|
||||||
if c == limit {
|
if c == limit {
|
||||||
return Some(p);
|
return Ok(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
|
||||||
|
Err(self.source.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scan_string(&mut self, start: usize) -> Token<'src> {
|
fn scan_string(&mut self) -> std::result::Result<usize, usize> {
|
||||||
let end = self.consume_until_eq('"').unwrap_or_else(|| {
|
self.consume_until_eq('"')
|
||||||
panic!("Undelimited String");
|
|
||||||
});
|
|
||||||
|
|
||||||
self.make_token(TokenType::String, start, end)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scan_number(&mut self, start: usize) -> Token<'src> {
|
fn scan_number(&mut self, start: usize) -> usize {
|
||||||
let mut end = start;
|
let mut end = start;
|
||||||
end = self
|
end = self
|
||||||
.consume_while(|c| c.is_ascii_alphanumeric())
|
.consume_while(|c| c.is_ascii_alphanumeric())
|
||||||
@@ -170,10 +172,10 @@ impl<'src> Scanner<'src> {
|
|||||||
.unwrap_or(end);
|
.unwrap_or(end);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.make_token(TokenType::Number, start, end)
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scan_identifier(&mut self, start: usize) -> Token<'src> {
|
fn scan_identifier(&mut self, line: usize, start: usize) -> Token<'src> {
|
||||||
let mut end = start;
|
let mut end = start;
|
||||||
|
|
||||||
end = self
|
end = self
|
||||||
@@ -202,11 +204,11 @@ impl<'src> Scanner<'src> {
|
|||||||
_ => TokenType::Identifier,
|
_ => TokenType::Identifier,
|
||||||
};
|
};
|
||||||
|
|
||||||
Token { ttype, span: slice }
|
Token { ttype, span: slice, line }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scan_comment(&mut self) {
|
fn scan_comment(&mut self) {
|
||||||
self.consume_until_eq('\n');
|
let _ = self.consume_until_eq('\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,21 +216,16 @@ impl<'src> Iterator for Scanner<'src> {
|
|||||||
type Item = Token<'src>;
|
type Item = Token<'src>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
// Skip Whitespace
|
self.consume_while(|ch| ch.is_ascii_whitespace());
|
||||||
while self
|
|
||||||
.iter
|
|
||||||
.next_if(|(_, b)| b.is_ascii_whitespace())
|
|
||||||
.is_some()
|
|
||||||
{}
|
|
||||||
|
|
||||||
if let Some((start_pos, start_ch)) = self.iter.next() {
|
if let Some((start_line, start_pos, start_ch)) = self.iter.next() {
|
||||||
let make_simple_token =
|
let make_simple_token =
|
||||||
|s: &Self, ttype: TokenType| Some(s.make_token(ttype, start_pos, start_pos));
|
|s: &Self, ttype: TokenType| Some(s.make_token(ttype, start_line, start_pos, start_pos));
|
||||||
|
|
||||||
let handle_eq_suffix = |s: &mut Self, if_present: TokenType, if_absent: TokenType| {
|
let handle_eq_suffix = |s: &mut Self, if_present: TokenType, if_absent: TokenType| {
|
||||||
Some(match s.consume_if_eq('=') {
|
Some(match s.consume_if_eq('=') {
|
||||||
Some(end) => s.make_token(if_present, start_pos, end),
|
Some(end) => s.make_token(if_present, start_line, start_pos, end),
|
||||||
None => s.make_token(if_absent, start_pos, start_pos),
|
None => s.make_token(if_absent, start_line, start_pos, start_pos),
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -256,11 +253,15 @@ impl<'src> Iterator for Scanner<'src> {
|
|||||||
'<' => handle_eq_suffix(self, TokenType::LessEqual, TokenType::Less),
|
'<' => handle_eq_suffix(self, TokenType::LessEqual, TokenType::Less),
|
||||||
_ => {
|
_ => {
|
||||||
let token = if start_ch.is_ascii_digit() {
|
let token = if start_ch.is_ascii_digit() {
|
||||||
self.scan_number(start_pos)
|
let end = self.scan_number(start_pos);
|
||||||
|
self.make_token(TokenType::Number, start_line, start_pos, end)
|
||||||
} else if start_ch.is_ascii_alphabetic() {
|
} else if start_ch.is_ascii_alphabetic() {
|
||||||
self.scan_identifier(start_pos)
|
self.scan_identifier(start_line, start_pos)
|
||||||
} else if start_ch == '"' {
|
} else if start_ch == '"' {
|
||||||
self.scan_string(start_pos)
|
match self.scan_string() {
|
||||||
|
Ok(end) => self.make_token(TokenType::String, start_line, start_pos, end),
|
||||||
|
Err(end) => self.make_token(TokenType::Error(ScanErrorKind::UndelimitedString), start_line, start_pos, end),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
panic!("Invalid character");
|
panic!("Invalid character");
|
||||||
};
|
};
|
||||||
@@ -278,6 +279,55 @@ struct Parser<'src> {
|
|||||||
scanner: Peekable<Scanner<'src>>,
|
scanner: Peekable<Scanner<'src>>,
|
||||||
errors: Vec<ParseError<'src>>,
|
errors: Vec<ParseError<'src>>,
|
||||||
intern_table: HashMap<&'src str, u8>,
|
intern_table: HashMap<&'src str, u8>,
|
||||||
|
end_line: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum ParseErrorKind {
|
||||||
|
InvalidNumber,
|
||||||
|
UnexpectedEOF,
|
||||||
|
IncompleteExpression,
|
||||||
|
NoSemicolonAfterValue,
|
||||||
|
NoSemicolonAfterExpression,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ParseErrorKind {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
ParseErrorKind::InvalidNumber => todo!(),
|
||||||
|
ParseErrorKind::UnexpectedEOF => todo!(),
|
||||||
|
ParseErrorKind::IncompleteExpression => {
|
||||||
|
write!(f, "Expect expression.")
|
||||||
|
},
|
||||||
|
ParseErrorKind::NoSemicolonAfterValue => todo!(),
|
||||||
|
ParseErrorKind::NoSemicolonAfterExpression => {
|
||||||
|
write!(f, "Expect ';' after expression.")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct ParseError<'src> {
|
||||||
|
location: Option<Token<'src>>,
|
||||||
|
line: usize,
|
||||||
|
kind: ParseErrorKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'src> fmt::Display for ParseError<'src> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match &self.location {
|
||||||
|
Some(location) => {
|
||||||
|
match location.ttype {
|
||||||
|
TokenType::Error(_) => write!(f, "[line {}] Error: {}", self.line, self.kind),
|
||||||
|
_ => write!(f, "[line {}] Error at '{}': {}", self.line, location.span, self.kind),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
write!(f, "[line {}] Error at end: {}", self.line, self.kind)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Associativity {
|
enum Associativity {
|
||||||
@@ -301,32 +351,27 @@ enum Precedence {
|
|||||||
Primary,
|
Primary,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ParseError<'src> {
|
type Result<'src, T> = std::result::Result<T, ParseError<'src>>;
|
||||||
InvalidNumber(Token<'src>),
|
|
||||||
UnexpectedToken(Vec<TokenType>, Token<'src>),
|
|
||||||
UnexpectedEOF,
|
|
||||||
}
|
|
||||||
|
|
||||||
type Result<'src> = std::result::Result<(), ParseError<'src>>;
|
|
||||||
|
|
||||||
impl<'src> Parser<'src> {
|
impl<'src> Parser<'src> {
|
||||||
fn new(sc: Scanner<'src>) -> Self {
|
fn new(sc: Scanner<'src>) -> Self {
|
||||||
|
let line_count = sc.source.chars().filter(|c| *c == '\n').count() + 1;
|
||||||
Parser {
|
Parser {
|
||||||
scanner: sc.into_iter().peekable(),
|
scanner: sc.into_iter().peekable(),
|
||||||
errors: Vec::new(),
|
errors: Vec::new(),
|
||||||
intern_table: HashMap::new(),
|
intern_table: HashMap::new(),
|
||||||
|
end_line: line_count,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn precedence(ttype: TokenType) -> Precedence {
|
fn precedence(ttype: TokenType) -> Option<Precedence> {
|
||||||
use TokenType::*;
|
use TokenType::*;
|
||||||
match ttype {
|
match ttype {
|
||||||
Plus | Minus => Precedence::Term,
|
Plus | Minus => Some(Precedence::Term),
|
||||||
Star | Slash => Precedence::Factor,
|
Star | Slash => Some(Precedence::Factor),
|
||||||
EqualEqual | BangEqual => Precedence::Equality,
|
EqualEqual | BangEqual => Some(Precedence::Equality),
|
||||||
Greater | GreaterEqual | Less | LessEqual => Precedence::Comparison,
|
Greater | GreaterEqual | Less | LessEqual => Some(Precedence::Comparison),
|
||||||
RightParen => Precedence::None,
|
_ => None,
|
||||||
_ => panic!("Undefined precedence: {:?}", ttype),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,9 +385,33 @@ impl<'src> Parser<'src> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _expression(&mut self, chunk: &mut Chunk, min_prec: Precedence) -> Result<'src> {
|
fn error_end(&self, kind: ParseErrorKind) -> ParseError<'src> {
|
||||||
|
ParseError {
|
||||||
|
location: None,
|
||||||
|
line: self.end_line,
|
||||||
|
kind
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_at(&self, location: Token<'src>, kind: ParseErrorKind) -> ParseError<'src> {
|
||||||
|
let line = location.line;
|
||||||
|
ParseError {
|
||||||
|
location: Some(location),
|
||||||
|
line,
|
||||||
|
kind
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_at_or_end(&self, location: Option<Token<'src>>, kind: ParseErrorKind) -> ParseError<'src> {
|
||||||
|
match location {
|
||||||
|
Some(location) => self.error_at(location, kind),
|
||||||
|
None => self.error_end(kind),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _expression(&mut self, chunk: &mut Chunk, min_prec: Precedence) -> Result<'src, ()> {
|
||||||
match self.scanner.next() {
|
match self.scanner.next() {
|
||||||
None => return Err(ParseError::UnexpectedEOF),
|
None => return Err(self.error_end(ParseErrorKind::IncompleteExpression)),
|
||||||
Some(token) => match token.ttype {
|
Some(token) => match token.ttype {
|
||||||
TokenType::Minus | TokenType::Bang => {
|
TokenType::Minus | TokenType::Bang => {
|
||||||
self._expression(chunk, Precedence::Unary)?;
|
self._expression(chunk, Precedence::Unary)?;
|
||||||
@@ -351,23 +420,23 @@ impl<'src> Parser<'src> {
|
|||||||
TokenType::Bang => Op::Not,
|
TokenType::Bang => Op::Not,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
chunk.add_op(op, 0);
|
chunk.add_op(op, token.line);
|
||||||
}
|
}
|
||||||
TokenType::Number => {
|
TokenType::Number => {
|
||||||
match token.span.parse::<f64>() {
|
match token.span.parse::<f64>() {
|
||||||
Ok(c) => Ok(chunk.add_constant(c.into(), 0)),
|
Ok(c) => Ok(chunk.add_constant(c.into(), token.line)),
|
||||||
_ => Err(ParseError::InvalidNumber(token)),
|
_ => Err(self.error_at(token, ParseErrorKind::InvalidNumber)),
|
||||||
}?;
|
}?;
|
||||||
}
|
}
|
||||||
TokenType::String => {
|
TokenType::String => {
|
||||||
let without_quotes = &token.span[1..(token.span.len() - 1)];
|
let without_quotes = &token.span[1..(token.span.len() - 1)];
|
||||||
match self.intern_table.get(without_quotes) {
|
match self.intern_table.get(without_quotes) {
|
||||||
Some(&index) => {
|
Some(&index) => {
|
||||||
chunk.add_op(Op::Constant { offset: index }, 0);
|
chunk.add_op(Op::Constant { offset: index }, token.line);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let object = unsafe { allocate_string(without_quotes) }.unwrap();
|
let object = unsafe { allocate_string(without_quotes) }.unwrap();
|
||||||
chunk.add_constant(object.get_object().into(), 0);
|
chunk.add_constant(object.get_object().into(), token.line);
|
||||||
self.intern_table
|
self.intern_table
|
||||||
.insert(without_quotes, chunk.constants.len() as u8 - 1);
|
.insert(without_quotes, chunk.constants.len() as u8 - 1);
|
||||||
chunk.allocations.push_front(object);
|
chunk.allocations.push_front(object);
|
||||||
@@ -379,125 +448,102 @@ impl<'src> Parser<'src> {
|
|||||||
assert_eq!(self.scanner.next().unwrap().ttype, TokenType::RightParen)
|
assert_eq!(self.scanner.next().unwrap().ttype, TokenType::RightParen)
|
||||||
}
|
}
|
||||||
TokenType::Nil => {
|
TokenType::Nil => {
|
||||||
chunk.add_op(Op::Nil, 0);
|
chunk.add_op(Op::Nil, token.line);
|
||||||
}
|
}
|
||||||
TokenType::True => {
|
TokenType::True => {
|
||||||
chunk.add_op(Op::True, 0);
|
chunk.add_op(Op::True, token.line);
|
||||||
}
|
}
|
||||||
TokenType::False => {
|
TokenType::False => {
|
||||||
chunk.add_op(Op::False, 0);
|
chunk.add_op(Op::False, token.line);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
use TokenType::*;
|
return Err(self.error_at(token, ParseErrorKind::IncompleteExpression));
|
||||||
return Err(ParseError::UnexpectedToken(
|
|
||||||
vec![Minus, Bang, Number, String, Nil, True, False, LeftParen],
|
|
||||||
token,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
while let Some(op) = self.scanner.next_if(|token| {
|
while let Some(op) = self.scanner.next_if(|token| {
|
||||||
if token.ttype == TokenType::Semicolon {
|
if let Some(op_prec) = Self::precedence(token.ttype) {
|
||||||
return false;
|
if op_prec == min_prec {
|
||||||
}
|
match Self::associativity(min_prec) {
|
||||||
|
Associativity::Left => false,
|
||||||
let op_prec = Self::precedence(token.ttype);
|
Associativity::Right => true,
|
||||||
if op_prec == min_prec {
|
Associativity::NonAssoc => {
|
||||||
match Self::associativity(min_prec) {
|
panic!("NonAssoc operation found in associative position")
|
||||||
Associativity::Left => false,
|
}
|
||||||
Associativity::Right => true,
|
|
||||||
Associativity::NonAssoc => {
|
|
||||||
panic!("NonAssoc operation found in associative position")
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
op_prec > min_prec
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
op_prec > min_prec
|
false
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
// Generates code for rhs
|
// Generates code for rhs
|
||||||
self._expression(chunk, Self::precedence(op.ttype))?;
|
self._expression(chunk, Self::precedence(op.ttype).unwrap())?;
|
||||||
|
|
||||||
match op.ttype {
|
match op.ttype {
|
||||||
TokenType::Plus => chunk.add_op(Op::Add, 0),
|
TokenType::Plus => chunk.add_op(Op::Add, op.line),
|
||||||
TokenType::Minus => chunk.add_op(Op::Subtract, 0),
|
TokenType::Minus => chunk.add_op(Op::Subtract, op.line),
|
||||||
TokenType::Star => chunk.add_op(Op::Multiply, 0),
|
TokenType::Star => chunk.add_op(Op::Multiply, op.line),
|
||||||
TokenType::Slash => chunk.add_op(Op::Divide, 0),
|
TokenType::Slash => chunk.add_op(Op::Divide, op.line),
|
||||||
TokenType::EqualEqual => chunk.add_op(Op::Equal, 0),
|
TokenType::EqualEqual => chunk.add_op(Op::Equal, op.line),
|
||||||
TokenType::Greater => chunk.add_op(Op::Greater, 0),
|
TokenType::Greater => chunk.add_op(Op::Greater, op.line),
|
||||||
TokenType::Less => chunk.add_op(Op::Less, 0),
|
TokenType::Less => chunk.add_op(Op::Less, op.line),
|
||||||
TokenType::BangEqual => chunk.add_op(Op::Equal, 0).add_op(Op::Not, 0),
|
TokenType::BangEqual => chunk.add_op(Op::Equal, op.line).add_op(Op::Not, op.line),
|
||||||
TokenType::GreaterEqual => chunk.add_op(Op::Less, 0).add_op(Op::Not, 0),
|
TokenType::GreaterEqual => chunk.add_op(Op::Less, op.line).add_op(Op::Not, op.line),
|
||||||
TokenType::LessEqual => chunk.add_op(Op::Greater, 0).add_op(Op::Not, 0),
|
TokenType::LessEqual => chunk.add_op(Op::Greater, op.line).add_op(Op::Not, op.line),
|
||||||
_ => {
|
_ => unreachable!(),
|
||||||
use TokenType::*;
|
|
||||||
return Err(ParseError::UnexpectedToken(
|
|
||||||
vec![
|
|
||||||
Plus,
|
|
||||||
Minus,
|
|
||||||
Star,
|
|
||||||
Slash,
|
|
||||||
EqualEqual,
|
|
||||||
Greater,
|
|
||||||
Less,
|
|
||||||
BangEqual,
|
|
||||||
GreaterEqual,
|
|
||||||
LessEqual,
|
|
||||||
],
|
|
||||||
op,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expression(&mut self, chunk: &mut Chunk) -> Result<'src> {
|
pub fn expression(&mut self, chunk: &mut Chunk) -> Result<'src, ()> {
|
||||||
self._expression(chunk, Precedence::None)
|
self._expression(chunk, Precedence::None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn must_consume(&mut self, ttype: TokenType) -> Result<'src> {
|
pub fn print_statement(&mut self, print_token: Token<'src>, chunk: &mut Chunk) -> Result<'src, ()> {
|
||||||
match self.scanner.peek() {
|
self.expression(chunk)?;
|
||||||
Some(token) => {
|
chunk.add_op(Op::Print, print_token.line);
|
||||||
if token.ttype == ttype {
|
match self.scanner.next_if(|t| t.ttype == TokenType::Semicolon) {
|
||||||
self.scanner.next();
|
Some(_) => Ok(()),
|
||||||
Ok(())
|
None => {
|
||||||
} else {
|
let location = self.scanner.peek().cloned();
|
||||||
Err(ParseError::UnexpectedToken(vec![ttype], token.clone()))
|
Err(self.error_at_or_end(location, ParseErrorKind::NoSemicolonAfterValue))
|
||||||
}
|
|
||||||
},
|
},
|
||||||
None => Err(ParseError::UnexpectedEOF),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print_statement(&mut self, chunk: &mut Chunk) -> Result<'src> {
|
pub fn expr_statement(&mut self, chunk: &mut Chunk) -> Result<'src, ()> {
|
||||||
self.must_consume(TokenType::Print)?;
|
|
||||||
self.expression(chunk)?;
|
|
||||||
chunk.add_op(Op::Print, 0);
|
|
||||||
self.must_consume(TokenType::Semicolon)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn expr_statement(&mut self, chunk: &mut Chunk) -> Result<'src> {
|
|
||||||
self.expression(chunk)?;
|
self.expression(chunk)?;
|
||||||
chunk.add_op(Op::Pop, 0);
|
chunk.add_op(Op::Pop, 0);
|
||||||
self.must_consume(TokenType::Semicolon)
|
match self.scanner.next_if(|t| t.ttype == TokenType::Semicolon) {
|
||||||
|
Some(_) => Ok(()),
|
||||||
|
None => {
|
||||||
|
let location = self.scanner.peek().cloned();
|
||||||
|
Err(self.error_at_or_end(location, ParseErrorKind::NoSemicolonAfterExpression))
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn statement(&mut self, chunk: &mut Chunk) -> Result<'src> {
|
pub fn statement(&mut self, chunk: &mut Chunk) -> Result<'src, ()> {
|
||||||
match self.scanner.peek().unwrap().ttype {
|
match self.scanner.peek().unwrap().ttype {
|
||||||
TokenType::Print => self.print_statement(chunk),
|
TokenType::Print => {
|
||||||
|
let print_token = self.scanner.next().unwrap();
|
||||||
|
self.print_statement(print_token, chunk)
|
||||||
|
},
|
||||||
_ => self.expr_statement(chunk),
|
_ => self.expr_statement(chunk),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn synchronize(&mut self) {
|
pub fn synchronize(&mut self) {
|
||||||
use TokenType::*;
|
use TokenType::*;
|
||||||
while let Some(token) = self.scanner.next_if(
|
|
||||||
|
while let Some(_token) = self.scanner.next_if(
|
||||||
|tok| ![Semicolon, Class, Fun, Var, For, If, While, Print, Return].contains(&tok.ttype)
|
|tok| ![Semicolon, Class, Fun, Var, For, If, While, Print, Return].contains(&tok.ttype)
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn declaration(&mut self, chunk: &mut Chunk) {
|
pub fn declaration(&mut self, chunk: &mut Chunk) {
|
||||||
@@ -517,16 +563,17 @@ impl<'src> Parser<'src> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn compile_expr(source: &str, chunk: &mut Chunk) {
|
pub fn compile_expr<'src>(source: &'src str, chunk: &mut Chunk) -> Result<'src, ()>{
|
||||||
let scanner = Scanner::new(source);
|
let scanner = Scanner::new(source);
|
||||||
let mut parser = Parser::new(scanner);
|
let mut parser = Parser::new(scanner);
|
||||||
parser.expression(chunk);
|
parser.expression(chunk)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compile(source: &str, chunk: &mut Chunk) {
|
pub fn compile<'src>(source: &'src str, chunk: &mut Chunk) -> Vec<ParseError<'src>> {
|
||||||
let scanner = Scanner::new(source);
|
let scanner = Scanner::new(source);
|
||||||
let mut parser = Parser::new(scanner);
|
let mut parser = Parser::new(scanner);
|
||||||
parser.compile(chunk);
|
parser.compile(chunk);
|
||||||
|
return parser.errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -549,39 +596,48 @@ mod tests {
|
|||||||
vec![
|
vec![
|
||||||
Token {
|
Token {
|
||||||
ttype: TokenType::Print,
|
ttype: TokenType::Print,
|
||||||
span: "print"
|
span: "print",
|
||||||
|
line: 1,
|
||||||
},
|
},
|
||||||
Token {
|
Token {
|
||||||
ttype: TokenType::LeftParen,
|
ttype: TokenType::LeftParen,
|
||||||
span: "("
|
span: "(",
|
||||||
|
line: 1,
|
||||||
},
|
},
|
||||||
Token {
|
Token {
|
||||||
ttype: TokenType::Number,
|
ttype: TokenType::Number,
|
||||||
span: "1"
|
span: "1",
|
||||||
|
line: 1,
|
||||||
},
|
},
|
||||||
Token {
|
Token {
|
||||||
ttype: TokenType::Plus,
|
ttype: TokenType::Plus,
|
||||||
span: "+"
|
span: "+",
|
||||||
|
line: 1,
|
||||||
},
|
},
|
||||||
Token {
|
Token {
|
||||||
ttype: TokenType::Number,
|
ttype: TokenType::Number,
|
||||||
span: "2"
|
span: "2",
|
||||||
|
line: 1,
|
||||||
},
|
},
|
||||||
Token {
|
Token {
|
||||||
ttype: TokenType::Star,
|
ttype: TokenType::Star,
|
||||||
span: "*"
|
span: "*",
|
||||||
|
line: 1,
|
||||||
},
|
},
|
||||||
Token {
|
Token {
|
||||||
ttype: TokenType::Number,
|
ttype: TokenType::Number,
|
||||||
span: "3"
|
span: "3",
|
||||||
|
line: 1,
|
||||||
},
|
},
|
||||||
Token {
|
Token {
|
||||||
ttype: TokenType::RightParen,
|
ttype: TokenType::RightParen,
|
||||||
span: ")"
|
span: ")",
|
||||||
|
line: 1,
|
||||||
},
|
},
|
||||||
Token {
|
Token {
|
||||||
ttype: TokenType::Semicolon,
|
ttype: TokenType::Semicolon,
|
||||||
span: ";"
|
span: ";",
|
||||||
|
line: 1,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -609,15 +665,18 @@ mod tests {
|
|||||||
vec![
|
vec![
|
||||||
Token {
|
Token {
|
||||||
ttype: TokenType::Number,
|
ttype: TokenType::Number,
|
||||||
span: "1"
|
span: "1",
|
||||||
|
line: 1,
|
||||||
},
|
},
|
||||||
Token {
|
Token {
|
||||||
ttype: TokenType::Number,
|
ttype: TokenType::Number,
|
||||||
span: "2"
|
span: "2",
|
||||||
|
line: 2,
|
||||||
},
|
},
|
||||||
Token {
|
Token {
|
||||||
ttype: TokenType::Number,
|
ttype: TokenType::Number,
|
||||||
span: "3"
|
span: "3",
|
||||||
|
line: 3,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -633,7 +692,8 @@ mod tests {
|
|||||||
tokens,
|
tokens,
|
||||||
vec![Token {
|
vec![Token {
|
||||||
ttype: TokenType::String,
|
ttype: TokenType::String,
|
||||||
span: "\"hello world\""
|
span: "\"hello world\"",
|
||||||
|
line: 1,
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -644,7 +704,8 @@ mod tests {
|
|||||||
let scanner = Scanner::new(source);
|
let scanner = Scanner::new(source);
|
||||||
let mut parser = Parser::new(scanner);
|
let mut parser = Parser::new(scanner);
|
||||||
let mut chunk = Chunk::new();
|
let mut chunk = Chunk::new();
|
||||||
parser.expression(&mut chunk);
|
let result = parser.expression(&mut chunk);
|
||||||
|
assert_eq!(result, Ok(()));
|
||||||
assert!(chunk.instr_eq(expected));
|
assert!(chunk.instr_eq(expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -653,7 +714,7 @@ mod tests {
|
|||||||
let mut parser = Parser::new(scanner);
|
let mut parser = Parser::new(scanner);
|
||||||
let mut chunk = Chunk::new();
|
let mut chunk = Chunk::new();
|
||||||
parser.compile(&mut chunk);
|
parser.compile(&mut chunk);
|
||||||
assert!(parser.errors.is_empty());
|
assert_eq!(parser.errors, vec![]);
|
||||||
assert!(chunk.instr_eq(expected));
|
assert!(chunk.instr_eq(expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -725,8 +786,9 @@ mod tests {
|
|||||||
let scanner = Scanner::new(source);
|
let scanner = Scanner::new(source);
|
||||||
let mut parser = Parser::new(scanner);
|
let mut parser = Parser::new(scanner);
|
||||||
let mut chunk = Chunk::new();
|
let mut chunk = Chunk::new();
|
||||||
parser.expression(&mut chunk);
|
let result = parser.expression(&mut chunk);
|
||||||
|
|
||||||
|
assert_eq!(result, Ok(()));
|
||||||
assert_eq!(chunk.allocations.len(), 1);
|
assert_eq!(chunk.allocations.len(), 1);
|
||||||
assert_eq!(chunk.constants.len(), 1);
|
assert_eq!(chunk.constants.len(), 1);
|
||||||
}
|
}
|
||||||
@@ -776,36 +838,4 @@ mod tests {
|
|||||||
|
|
||||||
test_parse_program(source, &expected);
|
test_parse_program(source, &expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn no_line_breaks() {
|
|
||||||
let line_map = LineMap::new("0123456789");
|
|
||||||
|
|
||||||
for i in 0..=9 {
|
|
||||||
assert_eq!(line_map.pos_to_line(i), 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn some_line_breaks() {
|
|
||||||
let line_map = LineMap::new("012\n456\n89\n");
|
|
||||||
|
|
||||||
for i in 0..=2 {
|
|
||||||
assert_eq!(line_map.pos_to_line(i), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(line_map.pos_to_line(3), 1);
|
|
||||||
|
|
||||||
for i in 4..=6 {
|
|
||||||
assert_eq!(line_map.pos_to_line(i), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(line_map.pos_to_line(7), 2);
|
|
||||||
|
|
||||||
for i in 8..=9 {
|
|
||||||
assert_eq!(line_map.pos_to_line(i), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(line_map.pos_to_line(10), 3);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,31 @@ mod vm;
|
|||||||
mod gc;
|
mod gc;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use bc::Chunk;
|
use bc::Chunk;
|
||||||
use vm::VM;
|
use vm::VM;
|
||||||
|
|
||||||
|
|
||||||
|
fn compile_and_run(source: &str, do_trace: bool) {
|
||||||
|
let mut chunk = Chunk::new();
|
||||||
|
let errors = lc::compile(source, &mut chunk);
|
||||||
|
|
||||||
|
if errors.is_empty() {
|
||||||
|
let mut vm = VM::new();
|
||||||
|
vm.set_trace(do_trace);
|
||||||
|
if let Err(err) = vm.stdrun(&chunk) {
|
||||||
|
eprintln!("{:?}", err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for error in errors {
|
||||||
|
eprintln!("{}", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fn repl() {
|
fn repl() {
|
||||||
let mut buffer = String::new();
|
let mut buffer = String::new();
|
||||||
|
|
||||||
@@ -18,22 +37,18 @@ fn repl() {
|
|||||||
loop {
|
loop {
|
||||||
match io::stdin().read_line(&mut buffer) {
|
match io::stdin().read_line(&mut buffer) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let mut chunk = Chunk::new();
|
compile_and_run(buffer.as_str(), do_trace);
|
||||||
lc::compile(buffer.as_str(), &mut chunk);
|
|
||||||
let mut vm = VM::new();
|
|
||||||
vm.set_trace(do_trace);
|
|
||||||
let result = vm.stdrun(&chunk);
|
|
||||||
println!("{:?}", result);
|
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
}
|
}
|
||||||
Err(error) =>
|
Err(error) => println!("{:?}", error),
|
||||||
println!("{:?}", error),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_file() {
|
fn run_file(path: String) {
|
||||||
todo!()
|
let do_trace = env::var("LOX_TRACE").is_ok();
|
||||||
|
let source = fs::read_to_string(path).unwrap();
|
||||||
|
compile_and_run(source.as_str(), do_trace);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -42,48 +57,9 @@ fn main() {
|
|||||||
if num_args == 1 {
|
if num_args == 1 {
|
||||||
repl()
|
repl()
|
||||||
} else if num_args == 2 {
|
} else if num_args == 2 {
|
||||||
run_file()
|
let source = env::args().nth(1).unwrap();
|
||||||
|
run_file(source);
|
||||||
} else {
|
} else {
|
||||||
println!("Usage: rlox [path]")
|
println!("Usage: rlox [path]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::io::BufWriter;
|
|
||||||
|
|
||||||
use crate::{bc::{Chunk, Value}, gc::allocate_string, lc::{compile, compile_expr}, vm::VM};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_compile_and_run_pi_math() {
|
|
||||||
let source = "-(3 * 7 * 11 * 17) / -(500 + 1000 - 250)";
|
|
||||||
let mut chunk = Chunk::new();
|
|
||||||
compile_expr(source, &mut chunk);
|
|
||||||
let mut vm = VM::new();
|
|
||||||
vm.stdrun(&chunk).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn string_concatenation() {
|
|
||||||
let source = "\"hello\" + \" \" + \"world\"";
|
|
||||||
let mut chunk = Chunk::new();
|
|
||||||
compile_expr(source, &mut chunk);
|
|
||||||
let mut vm = VM::new();
|
|
||||||
let (result, _allocs) = vm.stdrun(&chunk).unwrap().unwrap();
|
|
||||||
let target_alloc = unsafe { allocate_string("hello world").unwrap() };
|
|
||||||
let target = Value::from(target_alloc.get_object());
|
|
||||||
assert_eq!(result, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn print_hello_world() {
|
|
||||||
let source = "print \"hello\" + \" \" + \"world\";";
|
|
||||||
let mut chunk = Chunk::new();
|
|
||||||
let mut vm = VM::new();
|
|
||||||
compile(source, &mut chunk);
|
|
||||||
let mut buf = BufWriter::new(Vec::new());
|
|
||||||
vm.run(&chunk, &mut buf).unwrap();
|
|
||||||
let stdoutput = String::from_utf8(buf.into_inner().unwrap()).unwrap();
|
|
||||||
assert_eq!(stdoutput, "hello world\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ pub enum VMError {
|
|||||||
Runtime(Rc<str>, usize),
|
Runtime(Rc<str>, usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Result<T> = std::result::Result<T, VMError>;
|
||||||
|
|
||||||
impl VM {
|
impl VM {
|
||||||
pub fn new() -> VM {
|
pub fn new() -> VM {
|
||||||
VM {
|
VM {
|
||||||
@@ -44,13 +46,13 @@ impl VM {
|
|||||||
self.stack.push(value);
|
self.stack.push(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pop(&mut self) -> Result<Value, VMError> {
|
fn pop(&mut self) -> Result<Value> {
|
||||||
self.stack
|
self.stack
|
||||||
.pop()
|
.pop()
|
||||||
.ok_or(self.runtime_err("Attempt to pop of empty stack."))
|
.ok_or(self.runtime_err("Attempt to pop of empty stack."))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pop_num(&mut self) -> Result<f64, VMError> {
|
fn pop_num(&mut self) -> Result<f64> {
|
||||||
let top_of_stack = self.pop()?;
|
let top_of_stack = self.pop()?;
|
||||||
top_of_stack
|
top_of_stack
|
||||||
.as_num()
|
.as_num()
|
||||||
@@ -60,15 +62,15 @@ impl VM {
|
|||||||
pub fn stdrun(
|
pub fn stdrun(
|
||||||
&mut self,
|
&mut self,
|
||||||
chunk: &Chunk,
|
chunk: &Chunk,
|
||||||
) -> Result<Option<(Value, LinkedList<GcHandle>)>, VMError> {
|
) -> Result<()> {
|
||||||
return self.run(chunk, &mut io::stdout());
|
self.run(chunk, &mut io::stdout())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run<Output: io::Write>(
|
pub fn run<Output: io::Write>(
|
||||||
&mut self,
|
&mut self,
|
||||||
chunk: &Chunk,
|
chunk: &Chunk,
|
||||||
output: &mut Output,
|
output: &mut Output,
|
||||||
) -> Result<Option<(Value, LinkedList<GcHandle>)>, VMError> {
|
) -> Result<()> {
|
||||||
let mut allocations: LinkedList<GcHandle> = LinkedList::new();
|
let mut allocations: LinkedList<GcHandle> = LinkedList::new();
|
||||||
|
|
||||||
while self.pc < chunk.code.len() {
|
while self.pc < chunk.code.len() {
|
||||||
@@ -180,17 +182,7 @@ impl VM {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.stack.pop() {
|
Ok(())
|
||||||
None => Ok(None),
|
|
||||||
Some(result_value) => {
|
|
||||||
let escaping_allocs = allocations
|
|
||||||
.into_iter()
|
|
||||||
.filter(|handle| Value::from(handle.get_object()) == result_value)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(Some((result_value, escaping_allocs)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +193,7 @@ mod tests {
|
|||||||
use super::{Chunk, Op, VMError, Value, VM};
|
use super::{Chunk, Op, VMError, Value, VM};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn simple_arithmetic() {
|
fn simple_arithmetic() -> Result<(), VMError>{
|
||||||
let chunk = Chunk::new_with(
|
let chunk = Chunk::new_with(
|
||||||
vec![
|
vec![
|
||||||
Op::Constant { offset: 0 },
|
Op::Constant { offset: 0 },
|
||||||
@@ -229,11 +221,13 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let mut vm = VM::new();
|
let mut vm = VM::new();
|
||||||
let (result, allocs) = vm.stdrun(&chunk).unwrap().unwrap();
|
vm.stdrun(&chunk)?;
|
||||||
|
|
||||||
assert_eq!(result, Value::from(3.1416));
|
let tos = vm.stack.last().unwrap();
|
||||||
assert!(vm.stack.is_empty());
|
|
||||||
assert!(allocs.is_empty());
|
assert_eq!(*tos, 3.1416.into());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -255,24 +249,24 @@ mod tests {
|
|||||||
vec![],
|
vec![],
|
||||||
LinkedList::new(),
|
LinkedList::new(),
|
||||||
);
|
);
|
||||||
let mut vm = VM::new();
|
|
||||||
let (result, allocs) = vm.stdrun(&chunk)?.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(result, true.into());
|
let mut vm = VM::new();
|
||||||
assert!(vm.stack.is_empty());
|
vm.stdrun(&chunk)?;
|
||||||
assert!(allocs.is_empty());
|
|
||||||
|
assert_eq!(vm.stack, vec![Value::Bool(true)]);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn not_nil_is_true() {
|
fn not_nil_is_true() -> Result<(), VMError>{
|
||||||
let chunk = Chunk::new_with(vec![Op::Nil, Op::Not], vec![], vec![], LinkedList::new());
|
let chunk = Chunk::new_with(vec![Op::Nil, Op::Not], vec![], vec![], LinkedList::new());
|
||||||
let mut vm = VM::new();
|
|
||||||
let (result, allocs) = vm.stdrun(&chunk).unwrap().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(result, true.into());
|
let mut vm = VM::new();
|
||||||
assert!(vm.stack.is_empty());
|
vm.stdrun(&chunk)?;
|
||||||
assert!(allocs.is_empty());
|
|
||||||
|
assert_eq!(vm.stack, vec![Value::Bool(true)]);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user