* implemented parser without error handling and without identifiers. It works now for arithmetic expressions.

* Updated the way literals are stored in tokens so that they are no longer of type Any but proper enum Value types, for strings, numeric and boolean values.
This commit is contained in:
Sander Hautvast 2020-01-28 09:28:07 +01:00
parent 9639ca051b
commit a66bf2e16e
7 changed files with 301 additions and 47 deletions

52
src/expression.rs Normal file
View file

@ -0,0 +1,52 @@
use crate::tokens::{Token, Value};
pub trait Visitor<R> {
fn visit_expr(&mut self, expr: &Expr) -> R;
}
#[derive(Debug, PartialOrd, PartialEq)]
pub enum Expr {
Binary(Box<Expr>, Token, Box<Expr>),
Grouping(Box<Expr>),
Literal(Value),
Unary(Token, Box<Expr>),
}
pub struct AstPrinter {}
impl Visitor<String> for AstPrinter {
fn visit_expr(&mut self, expr: &Expr) -> String {
return match expr {
Expr::Binary(left, operator, right) => {
self.parenthesize(&operator.lexeme, &[left, right])
}
Expr::Grouping(expression) => {
self.parenthesize("group", &[expression])
}
Expr::Literal(value) => {
format!("{:?}", value)
}
Expr::Unary(operator, right) => {
self.parenthesize(&operator.lexeme, &[right])
}
};
}
}
impl AstPrinter {
fn parenthesize(&mut self, name: &str, expressions: &[&Expr]) -> String {
let mut buf = String::from("(");
buf.push_str(name);
buf.push_str(" ");
let mut index = 0;
for expr in expressions {
if index > 0 { buf.push_str(" "); }
buf.push_str(&self.visit_expr(expr));
index += 1;
}
buf.push_str(")");
buf
}
}

View file

