add tests to scanner.
move chunk out of lib.rs add -f and -s and -c flags to main
This commit is contained in:
parent
9b0ad58e71
commit
ce1a71899d
|
@ -101,10 +101,17 @@ name = "mox"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
|
"peekmore",
|
||||||
"strum",
|
"strum",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "peekmore"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9163e1259760e83d528d1b3171e5100c1767f10c52e1c4d6afad26e63d47d758"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.81"
|
version = "1.0.81"
|
||||||
|
|
|
@ -7,5 +7,6 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = "4.5.4"
|
clap = "4.5.4"
|
||||||
|
peekmore = "1.3.0"
|
||||||
strum = "0.26"
|
strum = "0.26"
|
||||||
strum_macros = "0.26"
|
strum_macros = "0.26"
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
use std::u16;
|
||||||
|
|
||||||
|
use crate::value::Value;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, strum_macros::Display)]
|
||||||
|
pub enum OptCode {
|
||||||
|
OpReturn,
|
||||||
|
OpConstant,
|
||||||
|
OpNegate,
|
||||||
|
OpAdd,
|
||||||
|
OpSubstract,
|
||||||
|
OpMultiply,
|
||||||
|
OpDivide,
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Chunk {
|
||||||
|
pub code: Vec<u8>,
|
||||||
|
pub constants: Vec<Value>,
|
||||||
|
pub lines: Vec<u16>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<OptCode> for u8 {
|
||||||
|
fn from(code: OptCode) -> Self {
|
||||||
|
match code {
|
||||||
|
OptCode::OpReturn => 0,
|
||||||
|
OptCode::OpConstant => 1,
|
||||||
|
OptCode::OpNegate => 2,
|
||||||
|
OptCode::OpAdd => 3,
|
||||||
|
OptCode::OpSubstract => 4,
|
||||||
|
OptCode::OpMultiply => 5,
|
||||||
|
OptCode::OpDivide => 6,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ConversionError {
|
||||||
|
pub invalid_value: u8,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConversionError {
|
||||||
|
fn new(invalid_value: u8) -> Self {
|
||||||
|
ConversionError {
|
||||||
|
invalid_value,
|
||||||
|
message: format!("Invalid opcode"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for OptCode {
|
||||||
|
type Error = ConversionError;
|
||||||
|
fn try_from(code: u8) -> Result<Self, Self::Error> {
|
||||||
|
match code {
|
||||||
|
0 => Ok(OptCode::OpReturn),
|
||||||
|
1 => Ok(OptCode::OpConstant),
|
||||||
|
2 => Ok(OptCode::OpNegate),
|
||||||
|
3 => Ok(OptCode::OpAdd),
|
||||||
|
4 => Ok(OptCode::OpSubstract),
|
||||||
|
5 => Ok(OptCode::OpMultiply),
|
||||||
|
6 => Ok(OptCode::OpDivide),
|
||||||
|
_ => Err(ConversionError::new(code)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Chunk {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
code: vec![],
|
||||||
|
constants: vec![],
|
||||||
|
lines: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn write(&mut self, byte: u8, line: u16) {
|
||||||
|
self.code.push(byte);
|
||||||
|
self.lines.push(line);
|
||||||
|
}
|
||||||
|
pub fn write_value(&mut self, add: Value) -> u8 {
|
||||||
|
self.constants.push(add);
|
||||||
|
return (self.constants.len() - 1).try_into().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn u8_to_optcode() {
|
||||||
|
let pred: OptCode = 0.try_into().unwrap();
|
||||||
|
assert_eq!(pred, OptCode::OpReturn);
|
||||||
|
let pred: OptCode = 1.try_into().unwrap();
|
||||||
|
assert_eq!(pred, OptCode::OpConstant);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn basic_write() {
|
||||||
|
let mut chunk = Chunk::new();
|
||||||
|
let constant_idx: u8 = chunk.write_value(1.2);
|
||||||
|
assert_eq!(constant_idx, 0);
|
||||||
|
chunk.write(OptCode::OpConstant.into(), 123);
|
||||||
|
chunk.write(constant_idx, 123);
|
||||||
|
let pred: OptCode = chunk.code[0].try_into().unwrap();
|
||||||
|
assert_eq!(pred, OptCode::OpConstant);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn try_into_constant() {
|
||||||
|
let mut chunk = Chunk::new();
|
||||||
|
let constant_idx: u8 = chunk.write_value(1.2);
|
||||||
|
chunk.write(OptCode::OpConstant.into(), 123);
|
||||||
|
chunk.write(constant_idx, 123);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,26 +1,22 @@
|
||||||
use crate::scanner::Scanner;
|
use strum_macros::Display;
|
||||||
use crate::scanner::TokenType;
|
|
||||||
|
|
||||||
pub fn compile(source: &str) {
|
use crate::chunk::Chunk;
|
||||||
let mut scanner: Scanner = Scanner::new(source);
|
|
||||||
let mut line = 0;
|
|
||||||
loop {
|
|
||||||
let token = scanner.scan_token();
|
|
||||||
|
|
||||||
if token.line != line {
|
pub fn compile(source: &str, chunk: &Chunk) -> bool {
|
||||||
print!("{:4}", token.line);
|
return true;
|
||||||
line = token.line;
|
}
|
||||||
} else {
|
|
||||||
print!(" |");
|
#[derive(Display, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
}
|
enum Precedence {
|
||||||
|
None,
|
||||||
println!(
|
Assignment,
|
||||||
":{:<3} {:20} {:20}",
|
Or,
|
||||||
token.start, token.token_type, token.lexeme
|
And,
|
||||||
);
|
Equality,
|
||||||
|
Comparison,
|
||||||
if let TokenType::TokenEof = token.token_type {
|
Term,
|
||||||
break;
|
Factor,
|
||||||
}
|
Unary,
|
||||||
}
|
Call,
|
||||||
|
Primary,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
use crate::chunk::{Chunk, ConversionError, OptCode};
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
use crate::{Chunk, ConversionError, OptCode};
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
pub fn trace_enabled() -> bool {
|
pub fn trace_enabled() -> bool {
|
||||||
|
|
114
src/lib.rs
114
src/lib.rs
|
@ -1,118 +1,6 @@
|
||||||
|
pub mod chunk;
|
||||||
pub mod compiler;
|
pub mod compiler;
|
||||||
pub mod debug;
|
pub mod debug;
|
||||||
pub mod scanner;
|
pub mod scanner;
|
||||||
pub mod value;
|
pub mod value;
|
||||||
pub mod vm;
|
pub mod vm;
|
||||||
|
|
||||||
use std::u16;
|
|
||||||
|
|
||||||
use value::Value;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, strum_macros::Display)]
|
|
||||||
pub enum OptCode {
|
|
||||||
OpReturn,
|
|
||||||
OpConstant,
|
|
||||||
OpNegate,
|
|
||||||
OpAdd,
|
|
||||||
OpSubstract,
|
|
||||||
OpMultiply,
|
|
||||||
OpDivide,
|
|
||||||
}
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Chunk {
|
|
||||||
pub code: Vec<u8>,
|
|
||||||
pub constants: Vec<Value>,
|
|
||||||
pub lines: Vec<u16>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<OptCode> for u8 {
|
|
||||||
fn from(code: OptCode) -> Self {
|
|
||||||
match code {
|
|
||||||
OptCode::OpReturn => 0,
|
|
||||||
OptCode::OpConstant => 1,
|
|
||||||
OptCode::OpNegate => 2,
|
|
||||||
OptCode::OpAdd => 3,
|
|
||||||
OptCode::OpSubstract => 4,
|
|
||||||
OptCode::OpMultiply => 5,
|
|
||||||
OptCode::OpDivide => 6,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ConversionError {
|
|
||||||
invalid_value: u8,
|
|
||||||
message: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConversionError {
|
|
||||||
fn new(invalid_value: u8) -> Self {
|
|
||||||
ConversionError {
|
|
||||||
invalid_value,
|
|
||||||
message: format!("Invalid opcode"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<u8> for OptCode {
|
|
||||||
type Error = ConversionError;
|
|
||||||
fn try_from(code: u8) -> Result<Self, Self::Error> {
|
|
||||||
match code {
|
|
||||||
0 => Ok(OptCode::OpReturn),
|
|
||||||
1 => Ok(OptCode::OpConstant),
|
|
||||||
2 => Ok(OptCode::OpNegate),
|
|
||||||
3 => Ok(OptCode::OpAdd),
|
|
||||||
4 => Ok(OptCode::OpSubstract),
|
|
||||||
5 => Ok(OptCode::OpMultiply),
|
|
||||||
6 => Ok(OptCode::OpDivide),
|
|
||||||
_ => Err(ConversionError::new(code)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Chunk {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
code: vec![],
|
|
||||||
constants: vec![],
|
|
||||||
lines: vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn write(&mut self, byte: u8, line: u16) {
|
|
||||||
self.code.push(byte);
|
|
||||||
self.lines.push(line);
|
|
||||||
}
|
|
||||||
pub fn write_value(&mut self, add: Value) -> u8 {
|
|
||||||
self.constants.push(add);
|
|
||||||
return (self.constants.len() - 1).try_into().unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn u8_to_optcode() {
|
|
||||||
let pred: OptCode = 0.try_into().unwrap();
|
|
||||||
assert_eq!(pred, OptCode::OpReturn);
|
|
||||||
let pred: OptCode = 1.try_into().unwrap();
|
|
||||||
assert_eq!(pred, OptCode::OpConstant);
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn basic_write() {
|
|
||||||
let mut chunk = Chunk::new();
|
|
||||||
let constant_idx: u8 = chunk.write_value(1.2);
|
|
||||||
assert_eq!(constant_idx, 0);
|
|
||||||
chunk.write(OptCode::OpConstant.into(), 123);
|
|
||||||
chunk.write(constant_idx, 123);
|
|
||||||
let pred: OptCode = chunk.code[0].try_into().unwrap();
|
|
||||||
assert_eq!(pred, OptCode::OpConstant);
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn try_into_constant() {
|
|
||||||
let mut chunk = Chunk::new();
|
|
||||||
let constant_idx: u8 = chunk.write_value(1.2);
|
|
||||||
chunk.write(OptCode::OpConstant.into(), 123);
|
|
||||||
chunk.write(constant_idx, 123);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
107
src/main.rs
107
src/main.rs
|
@ -1,9 +1,11 @@
|
||||||
use clap::{Arg, ArgAction, Command};
|
use clap::{Arg, ArgAction, Command};
|
||||||
|
use mox::chunk::Chunk;
|
||||||
|
use mox::scanner::Scanner;
|
||||||
use mox::vm::InterpretResult;
|
use mox::vm::InterpretResult;
|
||||||
use mox::vm::VM;
|
use mox::vm::VM;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io;
|
// use std::io;
|
||||||
use std::io::Write;
|
// use std::io::Write;
|
||||||
use std::process;
|
use std::process;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
|
@ -27,59 +29,74 @@ fn main() {
|
||||||
.action(ArgAction::Set)
|
.action(ArgAction::Set)
|
||||||
.value_name("COMMAND")
|
.value_name("COMMAND")
|
||||||
.help("command to run"),
|
.help("command to run"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("scan")
|
||||||
|
.short('s')
|
||||||
|
.long("scan")
|
||||||
|
.action(ArgAction::SetTrue)
|
||||||
|
.value_name("SCAN")
|
||||||
|
.help("scan only"),
|
||||||
);
|
);
|
||||||
|
|
||||||
let matches = app.get_matches();
|
let matches = app.get_matches();
|
||||||
|
|
||||||
if let Some(command) = matches.get_one::<String>("command") {
|
if let Some(source) = matches.get_one::<String>("command") {
|
||||||
run_content(command);
|
if let Some(_) = matches.get_one::<bool>("scan") {
|
||||||
} else if let Some(file) = matches.get_one::<String>("file") {
|
scan_content(source);
|
||||||
run_file(file);
|
} else {
|
||||||
} else {
|
run_content(source);
|
||||||
repl();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn repl() {
|
|
||||||
let mut input = String::new();
|
|
||||||
let mut vm: VM = VM::new();
|
|
||||||
loop {
|
|
||||||
input.clear();
|
|
||||||
print!("> ");
|
|
||||||
io::stdout().flush().unwrap();
|
|
||||||
|
|
||||||
match io::stdin().read_line(&mut input) {
|
|
||||||
Ok(bytes) => {
|
|
||||||
if bytes == 0 || input.trim() == "exit" {
|
|
||||||
println!("Bye!");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
vm.interpret(&input);
|
|
||||||
}
|
|
||||||
Err(_error) => {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else if let Some(file) = matches.get_one::<String>("file") {
|
||||||
|
let source = fs::read_to_string(file).unwrap_or_else(|err| {
|
||||||
|
eprintln!("Error reading '{}': {}", file, err);
|
||||||
|
process::exit(74);
|
||||||
|
});
|
||||||
|
println!("{}", source);
|
||||||
|
if let Some(_) = matches.get_one::<bool>("scan") {
|
||||||
|
scan_content(&source);
|
||||||
|
} else {
|
||||||
|
run_content(&source);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
todo!("repl not done yet")
|
||||||
|
// repl();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fn repl() {
|
||||||
|
// let mut input = String::new();
|
||||||
|
// let mut vm: VM = VM::new();
|
||||||
|
// let mut chunk: Chunk = Chunk::new();
|
||||||
|
// loop {
|
||||||
|
// input.clear();
|
||||||
|
// print!("> ");
|
||||||
|
// io::stdout().flush().unwrap();
|
||||||
|
//
|
||||||
|
// match io::stdin().read_line(&mut input) {
|
||||||
|
// Ok(bytes) => {
|
||||||
|
// if bytes == 0 || input.trim() == "exit" {
|
||||||
|
// println!("Bye!");
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// vm.interpret(&input, &mut chunk);
|
||||||
|
// }
|
||||||
|
// Err(_error) => {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
fn scan_content(source: &str) {
|
||||||
|
let input = source.to_owned();
|
||||||
|
let mut scanner = Scanner::new(&input);
|
||||||
|
scanner.compile();
|
||||||
|
}
|
||||||
fn run_content(source: &str) {
|
fn run_content(source: &str) {
|
||||||
let mut vm: VM = VM::new();
|
let mut vm: VM = VM::new();
|
||||||
match vm.interpret(source) {
|
let mut chunk: Chunk = Chunk::new();
|
||||||
InterpretResult::InterpretOk => exit(0),
|
match vm.interpret(source, &mut chunk) {
|
||||||
InterpretResult::InterpretCompileError => exit(65),
|
|
||||||
InterpretResult::InterpretRuntimeError => exit(70),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_file(path: &str) {
|
|
||||||
let mut vm: VM = VM::new();
|
|
||||||
let content = fs::read_to_string(path).unwrap_or_else(|err| {
|
|
||||||
eprintln!("Error reading '{}': {}", path, err);
|
|
||||||
process::exit(74);
|
|
||||||
});
|
|
||||||
|
|
||||||
match vm.interpret(&content) {
|
|
||||||
InterpretResult::InterpretOk => exit(0),
|
InterpretResult::InterpretOk => exit(0),
|
||||||
InterpretResult::InterpretCompileError => exit(65),
|
InterpretResult::InterpretCompileError => exit(65),
|
||||||
InterpretResult::InterpretRuntimeError => exit(70),
|
InterpretResult::InterpretRuntimeError => exit(70),
|
||||||
|
|
364
src/scanner.rs
364
src/scanner.rs
|
@ -1,10 +1,13 @@
|
||||||
|
use peekmore::{PeekMore, PeekMoreIterator};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::str::Chars;
|
||||||
|
|
||||||
pub struct Scanner<'a> {
|
pub struct Scanner<'a> {
|
||||||
|
source: &'a String,
|
||||||
start: usize,
|
start: usize,
|
||||||
|
chars: PeekMoreIterator<Chars<'a>>,
|
||||||
current: usize,
|
current: usize,
|
||||||
line: u16,
|
line: u16,
|
||||||
source: &'a str,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> fmt::Display for Scanner<'a> {
|
impl<'a> fmt::Display for Scanner<'a> {
|
||||||
|
@ -21,147 +24,149 @@ impl<'a> fmt::Display for Scanner<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Scanner<'a> {
|
impl<'a> Scanner<'a> {
|
||||||
pub fn new(source: &'a str) -> Self {
|
pub fn new(source: &'a String) -> Self {
|
||||||
Scanner {
|
Scanner {
|
||||||
start: 0,
|
start: 0,
|
||||||
current: 0,
|
current: 0,
|
||||||
line: 1,
|
line: 1,
|
||||||
|
chars: source.chars().peekmore(),
|
||||||
source,
|
source,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn compile(&mut self) {
|
||||||
|
let mut line = 0;
|
||||||
|
loop {
|
||||||
|
let token = self.scan_token();
|
||||||
|
|
||||||
|
if token.line != line {
|
||||||
|
print!("{:4}", token.line);
|
||||||
|
line = token.line;
|
||||||
|
} else {
|
||||||
|
print!(" |");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!(
|
||||||
|
":{:<3} {:20} {:20}",
|
||||||
|
token.start, token.token_type, token.lexeme
|
||||||
|
);
|
||||||
|
|
||||||
|
if let TokenType::TokenEof = token.token_type {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn scan_token(&mut self) -> Token {
|
pub fn scan_token(&mut self) -> Token {
|
||||||
self.skip_whitespace();
|
self.skip_whitespace();
|
||||||
self.start = self.current;
|
self.start = self.current;
|
||||||
if self.is_at_end() {
|
match self.advance() {
|
||||||
return self.make_token(TokenType::TokenEof);
|
Some(c) => match c {
|
||||||
}
|
c if c.is_digit(10) => return self.make_number(),
|
||||||
let c: char = self.advance();
|
c if c.is_ascii_alphabetic() || c == '_' => return self.make_identifier(),
|
||||||
|
'(' => return self.make_token(TokenType::TokenLeftParen),
|
||||||
match c {
|
')' => return self.make_token(TokenType::TokenRightParen),
|
||||||
c if c.is_digit(10) => return self.make_number(),
|
'{' => return self.make_token(TokenType::TokenLeftBrace),
|
||||||
c if c.is_alphabetic() || c == '_' => return self.make_identifier(),
|
'}' => return self.make_token(TokenType::TokenRightBrace),
|
||||||
'(' => return self.make_token(TokenType::TokenLeftParen),
|
';' => return self.make_token(TokenType::TokenSemicolon),
|
||||||
')' => return self.make_token(TokenType::TokenRightParen),
|
',' => return self.make_token(TokenType::TokenComma),
|
||||||
'{' => return self.make_token(TokenType::TokenLeftBrace),
|
'.' => return self.make_token(TokenType::TokenDot),
|
||||||
'}' => return self.make_token(TokenType::TokenRightBrace),
|
'-' => return self.make_token(TokenType::TokenMinus),
|
||||||
';' => return self.make_token(TokenType::TokenSemicolon),
|
'+' => return self.make_token(TokenType::TokenPlus),
|
||||||
',' => return self.make_token(TokenType::TokenComma),
|
'/' => return self.make_token(TokenType::TokenSlash),
|
||||||
'.' => return self.make_token(TokenType::TokenDot),
|
'*' => return self.make_token(TokenType::TokenStar),
|
||||||
'-' => return self.make_token(TokenType::TokenMinus),
|
'!' => {
|
||||||
'+' => return self.make_token(TokenType::TokenPlus),
|
return self.make_token_if_matches(
|
||||||
'/' => return self.make_token(TokenType::TokenSlash),
|
'=',
|
||||||
'*' => return self.make_token(TokenType::TokenStar),
|
TokenType::TokenBangEqual,
|
||||||
'!' => {
|
TokenType::TokenBang,
|
||||||
if self.matches('=') {
|
)
|
||||||
return self.make_token(TokenType::TokenBangEqual);
|
|
||||||
} else {
|
|
||||||
return self.make_token(TokenType::TokenBang);
|
|
||||||
}
|
}
|
||||||
}
|
'=' => {
|
||||||
'=' => {
|
return self.make_token_if_matches(
|
||||||
if self.matches('=') {
|
'=',
|
||||||
return self.make_token(TokenType::TokenEqualEqual);
|
TokenType::TokenEqualEqual,
|
||||||
} else {
|
TokenType::TokenEqual,
|
||||||
return self.make_token(TokenType::TokenEqual);
|
)
|
||||||
}
|
}
|
||||||
}
|
'<' => {
|
||||||
'<' => {
|
return self.make_token_if_matches(
|
||||||
if self.matches('=') {
|
'=',
|
||||||
return self.make_token(TokenType::TokenLessEqual);
|
TokenType::TokenLessEqual,
|
||||||
} else {
|
TokenType::TokenLess,
|
||||||
return self.make_token(TokenType::TokenLess);
|
)
|
||||||
}
|
}
|
||||||
}
|
'>' => {
|
||||||
'>' => {
|
return self.make_token_if_matches(
|
||||||
if self.matches('=') {
|
'=',
|
||||||
return self.make_token(TokenType::TokenGreaterEqual);
|
TokenType::TokenGreaterEqual,
|
||||||
} else {
|
TokenType::TokenGreater,
|
||||||
return self.make_token(TokenType::TokenGreater);
|
)
|
||||||
}
|
}
|
||||||
}
|
'"' => return self.make_string(),
|
||||||
'"' => return self.make_string(),
|
_ => return self.make_error_token("Unexpected character."),
|
||||||
_ => return self.make_error_token("Unexpected character."),
|
},
|
||||||
|
None => return self.make_eof(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
fn make_identifier(&mut self) -> Token {
|
fn make_identifier(&mut self) -> Token {
|
||||||
while let Some(c) = self.peek() {
|
while let Some(c) = self.chars.peek() {
|
||||||
if c.is_alphabetic() || c == '_' || c.is_digit(10) {
|
if c.is_alphabetic() || *c == '_' || c.is_digit(10) {
|
||||||
self.advance();
|
self.advance();
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return self.make_token(self.identifier_type());
|
let lexeme = &self.source[self.start..self.current];
|
||||||
}
|
|
||||||
fn identifier_type(&self) -> TokenType {
|
|
||||||
let c = self.source.chars().nth(self.start);
|
|
||||||
|
|
||||||
match c {
|
match lexeme {
|
||||||
Some('a') => return self.check_keyword(1, 2, "nd", TokenType::TokenAnd),
|
"and" => self.make_token(TokenType::TokenAnd),
|
||||||
Some('c') if self.current - self.start > 1 => {
|
"class" => self.make_token(TokenType::TokenClass),
|
||||||
match self.source.chars().nth(self.start + 1) {
|
"cap" => self.make_token(TokenType::TokenFalse),
|
||||||
Some('l') => return self.check_keyword(2, 3, "ass", TokenType::TokenClass),
|
"else" => self.make_token(TokenType::TokenElse),
|
||||||
Some('a') => return self.check_keyword(2, 1, "p", TokenType::TokenFalse),
|
"if" => self.make_token(TokenType::TokenIf),
|
||||||
_ => panic!("bad keyword"),
|
"nil" => self.make_token(TokenType::TokenNil),
|
||||||
}
|
"no" => self.make_token(TokenType::TokenFalse),
|
||||||
}
|
"or" => self.make_token(TokenType::TokenOr),
|
||||||
Some('e') => return self.check_keyword(1, 3, "lse", TokenType::TokenElse),
|
"print" => self.make_token(TokenType::TokenPrint),
|
||||||
Some('i') => return self.check_keyword(1, 1, "f", TokenType::TokenIf),
|
"return" => self.make_token(TokenType::TokenReturn),
|
||||||
Some('n') if self.current - self.start > 1 => {
|
"super" => self.make_token(TokenType::TokenSuper),
|
||||||
match self.source.chars().nth(self.start + 1) {
|
"var" => self.make_token(TokenType::TokenVar),
|
||||||
Some('i') => return self.check_keyword(2, 1, "l", TokenType::TokenNil),
|
"while" => self.make_token(TokenType::TokenWhile),
|
||||||
Some('o') => return self.check_keyword(2, 0, "", TokenType::TokenFalse),
|
"false" => self.make_token(TokenType::TokenFalse),
|
||||||
_ => panic!("bad keyword"),
|
"for" => self.make_token(TokenType::TokenFor),
|
||||||
}
|
"fun" => self.make_token(TokenType::TokenFun),
|
||||||
}
|
"this" => self.make_token(TokenType::TokenThis),
|
||||||
Some('o') => return self.check_keyword(1, 1, "r", TokenType::TokenOr),
|
"true" => self.make_token(TokenType::TokenTrue),
|
||||||
Some('p') => return self.check_keyword(1, 4, "rint", TokenType::TokenPrint),
|
_ => return self.make_token(TokenType::TokenIdentifier),
|
||||||
Some('r') => return self.check_keyword(1, 5, "eturn", TokenType::TokenReturn),
|
}
|
||||||
Some('s') => return self.check_keyword(1, 4, "uper", TokenType::TokenSuper),
|
|
||||||
Some('v') => return self.check_keyword(1, 2, "ar", TokenType::TokenVar),
|
|
||||||
Some('w') => return self.check_keyword(1, 4, "hile", TokenType::TokenWhile),
|
|
||||||
Some('f') if self.current - self.start > 1 => {
|
|
||||||
match self.source.chars().nth(self.start + 1) {
|
|
||||||
Some('a') => return self.check_keyword(2, 3, "lse", TokenType::TokenFalse),
|
|
||||||
Some('o') => return self.check_keyword(2, 1, "r", TokenType::TokenFor),
|
|
||||||
Some('u') => return self.check_keyword(2, 1, "n", TokenType::TokenFun),
|
|
||||||
_ => panic!("bad keyword"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some('t') if self.current - self.start > 1 => {
|
|
||||||
match self.source.chars().nth(self.start + 1) {
|
|
||||||
Some('h') => return self.check_keyword(2, 2, "is", TokenType::TokenThis),
|
|
||||||
Some('r') => return self.check_keyword(2, 2, "ue", TokenType::TokenTrue),
|
|
||||||
_ => panic!("bad keyword"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => return TokenType::TokenIdentifier,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
fn check_keyword(
|
fn make_token_if_matches(
|
||||||
&self,
|
&mut self,
|
||||||
start: usize,
|
expected: char,
|
||||||
length: usize,
|
on_match: TokenType,
|
||||||
rest: &str,
|
otherwise: TokenType,
|
||||||
token_type: TokenType,
|
) -> Token {
|
||||||
) -> TokenType {
|
if self.matches(expected) {
|
||||||
let end = self.start + start + length;
|
self.make_token(on_match)
|
||||||
let s = &self.source[self.start + start..end];
|
|
||||||
let next = self.source.chars().nth(end);
|
|
||||||
if (self.current == end) && (s == rest) {
|
|
||||||
match next {
|
|
||||||
Some(n) if n.is_whitespace() => {
|
|
||||||
return token_type;
|
|
||||||
}
|
|
||||||
_ => return TokenType::TokenIdentifier,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return TokenType::TokenIdentifier;
|
self.make_token(otherwise)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn matches(&mut self, expected: char) -> bool {
|
||||||
|
match self.chars.peek() {
|
||||||
|
Some(c) => {
|
||||||
|
if c == &expected {
|
||||||
|
self.advance();
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn make_string(&mut self) -> Token {
|
fn make_string(&mut self) -> Token {
|
||||||
loop {
|
loop {
|
||||||
match self.peek() {
|
match self.chars.peek() {
|
||||||
Some(c) => match c {
|
Some(c) => match c {
|
||||||
'\n' => {
|
'\n' => {
|
||||||
self.line += 1;
|
self.line += 1;
|
||||||
|
@ -216,8 +221,7 @@ impl<'a> Scanner<'a> {
|
||||||
}
|
}
|
||||||
fn skip_whitespace(&mut self) {
|
fn skip_whitespace(&mut self) {
|
||||||
loop {
|
loop {
|
||||||
let peek = self.peek();
|
match self.chars.peek() {
|
||||||
match peek {
|
|
||||||
None => return,
|
None => return,
|
||||||
Some(c) => match c {
|
Some(c) => match c {
|
||||||
' ' | '\r' | '\t' => {
|
' ' | '\r' | '\t' => {
|
||||||
|
@ -229,10 +233,14 @@ impl<'a> Scanner<'a> {
|
||||||
self.advance();
|
self.advance();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
'/' => match self.peek_next() {
|
'/' => match self.chars.peek_nth(1) {
|
||||||
Some(c) if c == '/' => {
|
Some(c) if *c == '/' => {
|
||||||
while let Some(_) = self.peek() {
|
while let Some(c) = self.peek() {
|
||||||
if !self.is_at_end() {
|
if c == '\n' {
|
||||||
|
self.line += 1;
|
||||||
|
self.advance();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
self.advance();
|
self.advance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -255,28 +263,9 @@ impl<'a> Scanner<'a> {
|
||||||
line: self.line,
|
line: self.line,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn advance(&mut self) -> char {
|
fn advance(&mut self) -> Option<char> {
|
||||||
self.current += 1;
|
self.current += 1;
|
||||||
|
self.chars.next()
|
||||||
match self.source.chars().nth(self.current - 1) {
|
|
||||||
Some(c) => c,
|
|
||||||
None => panic!(
|
|
||||||
"advance failed: fell off the end. {}:{}",
|
|
||||||
self.line, self.current
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn matches(&mut self, expected: char) -> bool {
|
|
||||||
match self.source.chars().nth(self.current) {
|
|
||||||
Some(c) => {
|
|
||||||
if c != expected {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
self.current += 1;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
None => return false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fn make_error_token(&self, message: &str) -> Token {
|
fn make_error_token(&self, message: &str) -> Token {
|
||||||
Token {
|
Token {
|
||||||
|
@ -286,6 +275,14 @@ impl<'a> Scanner<'a> {
|
||||||
line: self.line,
|
line: self.line,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn make_eof(&self) -> Token {
|
||||||
|
Token {
|
||||||
|
token_type: TokenType::TokenEof,
|
||||||
|
start: self.start,
|
||||||
|
lexeme: "".to_string(),
|
||||||
|
line: self.line,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Token {
|
pub struct Token {
|
||||||
|
@ -295,9 +292,7 @@ pub struct Token {
|
||||||
pub line: u16,
|
pub line: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Token {}
|
#[derive(Clone, Copy, strum_macros::Display, Debug, PartialEq, Eq)]
|
||||||
|
|
||||||
#[derive(Clone, Copy, strum_macros::Display)]
|
|
||||||
pub enum TokenType {
|
pub enum TokenType {
|
||||||
// Single-character tokens.
|
// Single-character tokens.
|
||||||
TokenLeftParen,
|
TokenLeftParen,
|
||||||
|
@ -345,3 +340,86 @@ pub enum TokenType {
|
||||||
TokenError,
|
TokenError,
|
||||||
TokenEof,
|
TokenEof,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::scanner;
|
||||||
|
use crate::scanner::TokenType;
|
||||||
|
#[test]
|
||||||
|
fn single_chars() {
|
||||||
|
assert_token(String::from(""), TokenType::TokenEof);
|
||||||
|
assert_token(String::from("("), TokenType::TokenLeftParen);
|
||||||
|
assert_token(String::from("}"), TokenType::TokenRightBrace);
|
||||||
|
assert_token(String::from("-"), TokenType::TokenMinus);
|
||||||
|
assert_token(String::from("+"), TokenType::TokenPlus);
|
||||||
|
assert_token(String::from("/"), TokenType::TokenSlash);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn double_chars() {
|
||||||
|
assert_token(String::from("=="), TokenType::TokenEqualEqual);
|
||||||
|
assert_token(String::from("!="), TokenType::TokenBangEqual);
|
||||||
|
assert_token(String::from(">"), TokenType::TokenGreater);
|
||||||
|
assert_token(String::from(">="), TokenType::TokenGreaterEqual);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn strings() {
|
||||||
|
assert_token_lexeme(String::from("\"mox\""), TokenType::TokenString, "\"mox\"");
|
||||||
|
assert_token_lexeme(String::from("\"\""), TokenType::TokenString, "\"\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn numbers() {
|
||||||
|
assert_token_lexeme(String::from("0"), TokenType::TokenNumber, "0");
|
||||||
|
assert_token_lexeme(String::from("4"), TokenType::TokenNumber, "4");
|
||||||
|
assert_token_lexeme(String::from("42"), TokenType::TokenNumber, "42");
|
||||||
|
assert_token_lexeme(String::from("13.99"), TokenType::TokenNumber, "13.99");
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn newlines() {
|
||||||
|
assert_tokens(
|
||||||
|
String::from("+\n//comment\n-"),
|
||||||
|
&vec![TokenType::TokenPlus, TokenType::TokenMinus],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn identifier() {
|
||||||
|
assert_token(String::from("class"), TokenType::TokenClass);
|
||||||
|
assert_token(String::from("if"), TokenType::TokenIf);
|
||||||
|
assert_token(String::from("while"), TokenType::TokenWhile);
|
||||||
|
assert_token(String::from("true"), TokenType::TokenTrue);
|
||||||
|
assert_token(String::from("false"), TokenType::TokenFalse);
|
||||||
|
|
||||||
|
assert_token(String::from("cap"), TokenType::TokenFalse);
|
||||||
|
|
||||||
|
assert_token_lexeme(String::from("mox"), TokenType::TokenIdentifier, "mox");
|
||||||
|
}
|
||||||
|
fn assert_token(source: String, expected: scanner::TokenType) {
|
||||||
|
let mut scanner = scanner::Scanner::new(&source);
|
||||||
|
let token = scanner.scan_token();
|
||||||
|
|
||||||
|
assert_eq!(token.token_type, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_token_lexeme(
|
||||||
|
source: String,
|
||||||
|
expected_type: scanner::TokenType,
|
||||||
|
expected_lexeme: &str,
|
||||||
|
) {
|
||||||
|
let mut scanner = scanner::Scanner::new(&source);
|
||||||
|
let token = scanner.scan_token();
|
||||||
|
|
||||||
|
assert_eq!(token.token_type, expected_type);
|
||||||
|
assert_eq!(token.lexeme, expected_lexeme);
|
||||||
|
}
|
||||||
|
fn assert_tokens(source: String, expected_tokens: &Vec<TokenType>) {
|
||||||
|
let mut scanner = scanner::Scanner::new(&source);
|
||||||
|
for expected in expected_tokens {
|
||||||
|
let actual = scanner.scan_token();
|
||||||
|
assert_eq!(actual.token_type, *expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(scanner.scan_token().token_type, TokenType::TokenEof);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
20
src/vm.rs
20
src/vm.rs
|
@ -1,7 +1,7 @@
|
||||||
use crate::compiler::compile;
|
use crate::chunk::{Chunk, ConversionError, OptCode};
|
||||||
|
use crate::compiler;
|
||||||
use crate::debug::{disassemble_instruction, print_value, trace_enabled};
|
use crate::debug::{disassemble_instruction, print_value, trace_enabled};
|
||||||
use crate::Value;
|
use crate::value::Value;
|
||||||
use crate::{Chunk, ConversionError, OptCode};
|
|
||||||
|
|
||||||
pub struct VM<'a> {
|
pub struct VM<'a> {
|
||||||
chunk: Option<&'a Chunk>,
|
chunk: Option<&'a Chunk>,
|
||||||
|
@ -29,9 +29,17 @@ impl<'a> VM<'a> {
|
||||||
self.ip = 0;
|
self.ip = 0;
|
||||||
self.run()
|
self.run()
|
||||||
}
|
}
|
||||||
pub fn interpret(&mut self, source: &str) -> InterpretResult {
|
pub fn interpret(&mut self, source: &str, chunk: &'a mut Chunk) -> InterpretResult {
|
||||||
compile(source);
|
match compiler::compile(source, &chunk) {
|
||||||
InterpretResult::InterpretOk
|
false => {
|
||||||
|
return InterpretResult::InterpretCompileError;
|
||||||
|
}
|
||||||
|
true => {
|
||||||
|
self.chunk = Some(chunk);
|
||||||
|
self.ip = 0;
|
||||||
|
return self.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_byte(&mut self) -> u8 {
|
fn read_byte(&mut self) -> u8 {
|
||||||
|
|
Loading…
Reference in New Issue