Browse Source

Initial commit - does hello world

pull/1/head
Stephen 1 month ago
commit
60e1064b2a
17 changed files with 1405 additions and 0 deletions
  1. +2
    -0
      .gitignore
  2. +248
    -0
      Cargo.lock
  3. +12
    -0
      Cargo.toml
  4. +2
    -0
      rustfmt.toml
  5. +4
    -0
      src/basic_io.rs
  6. +94
    -0
      src/compiler.rs
  7. +46
    -0
      src/error_handler.rs
  8. +114
    -0
      src/expr.rs
  9. +56
    -0
      src/main.rs
  10. +9
    -0
      src/opcodes.rs
  11. +309
    -0
      src/parser.rs
  12. +237
    -0
      src/scanner.rs
  13. +53
    -0
      src/stmt.rs
  14. +62
    -0
      src/tokens.rs
  15. +19
    -0
      src/visitor.rs
  16. +78
    -0
      src/vm.rs
  17. +60
    -0
      src/xbasic.rs

+ 2
- 0
.gitignore View File

@@ -0,0 +1,2 @@
/target
.idea

+ 248
- 0
Cargo.lock View File

@@ -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",
]

+ 12
- 0
Cargo.toml View File

@@ -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"

+ 2
- 0
rustfmt.toml View File

@@ -0,0 +1,2 @@
tab_spaces = 4
hard_tabs = true

+ 4
- 0
src/basic_io.rs View File

@@ -0,0 +1,4 @@
pub trait BasicIO {
fn read_line(&mut self);
fn write_line(&mut self, line: String);
}

+ 94
- 0
src/compiler.rs View File

@@ -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());
}
}
}

+ 46
- 0
src/error_handler.rs View File

@@ -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));
}
}

+ 114
- 0
src/expr.rs View File

@@ -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),
}

+ 56
- 0
src/main.rs View File

@@ -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,
}
}
}

+ 9
- 0
src/opcodes.rs View File

@@ -0,0 +1,9 @@
use num_derive::FromPrimitive;

#[derive(Copy, Clone, FromPrimitive)]
pub(crate) enum OpCode {
Pop,
Print,
Literal8,
Literal16,
}

+ 309
- 0
src/parser.rs View File

@@ -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()
}
}

+ 237
- 0
src/scanner.rs View File

@@ -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)
}

+ 53
- 0
src/stmt.rs View File

@@ -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,
}

+ 62
- 0
src/tokens.rs View File

@@ -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,
}
}
}

+ 19
- 0
src/visitor.rs View File

@@ -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;
}

+ 78
- 0
src/vm.rs View File

@@ -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,
});
}
}

+ 60
- 0
src/xbasic.rs View File

@@ -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(())
}
}

Loading…
Cancel
Save