@ -0,0 +1,2 @@ | |||
/target | |||
.idea |
@ -0,0 +1,248 @@ | |||
# This file is automatically @generated by Cargo. | |||
# It is not intended for manual editing. | |||
[[package]] | |||
name = "autocfg" | |||
version = "1.0.1" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" | |||
[[package]] | |||
name = "cfg-if" | |||
version = "0.1.10" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" | |||
[[package]] | |||
name = "getrandom" | |||
version = "0.1.15" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" | |||
dependencies = [ | |||
"cfg-if", | |||
"libc", | |||
"wasi", | |||
] | |||
[[package]] | |||
name = "libc" | |||
version = "0.2.79" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743" | |||
[[package]] | |||
name = "num-derive" | |||
version = "0.2.5" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "eafd0b45c5537c3ba526f79d3e75120036502bebacbb3f3220914067ce39dbf2" | |||
dependencies = [ | |||
"proc-macro2 0.4.30", | |||
"quote 0.6.13", | |||
"syn 0.15.44", | |||
] | |||
[[package]] | |||
name = "num-traits" | |||
version = "0.2.12" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" | |||
dependencies = [ | |||
"autocfg", | |||
] | |||
[[package]] | |||
name = "phf" | |||
version = "0.8.0" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" | |||
dependencies = [ | |||
"phf_macros", | |||
"phf_shared", | |||
"proc-macro-hack", | |||
] | |||
[[package]] | |||
name = "phf_generator" | |||
version = "0.8.0" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" | |||
dependencies = [ | |||
"phf_shared", | |||
"rand", | |||
] | |||
[[package]] | |||
name = "phf_macros" | |||
version = "0.8.0" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" | |||
dependencies = [ | |||
"phf_generator", | |||
"phf_shared", | |||
"proc-macro-hack", | |||
"proc-macro2 1.0.24", | |||
"quote 1.0.7", | |||
"syn 1.0.44", | |||
] | |||
[[package]] | |||
name = "phf_shared" | |||
version = "0.8.0" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" | |||
dependencies = [ | |||
"siphasher", | |||
] | |||
[[package]] | |||
name = "ppv-lite86" | |||
version = "0.2.9" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20" | |||
[[package]] | |||
name = "proc-macro-hack" | |||
version = "0.5.18" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598" | |||
[[package]] | |||
name = "proc-macro2" | |||
version = "0.4.30" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" | |||
dependencies = [ | |||
"unicode-xid 0.1.0", | |||
] | |||
[[package]] | |||
name = "proc-macro2" | |||
version = "1.0.24" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" | |||
dependencies = [ | |||
"unicode-xid 0.2.1", | |||
] | |||
[[package]] | |||
name = "quote" | |||
version = "0.6.13" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" | |||
dependencies = [ | |||
"proc-macro2 0.4.30", | |||
] | |||
[[package]] | |||
name = "quote" | |||
version = "1.0.7" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" | |||
dependencies = [ | |||
"proc-macro2 1.0.24", | |||
] | |||
[[package]] | |||
name = "rand" | |||
version = "0.7.3" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" | |||
dependencies = [ | |||
"getrandom", | |||
"libc", | |||
"rand_chacha", | |||
"rand_core", | |||
"rand_hc", | |||
"rand_pcg", | |||
] | |||
[[package]] | |||
name = "rand_chacha" | |||
version = "0.2.2" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" | |||
dependencies = [ | |||
"ppv-lite86", | |||
"rand_core", | |||
] | |||
[[package]] | |||
name = "rand_core" | |||
version = "0.5.1" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" | |||
dependencies = [ | |||
"getrandom", | |||
] | |||
[[package]] | |||
name = "rand_hc" | |||
version = "0.2.0" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" | |||
dependencies = [ | |||
"rand_core", | |||
] | |||
[[package]] | |||
name = "rand_pcg" | |||
version = "0.2.1" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" | |||
dependencies = [ | |||
"rand_core", | |||
] | |||
[[package]] | |||
name = "siphasher" | |||
version = "0.3.3" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7" | |||
[[package]] | |||
name = "syn" | |||
version = "0.15.44" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" | |||
dependencies = [ | |||
"proc-macro2 0.4.30", | |||
"quote 0.6.13", | |||
"unicode-xid 0.1.0", | |||
] | |||
[[package]] | |||
name = "syn" | |||
version = "1.0.44" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "e03e57e4fcbfe7749842d53e24ccb9aa12b7252dbe5e91d2acad31834c8b8fdd" | |||
dependencies = [ | |||
"proc-macro2 1.0.24", | |||
"quote 1.0.7", | |||
"unicode-xid 0.2.1", | |||
] | |||
[[package]] | |||
name = "unicode-xid" | |||
version = "0.1.0" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" | |||
[[package]] | |||
name = "unicode-xid" | |||
version = "0.2.1" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" | |||
[[package]] | |||
name = "wasi" | |||
version = "0.9.0+wasi-snapshot-preview1" | |||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" | |||
[[package]] | |||
name = "xbasic" | |||
version = "0.1.0" | |||
dependencies = [ | |||
"num-derive", | |||
"num-traits", | |||
"phf", | |||
] |
@ -0,0 +1,12 @@ | |||
[package] | |||
name = "xbasic" | |||
version = "0.1.0" | |||
authors = ["Stephen <stephen@stephendownward.ca>"] | |||
edition = "2018" | |||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | |||
[dependencies] | |||
phf = { version = "0.8", features = ["macros"] } | |||
num-traits = "0.2" | |||
num-derive = "0.2" |
@ -0,0 +1,2 @@ | |||
tab_spaces = 4 | |||
hard_tabs = true |
@ -0,0 +1,4 @@ | |||
pub trait BasicIO { | |||
fn read_line(&mut self); | |||
fn write_line(&mut self, line: String); | |||
} |
@ -0,0 +1,94 @@ | |||
use crate::error_handler::ErrorHandler; | |||
use crate::expr::{BinaryExpr, CallExpr, ExprValue, LogicalExpr, UnaryExpr, VariableExpr}; | |||
use crate::opcodes::OpCode; | |||
use crate::stmt::{AssignStmt, ExpressionStmt, PrintStmt, Stmt}; | |||
use crate::visitor::{ExprVisitor, StmtVisitor}; | |||
pub struct Compiler<'a> { | |||
stmts: Vec<Stmt>, | |||
error_handler: &'a mut ErrorHandler, | |||
pub(crate) instructions: Vec<u8>, | |||
pub(crate) literals: Vec<ExprValue>, | |||
} | |||
impl<'a> Compiler<'a> { | |||
pub fn new(stmts: Vec<Stmt>, error_handler: &'a mut ErrorHandler) -> Self { | |||
Self { | |||
stmts, | |||
error_handler, | |||
instructions: Vec::new(), | |||
literals: Vec::new(), | |||
} | |||
} | |||
pub fn compile(&mut self) -> Result<(), ()> { | |||
for stmt in self.stmts.clone() { | |||
stmt.accept(self); | |||
} | |||
if self.error_handler.had_errors { | |||
Err(()) | |||
} else { | |||
Ok(()) | |||
} | |||
} | |||
fn emit_opcode(&mut self, opcode: OpCode) { | |||
self.instructions.push(opcode as u8); | |||
} | |||
} | |||
impl<'a> StmtVisitor<()> for Compiler<'a> { | |||
fn visit_expression_stmt(&mut self, stmt: &ExpressionStmt) { | |||
stmt.value.accept(self); | |||
} | |||
fn visit_print_stmt(&mut self, stmt: &PrintStmt) { | |||
stmt.value.accept(self); | |||
self.emit_opcode(OpCode::Print); | |||
} | |||
fn visit_assign_stmt(&mut self, stmt: &AssignStmt) { | |||
unimplemented!() | |||
} | |||
} | |||
impl<'a> ExprVisitor<()> for Compiler<'a> { | |||
fn visit_variable_expr(&mut self, expr: &VariableExpr) { | |||
unimplemented!() | |||
} | |||
fn visit_logical_expr(&mut self, expr: &LogicalExpr) { | |||
unimplemented!() | |||
} | |||
fn visit_binary_expr(&mut self, expr: &BinaryExpr) { | |||
unimplemented!() | |||
} | |||
fn visit_unary_expr(&mut self, expr: &UnaryExpr) { | |||
unimplemented!() | |||
} | |||
fn visit_call_expr(&mut self, expr: &CallExpr) { | |||
unimplemented!() | |||
} | |||
fn visit_literal_expr(&mut self, expr: &ExprValue) { | |||
let index = self.literals.len(); | |||
self.literals.push(expr.clone()); | |||
if index <= 255 { | |||
self.emit_opcode(OpCode::Literal8); | |||
self.instructions.push(index as u8); | |||
} else if index <= 65535 { | |||
self.emit_opcode(OpCode::Literal16); | |||
self.instructions.push((index >> 8) as u8); | |||
self.instructions.push((index & 0xFF) as u8); | |||
} else { | |||
// Too many literals | |||
self.error_handler | |||
.errors | |||
.push("Cannot have more than 65,535 literals".to_string()); | |||
} | |||
} | |||
} |
@ -0,0 +1,46 @@ | |||
use crate::tokens::{Token, TokenType}; | |||
pub struct ErrorHandler { | |||
pub had_errors: bool, | |||
pub had_runtime_error: bool, | |||
pub errors: Vec<String>, | |||
} | |||
impl ErrorHandler { | |||
pub fn new() -> Self { | |||
Self { | |||
had_errors: false, | |||
had_runtime_error: false, | |||
errors: Vec::new(), | |||
} | |||
} | |||
pub fn reset(&mut self) { | |||
self.had_errors = false; | |||
self.had_runtime_error = false; | |||
self.errors = Vec::new() | |||
} | |||
pub fn error(&mut self, line: usize, msg: &str) { | |||
self.report(line, "", msg); | |||
} | |||
pub fn error_token(&mut self, token: &Token, msg: &str) { | |||
if token.token_type == TokenType::Eof { | |||
self.report(token.line, "at end", msg); | |||
} else { | |||
self.report(token.line, &format!("at '{}'", token.lexeme), msg); | |||
} | |||
} | |||
pub fn runtime_error_token(&mut self, token: Token, msg: &str) { | |||
self.had_runtime_error = true; | |||
self.errors.push(format!("{}\n[line {}]", msg, token.line)); | |||
} | |||
fn report(&mut self, line: usize, location: &str, msg: &str) { | |||
self.had_errors = true; | |||
self.errors | |||
.push(format!("[line {}] Error {}: {}", line, location, msg)); | |||
} | |||
} |
@ -0,0 +1,114 @@ | |||
use crate::tokens::{Token, TokenType}; | |||
use crate::visitor::ExprVisitor; | |||
#[derive(Clone)] | |||
pub enum Expr { | |||
Variable(VariableExpr), | |||
Logical(LogicalExpr), | |||
Binary(BinaryExpr), | |||
Unary(UnaryExpr), | |||
Call(CallExpr), | |||
Literal(ExprValue), | |||
} | |||
impl Expr { | |||
pub fn new_variable(name: Token) -> Self { | |||
Self::Variable(VariableExpr { name }) | |||
} | |||
pub fn new_logical(left: Expr, operator: TokenType, right: Expr) -> Self { | |||
Self::Logical(LogicalExpr { | |||
left: Box::new(left), | |||
operator, | |||
right: Box::new(right), | |||
}) | |||
} | |||
pub fn new_binary(left: Expr, operator: TokenType, right: Expr) -> Self { | |||
Self::Binary(BinaryExpr { | |||
left: Box::new(left), | |||
operator, | |||
right: Box::new(right), | |||
}) | |||
} | |||
pub fn new_unary(operator: TokenType, right: Expr) -> Self { | |||
Self::Unary(UnaryExpr { | |||
operator, | |||
right: Box::new(right), | |||
}) | |||
} | |||
pub fn new_call(callee: Expr, paren: Token, arguments: Vec<Expr>) -> Self { | |||
Self::Call(CallExpr { | |||
callee: Box::new(callee), | |||
paren, | |||
arguments, | |||
}) | |||
} | |||
pub fn new_literal(value: ExprValue) -> Self { | |||
Self::Literal(value) | |||
} | |||
pub fn accept<U, V>(&self, visitor: &mut V) -> U | |||
where | |||
V: ExprVisitor<U>, | |||
{ | |||
match self { | |||
Expr::Variable(expr) => visitor.visit_variable_expr(expr), | |||
Expr::Logical(expr) => visitor.visit_logical_expr(expr), | |||
Expr::Binary(expr) => visitor.visit_binary_expr(expr), | |||
Expr::Unary(expr) => visitor.visit_unary_expr(expr), | |||
Expr::Call(expr) => visitor.visit_call_expr(expr), | |||
Expr::Literal(expr) => visitor.visit_literal_expr(expr), | |||
} | |||
} | |||
} | |||
#[derive(Clone)] | |||
pub struct AssignExpr { | |||
pub name: Token, | |||
pub value: Box<Expr>, | |||
} | |||
#[derive(Clone)] | |||
pub struct VariableExpr { | |||
pub name: Token, | |||
} | |||
#[derive(Clone)] | |||
pub struct LogicalExpr { | |||
pub left: Box<Expr>, | |||
pub operator: TokenType, | |||
pub right: Box<Expr>, | |||
} | |||
#[derive(Clone)] | |||
pub struct BinaryExpr { | |||
pub left: Box<Expr>, | |||
pub operator: TokenType, | |||
pub right: Box<Expr>, | |||
} | |||
#[derive(Clone)] | |||
pub struct UnaryExpr { | |||
pub operator: TokenType, | |||
pub right: Box<Expr>, | |||
} | |||
#[derive(Clone)] | |||
pub struct CallExpr { | |||
pub callee: Box<Expr>, | |||
pub paren: Token, | |||
pub arguments: Vec<Expr>, | |||
} | |||
#[derive(Clone)] | |||
pub enum ExprValue { | |||
Boolean(bool), | |||
Integer(i16), | |||
Long(i32), | |||
Single(f32), | |||
Double(f64), | |||
String(String), | |||
} |
@ -0,0 +1,56 @@ | |||
use crate::basic_io::BasicIO; | |||
use crate::xbasic::XBasic; | |||
use std::io; | |||
use std::io::Write; | |||
mod basic_io; | |||
mod compiler; | |||
mod error_handler; | |||
mod expr; | |||
mod opcodes; | |||
mod parser; | |||
mod scanner; | |||
mod stmt; | |||
mod tokens; | |||
mod visitor; | |||
mod vm; | |||
mod xbasic; | |||
struct ShellIO {} | |||
impl BasicIO for ShellIO { | |||
fn read_line(&mut self) { | |||
unimplemented!() | |||
} | |||
fn write_line(&mut self, line: String) { | |||
println!("{}", line); | |||
} | |||
} | |||
fn main() { | |||
run_prompt(); | |||
} | |||
fn run_prompt() { | |||
let mut io = ShellIO {}; | |||
let mut xb = XBasic::new(&mut io); | |||
loop { | |||
let mut input = String::new(); | |||
print!("> "); | |||
io::stdout().flush().unwrap(); | |||
match io::stdin().read_line(&mut input) { | |||
Ok(_) => { | |||
xb.error_handler.reset(); | |||
match xb.run(&input) { | |||
Ok(_) => {} | |||
Err(_) => { | |||
for e in &xb.error_handler.errors { | |||
println!("{}", e); | |||
} | |||
} | |||
} | |||
} | |||
Err(_) => return, | |||
} | |||
} | |||
} |
@ -0,0 +1,9 @@ | |||
use num_derive::FromPrimitive; | |||
#[derive(Copy, Clone, FromPrimitive)] | |||
pub(crate) enum OpCode { | |||
Pop, | |||
Print, | |||
Literal8, | |||
Literal16, | |||
} |
@ -0,0 +1,309 @@ | |||
use crate::error_handler::ErrorHandler; | |||
use crate::expr::{Expr, ExprValue}; | |||
use crate::stmt::Stmt; | |||
use crate::tokens::{Token, TokenType}; | |||
pub struct Parser<'a> { | |||
tokens: Vec<Token>, | |||
current: usize, | |||
error_handler: &'a mut ErrorHandler, | |||
} | |||
impl<'a> Parser<'a> { | |||
pub fn new(tokens: Vec<Token>, error_handler: &'a mut ErrorHandler) -> Self { | |||
Self { | |||
current: 0, | |||
error_handler, | |||
tokens, | |||
} | |||
} | |||
pub fn parse(&mut self) -> Option<Vec<Stmt>> { | |||
let mut v: Vec<Stmt> = Vec::new(); | |||
while !self.is_at_end() { | |||
v.push(self.declaration()?); | |||
} | |||
Some(v) | |||
} | |||
fn declaration(&mut self) -> Option<Stmt> { | |||
match self.statement() { | |||
Some(stmt) => Some(stmt), | |||
None => { | |||
self.synchronize(); | |||
None | |||
} | |||
} | |||
} | |||
fn statement(&mut self) -> Option<Stmt> { | |||
if self.match_type(&TokenType::Print).is_some() { | |||
return self.print_statement(); | |||
} | |||
if self.peek_next().token_type == TokenType::Equal { | |||
let expr = self.expression()?; | |||
// Consume equal | |||
let equals = self.advance(); | |||
let val = self.expression()?; | |||
return match expr { | |||
Expr::Variable(expr) => Some(Stmt::new_assign(expr.name, val)), | |||
_ => { | |||
self.error_handler | |||
.error_token(&equals, "Invalid assignment target."); | |||
None | |||
} | |||
}; | |||
} | |||
self.expression_statement() | |||
} | |||
fn print_statement(&mut self) -> Option<Stmt> { | |||
let value = self.expression()?; | |||
self.consume(TokenType::Newline, "Expected end of line after value.")?; | |||
Some(Stmt::new_print(value)) | |||
} | |||
fn expression_statement(&mut self) -> Option<Stmt> { | |||
let expr = self.expression()?; | |||
self.consume( | |||
TokenType::Newline, | |||
"Expected newline after expression statement.", | |||
)?; | |||
Some(Stmt::new_expression(expr)) | |||
} | |||
fn expression(&mut self) -> Option<Expr> { | |||
self.or() | |||
} | |||
fn or(&mut self) -> Option<Expr> { | |||
let mut left = self.and()?; | |||
while let Some(_operator) = self.match_type(&TokenType::Or) { | |||
let right = self.and()?; | |||
left = Expr::new_logical(left, TokenType::Or, right); | |||
} | |||
Some(left) | |||
} | |||
fn and(&mut self) -> Option<Expr> { | |||
let mut left = self.equality()?; | |||
while let Some(_operator) = self.match_type(&TokenType::And) { | |||
let right = self.and()?; | |||
left = Expr::new_logical(left, TokenType::And, right); | |||
} | |||
Some(left) | |||
} | |||
fn equality(&mut self) -> Option<Expr> { | |||
let mut expr = self.comparison()?; | |||
while let Some(op) = self.match_type(&TokenType::Equal) { | |||
match self.comparison() { | |||
Some(right) => expr = Expr::new_binary(expr, op.token_type.clone(), right), | |||
None => return None, | |||
} | |||
} | |||
Some(expr) | |||
} | |||
fn comparison(&mut self) -> Option<Expr> { | |||
let mut expr = self.addition()?; | |||
while let Some(operator) = self.match_types(&[ | |||
TokenType::Greater, | |||
TokenType::GreaterEqual, | |||
TokenType::Less, | |||
TokenType::LessEqual, | |||
]) { | |||
match self.addition() { | |||
Some(right) => expr = Expr::new_binary(expr, operator.token_type.clone(), right), | |||
None => return None, | |||
} | |||
} | |||
Some(expr) | |||
} | |||
fn addition(&mut self) -> Option<Expr> { | |||
let mut expr = self.multiplication()?; | |||
while let Some(operator) = self.match_types(&[TokenType::Minus, TokenType::Plus]) { | |||
match self.multiplication() { | |||
Some(right) => expr = Expr::new_binary(expr, operator.token_type.clone(), right), | |||
None => return None, | |||
} | |||
} | |||
Some(expr) | |||
} | |||
fn multiplication(&mut self) -> Option<Expr> { | |||
let mut expr = self.unary()?; | |||
while let Some(operator) = self.match_types(&[TokenType::Star, TokenType::Slash]) { | |||
match self.unary() { | |||
Some(right) => expr = Expr::new_binary(expr, operator.token_type.clone(), right), | |||
None => return None, | |||
} | |||
} | |||
Some(expr) | |||
} | |||
fn unary(&mut self) -> Option<Expr> { | |||
if let Some(operator) = self.match_types(&[TokenType::Not, TokenType::Minus]) { | |||
return match self.unary() { | |||
Some(right) => Some(Expr::new_unary(operator.token_type, right)), | |||
None => None, | |||
}; | |||
} | |||
self.call() | |||
} | |||
fn call(&mut self) -> Option<Expr> { | |||
let mut expr = self.primary()?; | |||
while self.match_type(&TokenType::LeftParen).is_some() { | |||
expr = self.finish_call(expr)?; | |||
} | |||
Some(expr) | |||
} | |||
fn finish_call(&mut self, callee: Expr) -> Option<Expr> { | |||
let mut args: Vec<Expr> = Vec::new(); | |||
if !self.check(&TokenType::RightParen) { | |||
args.push(self.expression()?); | |||
while self.match_type(&TokenType::Comma).is_some() { | |||
if args.len() >= 255 { | |||
self.error_handler | |||
.error_token(&self.peek(), "Cannot have more than 255 arguments."); | |||
} | |||
args.push(self.expression()?); | |||
} | |||
} | |||
let paren = self.consume(TokenType::RightParen, "Expected ')' after arguments.")?; | |||
Some(Expr::new_call(callee, paren, args)) | |||
} | |||
fn primary(&mut self) -> Option<Expr> { | |||
Some(match &self.advance().token_type { | |||
TokenType::False => Expr::new_literal(ExprValue::Boolean(false)), | |||
TokenType::True => Expr::new_literal(ExprValue::Boolean(true)), | |||
TokenType::Integer(x) => Expr::new_literal(ExprValue::Integer(*x)), | |||
TokenType::Long(x) => Expr::new_literal(ExprValue::Long(*x)), | |||
TokenType::Single(x) => Expr::new_literal(ExprValue::Single(*x)), | |||
TokenType::Double(x) => Expr::new_literal(ExprValue::Double(*x)), | |||
TokenType::String(x) => Expr::new_literal(ExprValue::String(x.to_string())), | |||
TokenType::LeftParen => { | |||
if self.is_at_end() { | |||
self.error_handler | |||
.error_token(&self.previous(), "Expected ')' after expression."); | |||
return None; | |||
} | |||
match self.expression() { | |||
Some(expr) => { | |||
self.consume(TokenType::RightParen, "Expected ')' after expression."); | |||
// TODO I don't think we need a grouping here. | |||
// Expr::new_grouping(expr) | |||
expr | |||
} | |||
None => return None, | |||
} | |||
} | |||
TokenType::Identifier => Expr::new_variable(self.previous()), | |||
_ => { | |||
self.error_handler | |||
.error_token(&self.peek(), "Expected expression."); | |||
return None; | |||
} | |||
}) | |||
} | |||
fn synchronize(&mut self) { | |||
self.advance(); | |||
while !self.is_at_end() { | |||
// TODO more here? | |||
match self.peek().token_type { | |||
TokenType::Newline => { | |||
// jump over newline | |||
self.advance(); | |||
return; | |||
} | |||
_ => (), | |||
} | |||
self.advance(); | |||
} | |||
} | |||
fn consume(&mut self, token_type: TokenType, message: &str) -> Option<Token> { | |||
if self.check(&token_type) { | |||
return Some(self.advance()); | |||
} | |||
self.error_handler.error_token(&self.peek(), message); | |||
None | |||
} | |||
fn match_type(&mut self, token_type: &TokenType) -> Option<Token> { | |||
if self.check(token_type) { | |||
return Some(self.advance()); | |||
} | |||
None | |||
} | |||
fn match_types(&mut self, types: &[TokenType]) -> Option<Token> { | |||
for t in types { | |||
if let Some(x) = self.match_type(t) { | |||
return Some(x); | |||
} | |||
} | |||
None | |||
} | |||
fn is_at_end(&self) -> bool { | |||
self.peek().token_type == TokenType::Eof | |||
} | |||
fn check(&self, token_type: &TokenType) -> bool { | |||
if self.is_at_end() { | |||
return false; | |||
} | |||
&self.peek().token_type == token_type | |||
} | |||
fn advance(&mut self) -> Token { | |||
let v = self.peek(); | |||
if !self.is_at_end() { | |||
self.current += 1; | |||
} | |||
v | |||
} | |||
fn peek(&self) -> Token { | |||
self.tokens[self.current].clone() | |||
} | |||
fn peek_next(&self) -> Token { | |||
self.tokens[self.current + 1].clone() | |||
} | |||
fn previous(&self) -> Token { | |||
self.tokens[self.current - 1].clone() | |||
} | |||
} |
@ -0,0 +1,237 @@ | |||
use crate::error_handler::ErrorHandler; | |||
use crate::tokens::{Token, TokenType}; | |||
use phf::phf_map; | |||
use std::str::FromStr; | |||
static KEYWORDS: phf::Map<&'static str, TokenType> = phf_map! { | |||
"and" => TokenType::And, | |||
"else" => TokenType::Else, | |||
"false" => TokenType::False, | |||
"function" => TokenType::Function, | |||
"fn" => TokenType::Function, | |||
"if" => TokenType::If, | |||
"or" => TokenType::Or, | |||
"print" => TokenType::Print, | |||
"return" => TokenType::Return, | |||
"true" => TokenType::True, | |||
"while" => TokenType::While | |||
}; | |||
pub struct Scanner<'a> { | |||
chars: Vec<char>, | |||
tokens: Vec<Token>, | |||
start: usize, | |||
current: usize, | |||
line: usize, | |||
error_handler: &'a mut ErrorHandler, | |||
} | |||
impl<'a> Scanner<'a> { | |||
pub fn new(source: &str, error_handler: &'a mut ErrorHandler) -> Self { | |||
let chars = source.chars().collect(); | |||
Self { | |||
chars, | |||
tokens: Vec::new(), | |||
start: 0, | |||
current: 0, | |||
line: 1, | |||
error_handler, | |||
} | |||
} | |||
pub fn scan_tokens(&mut self) -> Result<Vec<Token>, ()> { | |||
while !self.is_at_end() { | |||
self.start = self.current; | |||
if let Err((line, msg)) = self.scan_token() { | |||
self.error_handler.error(line, &msg); | |||
return Err(()); | |||
} | |||
} | |||
self.tokens | |||
.push(Token::new(TokenType::Eof, "".to_string(), self.line)); | |||
Ok(self.tokens.clone()) | |||
} | |||
pub fn scan_token(&mut self) -> Result<(), (usize, String)> { | |||
let c = self.advance(); | |||
let token = match c { | |||
'(' => TokenType::LeftParen, | |||
')' => TokenType::RightParen, | |||
'{' => TokenType::LeftBrace, | |||
'}' => TokenType::RightBrace, | |||
',' => TokenType::Comma, | |||
'.' => TokenType::Dot, | |||
'-' => { | |||
if self.match_next('-') { | |||
TokenType::MinusMinus | |||
} else { | |||
TokenType::Minus | |||
} | |||
} | |||
'+' => { | |||
if self.match_next('+') { | |||
TokenType::PlusPlus | |||
} else { | |||
TokenType::Plus | |||
} | |||
} | |||
'*' => TokenType::Star, | |||
'/' => TokenType::Slash, | |||
'=' => TokenType::Equal, | |||
' ' | '\r' | '\t' => TokenType::NoToken, | |||
'\n' => { | |||
self.line += 1; | |||
TokenType::Newline | |||
} | |||
'"' => { | |||
if let Err(x) = self.string() { | |||
return Err(x); | |||
} | |||
TokenType::NoToken | |||
} | |||
'0'..='9' => { | |||
self.number(); | |||
TokenType::NoToken | |||
} | |||
_ => { | |||
if is_alpha(c) { | |||
self.identifier(); | |||
TokenType::NoToken | |||
} else { | |||
return Err((self.line, "Unexpected character.".to_string())); | |||
} | |||
} | |||
}; | |||
self.add_token(token); | |||
Ok(()) | |||
} | |||
fn is_at_end(&self) -> bool { | |||
self.current >= self.chars.len() | |||
} | |||
fn add_token(&mut self, token_type: TokenType) { | |||
if token_type == TokenType::NoToken { | |||
return; | |||
} | |||
let text: String = self.chars[self.start..self.current].iter().collect(); | |||
self.tokens.push(Token::new(token_type, text, self.line)); | |||
} | |||
fn advance(&mut self) -> char { | |||
self.current += 1; | |||
self.chars[self.current - 1] | |||
} | |||
fn peek(&self) -> char { | |||
if self.is_at_end() { | |||
return '\0'; | |||
} | |||
self.chars[self.current] | |||
} | |||
fn peek_next(&self) -> char { | |||
if self.current + 1 >= self.chars.len() { | |||
return '\0'; | |||
} | |||
self.chars[self.current + 1] | |||
} | |||
fn match_next(&mut self, expected: char) -> bool { | |||
if self.is_at_end() { | |||
return false; | |||
} | |||
if self.chars[self.current] != expected { | |||
return false; | |||
} | |||
self.current += 1; | |||
true | |||
} | |||
fn string(&mut self) -> Result<(), (usize, String)> { | |||
while self.peek() != '"' && !self.is_at_end() { | |||
if self.peek() == '\n' { | |||
self.line += 1; | |||
} | |||
self.advance(); | |||
} | |||
if self.is_at_end() { | |||
return Err((self.line, "Unterminated string.".to_string())); | |||
} | |||
self.advance(); // Closing " | |||
let value: String = self.chars[(self.start + 1)..(self.current - 1)] | |||
.iter() | |||
.collect(); | |||
self.add_token(TokenType::String(value)); | |||
Ok(()) | |||
} | |||
fn number(&mut self) { | |||
while is_digit(self.peek()) { | |||
self.advance(); | |||
} | |||
if self.peek() == '.' && is_digit(self.peek_next()) { | |||
self.advance(); // Consume "." | |||
while is_digit(self.peek()) { | |||
self.advance(); | |||
} // Keep consuming digits | |||
} | |||
let next_char = self.peek(); | |||
let s: String = self.chars[self.start..self.current].iter().collect(); | |||
// Consume variable type, if available | |||
if next_char == '%' || next_char == '&' || next_char == '!' || next_char == '#' { | |||
self.advance(); | |||
} | |||
// TODO don't unwrap here | |||
let t = match next_char { | |||
'%' => TokenType::Integer(i16::from_str(&s).unwrap()), | |||
'&' => TokenType::Long(i32::from_str(&s).unwrap()), | |||
'#' => TokenType::Double(f64::from_str(&s).unwrap()), | |||
_ => TokenType::Single(f32::from_str(&s).unwrap()), | |||
}; | |||
self.add_token(t) | |||
} | |||
fn identifier(&mut self) { | |||
while is_alphanumeric(self.peek()) { | |||
self.advance(); | |||
} | |||
let text: String = self.chars[self.start..self.current].iter().collect(); | |||
let token: TokenType = match KEYWORDS.get(text.as_str()) { | |||
Some(x) => x.clone(), | |||
None => TokenType::Identifier, | |||
}; | |||
self.add_token(token); | |||
} | |||
} | |||
fn is_digit(c: char) -> bool { | |||
('0'..='9').contains(&c) | |||
} | |||
fn is_alpha(c: char) -> bool { | |||
('a'..='z').contains(&c) || ('A'..'Z').contains(&c) || c == '_' | |||
} | |||
fn is_alphanumeric(c: char) -> bool { | |||
is_digit(c) || is_alpha(c) | |||
} |
@ -0,0 +1,53 @@ | |||
use crate::expr::Expr; | |||
use crate::tokens::Token; | |||
use crate::visitor::StmtVisitor; | |||
#[derive(Clone)] | |||
pub enum Stmt { | |||
Expression(ExpressionStmt), | |||
Print(PrintStmt), | |||
Assign(AssignStmt), | |||
//If(IfStmt), | |||
//While(WhileStmt) | |||
} | |||
impl Stmt { | |||
pub fn new_print(value: Expr) -> Self { | |||
Self::Print(PrintStmt { value }) | |||
} | |||
pub fn new_expression(value: Expr) -> Self { | |||
Self::Expression(ExpressionStmt { value }) | |||
} | |||
pub fn new_assign(name: Token, value: Expr) -> Self { | |||
Self::Assign(AssignStmt { name, value }) | |||
} | |||
pub fn accept<U, V>(&self, visitor: &mut V) -> U | |||
where | |||
V: StmtVisitor<U>, | |||
{ | |||
match self { | |||
Stmt::Assign(stmt) => visitor.visit_assign_stmt(stmt), | |||
Stmt::Expression(stmt) => visitor.visit_expression_stmt(stmt), | |||
Stmt::Print(stmt) => visitor.visit_print_stmt(stmt), | |||
} | |||
} | |||
} | |||
#[derive(Clone)] | |||
pub struct PrintStmt { | |||
pub(crate) value: Expr, | |||
} | |||
#[derive(Clone)] | |||
pub struct ExpressionStmt { | |||
pub value: Expr, | |||
} | |||
#[derive(Clone)] | |||
pub struct AssignStmt { | |||
name: Token, | |||
value: Expr, | |||
} |
@ -0,0 +1,62 @@ | |||
#[derive(Clone, PartialEq, Debug)] | |||
pub enum TokenType { | |||
Identifier, | |||
Integer(i16), | |||
Long(i32), | |||
Single(f32), | |||
Double(f64), | |||
String(String), | |||
True, | |||
False, | |||
Comma, | |||
Dot, | |||
Print, | |||
PlusPlus, | |||
MinusMinus, | |||
Plus, | |||
Minus, | |||
Star, | |||
Slash, | |||
LeftBrace, | |||
RightBrace, | |||
LeftParen, | |||
RightParen, | |||
Return, | |||
Function, | |||
Equal, | |||
If, | |||
Else, | |||
While, | |||
And, | |||
Or, | |||
Not, | |||
Greater, | |||
GreaterEqual, | |||
Less, | |||
LessEqual, | |||
Newline, | |||
Eof, | |||
NoToken, | |||
} | |||
#[derive(Clone)] | |||
pub struct Token { | |||
pub line: usize, | |||
pub lexeme: String, | |||
pub token_type: TokenType, | |||
} | |||
impl Token { | |||
pub fn new(token_type: TokenType, lexeme: String, line: usize) -> Self { | |||
Self { | |||
token_type, | |||
lexeme, | |||
line, | |||
} | |||
} | |||
} |
@ -0,0 +1,19 @@ | |||
use crate::expr::{BinaryExpr, CallExpr, ExprValue, LogicalExpr, UnaryExpr, VariableExpr}; | |||
use crate::stmt::{AssignStmt, ExpressionStmt, PrintStmt}; | |||
// TODO generate these with macros | |||
pub trait StmtVisitor<U> { | |||
fn visit_expression_stmt(&mut self, stmt: &ExpressionStmt) -> U; | |||
fn visit_print_stmt(&mut self, stmt: &PrintStmt) -> U; | |||
fn visit_assign_stmt(&mut self, stmt: &AssignStmt) -> U; | |||
} | |||
pub trait ExprVisitor<U> { | |||
fn visit_variable_expr(&mut self, expr: &VariableExpr) -> U; | |||
fn visit_logical_expr(&mut self, expr: &LogicalExpr) -> U; | |||
fn visit_binary_expr(&mut self, expr: &BinaryExpr) -> U; | |||
fn visit_unary_expr(&mut self, expr: &UnaryExpr) -> U; | |||
fn visit_call_expr(&mut self, expr: &CallExpr) -> U; | |||
fn visit_literal_expr(&mut self, expr: &ExprValue) -> U; | |||
} |
@ -0,0 +1,78 @@ | |||
use crate::basic_io::BasicIO; | |||
use crate::expr::ExprValue; | |||
use crate::opcodes::OpCode; | |||
use num_traits::FromPrimitive; | |||
pub(crate) struct VirtualMachine<'a, T> { | |||
ip: usize, | |||
stack: Vec<ExprValue>, | |||
stdio: &'a mut T, | |||
instructions: Vec<u8>, | |||
literals: Vec<ExprValue>, // TODO store these on stack for increased performance? | |||
} | |||
impl<'a, T> VirtualMachine<'a, T> | |||
where | |||
T: BasicIO, | |||
{ | |||
pub(crate) fn new(instructions: Vec<u8>, literals: Vec<ExprValue>, stdio: &'a mut T) -> Self { | |||
Self { | |||
ip: 0, | |||
stack: Vec::new(), | |||
stdio, | |||
instructions, | |||
literals, | |||
} | |||
} | |||
pub(crate) fn run(&mut self) { | |||
while self.ip < self.instructions.len() { | |||
self.process_instruction() | |||
} | |||
} | |||
fn process_instruction(&mut self) { | |||
let opcode = FromPrimitive::from_u8(self.instructions[self.ip]).unwrap(); | |||
self.ip += 1; | |||
match opcode { | |||
OpCode::Pop => {} | |||
OpCode::Print => { | |||
let value = self.stack.pop().unwrap(); | |||
self.write_exprvalue(value); | |||
} | |||
OpCode::Literal8 => { | |||
let byte = self.read_byte(); | |||
// TODO cloning is very slow - especially if the literal is a string! | |||
self.stack.push(self.literals[byte as usize].clone()) | |||
} | |||
OpCode::Literal16 => { | |||
let word = self.read_word(); | |||
self.stack.push(self.literals[word as usize].clone()); | |||
} | |||
} | |||
} | |||
fn read_byte(&mut self) -> u8 { | |||
let byte = self.instructions[self.ip]; | |||
self.ip += 1; | |||
byte | |||
} | |||
fn read_word(&mut self) -> u16 { | |||
let upper = self.read_byte() as u16; | |||
let lower = self.read_byte(); | |||
(upper << 8) | (lower as u16) | |||
} | |||
fn write_exprvalue(&mut self, value: ExprValue) { | |||
self.stdio.write_line(match value { | |||
ExprValue::Boolean(x) => format!("{}", x), | |||
ExprValue::Integer(x) => format!("{}", x), | |||
ExprValue::Long(x) => format!("{}", x), | |||
ExprValue::Single(x) => format!("{}", x), | |||
ExprValue::Double(x) => format!("{}", x), | |||
ExprValue::String(x) => x, | |||
}); | |||
} | |||
} |
@ -0,0 +1,60 @@ | |||
use crate::basic_io::BasicIO; | |||
use crate::compiler::Compiler; | |||
use crate::error_handler::ErrorHandler; | |||
use crate::parser::Parser; | |||
use crate::scanner::Scanner; | |||
use crate::vm::VirtualMachine; | |||
pub struct XBasic<'a, T> { | |||
pub error_handler: ErrorHandler, | |||
pub stdio: &'a mut T, | |||
} | |||
impl<'a, T> XBasic<'a, T> | |||
where | |||
T: BasicIO, | |||
{ | |||
pub fn new(stdio: &'a mut T) -> Self { | |||
Self { | |||
error_handler: ErrorHandler::new(), | |||
stdio, | |||
} | |||
} | |||
pub fn run(&mut self, source: &str) -> Result<(), ()> { | |||
let mut sc = Scanner::new(source, &mut self.error_handler); | |||
let tokens = sc.scan_tokens(); | |||
let tokens = match tokens { | |||
Ok(x) => x, | |||
Err(_) => return Err(()), | |||
}; | |||
/*for token in &tokens { | |||
println!("{:?}", token.token_type); | |||
}*/ | |||
let mut parser = Parser::new(tokens, &mut self.error_handler); | |||
let stmts = match parser.parse() { | |||
Some(x) => x, | |||
None => return Err(()), | |||
}; | |||
let mut compiler = Compiler::new(stmts, &mut self.error_handler); | |||
if compiler.compile().is_err() { | |||
return Err(()); | |||
} | |||
// Debugging only | |||
// println!("Instructions: {:?}", compiler.instructions); | |||
let mut vm = VirtualMachine::new(compiler.instructions, compiler.literals, self.stdio); | |||
vm.run(); | |||
if self.error_handler.had_runtime_error { | |||
return Err(()); | |||
} | |||
Ok(()) | |||
} | |||
} |