Chapter 17: Compiling Expressions done

This commit is contained in:
Moritz Gmeiner 2023-01-31 22:54:12 +01:00
commit 1cca1494a4
20 changed files with 702 additions and 129 deletions

7
Cargo.lock generated
View file

@ -350,6 +350,7 @@ dependencies = [
"num-traits", "num-traits",
"regex", "regex",
"rlox2-frontend", "rlox2-frontend",
"static_assertions",
"thiserror", "thiserror",
] ]
@ -379,6 +380,12 @@ version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.10.0" version = "0.10.0"

View file

@ -85,43 +85,43 @@ impl Lexer {
let c = self.advance(); let c = self.advance();
let token_type = match c { match c {
'(' => Some(LeftParen), '(' => self.push_token(LeftParen),
')' => Some(RightParen), ')' => self.push_token(RightParen),
'{' => Some(LeftBrace), '{' => self.push_token(LeftBrace),
'}' => Some(RightBrace), '}' => self.push_token(RightBrace),
',' => Some(Comma), ',' => self.push_token(Comma),
'.' => Some(Dot), '.' => self.push_token(Dot),
'+' => Some(Plus), '+' => self.push_token(Plus),
'-' => Some(Minus), '-' => self.push_token(Minus),
';' => Some(Semicolon), ';' => self.push_token(Semicolon),
'*' => Some(Star), '*' => self.push_token(Star),
'!' => { '!' => {
if self.consume('=') { if self.consume('=') {
Some(BangEqual) self.push_token(BangEqual)
} else { } else {
Some(Bang) self.push_token(Bang)
} }
} }
'=' => { '=' => {
if self.consume('=') { if self.consume('=') {
Some(EqualEqual) self.push_token(EqualEqual)
} else { } else {
Some(Equal) self.push_token(Equal)
} }
} }
'<' => { '<' => {
if self.consume('=') { if self.consume('=') {
Some(LessEqual) self.push_token(LessEqual)
} else { } else {
Some(Less) self.push_token(Less)
} }
} }
'>' => { '>' => {
if self.consume('=') { if self.consume('=') {
Some(GreaterEqual) self.push_token(GreaterEqual)
} else { } else {
Some(Greater) self.push_token(Greater)
} }
} }
'/' => { '/' => {
@ -129,8 +129,6 @@ impl Lexer {
// line comment // line comment
// advance until either source is empty or newline if found // advance until either source is empty or newline if found
while !self.source_is_empty() && self.advance() != '\n' {} while !self.source_is_empty() && self.advance() != '\n' {}
None
} else if self.consume('*') { } else if self.consume('*') {
// block comment // block comment
@ -166,28 +164,21 @@ impl Lexer {
self.advance(); self.advance();
} }
None
} else { } else {
Some(Slash) self.push_token(Slash)
} }
} }
'"' => self.try_parse_string(), '"' => self.try_parse_string(),
'0'..='9' => self.try_parse_number(), '0'..='9' => self.try_parse_number(),
' ' | '\r' | '\n' | '\t' => None, // handled automatically in advance() ' ' | '\r' | '\n' | '\t' => {} // handled automatically in advance()
c @ '_' | c if c.is_ascii_alphabetic() => self.try_parse_identifier(), c @ '_' | c if c.is_ascii_alphabetic() => self.try_parse_identifier(),
_ => { _ => {
self.errors.push(LexerError::UnexpectedCharacter { self.errors.push(LexerError::UnexpectedCharacter {
c, c,
code_pos: self.code_pos, code_pos: self.code_pos,
}); });
None
} }
}; };
if let Some(token_type) = token_type {
self.push_token(token_type);
}
} }
fn source_is_empty(&self) -> bool { fn source_is_empty(&self) -> bool {
@ -235,23 +226,24 @@ impl Lexer {
self.tokens.push(Token::new(token_type, self.code_pos)); self.tokens.push(Token::new(token_type, self.code_pos));
} }
fn try_parse_string(&mut self) -> Option<TokenType> { fn try_parse_string(&mut self) {
// advance until second " // advance until second "
while self.advance() != '"' { while self.advance() != '"' {
if self.source_is_empty() { if self.source_is_empty() {
self.errors.push(LexerError::UnterminatedStringLiteral { self.errors.push(LexerError::UnterminatedStringLiteral {
code_pos: self.code_pos, code_pos: self.code_pos,
}); });
return None; return;
} }
} }
let string_literal = self.source[self.start + 1..self.current - 1].iter().collect(); let string_literal = self.source[self.start + 1..self.current - 1].iter().collect();
Some(TokenType::String(string_literal)) // Some(TokenType::String(Box::new(string_literal)))
self.tokens.push(Token::new_string(string_literal, self.code_pos));
} }
fn try_parse_number(&mut self) -> Option<TokenType> { fn try_parse_number(&mut self) {
let is_some_digit = |c: Option<char>| c.map_or(false, |c| c.is_ascii_digit()); let is_some_digit = |c: Option<char>| c.map_or(false, |c| c.is_ascii_digit());
// eat all digits // eat all digits
@ -289,14 +281,15 @@ impl Lexer {
msg: err.to_string(), msg: err.to_string(),
code_pos: self.code_pos, code_pos: self.code_pos,
}); });
return None; return;
} }
}; };
Some(TokenType::Number(num)) // Some(TokenType::Number(num))
self.tokens.push(Token::new_number(num, self.code_pos));
} }
fn try_parse_identifier(&mut self) -> Option<TokenType> { fn try_parse_identifier(&mut self) {
let is_alpha_num_underscore = let is_alpha_num_underscore =
|c: Option<char>| c.map_or(false, |c| matches!(c, '0'..='9' | 'A'..='Z' | '_' | 'a'..='z')); |c: Option<char>| c.map_or(false, |c| matches!(c, '0'..='9' | 'A'..='Z' | '_' | 'a'..='z'));
@ -306,8 +299,18 @@ impl Lexer {
let lexeme: String = self.source[self.start..self.current].iter().collect(); let lexeme: String = self.source[self.start..self.current].iter().collect();
let token_type = KEYWORDS.get(&lexeme).cloned().unwrap_or(TokenType::Identifier(lexeme)); /* let token_type = KEYWORDS
.get(&lexeme)
.cloned()
.unwrap_or(TokenType::Identifier(Box::new(lexeme))); */
Some(token_type) if let Some(&token_type) = KEYWORDS.get(&lexeme) {
// Token::new(token_type, self.code_pos)
self.push_token(token_type);
} else {
self.tokens.push(Token::new_identifier(lexeme, self.code_pos));
}
// Some(token_type)
} }
} }