@ -6,12 +6,17 @@ use std::fs::File;
use std::io::{self, BufRead, Read, Write}; use std::io::{self, BufRead, Read, Write};
use std::process; use std::process;
use crate::expression::{Visitor, AstPrinter};
mod scanner; mod scanner;
mod tokens; mod tokens;
mod keywords; mod keywords;
mod expression;
mod parser;
#[cfg(test)] #[cfg(test)]
mod tests; mod scanner_tests;
mod parser_tests;
/// main /// main
/// no arguments: run interactively /// no arguments: run interactively
@ -84,9 +89,8 @@ fn run_prompt() {
fn run(source: String) -> Result<&'static str, &'static str> { fn run(source: String) -> Result<&'static str, &'static str> {
return match scanner::scan_tokens(source.as_str()) { return match scanner::scan_tokens(source.as_str()) {
Ok(tokens) => { Ok(tokens) => {
for token in tokens { let expr = parser::parse(tokens);
println!("{:?}", token); println!("{:?}", AstPrinter {}.visit_expr(&expr));
}
Ok("Ok") Ok("Ok")
} }
Err(code) => { Err(code) => {

157
src/parser.rs Normal file
View file

@ -0,0 +1,157 @@
use crate::expression::Expr;
use crate::expression::Expr::*;
use crate::tokens::{Token, TokenType};
use crate::tokens::TokenType::*;
use crate::tokens::Value::*;
pub fn parse(tokens: Vec<Token>) -> Expr {
Parser::new(tokens).parse()
}
struct Parser {
tokens: Vec<Token>,
current: usize,
}
impl Parser {
fn new(tokens: Vec<Token>) -> Parser {
Parser { tokens, current: 0 }
}
fn parse(&mut self) -> Expr {
self.expression()
}
fn expression(&mut self) -> Expr {
self.equality()
}
fn equality(&mut self) -> Expr {
let mut expr = self.comparison();
while self.match_token(&[BANGEQUAL, EQUALEQUAL]) {
let operator = self.previous();
let right = self.comparison();
expr = Binary(Box::new(expr), operator, Box::new(right));
}
expr
}
fn comparison(&mut self) -> Expr {
let mut expr = self.addition();
while self.match_token(&[GREATER, GREATEREQUAL, LESS, LESSEQUAL]) {
let operator = self.previous();
let right = self.addition();
expr = Binary(Box::new(expr), operator, Box::new(right));
}
expr
}
fn match_token(&mut self, tokens: &[TokenType]) -> bool {
for token in tokens {
if self.check(*token) {
self.advance();
return true;
}
}
false
}
fn check(&self, token_type: TokenType) -> bool {
return if self.is_at_end() {
false
} else {
self.peek().token_type == token_type
};
}
fn peek(&self) -> Token {
return self.tokens[self.current].clone();
}
fn advance(&mut self) -> Token {
if !self.is_at_end() {
self.current += 1;
}
self.previous()
}
fn is_at_end(&self) -> bool {
self.peek().token_type == EOF
}
fn previous(&self) -> Token {
self.tokens[self.current - 1].clone()
}
fn addition(&mut self) -> Expr {
let mut expr = self.multiplication();
while self.match_token(&[MINUS, PLUS]) {
let operator = self.previous();
let right = self.multiplication();
expr = Binary(Box::new(expr), operator, Box::new(right));
}
expr
}
fn multiplication(&mut self) -> Expr {
let mut expr = self.unary();
while self.match_token(&[SLASH, STAR]) {
let operator = self.previous();
let right = self.unary();
expr = Binary(Box::new(expr), operator, Box::new(right));
}
return expr;
}
fn unary(&mut self) -> Expr {
if self.match_token(&[BANG, MINUS]) {
let operator = self.previous();
let right = self.unary();
return Unary(operator, Box::new(right));
}
return self.primary();
}
fn primary(&mut self) -> Expr {
if self.match_token(&[FALSE]) {
return Literal(Boolean(false));
}
if self.match_token(&[TRUE]) {
return Literal(Boolean(true));
}
if self.match_token(&[NIL]) {
return Literal(None);
}
if self.match_token(&[NUMBER, STRING]) {
return Literal(self.previous().literal);
}
if self.match_token(&[LEFTPAREN]) {
let expr = self.expression();
self.consume_token(RIGHTPAREN, "Expect ')' after expression.");
return Grouping(Box::new(expr));
} else {
Literal(None)
}
}
fn consume_token(&mut self, token_type: TokenType, _message: &str) -> Token {
if self.check(token_type) {
return self.advance();
}
panic!()
}
}

30
src/parser_tests.rs Normal file
View file

@ -0,0 +1,30 @@
use crate::expression::Expr::Binary;
use crate::expression::Expr::Literal;
use crate::parser::parse;
use crate::scanner::scan_tokens;
use crate::tokens::Token;
use crate::tokens::TokenType::PLUS;
use crate::tokens::Value::{None,Numeric};
#[test]
fn test_scan_empty_source() {
let tokens = scan_tokens("").unwrap();
let expression = parse(tokens);
assert_eq!(expression, Literal(None));
}
#[test]
fn test_scan_arithmetic() {
let tokens = scan_tokens("1+1").unwrap();
let expression = parse(tokens);
assert_eq!(expression, Binary(Box::new(Literal(Numeric(1.0))),
Token {
token_type: PLUS,
lexeme: String::from("+"),
literal: None,
line: 1,
},
Box::new(Literal(Numeric(1.0)))));
}

View file

@ -1,7 +1,5 @@
use std::any::Any;
use crate::keywords::KEYWORDS; use crate::keywords::KEYWORDS;
use crate::tokens::{Token, TokenType}; use crate::tokens::{Token, TokenType, Value};
use crate::tokens::TokenType::*; use crate::tokens::TokenType::*;
/// public function for scanning lox source /// public function for scanning lox source
@ -16,8 +14,8 @@ pub fn scan_tokens(source: &str) -> Result<Vec<Token>, &'static str> {
scanner.tokens.push(Token { scanner.tokens.push(Token {
token_type: EOF, token_type: EOF,
lexeme: "lexeme", lexeme: String::new(),
literal: Box::new(""), literal: Value::None,
line: scanner.line, line: scanner.line,
}); });
@ -34,7 +32,7 @@ struct Scanner<'a> {
source: &'a str, source: &'a str,
// the tokens that will be the output of the scan function // the tokens that will be the output of the scan function
tokens: Vec<Token<'a>>, tokens: Vec<Token>,
// start of unscanned source (updated after part of the source was scanned) // start of unscanned source (updated after part of the source was scanned)
start: usize, start: usize,
@ -147,7 +145,7 @@ impl Scanner<'_> {
} }
let value: f64 = self.source[self.start..self.current].parse().expect("not a number"); let value: f64 = self.source[self.start..self.current].parse().expect("not a number");
self.add_token_literal(NUMBER, Box::new(value)); self.add_token_literal(NUMBER, Value::Numeric(value));
} }
/// handle string literals /// handle string literals
@ -167,7 +165,7 @@ impl Scanner<'_> {
self.advance(); self.advance();
let value = String::from(&self.source[self.start + 1..self.current - 1]); let value = String::from(&self.source[self.start + 1..self.current - 1]);
self.add_token_literal(STRING, Box::new(value)); self.add_token_literal(STRING, Value::Text(value));
} }
} }
@ -180,14 +178,14 @@ impl Scanner<'_> {
/// adds a token of the given type /// adds a token of the given type
fn add_token(&mut self, token_type: TokenType) { fn add_token(&mut self, token_type: TokenType) {
let text = &self.source[self.start..self.current]; let text = &self.source[self.start..self.current];
let token = Token { token_type: token_type, lexeme: text, literal: Box::new(""), line: self.line }; let token = Token { token_type: token_type, lexeme: String::from(text), literal: Value::None, line: self.line };
self.tokens.push(token); self.tokens.push(token);
} }
/// adds a token of the given type and content /// adds a token of the given type and content
fn add_token_literal(&mut self, token_type: TokenType, literal: Box<dyn Any>) { fn add_token_literal(&mut self, token_type: TokenType, literal: Value) {
let text = &self.source[self.start..self.current]; let text = &self.source[self.start..self.current];
let token = Token { token_type: token_type, lexeme: text, literal: literal, line: self.line }; let token = Token { token_type: token_type, lexeme: String::from(text), literal, line: self.line };
self.tokens.push(token); self.tokens.push(token);
} }

