Skip to content

Commit

Permalink
Adding a simple test for the virtual machine
Browse files Browse the repository at this point in the history
Also some cleanups
  • Loading branch information
patbuc committed Nov 14, 2023
1 parent 99febc5 commit d3b4718
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 91 deletions.
165 changes: 78 additions & 87 deletions src/compiler/scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ impl<'a> Iterator for ScannerIterator<'a> {
}

impl Scanner {
//noinspection DuplicatedCode
fn scan(&mut self) -> Option<Token> {
self.skip_whitespace();
self.start = self.current;
Expand All @@ -69,14 +70,14 @@ impl Scanner {
self.current += 1;
return Option::from(self.make_token(TokenType::Eof));
}

let c = self.advance();
if Scanner::is_alpha(c) {
return self.make_identifier();
return Option::from(self.make_identifier());
}
if Scanner::is_digit(c) {
return Option::from(self.make_number());
}

match c {
'(' => return Option::from(self.make_token(TokenType::LeftParen)),
')' => return Option::from(self.make_token(TokenType::RightParen)),
Expand All @@ -89,54 +90,52 @@ impl Scanner {
';' => return Option::from(self.make_token(TokenType::Semicolon)),
'*' => return Option::from(self.make_token(TokenType::Star)),
'!' => {
if self.matches('=') {
return Option::from(self.make_token(TokenType::BangEqual));
return if self.matches('=') {
Option::from(self.make_token(TokenType::BangEqual))
} else {
return Option::from(self.make_token(TokenType::Bang));
Option::from(self.make_token(TokenType::Bang))
}
}
'=' => {
if self.matches('=') {
return Option::from(self.make_token(TokenType::EqualEqual));
return if self.matches('=') {
Option::from(self.make_token(TokenType::EqualEqual))
} else {
return Option::from(self.make_token(TokenType::Equal));
Option::from(self.make_token(TokenType::Equal))
}
}
'<' => {
if self.matches('=') {
return Option::from(self.make_token(TokenType::LessEqual));
return if self.matches('=') {
Option::from(self.make_token(TokenType::LessEqual))
} else {
return Option::from(self.make_token(TokenType::Less));
Option::from(self.make_token(TokenType::Less))
}
}
'>' => {
if self.matches('=') {
return Option::from(self.make_token(TokenType::GreaterEqual));
return if self.matches('=') {
Option::from(self.make_token(TokenType::GreaterEqual))
} else {
return Option::from(self.make_token(TokenType::Greater));
Option::from(self.make_token(TokenType::Greater))
}
}
'/' => {
if self.matches('/') {
return if self.matches('/') {
while self.peek_next() != '\n' && !self.is_at_end() {
self.advance();
}
return self.scan();
self.scan()
} else {
return Option::from(self.make_token(TokenType::Slash));
Option::from(self.make_token(TokenType::Slash))
}
}
'"' => return self.make_string(),
_ => Option::from(self.error_token("Unexpected character.")),
_ => Option::from(self.make_error_token("Unexpected character.")),
}
}
}

impl Scanner {
fn make_string(&mut self) -> Option<Token> {
loop {
if self.is_at_end() {
return Option::from(self.error_token("Unterminated string."));
return Option::from(self.make_error_token("Unterminated string."));
}
if self.peek() == '"' {
break;
Expand All @@ -146,20 +145,18 @@ impl Scanner {
}
self.advance();
}

self.advance();
return Option::from(self.make_token(TokenType::String));
Option::from(self.make_token(TokenType::String))
}

fn make_identifier(&mut self) -> Option<Token> {
fn make_identifier(&mut self) -> Token {
loop {
if !Scanner::is_alpha(self.peek()) && !Scanner::is_digit(self.peek()) {
break;
}
self.advance();
}

return Option::from(self.make_token(self.make_identifier_type()));
self.make_token(self.make_identifier_type())
}

fn make_number(&mut self) -> Token {
Expand All @@ -170,62 +167,19 @@ impl Scanner {
self.advance();
}

// Look for a fractional part.
if self.peek() == '.' && Scanner::is_digit(self.peek_next()) {
// Consume the ".".
self.advance();
}

loop {
if !Scanner::is_digit(self.peek()) {
break;
}

self.advance();
}

self.make_token(TokenType::Number)
}

fn make_identifier_type(&self) -> TokenType {
let chr = self.source[self.start];
return match chr {
'a' => self.check_keyword(1, 2, "nd", TokenType::And),
'c' => self.check_keyword(1, 4, "lass", TokenType::Class),
'e' => self.check_keyword(1, 3, "lse", TokenType::Else),
'i' => self.check_keyword(1, 1, "f", TokenType::If),
'n' => self.check_keyword(1, 2, "il", TokenType::Nil),
'o' => self.check_keyword(1, 1, "r", TokenType::Or),
'p' => self.check_keyword(1, 4, "rint", TokenType::Print),
'r' => self.check_keyword(1, 5, "eturn", TokenType::Return),
's' => self.check_keyword(1, 4, "uper", TokenType::Super),
'v' => self.check_keyword(1, 2, "ar", TokenType::Var),
'w' => self.check_keyword(1, 4, "hile", TokenType::While),
'f' => {
if self.current - self.start > 1 {
return match self.source[self.start + 1] {
'a' => self.check_keyword(2, 3, "lse", TokenType::False),
'o' => self.check_keyword(2, 1, "r", TokenType::For),
'u' => self.check_keyword(2, 1, "n", TokenType::Fun),
_ => TokenType::Identifier,
};
}
TokenType::Identifier
}
't' => {
if self.current - self.start > 1 {
return match self.source[self.start + 1] {
'h' => self.check_keyword(2, 2, "is", TokenType::This),
'r' => self.check_keyword(2, 1, "ue", TokenType::True),
_ => TokenType::Identifier,
};
}
TokenType::Identifier
}
_ => TokenType::Identifier,
};
}

fn check_keyword(
&self,
start: usize,
Expand All @@ -239,29 +193,29 @@ impl Scanner {
return token_type;
}
}
return TokenType::Identifier;
TokenType::Identifier
}

fn is_alpha(c: char) -> bool {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
(c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'
}

fn is_digit(c: char) -> bool {
return c >= '0' && c <= '9';
c >= '0' && c <= '9'
}

fn peek_next(&self) -> char {
if self.current + 1 >= self.source.len() {
fn peek(&self) -> char {
if self.is_at_end() {
return '\0';
}
return self.source[self.current + 1];
self.source[self.current]
}

fn peek(&self) -> char {
if self.is_at_end() {
fn peek_next(&self) -> char {
if self.current + 1 >= self.source.len() {
return '\0';
}
return self.source[self.current];
self.source[self.current + 1]
}

fn skip_whitespace(&mut self) {
Expand All @@ -282,22 +236,20 @@ impl Scanner {
}
}

fn advance(&mut self) -> char {
self.current += 1;
self.source[self.current - 1]
}

fn matches(&mut self, chr: char) -> bool {
if self.is_at_end() {
return false;
}

if self.source[self.current] != chr {
return false;
}

self.current += 1;
return true;
}

fn advance(&mut self) -> char {
self.current += 1;
self.source[self.current - 1]
true
}

fn is_at_end(&self) -> bool {
Expand All @@ -308,7 +260,46 @@ impl Scanner {
self.current > self.source.len()
}

fn error_token(&self, message: &str) -> Token {
fn make_identifier_type(&self) -> TokenType {
let chr = self.source[self.start];
return match chr {
'a' => self.check_keyword(1, 2, "nd", TokenType::And),
'c' => self.check_keyword(1, 4, "lass", TokenType::Class),
'e' => self.check_keyword(1, 3, "lse", TokenType::Else),
'i' => self.check_keyword(1, 1, "f", TokenType::If),
'n' => self.check_keyword(1, 2, "il", TokenType::Nil),
'o' => self.check_keyword(1, 1, "r", TokenType::Or),
'p' => self.check_keyword(1, 4, "rint", TokenType::Print),
'r' => self.check_keyword(1, 5, "eturn", TokenType::Return),
's' => self.check_keyword(1, 4, "uper", TokenType::Super),
'v' => self.check_keyword(1, 2, "ar", TokenType::Var),
'w' => self.check_keyword(1, 4, "hile", TokenType::While),
'f' => {
if self.current - self.start > 1 {
return match self.source[self.start + 1] {
'a' => self.check_keyword(2, 3, "lse", TokenType::False),
'o' => self.check_keyword(2, 1, "r", TokenType::For),
'u' => self.check_keyword(2, 1, "n", TokenType::Fun),
_ => TokenType::Identifier,
};
}
TokenType::Identifier
}
't' => {
if self.current - self.start > 1 {
return match self.source[self.start + 1] {
'h' => self.check_keyword(2, 2, "is", TokenType::This),
'r' => self.check_keyword(2, 1, "ue", TokenType::True),
_ => TokenType::Identifier,
};
}
TokenType::Identifier
}
_ => TokenType::Identifier,
};
}

fn make_error_token(&self, message: &str) -> Token {
Token::new(TokenType::Error, self.start, message.len(), self.line)
}

Expand Down
4 changes: 0 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,10 @@ fn run_repl() {
println!("Running REPL");

let mut vm = VirtualMachine::new();

loop {
print_prompt();

let line = read_line();

vm.interpret(line);

println!();
}
}
Expand Down
1 change: 1 addition & 0 deletions src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod virtual_machine;

pub type Value = f64;

#[derive(Debug, PartialEq)]
pub enum Result {
Ok,
CompileError,
Expand Down
42 changes: 42 additions & 0 deletions src/vm/virtual_machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,45 @@ impl VirtualMachine {
self.stack.pop().unwrap()
}
}

#[cfg(test)]
mod tests {
use colored::Color::Black;

#[test]
fn can_create_vm() {
let vm = super::VirtualMachine::new();
assert_eq!(0, vm.ip);
assert_eq!(0, vm.stack.len());
}

#[test]
fn can_execute_simple_arithmetics() {
let mut block = super::Block::new("ZeBlock");

block.write_constant(1.0, 0);
block.write_constant(2.0, 0);
block.write_op_code(super::OpCode::Add, 0);
block.write_constant(3.0, 0);
block.write_op_code(super::OpCode::Multiply, 0);
block.write_constant(2.0, 0);
block.write_op_code(super::OpCode::Subtract, 0);
block.write_constant(2.0, 0);
block.write_op_code(super::OpCode::Divide, 0);

// Pushing throw away value to the stack.
// This is needed because the Return OpCode will pop a value from the stack and print it.
block.write_constant(0.0, 0);
block.write_op_code(super::OpCode::Return, 0);

let mut vm = super::VirtualMachine {
ip: 0,
block,
stack: Vec::new(),
};

let result = vm.run();
assert_eq!(super::Result::Ok, result);
assert_eq!(3.5, vm.pop());
}
}

0 comments on commit d3b4718

Please sign in to comment.