View file

@ -1,7 +1,10 @@
use std::fmt::{Debug, Display};
use std::mem::ManuallyDrop;
use super::CodePos; use super::CodePos;
#[allow(dead_code, clippy::upper_case_acronyms)] #[repr(u8)]
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
#[rustfmt::skip] #[rustfmt::skip]
pub enum TokenType { pub enum TokenType {
// Single-character tokens // Single-character tokens
@ -14,44 +17,139 @@ pub enum TokenType {
Greater, GreaterEqual, Greater, GreaterEqual,
Less, LessEqual, Less, LessEqual,
// Literals // Identifier and literals
Identifier(String), Identifier, String, Number,
String(String),
Number(f64),
// Keywords // Keywords
And, Break, Class, Else, False, Fun, For, If, Nil, Or, And, Break, Class, Else, False, Fun, For, If, Nil, Or,
Print, Return, Super, This, True, Var, While, Print, Return, Super, This, True, Var, While,
#[allow(dead_code, clippy::upper_case_acronyms)]
EOF EOF
} }
#[derive(Clone)] union TokenData {
none: (),
#[allow(clippy::box_collection)]
s: ManuallyDrop<Box<String>>,
num: f64,
}
impl TokenData {
fn none() -> Self {
TokenData { none: () }
}
fn string(s: String) -> Self {
let s = ManuallyDrop::new(Box::new(s));
TokenData { s }
}
fn num(num: f64) -> Self {
TokenData { num }
}
}
pub struct Token { pub struct Token {
pub token_type: TokenType, pub token_type: TokenType,
// pub lexeme: String, // pub lexeme: String,
data: TokenData,
pub code_pos: CodePos, pub code_pos: CodePos,
} }
impl Token { impl Token {
pub fn new(token_type: TokenType, pos: CodePos) -> Self { pub fn new(token_type: TokenType, code_pos: CodePos) -> Self {
Token { Token {
token_type, token_type,
// lexeme, // lexeme,
code_pos: pos, data: TokenData::none(),
code_pos,
}
}
pub fn new_string(s: String, code_pos: CodePos) -> Self {
Token {
token_type: TokenType::String,
data: TokenData::string(s),
code_pos,
}
}
pub fn new_identifier(name: String, code_pos: CodePos) -> Self {
Token {
token_type: TokenType::Identifier,
data: TokenData::string(name),
code_pos,
}
}
pub fn new_number(num: f64, code_pos: CodePos) -> Self {
Token {
token_type: TokenType::Number,
data: TokenData::num(num),
code_pos,
}
}
pub fn string_data(self) -> String {
assert!(self.token_type == TokenType::String || self.token_type == TokenType::Identifier);
// std::mem::take(&mut self.data.s)
unsafe {
let mut me = self;
let s = std::mem::take(&mut me.data.s);
*ManuallyDrop::into_inner(s)
}
}
pub fn num_data(self) -> f64 {
assert_eq!(self.token_type, TokenType::Number);
unsafe { self.data.num }
}
}
impl Debug for Token {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// write!(f, "<{:?}>", self.token_type)
match self.token_type {
TokenType::Number => unsafe { write!(f, "<{:?}({})>", self.token_type, self.data.num) },
TokenType::String => unsafe { write!(f, "<{:?}({})>", self.token_type, self.data.s.as_ref()) },
TokenType::Identifier => unsafe { write!(f, "<{:?}({})>", self.token_type, self.data.s.as_ref()) },
_ => write!(f, "<{:?}>", self.token_type),
} }
} }
} }
impl std::fmt::Debug for Token { impl Display for Token {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "<{:?}>", self.token_type) // write!(f, "<{:?}>", self.token_type)
// write!(f, "<{:?}> (\"{}\")", self.token_type, self.lexeme) match self.token_type {
TokenType::Number => unsafe { write!(f, "<{:?}({})>", self.token_type, self.data.num) },
TokenType::String => unsafe { write!(f, "<{:?}({})>", self.token_type, self.data.s.as_ref()) },
TokenType::Identifier => unsafe { write!(f, "<{:?}({})>", self.token_type, self.data.s.as_ref()) },
_ => write!(f, "<{:?}>", self.token_type),
}
} }
} }
impl std::fmt::Display for Token { /* impl Clone for Token {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn clone(&self) -> Self {
write!(f, "<{:?}>", self.token_type) let code_pos = self.code_pos;
match self.token_type {
TokenType::Number => Token::new_number(self.num_data(), code_pos),
TokenType::String => unsafe { Token::new_string(self.data.s.as_ref().clone(), code_pos) },
TokenType::Identifier => unsafe { Token::new_identifier(self.data.s.as_ref().clone(), code_pos) },
token_type => Token::new(token_type, code_pos),
}
}
} */
impl Drop for Token {
fn drop(&mut self) {
if self.token_type == TokenType::String {}
} }
} }