View file

@ -1,6 +1,7 @@
#[cfg(test)] #[cfg(test)]
use crate::scanner::scan_tokens; use crate::scanner::scan_tokens;
use crate::tokens::TokenType::*; use crate::tokens::TokenType::*;
use crate::tokens::Value::{Numeric, Text};
#[test] #[test]
fn test_scan_empty_source() { fn test_scan_empty_source() {
@ -44,7 +45,12 @@ fn test_scan_string_literals() {
let token = tokens.get(0).unwrap(); let token = tokens.get(0).unwrap();
assert_eq!(token.token_type, STRING); assert_eq!(token.token_type, STRING);
assert_eq!(token.lexeme, "\"hello world\""); assert_eq!(token.lexeme, "\"hello world\"");
assert_eq!(token.get_literal_as_string().unwrap(), "hello world"); match token.literal.clone() {
Text(value) => {
assert_eq!(value, "hello world");
}
_ => { assert_eq!(true,false, "token value != hello world") }
}
} }
#[test] #[test]
@ -55,7 +61,12 @@ fn test_scan_numeric_literals() {
let token = tokens.get(0).unwrap(); let token = tokens.get(0).unwrap();
assert_eq!(token.token_type, NUMBER); assert_eq!(token.token_type, NUMBER);
assert_eq!(token.lexeme, "0.1"); assert_eq!(token.lexeme, "0.1");
assert_eq!(token.get_literal_as_float().unwrap(), 0.1); match token.literal {
Numeric(value) => {
assert_eq!(value, 0.1);
}
_ => { assert_eq!(true, false, "token value != 0.1") }
}
} }
#[test] #[test]

View file

@ -1,47 +1,49 @@
use std::any::Any;
use std::fmt; use std::fmt;
#[derive(Clone, PartialOrd, PartialEq)]
pub enum Value {
Text(String),
Numeric(f64),
Boolean(bool),
None,
}
impl fmt::Debug for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::Text(value) => {
write!(f, "{}", value.to_string())
}
Value::Numeric(value) => {
write!(f, "{}", value)
}
Value::Boolean(value) => {
write!(f, "{}", value)
}
Value::None => {
write!(f, "Nil")
}
}
}
}
/// struct that contains a single token /// struct that contains a single token
pub struct Token<'a> { #[derive(Debug, Clone, PartialOrd, PartialEq)]
pub struct Token {
// the type // the type
pub token_type: TokenType, pub token_type: TokenType,
// the actual part of the code that resulted in this token // the actual part of the code that resulted in this token
pub lexeme: &'a str, pub lexeme: String,
// numeric (ie 1,2, 1.0 etc) and alphanumeric (any quoted text) values // numeric (ie 1,2, 1.0 etc) and alphanumeric (any quoted text) values
pub literal: Box<dyn Any>, pub literal: Value,
// the line that contains the code for this token instance // the line that contains the code for this token instance
pub line: usize, pub line: usize,
} }
impl Token<'_> { #[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd)]
pub fn get_literal_as_string(&self) -> Option<&str> {
self.literal.downcast_ref::<String>().map(|s| s.as_str())
}
pub fn get_literal_as_float(&self) -> Option<f64> {
self.literal.downcast_ref::<f64>().map(|f| *f)
}
}
impl fmt::Debug for Token<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let lit = match self.literal.downcast_ref::<String>() {
Some(as_string) => {
as_string.to_string()
}
None => {
format!("{:?}", self.literal)
}
};
write!(f, "Token [ type: {:?}, lexeme: {}, literal: {}, line: {} ]", self.token_type, self.lexeme, lit, self.line)
}
}
#[derive(Eq, PartialEq, Debug, Clone, Copy)]
pub enum TokenType { pub enum TokenType {
// Single-character tokens. // Single-character tokens.
LEFTPAREN, LEFTPAREN,