mirror of
https://github.com/TheM1Stery/izanami.git
synced 2025-04-20 00:41:11 +00:00
feat: finish chapter 6 (implement a parser)
there are challenges, which i wish to do (need to add support for stuff)
This commit is contained in:
parent
5f80dfe498
commit
91cfc0b938
@ -1,5 +1,6 @@
|
|||||||
use crate::token::{LiteralType, Token};
|
use crate::token::{LiteralType, Token};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub enum Expr {
|
pub enum Expr {
|
||||||
Binary {
|
Binary {
|
||||||
left: Box<Expr>,
|
left: Box<Expr>,
|
||||||
|
41
src/lib.rs
41
src/lib.rs
@ -1,13 +1,17 @@
|
|||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
|
fmt::Display,
|
||||||
fs,
|
fs,
|
||||||
io::{self, Write},
|
io::{self, Write},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use parser::{ParseError, Parser};
|
||||||
|
use printer::pretty_print;
|
||||||
use scanner::Scanner;
|
use scanner::Scanner;
|
||||||
use token::Token;
|
use token::{Token, TokenType};
|
||||||
|
|
||||||
mod ast;
|
mod ast;
|
||||||
|
mod parser;
|
||||||
mod printer;
|
mod printer;
|
||||||
mod scanner;
|
mod scanner;
|
||||||
mod token;
|
mod token;
|
||||||
@ -16,26 +20,26 @@ mod utils;
|
|||||||
pub fn run_file(path: &str) -> Result<(), Box<dyn Error>> {
|
pub fn run_file(path: &str) -> Result<(), Box<dyn Error>> {
|
||||||
let file = fs::read_to_string(path)?;
|
let file = fs::read_to_string(path)?;
|
||||||
|
|
||||||
run(&file);
|
run(&file)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(src: &str) {
|
pub fn run(src: &str) -> Result<(), Box<dyn Error>> {
|
||||||
let mut scanner = Scanner::new(src.to_string());
|
let mut scanner = Scanner::new(src.to_string());
|
||||||
let tokens = scanner.scan_tokens();
|
let tokens = scanner.scan_tokens()?;
|
||||||
|
|
||||||
match tokens {
|
let mut parser = Parser::new(tokens);
|
||||||
Err(ref errors) => {
|
|
||||||
for err in errors {
|
let expression = parser.parse();
|
||||||
report(err.line, "", &err.msg);
|
|
||||||
}
|
match expression {
|
||||||
}
|
Ok(expr) => println!("{}", pretty_print(&expr)),
|
||||||
Ok(tokens) => {
|
Err(e) => {
|
||||||
for token in tokens {
|
error(e.token, &e.msg);
|
||||||
println!("{}", token);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_prompt() -> Result<(), Box<dyn Error>> {
|
pub fn run_prompt() -> Result<(), Box<dyn Error>> {
|
||||||
@ -46,7 +50,7 @@ pub fn run_prompt() -> Result<(), Box<dyn Error>> {
|
|||||||
print!("> ");
|
print!("> ");
|
||||||
io::stdout().flush()?;
|
io::stdout().flush()?;
|
||||||
stdin.read_line(input)?;
|
stdin.read_line(input)?;
|
||||||
run(input);
|
let _ = run(input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,3 +63,10 @@ pub struct RloxError {
|
|||||||
pub fn report(line: usize, location: &str, message: &str) {
|
pub fn report(line: usize, location: &str, message: &str) {
|
||||||
eprintln!("[line {line}] Error {location}: {message}");
|
eprintln!("[line {line}] Error {location}: {message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn error(token: Token, message: &str) {
|
||||||
|
match token.t_type {
|
||||||
|
TokenType::EOF => report(token.line, " at end", message),
|
||||||
|
_ => report(token.line, &format!(" at '{}'", token.lexeme), message),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
189
src/parser.rs
Normal file
189
src/parser.rs
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
ast::Expr,
|
||||||
|
token::{LiteralType, Token, TokenType},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Parser<'a> {
|
||||||
|
tokens: &'a Vec<Token>,
|
||||||
|
current: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ParseError {
|
||||||
|
pub token: Token,
|
||||||
|
pub msg: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parser<'_> {
|
||||||
|
pub fn new(tokens: &Vec<Token>) -> Parser<'_> {
|
||||||
|
Parser { tokens, current: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(&mut self) -> Result<Expr, ParseError> {
|
||||||
|
self.expression()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expression(&mut self) -> Result<Expr, ParseError> {
|
||||||
|
self.equality()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn equality(&mut self) -> Result<Expr, ParseError> {
|
||||||
|
use TokenType::*;
|
||||||
|
self.left_association_binary(&[BangEqual, EqualEqual], Self::comparison)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn comparison(&mut self) -> Result<Expr, ParseError> {
|
||||||
|
use TokenType::*;
|
||||||
|
self.left_association_binary(&[Greater, GreaterEqual, Less, LessEqual], Self::term)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn term(&mut self) -> Result<Expr, ParseError> {
|
||||||
|
use TokenType::*;
|
||||||
|
self.left_association_binary(&[Minus, Plus], Self::factor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn factor(&mut self) -> Result<Expr, ParseError> {
|
||||||
|
use TokenType::*;
|
||||||
|
self.left_association_binary(&[Slash, Star], Self::unary)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unary(&mut self) -> Result<Expr, ParseError> {
|
||||||
|
use TokenType::*;
|
||||||
|
if self.match_token(&[Bang, Minus]) {
|
||||||
|
let op = self.previous().clone();
|
||||||
|
let right = self.unary()?;
|
||||||
|
return Ok(Expr::Unary {
|
||||||
|
op,
|
||||||
|
right: Box::new(right),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.primary()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn primary(&mut self) -> Result<Expr, ParseError> {
|
||||||
|
use LiteralType::*;
|
||||||
|
use TokenType::*;
|
||||||
|
|
||||||
|
fn create_literal(l_type: Option<LiteralType>) -> Expr {
|
||||||
|
Expr::Literal { value: l_type }
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.match_token(&[False]) {
|
||||||
|
return Ok(create_literal(Some(Bool(false))));
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.match_token(&[True]) {
|
||||||
|
return Ok(create_literal(Some(Bool(true))));
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.match_token(&[TokenType::Number, TokenType::String]) {
|
||||||
|
return Ok(create_literal(self.previous().literal.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// i included the enum name bcs of ambiguity of LiteralType and TokenType
|
||||||
|
if self.match_token(&[TokenType::Nil]) {
|
||||||
|
return Ok(create_literal(Some(LiteralType::Nil)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.match_token(&[LeftParen]) {
|
||||||
|
let expr = self.expression()?;
|
||||||
|
self.consume(RightParen, "Expect ')' after expression")?;
|
||||||
|
return Ok(Expr::Grouping {
|
||||||
|
expression: Box::new(expr),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(ParseError {
|
||||||
|
token: self.peek().clone(),
|
||||||
|
msg: "Expect expression.".to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn consume(&mut self, t_type: TokenType, err_msg: &str) -> Result<Token, ParseError> {
|
||||||
|
if self.check(t_type) {
|
||||||
|
return Ok(self.advance().clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(ParseError {
|
||||||
|
token: self.peek().clone(),
|
||||||
|
msg: err_msg.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// will not be used for the time being (per the book)
|
||||||
|
fn synchronize(&mut self) {
|
||||||
|
use TokenType::*;
|
||||||
|
self.advance();
|
||||||
|
|
||||||
|
while !self.is_at_end() {
|
||||||
|
if self.previous().t_type == TokenType::Semicolon {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Class | Fun | Var | For | If | While | Print | Return = self.peek().t_type {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn left_association_binary(
|
||||||
|
&mut self,
|
||||||
|
types: &[TokenType],
|
||||||
|
expr_fn: fn(&mut Self) -> Result<Expr, ParseError>,
|
||||||
|
) -> Result<Expr, ParseError> {
|
||||||
|
let mut expr = expr_fn(self)?;
|
||||||
|
while self.match_token(types) {
|
||||||
|
let op = self.previous().clone();
|
||||||
|
let right = expr_fn(self)?;
|
||||||
|
expr = Expr::Binary {
|
||||||
|
left: Box::new(expr),
|
||||||
|
op,
|
||||||
|
right: Box::new(right),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn match_token(&mut self, types: &[TokenType]) -> bool {
|
||||||
|
for t_type in types {
|
||||||
|
if self.check(*t_type) {
|
||||||
|
self.advance();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check(&self, t_type: TokenType) -> bool {
|
||||||
|
if self.is_at_end() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self.peek().t_type == t_type
|
||||||
|
}
|
||||||
|
|
||||||
|
fn advance(&mut self) -> &Token {
|
||||||
|
if !self.is_at_end() {
|
||||||
|
self.current += 1;
|
||||||
|
}
|
||||||
|
self.previous()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_at_end(&self) -> bool {
|
||||||
|
matches!(self.peek().t_type, TokenType::EOF)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peek(&self) -> &Token {
|
||||||
|
&self.tokens[self.current]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn previous(&self) -> &Token {
|
||||||
|
&self.tokens[self.current - 1]
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,8 @@ pub fn pretty_print(expr: &Expr) -> String {
|
|||||||
Expr::Literal { value } => match value {
|
Expr::Literal { value } => match value {
|
||||||
Some(LiteralType::String(v)) => v.to_string(),
|
Some(LiteralType::String(v)) => v.to_string(),
|
||||||
Some(LiteralType::Number(v)) => v.to_string(),
|
Some(LiteralType::Number(v)) => v.to_string(),
|
||||||
|
Some(LiteralType::Bool(v)) => v.to_string(),
|
||||||
|
Some(LiteralType::Nil) => "Nil".to_string(),
|
||||||
None => "None".to_string(),
|
None => "None".to_string(),
|
||||||
},
|
},
|
||||||
Expr::Unary { op, right } => parenthesize(&op.lexeme, &[right]),
|
Expr::Unary { op, right } => parenthesize(&op.lexeme, &[right]),
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::{iter::Peekable, mem, str::Chars};
|
use std::{fmt::Display, iter::Peekable, mem, str::Chars};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
token::{LiteralType, Token, TokenType},
|
token::{LiteralType, Token, TokenType},
|
||||||
@ -15,6 +15,19 @@ pub struct Scanner {
|
|||||||
line: usize,
|
line: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ScannerError {
|
||||||
|
errors: Vec<RloxError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ScannerError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "Errors: {:?}", self.errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for ScannerError {}
|
||||||
|
|
||||||
impl Scanner {
|
impl Scanner {
|
||||||
pub fn new(source: String) -> Self {
|
pub fn new(source: String) -> Self {
|
||||||
// the reason for using unsafe here is to have the ability to use utf-8 symbols
|
// the reason for using unsafe here is to have the ability to use utf-8 symbols
|
||||||
@ -35,7 +48,7 @@ impl Scanner {
|
|||||||
|
|
||||||
// this is so awful for me to write. This function needs to be not mutable in theory and it
|
// this is so awful for me to write. This function needs to be not mutable in theory and it
|
||||||
// could be accomplished. TODO!
|
// could be accomplished. TODO!
|
||||||
pub fn scan_tokens(&mut self) -> Result<&Vec<Token>, Vec<RloxError>> {
|
pub fn scan_tokens(&mut self) -> Result<&Vec<Token>, ScannerError> {
|
||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
while self.peek().is_some() {
|
while self.peek().is_some() {
|
||||||
self.start = self.current;
|
self.start = self.current;
|
||||||
@ -53,7 +66,7 @@ impl Scanner {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if !errors.is_empty() {
|
if !errors.is_empty() {
|
||||||
return Err(errors);
|
return Err(ScannerError { errors });
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(&self.tokens)
|
Ok(&self.tokens)
|
||||||
@ -331,6 +344,7 @@ mod tests {
|
|||||||
let tokens = scanner.scan_tokens().expect_err("Should be an error");
|
let tokens = scanner.scan_tokens().expect_err("Should be an error");
|
||||||
|
|
||||||
let actual_error = tokens
|
let actual_error = tokens
|
||||||
|
.errors
|
||||||
.iter()
|
.iter()
|
||||||
.find(|e| e.msg == "Unterminated string")
|
.find(|e| e.msg == "Unterminated string")
|
||||||
.expect("Error not found. There should be an error");
|
.expect("Error not found. There should be an error");
|
||||||
|
@ -52,19 +52,21 @@ pub enum TokenType {
|
|||||||
pub enum LiteralType {
|
pub enum LiteralType {
|
||||||
String(String),
|
String(String),
|
||||||
Number(f64),
|
Number(f64),
|
||||||
|
Bool(bool),
|
||||||
|
Nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LiteralType {
|
impl LiteralType {
|
||||||
pub fn string_literal(val: &str) -> LiteralType {
|
pub fn string_literal(val: &str) -> LiteralType {
|
||||||
return LiteralType::String(val.to_string());
|
LiteralType::String(val.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn number_literal(val: f64) -> LiteralType {
|
pub fn number_literal(val: f64) -> LiteralType {
|
||||||
return LiteralType::Number(val);
|
LiteralType::Number(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Token {
|
pub struct Token {
|
||||||
pub t_type: TokenType,
|
pub t_type: TokenType,
|
||||||
pub lexeme: String,
|
pub lexeme: String,
|
||||||
|
Loading…
Reference in New Issue
Block a user