View file

@ -101,7 +101,7 @@ impl Parser {
} }
} }
// me.consume_token(TokenType::EOF).unwrap(); assert_eq!(me.next_token().token_type, TokenType::EOF);
if !me.parse_errors.is_empty() { if !me.parse_errors.is_empty() {
Err(me.parse_errors) Err(me.parse_errors)
@ -324,12 +324,10 @@ impl Parser {
// self.consume_token(TokenType::Var)?; // self.consume_token(TokenType::Var)?;
assert_eq!(self.next_token().token_type, TokenType::Var); assert_eq!(self.next_token().token_type, TokenType::Var);
let name = match self.next_token() { let token = self.next_token();
Token { let name = match token.token_type {
token_type: TokenType::Identifier(name), TokenType::Identifier => token.string_data(),
.. _ => return Err(ParserError::ExpectedVarName { token }),
} => name,
token => return Err(ParserError::ExpectedVarName { token }),
}; };
let initializer = if self.peek_token().token_type == TokenType::Equal { let initializer = if self.peek_token().token_type == TokenType::Equal {
@ -738,8 +736,8 @@ impl Parser {
match token.token_type { match token.token_type {
TokenType::Fun => Ok(self.fun_params_and_body("<lambda>")?), TokenType::Fun => Ok(self.fun_params_and_body("<lambda>")?),
TokenType::Number(num) => Ok(Expr::number(num)), TokenType::Number => Ok(Expr::number(token.num_data())),
TokenType::String(s) => Ok(Expr::string(s)), TokenType::String => Ok(Expr::string(token.string_data())),
TokenType::False => Ok(Expr::bool(false)), TokenType::False => Ok(Expr::bool(false)),
TokenType::True => Ok(Expr::bool(true)), TokenType::True => Ok(Expr::bool(true)),
TokenType::Nil => Ok(Expr::nil()), TokenType::Nil => Ok(Expr::nil()),
@ -768,7 +766,9 @@ impl Parser {
Ok(Expr::grouping(expr)) Ok(Expr::grouping(expr))
} }
TokenType::Identifier(name) => Ok(Expr::Variable { name }), TokenType::Identifier => Ok(Expr::Variable {
name: token.string_data(),
}),
_ => Err(ParserError::ExpectedPrimary { token }), _ => Err(ParserError::ExpectedPrimary { token }),
} }
} }
@ -781,13 +781,7 @@ impl Parser {
fn identifier(&mut self, msg: &str) -> ParserResult<String> { fn identifier(&mut self, msg: &str) -> ParserResult<String> {
match self.peek_token().token_type { match self.peek_token().token_type {
TokenType::Identifier(_) => { TokenType::Identifier => Ok(self.next_token().string_data()),
if let TokenType::Identifier(name) = self.next_token().token_type {
Ok(name)
} else {
unreachable!()
}
}
_ => Err(ParserError::MissingIdentifier { _ => Err(ParserError::MissingIdentifier {
msg: msg.to_owned(), msg: msg.to_owned(),
code_pos: self.peek_token().code_pos, code_pos: self.peek_token().code_pos,
@ -834,7 +828,8 @@ impl Parser {
let _ = self.next_token(); let _ = self.next_token();
Ok(()) Ok(())
} }
TokenType::EOF => Err(err_fn(self.peek_token().clone())), // call err_fn with dummy token so we don't have to eat the EOF token
TokenType::EOF => Err(err_fn(Token::new(TokenType::EOF, self.peek_token().code_pos))),
_ => Err(err_fn(self.next_token())), _ => Err(err_fn(self.next_token())),
} }
} }

View file

@ -1,10 +1,10 @@
mod _parser;
mod error; mod error;
mod expr; mod expr;
mod misc; mod misc;
mod parse;
mod stmt; mod stmt;
pub use _parser::parse_tokens;
pub use error::ParserError; pub use error::ParserError;
pub use expr::{BinaryOp, Expr, Literal, LogicalOp, UnaryOp}; pub use expr::{BinaryOp, Expr, Literal, LogicalOp, UnaryOp};
pub use parse::parse_tokens;
pub use stmt::Stmt; pub use stmt::Stmt;

View file

@ -1,5 +1,5 @@
mod _resolver;
mod error; mod error;
mod resolve;
pub use _resolver::resolve;
pub use error::ResolverError; pub use error::ResolverError;
pub use resolve::resolve;

View file

@ -64,12 +64,9 @@ pub fn run_repl(runtime: &mut Runtime) {
pub fn run(source: &str, runtime: &mut Runtime) -> Result<(), LoxError> { pub fn run(source: &str, runtime: &mut Runtime) -> Result<(), LoxError> {
let tokens: Vec<Token> = scan_tokens(source)?; let tokens: Vec<Token> = scan_tokens(source)?;
/* let token_str = tokens let token_str = itertools::Itertools::join(&mut tokens.iter().map(|token| token.to_string()), " ");
.iter()
.map(|token| token.to_string())
.join(" ");
println!("{token_str}"); */ println!("{token_str}");
let statements = parse_tokens(tokens)?; let statements = parse_tokens(tokens)?;

View file

@ -22,7 +22,7 @@ fn main() {
let cli_args = CliArgs::parse(); let cli_args = CliArgs::parse();
if cli_args.vm { if cli_args.vm {
use rlox2_vm::InterpretError; use rlox2_vm::LoxError;
let mut vm = rlox2_vm::VM::default(); let mut vm = rlox2_vm::VM::default();
@ -35,9 +35,9 @@ fn main() {
if let Err(err) = rlox2_vm::run(&source, &mut vm) { if let Err(err) = rlox2_vm::run(&source, &mut vm) {
eprintln!("{err}"); eprintln!("{err}");
match err { match err {
InterpretError::LexerError { .. } | InterpretError::CompileError { .. } => std::process::exit(65), LoxError::LexerError { .. } | LoxError::CompileError { .. } => std::process::exit(65),
InterpretError::RuntimeError { .. } => std::process::exit(70), LoxError::RuntimeError { .. } => std::process::exit(70),
InterpretError::Exit { exit_code } => std::process::exit(exit_code), LoxError::Exit { exit_code } => std::process::exit(exit_code),
} }
} }

View file

@ -15,3 +15,4 @@ num-derive = "0.3.3"
num-traits = "0.2.15" num-traits = "0.2.15"
regex = "1.7.1" regex = "1.7.1"
thiserror = "1.0.38" thiserror = "1.0.38"
static_assertions = "1.1.0"

View file

@ -1,6 +0,0 @@
use itertools::Itertools;
use rlox2_frontend::lexer::Token;
pub fn compile(tokens: Vec<Token>) {
println!("{}", tokens.iter().map(|token| token.to_string()).join(" "));
}

433
vm/src/compiler.rs Normal file
View file

@ -0,0 +1,433 @@
use std::vec::IntoIter;
use static_assertions::const_assert_eq;
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use rlox2_frontend::lexer::{Token, TokenType};
use crate::debug::DEBUG_PRINT_CODE;
use crate::error::CompilerError;
use crate::misc::u16_to_bytes;
use crate::{gen_rules_table, single_rule, Chunk, Opcode, Value};
/*====================================================================================================================*/
pub fn compile(tokens: Vec<Token>) -> Result<Chunk, CompilerError> {
Compiler::new(tokens).compile()
}
/*====================================================================================================================*/
struct TokenIter {
token_iter: IntoIter<Token>,
peek_token: Option<Token>,
}
impl TokenIter {
pub fn new(tokens: Vec<Token>) -> Self {
TokenIter {
token_iter: tokens.into_iter(),
peek_token: None,
}
}
fn peek(&mut self) -> Option<&Token> {
// if peek_token is empty: fill with next token from token_iter
if self.peek_token.is_none() && self.token_iter.len() != 0 {
self.peek_token = self.token_iter.next();
}
self.peek_token.as_ref()
}
/* fn is_empty(&self) -> bool {
// peek_token is None and there are no more tokens to take from token_iter
self.peek_token.is_none() && self.token_iter.len() == 0
} */
}
impl Iterator for TokenIter {
type Item = Token;
fn next(&mut self) -> Option<Self::Item> {
// return the peeked token if any, or else next token from token_iter
self.peek_token.take().or_else(|| self.token_iter.next())
}
}
/*====================================================================================================================*/
type CompilerResult = Result<(), CompilerError>;
#[repr(u8)]
#[derive(Debug, FromPrimitive, Copy, Clone, PartialOrd, PartialEq)]
enum Precedence {
Null = 0,
Assignment = 1,
Or = 2,
And = 3,
Equality = 4,
Comparison = 5,
Term = 6,
Factor = 7,
Unary = 8,
Call = 9,
Primary = 10,
}
impl Precedence {
fn inc(self) -> Precedence {
FromPrimitive::from_u8(self as u8 + 1).unwrap()
}
}
/* impl Add<u8> for Precedence {
type Output = Precedence;
fn add(self, rhs: u8) -> Self::Output {
FromPrimitive::from_u8(self as u8 + rhs).unwrap()
}
} */
struct Compiler {
token_iter: TokenIter,
chunk: Chunk,
current_line: u32,
}
impl Compiler {
fn new(tokens: Vec<Token>) -> Self {
Compiler {
token_iter: TokenIter::new(tokens),
chunk: Chunk::default(),
current_line: u32::MAX,
}
}
fn current_chunk(&mut self) -> &mut Chunk {
&mut self.chunk
}
fn compile(self) -> Result<Chunk, CompilerError> {
let mut compiler = self;
compiler.expression()?;
compiler.emit_opcode(Opcode::Return);
// assert_eq!(compiler.token_iter.next().unwrap().token_type, TokenType::EOF);
if compiler.peek_token().token_type != TokenType::EOF {
return Err(CompilerError::Todo {
msg: format!("Expected EOF, found {}", compiler.next_token()),
});
}
if DEBUG_PRINT_CODE {
compiler.chunk.disassemble("code");
println!()
}
Ok(compiler.chunk)
}
fn parse_precedence(&mut self, precedence: Precedence) -> CompilerResult {
let prefix_rule = get_rule(self.peek_token().token_type).prefix;
// hacky, but can't be compared as raw types
if prefix_rule as usize == Compiler::null as usize {
return Err(CompilerError::Todo {
msg: "Expect expression".to_owned(),
});
}
prefix_rule(self)?;
loop {
let rule = get_rule(self.peek_token().token_type);
if precedence > rule.precedence {
break;
}
let infix_rule = get_rule(self.peek_token().token_type).infix;
infix_rule(self)?;
}
Ok(())
}
/* fn synchronise(&mut self) {
loop {
if self.token_iter.is_empty() {
return;
}
// when synchronising: assume all false
/* self.is_in_loop = false;
self.is_in_class = false;
self.is_in_function = false;
self.is_in_init = false; */
let peek_token = self.peek_token();
// if we match a synchronisation point: return
match peek_token.token_type {
TokenType::Class
| TokenType::Fun
| TokenType::Var
| TokenType::For
| TokenType::If
| TokenType::While
| TokenType::Print
| TokenType::Return
| TokenType::EOF => return,
TokenType::Semicolon => {
// discard semicolon first, then return
assert_eq!(self.next_token().token_type, TokenType::Semicolon);
return;
}
_ => {}
}
// no sync point: discard token
let _ = self.next_token();
// println!("Discarding {} token", self.next_token());
}
} */
fn expression(&mut self) -> CompilerResult {
self.parse_precedence(Precedence::Assignment)
}
fn binary(&mut self) -> CompilerResult {
let token = self.next_token();
let rule = get_rule(token.token_type);
// increment precedence to make binary operators left-associative
self.parse_precedence(rule.precedence.inc())?;
match token.token_type {
TokenType::Plus => self.emit_opcode(Opcode::Add),
TokenType::Minus => self.emit_opcode(Opcode::Subtract),
TokenType::Star => self.emit_opcode(Opcode::Multiply),
TokenType::Slash => self.emit_opcode(Opcode::Divide),
tt => unreachable!("Called binary() on token type {tt:?}"),
}
Ok(())
}
fn unary(&mut self) -> CompilerResult {
let token = self.next_token();
self.parse_precedence(Precedence::Unary)?;
self.set_line(&token);
match self.next_token().token_type {
TokenType::Minus => {
self.emit_opcode(Opcode::Negate);
}
TokenType::Bang => todo!(),
_ => unreachable!("Called unary, but next token had token_type {:?}", token.token_type),
}
Ok(())
}
fn grouping(&mut self) -> CompilerResult {
assert_eq!(self.next_token().token_type, TokenType::LeftParen);
self.expression()?;
self.consume_token(TokenType::RightParen, |token| CompilerError::MissingRightParen {
code_pos: token.code_pos,
})?;
Ok(())
}
fn number(&mut self) -> CompilerResult {
let token = self.next_token();
assert_eq!(token.token_type, TokenType::Number);
let num = token.num_data();
let value = Value::Number(num);
self.emit_constant(value);
Ok(())
}
fn string(&mut self) -> CompilerResult {
todo!()
}
fn literal(&mut self) -> CompilerResult {
todo!()
}
fn null(&mut self) -> CompilerResult {
panic!("Called null on token {}", self.peek_token());
}
fn next_token(&mut self) -> Token {
let token = self.token_iter.next().unwrap();
// println!("Next token: {next:?}");
if token.token_type == TokenType::EOF {
unreachable!("Someone ate a EOF token");
}
self.set_line(&token);
token
// self.token_iter.next().unwrap()
}
fn peek_token(&mut self) -> &Token {
self.token_iter.peek().unwrap()
}
fn consume_token<F>(&mut self, token_type: TokenType, err_fn: F) -> CompilerResult
where
F: Fn(Token) -> CompilerError,
{
match &self.peek_token().token_type {
tt if tt == &token_type => {
let _ = self.next_token();
Ok(())
}
// call err_fn with dummy token so we don't have to eat the EOF token
TokenType::EOF => Err(err_fn(Token::new(TokenType::EOF, self.peek_token().code_pos))),
_ => Err(err_fn(self.next_token())),
}
}
fn emit_opcode(&mut self, opcode: Opcode) {
let line = self.current_line;
self.current_chunk().write_opcode(opcode, line);
}
/* fn emit_byte(&mut self, byte: u8) {
let line = self.current_line;
self.current_chunk().write_byte(byte, line);
}
fn emit_bytes(&mut self, bytes: &[u8]) {
let line = self.current_line;
self.current_chunk().write_bytes(bytes, line);
} */
fn emit_opcode_byte(&mut self, opcode: Opcode, byte: u8) {
let line = self.current_line;
self.current_chunk().write_opcode(opcode, line);
self.current_chunk().write_byte(byte, line);
}
fn emit_opcode_bytes(&mut self, opcode: Opcode, bytes: &[u8]) {
let line = self.current_line;
self.current_chunk().write_opcode(opcode, line);
self.current_chunk().write_bytes(bytes, line);
}
fn emit_constant(&mut self, value: Value) {
let const_idx = self.current_chunk().add_constant(value);
if const_idx <= u8::MAX as usize {
self.emit_opcode_byte(Opcode::LoadConst, const_idx.try_into().unwrap());
} else if const_idx <= u16::MAX as usize {
self.emit_opcode_bytes(Opcode::LoadConstLong, &u16_to_bytes(const_idx.try_into().unwrap()))
} else {
panic!("Tried to add more than {} constants to current chunk", u16::MAX);
}
}
fn set_line(&mut self, token: &Token) {
self.current_line = token.code_pos.line;
}
}
/*====================================================================================================================*/
type ParseFn = fn(&mut Compiler) -> CompilerResult;
struct ParseRule {
#[allow(dead_code)] // supress unused warning
token_type: TokenType,
prefix: ParseFn,
infix: ParseFn,
precedence: Precedence,
}
fn get_rule(token_type: TokenType) -> &'static ParseRule {
let idx = token_type as usize;
&RULES_TABLE[idx]
}
const RULES_TABLE: [ParseRule; 40] = gen_rules_table![
LeftParen => (grouping, null, Null),
RightParen => (null, null, Null),
LeftBrace => (null, null, Null),
RightBrace => (null, null, Null),
Comma => (null, null, Null),
Dot => (null, null, Null),
Minus => (unary, binary, Term),
Plus => (null, binary, Term),
Semicolon => (null, null, Null),
Slash => (null, binary, Factor),
Star => (null, binary, Factor),
Bang => (unary, null, Unary),
BangEqual => (null, binary, Equality),
Equal => (null, null, Null),
EqualEqual => (null, binary, Equality),
Greater => (null, binary, Comparison),
GreaterEqual => (null, binary, Comparison),
Less => (null, binary, Comparison),
LessEqual => (null, binary, Comparison),
Identifier => (null, null, Null),
String => (string, null, Null),
Number => (number, null, Null),
And => (null, null, Null),
Break => (null, null, Null),
Class => (null, null, Null),
Else => (null, null, Null),
False => (literal, null, Null),
Fun => (null, null, Null),
For => (null, null, Null),
If => (null, null, Null),
Nil => (literal, null, Null),
Or => (null, null, Null),
Print => (null, null, Null),
Return => (null, null, Null),
Super => (null, null, Null),
This => (null, null, Null),
True => (literal, null, Null),
Var => (null, null, Null),
While => (null, null, Null),
EOF => (null, null, Null)
];
macro_rules! rules_table_tests {
() => {
rules_table_tests!(@counter);
};
(@counter 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1) => {
const_assert_eq!(40, RULES_TABLE.len());
};
(@counter $($counter:tt)*) => {
const_assert_eq!(RULES_TABLE[$($counter +)* 0].token_type as u8, $($counter +)* 0);
rules_table_tests!(@counter $($counter)* 1);
};
}
rules_table_tests!();

View file

@ -1,3 +1,8 @@
pub const DEBUG_PRINT_CODE: bool = true;
pub const DEBUG_TRACE_EXECUTION: bool = true;
/*====================================================================================================================*/
#[derive(Debug)] #[derive(Debug)]
struct LineInfo { struct LineInfo {
start_offset: usize, start_offset: usize,

View file

@ -1,5 +1,6 @@
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use crate::misc::u16_from_bytes;
use crate::Chunk; use crate::Chunk;
use crate::Opcode; use crate::Opcode;
@ -45,11 +46,11 @@ impl Chunk {
offset += 1; offset += 1;
} }
LoadConstLong => { LoadConstLong => {
let bytes = &self.code()[offset..offset + 3]; let bytes = &self.code()[offset..offset + 2];
let constant_idx = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], 0]); let constant_idx = u16_from_bytes([bytes[0], bytes[1]]);
let value = self.get_constant(constant_idx as usize); let value = self.get_constant(constant_idx as usize);
print!("{constant_idx:4} '{value}'"); print!("{constant_idx:4} '{value}'");
offset += 3; offset += 2;
} }
Add | Subtract | Multiply | Divide | Negate | Return => {} Add | Subtract | Multiply | Divide | Negate | Return => {}
} }

View file

@ -1,11 +1,16 @@
use itertools::Itertools; use itertools::Itertools;
use rlox2_frontend::lexer::LexerError; use rlox2_frontend::lexer::{CodePos, LexerError};
use thiserror::Error; use thiserror::Error;
use crate::{Opcode, Value}; use crate::{Opcode, Value};
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum CompileError {} pub enum CompilerError {
#[error("Missing closing parenthesis at {code_pos}")]
MissingRightParen { code_pos: CodePos },
#[error("{msg}")]
Todo { msg: String },
}
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum RuntimeError { pub enum RuntimeError {
@ -18,32 +23,32 @@ pub enum RuntimeError {
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum InterpretError { pub enum LoxError {
#[error("{0}", format_multiple_errors(inner))] #[error("{0}", format_multiple_errors(inner))]
LexerError { inner: Vec<LexerError> }, LexerError { inner: Vec<LexerError> },
#[error("{inner}")] #[error("{inner}")]
CompileError { inner: CompileError }, CompileError { inner: CompilerError },
#[error("{inner}")] #[error("{inner}")]
RuntimeError { inner: RuntimeError }, RuntimeError { inner: RuntimeError },
#[error("Called exit() with exit code {exit_code}")] #[error("Called exit() with exit code {exit_code}")]
Exit { exit_code: i32 }, Exit { exit_code: i32 },
} }
impl From<Vec<LexerError>> for InterpretError { impl From<Vec<LexerError>> for LoxError {
fn from(lexer_errs: Vec<LexerError>) -> Self { fn from(lexer_errs: Vec<LexerError>) -> Self {
InterpretError::LexerError { inner: lexer_errs } LoxError::LexerError { inner: lexer_errs }
} }
} }
impl From<CompileError> for InterpretError { impl From<CompilerError> for LoxError {
fn from(compile_err: CompileError) -> Self { fn from(compile_err: CompilerError) -> Self {
InterpretError::CompileError { inner: compile_err } LoxError::CompileError { inner: compile_err }
} }
} }
impl From<RuntimeError> for InterpretError { impl From<RuntimeError> for LoxError {
fn from(runtime_err: RuntimeError) -> Self { fn from(runtime_err: RuntimeError) -> Self {
InterpretError::RuntimeError { inner: runtime_err } LoxError::RuntimeError { inner: runtime_err }
} }
} }

View file

@ -1,16 +1,18 @@
mod chunk; mod chunk;
mod compile; mod compiler;
mod debug; mod debug;
mod disassemble; mod disassembler;
mod error; mod error;
mod macros;
mod misc;
mod opcode; mod opcode;
mod run; mod run;
mod value; mod value;
mod vm; mod vm;
pub use chunk::Chunk; pub use chunk::Chunk;
pub use compile::compile; pub use compiler::compile;
pub use error::InterpretError; pub use error::LoxError;
pub use opcode::Opcode; pub use opcode::Opcode;
pub use run::{run, run_repl}; pub use run::{run, run_repl};
pub use value::Value; pub use value::Value;

34
vm/src/macros.rs Normal file
View file

@ -0,0 +1,34 @@
#[macro_export]
macro_rules! debug_println {
($($arg:tt)*) => {
if cfg!(debug_assertions) {
println!($($arg)*);
}
};
}
#[macro_export]
macro_rules! single_rule {
( $token_type:tt, ( $prefix:tt, $infix:tt, $prec:ident ) ) => {
ParseRule {
token_type: rlox2_frontend::lexer::TokenType::$token_type,
prefix: Compiler::$prefix,
infix: Compiler::$infix,
precedence: Precedence::$prec,
}
};
}
#[macro_export]
macro_rules! gen_rules_table {
( $( $token_type:tt => $args:tt ),* ) => {
{
[
$(
single_rule!($token_type, $args),
)*
]
}
};
}

7
vm/src/misc.rs Normal file
View file

@ -0,0 +1,7 @@
pub(crate) fn u16_to_bytes(short: u16) -> [u8; 2] {
short.to_le_bytes()
}
pub(crate) fn u16_from_bytes(bytes: [u8; 2]) -> u16 {
u16::from_le_bytes(bytes)
}

View file

@ -2,7 +2,7 @@ use std::io::Write;
use rlox2_frontend::lexer::{scan_tokens, Token}; use rlox2_frontend::lexer::{scan_tokens, Token};
use crate::{compile, InterpretError, VM}; use crate::{compile, LoxError, VM};
/* pub fn vm_main() { /* pub fn vm_main() {
let mut chunk = Chunk::new(); let mut chunk = Chunk::new();
@ -82,16 +82,18 @@ pub fn run_repl(vm: &mut VM) {
match run(input_buf, vm) { match run(input_buf, vm) {
Ok(()) => {} Ok(()) => {}
Err(InterpretError::Exit { exit_code }) => std::process::exit(exit_code), Err(LoxError::Exit { exit_code }) => std::process::exit(exit_code),
Err(err) => eprintln!("{err}"), Err(err) => eprintln!("{err}"),
} }
} }
} }
pub fn run(source: &str, _vm: &mut VM) -> Result<(), InterpretError> { pub fn run(source: &str, vm: &mut VM) -> Result<(), LoxError> {
let tokens: Vec<Token> = scan_tokens(source)?; let tokens: Vec<Token> = scan_tokens(source)?;
compile(tokens); let chunk = compile(tokens)?;
vm.interpret(&chunk)?;
Ok(()) Ok(())
} }

View file

@ -2,14 +2,14 @@ use std::ptr;
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use crate::debug::DEBUG_TRACE_EXECUTION;
use crate::error::RuntimeError; use crate::error::RuntimeError;
use crate::{Chunk, Opcode}; use crate::{Chunk, Opcode};
use crate::{InterpretError, Value}; use crate::{LoxError, Value};
/*====================================================================================================================*/ /*====================================================================================================================*/
const STACK_MAX: usize = 256; const STACK_MAX: usize = 256;
const DEBUG_TRACE_EXECUTION: bool = true;
/*====================================================================================================================*/ /*====================================================================================================================*/
@ -23,14 +23,6 @@ pub struct VM {
stack_top: *mut Value, stack_top: *mut Value,
} }
/* macro_rules! debug_println {
($($arg:tt)*) => {
if cfg!(debug_assertions) {
println!($($arg)*);
}
};
} */
impl VM { impl VM {
pub fn new() -> Self { pub fn new() -> Self {
const NIL: Value = Value::Nil; const NIL: Value = Value::Nil;
@ -84,7 +76,7 @@ impl VM {
std::mem::take(&mut *self.stack_top) std::mem::take(&mut *self.stack_top)
} }
pub fn interpret(&mut self, chunk: &Chunk) -> Result<(), InterpretError> { pub fn interpret(&mut self, chunk: &Chunk) -> Result<(), LoxError> {
self.chunk_ptr = chunk; self.chunk_ptr = chunk;
self.ip = chunk.code().as_ptr(); self.ip = chunk.code().as_ptr();
@ -108,7 +100,7 @@ impl VM {
} }
unsafe fn read_constant_long(&mut self) -> &Value { unsafe fn read_constant_long(&mut self) -> &Value {
let bytes = [self.read_byte(), self.read_byte(), self.read_byte(), 0]; let bytes = [self.read_byte(), self.read_byte(), 0, 0];
let constant_idx = u32::from_le_bytes(bytes) as usize; let constant_idx = u32::from_le_bytes(bytes) as usize;
self.chunk().get_constant(constant_idx) self.chunk().get_constant(constant_idx)
@ -117,7 +109,6 @@ impl VM {
unsafe fn run(&mut self) -> Result<(), RuntimeError> { unsafe fn run(&mut self) -> Result<(), RuntimeError> {
loop { loop {
if DEBUG_TRACE_EXECUTION { if DEBUG_TRACE_EXECUTION {
println!();
self.print_stack(); self.print_stack();
self.chunk().disassemble_instruction(self.offset()); self.chunk().disassemble_instruction(self.offset());
println!(); println!();
@ -128,12 +119,10 @@ impl VM {
match opcode { match opcode {
Opcode::LoadConst => { Opcode::LoadConst => {
let value = self.read_constant().clone(); let value = self.read_constant().clone();
println!("Constant: {value}");
self.push_value(value); self.push_value(value);
} }
Opcode::LoadConstLong => { Opcode::LoadConstLong => {
let value = self.read_constant_long().clone(); let value = self.read_constant_long().clone();
println!("LongConstant: {value}");
self.push_value(value); self.push_value(value);
} }
@ -193,7 +182,7 @@ impl VM {
Opcode::Return => { Opcode::Return => {
let value = self.pop_value(); let value = self.pop_value();
debug_assert_eq!(self.stack_len(), 0); debug_assert_eq!(self.stack_len(), 0);
println!("Return: {value}"); println!("{value}");
return Ok(()); return Ok(());
} }
} }