mirror of
https://github.com/TheM1Stery/izanami.git
synced 2025-04-20 08:51:12 +00:00
Compare commits
3 Commits
9f903556f9
...
be15364e3b
Author | SHA1 | Date | |
---|---|---|---|
be15364e3b | |||
50a9e062e0 | |||
8b29a43900 |
@ -4,9 +4,7 @@ use crate::token::{LiteralType, Token};
|
|||||||
pub enum Expr {
|
pub enum Expr {
|
||||||
Ternary {
|
Ternary {
|
||||||
first: Box<Expr>,
|
first: Box<Expr>,
|
||||||
first_op: Token,
|
|
||||||
second: Box<Expr>,
|
second: Box<Expr>,
|
||||||
second_op: Token,
|
|
||||||
third: Box<Expr>,
|
third: Box<Expr>,
|
||||||
},
|
},
|
||||||
Binary {
|
Binary {
|
||||||
@ -18,7 +16,7 @@ pub enum Expr {
|
|||||||
expression: Box<Expr>,
|
expression: Box<Expr>,
|
||||||
},
|
},
|
||||||
Literal {
|
Literal {
|
||||||
value: Option<LiteralType>,
|
value: LiteralType,
|
||||||
},
|
},
|
||||||
Unary {
|
Unary {
|
||||||
op: Token,
|
op: Token,
|
||||||
|
98
src/interpreter.rs
Normal file
98
src/interpreter.rs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
use crate::{
|
||||||
|
ast::Expr,
|
||||||
|
token::{LiteralType, Token, TokenType},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RuntimeError {
|
||||||
|
pub token: Token,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RuntimeError {
|
||||||
|
pub fn new(token: &Token, message: &str) -> RuntimeError {
|
||||||
|
RuntimeError {
|
||||||
|
token: token.clone(),
|
||||||
|
message: message.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interpret(expr: &Expr) -> Result<LiteralType, RuntimeError> {
|
||||||
|
match expr {
|
||||||
|
Expr::Ternary {
|
||||||
|
first,
|
||||||
|
second,
|
||||||
|
third,
|
||||||
|
..
|
||||||
|
} => ternary(first, second, third),
|
||||||
|
Expr::Binary { left, op, right } => binary(&interpret(left)?, &interpret(right)?, op),
|
||||||
|
Expr::Grouping { expression } => interpret(expression),
|
||||||
|
Expr::Literal { value } => Ok(value.clone()),
|
||||||
|
Expr::Unary { op, right } => Ok(unary(&interpret(right)?, op)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ternary(first: &Expr, second: &Expr, third: &Expr) -> Result<LiteralType, RuntimeError> {
|
||||||
|
let first = interpret(first)?;
|
||||||
|
if is_truthy(&first) {
|
||||||
|
return interpret(second);
|
||||||
|
}
|
||||||
|
interpret(third)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn binary(
|
||||||
|
left: &LiteralType,
|
||||||
|
right: &LiteralType,
|
||||||
|
op: &Token,
|
||||||
|
) -> Result<LiteralType, RuntimeError> {
|
||||||
|
use LiteralType::{Bool, Number, String};
|
||||||
|
use TokenType::{
|
||||||
|
BangEqual, Comma, EqualEqual, Greater, GreaterEqual, Less, LessEqual, Minus, Plus, Slash,
|
||||||
|
Star,
|
||||||
|
};
|
||||||
|
|
||||||
|
match (op.t_type, &left, &right) {
|
||||||
|
(Greater, Number(left), Number(right)) => Ok(Bool(left > right)),
|
||||||
|
(GreaterEqual, Number(left), Number(right)) => Ok(Bool(left >= right)),
|
||||||
|
(Less, Number(left), Number(right)) => Ok(Bool(left < right)),
|
||||||
|
(LessEqual, Number(left), Number(right)) => Ok(Bool(left <= right)),
|
||||||
|
(BangEqual, _, _) => Ok(Bool(!is_equal(left, right))),
|
||||||
|
(EqualEqual, _, _) => Ok(Bool(is_equal(left, right))),
|
||||||
|
(Minus, Number(left), Number(right)) => Ok(Number(left - right)),
|
||||||
|
(Plus, Number(left), Number(right)) => Ok(Number(left + right)),
|
||||||
|
(Plus, String(left), String(right)) => Ok(String(format!("{left}{right}"))),
|
||||||
|
(Slash, Number(left), Number(right)) => Ok(Number(left / right)),
|
||||||
|
(Star, Number(left), Number(right)) => Ok(Number(left * right)),
|
||||||
|
(Comma, _,_) => Ok(right.clone()),
|
||||||
|
(Greater | GreaterEqual | Less | LessEqual | Minus | Slash | Star, _, _) => Err(RuntimeError::new(op, "Operands must be numbers")),
|
||||||
|
(Plus, _, _) => Err(RuntimeError::new(op, "Operands must be two numbers or two strings")),
|
||||||
|
/* comma operator discard the left operand, so we just return the evaluation of the right operand */
|
||||||
|
|
||||||
|
_ => unreachable!("Shouldn't happen. Expr::Binary for interpret. Some case is a binary operation that wasn't matched")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unary(right: &LiteralType, op: &Token) -> LiteralType {
|
||||||
|
match (op.t_type, &right) {
|
||||||
|
(TokenType::Minus, LiteralType::Number(num)) => LiteralType::Number(-num),
|
||||||
|
(TokenType::Bang, _) => LiteralType::Bool(!is_truthy(right)),
|
||||||
|
_ => unreachable!("Shouldn't happen. Expr::Unary for interpret"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_truthy(literal: &LiteralType) -> bool {
|
||||||
|
match literal {
|
||||||
|
LiteralType::Nil => false,
|
||||||
|
LiteralType::Bool(val) => *val,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_equal(left: &LiteralType, right: &LiteralType) -> bool {
|
||||||
|
match (left, right) {
|
||||||
|
(LiteralType::Nil, LiteralType::Nil) => true,
|
||||||
|
(LiteralType::Nil, _) => false,
|
||||||
|
_ => left == right,
|
||||||
|
}
|
||||||
|
}
|
44
src/lib.rs
44
src/lib.rs
@ -1,43 +1,55 @@
|
|||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
fmt::Display,
|
|
||||||
fs,
|
fs,
|
||||||
io::{self, Write},
|
io::{self, Write},
|
||||||
};
|
};
|
||||||
|
|
||||||
use parser::{ParseError, Parser};
|
use interpreter::RuntimeError;
|
||||||
use printer::pretty_print;
|
use parser::Parser;
|
||||||
use scanner::Scanner;
|
use scanner::Scanner;
|
||||||
use token::{Token, TokenType};
|
use token::{Token, TokenType};
|
||||||
|
|
||||||
mod ast;
|
mod ast;
|
||||||
|
mod interpreter;
|
||||||
mod parser;
|
mod parser;
|
||||||
mod printer;
|
mod printer;
|
||||||
mod scanner;
|
mod scanner;
|
||||||
mod token;
|
mod token;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
pub fn run_file(path: &str) -> Result<(), Box<dyn Error>> {
|
#[derive(Debug)]
|
||||||
let file = fs::read_to_string(path)?;
|
pub enum RunError {
|
||||||
|
FileReadError(io::Error),
|
||||||
|
OtherError(Box<dyn Error>), // to be added,
|
||||||
|
RuntimeError(RuntimeError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Error + 'static> From<E> for RunError {
|
||||||
|
fn from(value: E) -> Self {
|
||||||
|
Self::OtherError(Box::new(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_file(path: &str) -> Result<(), RunError> {
|
||||||
|
let file = fs::read_to_string(path).map_err(RunError::FileReadError)?;
|
||||||
|
|
||||||
run(&file)?;
|
run(&file)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(src: &str) -> Result<(), Box<dyn Error>> {
|
pub fn run(src: &str) -> Result<(), RunError> {
|
||||||
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()?;
|
||||||
|
|
||||||
let mut parser = Parser::new(tokens);
|
let mut parser = Parser::new(tokens);
|
||||||
|
|
||||||
let expression = parser.parse();
|
let expression = parser.parse().inspect_err(|e| error(&e.token, &e.msg))?;
|
||||||
|
|
||||||
match expression {
|
let interpreted_value = interpreter::interpret(&expression)
|
||||||
Ok(expr) => println!("{}", pretty_print(&expr)),
|
.inspect_err(runtime_error)
|
||||||
Err(e) => {
|
.map_err(RunError::RuntimeError)?;
|
||||||
error(e.token, &e.msg);
|
|
||||||
}
|
println!("{interpreted_value}");
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -64,9 +76,13 @@ 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) {
|
fn error(token: &Token, message: &str) {
|
||||||
match token.t_type {
|
match token.t_type {
|
||||||
TokenType::EOF => report(token.line, " at end", message),
|
TokenType::EOF => report(token.line, " at end", message),
|
||||||
_ => report(token.line, &format!(" at '{}'", token.lexeme), message),
|
_ => report(token.line, &format!(" at '{}'", token.lexeme), message),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn runtime_error(err: &RuntimeError) {
|
||||||
|
eprintln!("{}\n[line {}]", err.message, err.token.line);
|
||||||
|
}
|
||||||
|
14
src/main.rs
14
src/main.rs
@ -1,6 +1,6 @@
|
|||||||
use std::{env::args_os, ffi::OsString, process::ExitCode};
|
use std::{env::args_os, ffi::OsString, process::ExitCode};
|
||||||
|
|
||||||
use izanami::{run_file, run_prompt};
|
use izanami::{run_file, run_prompt, RunError};
|
||||||
|
|
||||||
fn main() -> ExitCode {
|
fn main() -> ExitCode {
|
||||||
let args: Vec<OsString> = args_os().collect();
|
let args: Vec<OsString> = args_os().collect();
|
||||||
@ -11,10 +11,18 @@ fn main() -> ExitCode {
|
|||||||
} else if args.len() == 2 {
|
} else if args.len() == 2 {
|
||||||
let result = run_file(args[1].to_str().unwrap());
|
let result = run_file(args[1].to_str().unwrap());
|
||||||
|
|
||||||
if let Err(res) = result {
|
if let Err(RunError::FileReadError(e)) = result {
|
||||||
println!("Couldn't read the file. Reason: {}", &*res);
|
println!("Couldn't read the file. Reason: {}", e);
|
||||||
return ExitCode::from(1);
|
return ExitCode::from(1);
|
||||||
}
|
}
|
||||||
|
if let Err(RunError::OtherError(e)) = result {
|
||||||
|
println!("Error occured. Error: {}", e);
|
||||||
|
return ExitCode::from(75);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(RunError::RuntimeError(r)) = result {
|
||||||
|
return ExitCode::from(70);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let result = run_prompt();
|
let result = run_prompt();
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::Expr,
|
ast::Expr,
|
||||||
token::{LiteralType, Token, TokenType},
|
token::{LiteralType, Token, TokenType},
|
||||||
@ -14,6 +16,14 @@ pub struct ParseError {
|
|||||||
pub msg: String,
|
pub msg: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for ParseError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "ParseError: {} {}", self.token, self.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for ParseError {}
|
||||||
|
|
||||||
impl Parser<'_> {
|
impl Parser<'_> {
|
||||||
pub fn new(tokens: &Vec<Token>) -> Parser<'_> {
|
pub fn new(tokens: &Vec<Token>) -> Parser<'_> {
|
||||||
Parser { tokens, current: 0 }
|
Parser { tokens, current: 0 }
|
||||||
@ -40,15 +50,12 @@ impl Parser<'_> {
|
|||||||
let expr = self.equality()?;
|
let expr = self.equality()?;
|
||||||
|
|
||||||
if self.match_token(&[Question]) {
|
if self.match_token(&[Question]) {
|
||||||
let first_op = self.previous().clone();
|
|
||||||
let second = self.expression()?;
|
let second = self.expression()?;
|
||||||
let second_op = self.consume(Colon, "Expected : after ternary operator ?")?;
|
let _ = self.consume(Colon, "Expected : after ternary operator ?")?;
|
||||||
let third = self.ternary()?;
|
let third = self.ternary()?;
|
||||||
return Ok(Expr::Ternary {
|
return Ok(Expr::Ternary {
|
||||||
first: Box::new(expr),
|
first: Box::new(expr),
|
||||||
first_op,
|
|
||||||
second: Box::new(second),
|
second: Box::new(second),
|
||||||
second_op,
|
|
||||||
third: Box::new(third),
|
third: Box::new(third),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -99,25 +106,30 @@ impl Parser<'_> {
|
|||||||
use LiteralType::*;
|
use LiteralType::*;
|
||||||
use TokenType::*;
|
use TokenType::*;
|
||||||
|
|
||||||
fn create_literal(l_type: Option<LiteralType>) -> Expr {
|
fn create_literal(l_type: LiteralType) -> Expr {
|
||||||
Expr::Literal { value: l_type }
|
Expr::Literal { value: l_type }
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.match_token(&[False]) {
|
if self.match_token(&[False]) {
|
||||||
return Ok(create_literal(Some(Bool(false))));
|
return Ok(create_literal(Bool(false)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.match_token(&[True]) {
|
if self.match_token(&[True]) {
|
||||||
return Ok(create_literal(Some(Bool(true))));
|
return Ok(create_literal(Bool(true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.match_token(&[TokenType::Number, TokenType::String]) {
|
if self.match_token(&[TokenType::Number, TokenType::String]) {
|
||||||
return Ok(create_literal(self.previous().literal.clone()));
|
return Ok(create_literal(
|
||||||
|
self.previous()
|
||||||
|
.literal
|
||||||
|
.clone()
|
||||||
|
.expect("The number and string token should have a literal"),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// i included the enum name bcs of ambiguity of LiteralType and TokenType
|
// i included the enum name bcs of ambiguity of LiteralType and TokenType
|
||||||
if self.match_token(&[TokenType::Nil]) {
|
if self.match_token(&[TokenType::Nil]) {
|
||||||
return Ok(create_literal(Some(LiteralType::Nil)));
|
return Ok(create_literal(LiteralType::Nil));
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.match_token(&[LeftParen]) {
|
if self.match_token(&[LeftParen]) {
|
||||||
|
@ -5,23 +5,17 @@ pub fn pretty_print(expr: &Expr) -> String {
|
|||||||
Expr::Binary { left, op, right } => parenthesize(&op.lexeme, &[left, right]),
|
Expr::Binary { left, op, right } => parenthesize(&op.lexeme, &[left, right]),
|
||||||
Expr::Grouping { expression } => parenthesize("group", &[expression]),
|
Expr::Grouping { expression } => parenthesize("group", &[expression]),
|
||||||
Expr::Literal { value } => match value {
|
Expr::Literal { value } => match value {
|
||||||
Some(LiteralType::String(v)) => v.to_string(),
|
LiteralType::String(v) => v.to_string(),
|
||||||
Some(LiteralType::Number(v)) => v.to_string(),
|
LiteralType::Number(v) => v.to_string(),
|
||||||
Some(LiteralType::Bool(v)) => v.to_string(),
|
LiteralType::Bool(v) => v.to_string(),
|
||||||
Some(LiteralType::Nil) => "Nil".to_string(),
|
LiteralType::Nil => "Nil".to_string(),
|
||||||
None => "None".to_string(),
|
|
||||||
},
|
},
|
||||||
Expr::Unary { op, right } => parenthesize(&op.lexeme, &[right]),
|
Expr::Unary { op, right } => parenthesize(&op.lexeme, &[right]),
|
||||||
Expr::Ternary {
|
Expr::Ternary {
|
||||||
first,
|
first,
|
||||||
first_op,
|
|
||||||
second,
|
second,
|
||||||
second_op,
|
|
||||||
third,
|
third,
|
||||||
} => parenthesize(
|
} => parenthesize("?:", &[first, second, third]),
|
||||||
&format!("{}{}", first_op.lexeme, second_op.lexeme),
|
|
||||||
&[first, second, third],
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,7 +44,7 @@ mod test {
|
|||||||
use TokenType::*;
|
use TokenType::*;
|
||||||
let expression = Binary {
|
let expression = Binary {
|
||||||
left: Box::new(Literal {
|
left: Box::new(Literal {
|
||||||
value: Some(LiteralType::Number(10.2)),
|
value: LiteralType::Number(10.2),
|
||||||
}),
|
}),
|
||||||
op: Token {
|
op: Token {
|
||||||
t_type: Plus,
|
t_type: Plus,
|
||||||
@ -59,7 +53,7 @@ mod test {
|
|||||||
line: 0,
|
line: 0,
|
||||||
},
|
},
|
||||||
right: Box::new(Literal {
|
right: Box::new(Literal {
|
||||||
value: Some(LiteralType::Number(10.2)),
|
value: LiteralType::Number(10.2),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -77,13 +71,13 @@ mod test {
|
|||||||
left: Box::new(Unary {
|
left: Box::new(Unary {
|
||||||
op: Token::new(Minus, "-", None, 0),
|
op: Token::new(Minus, "-", None, 0),
|
||||||
right: Box::new(Expr::Literal {
|
right: Box::new(Expr::Literal {
|
||||||
value: Some(LiteralType::number_literal(123.0)),
|
value: LiteralType::number_literal(123.0),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
op: Token::new(Star, "*", None, 0),
|
op: Token::new(Star, "*", None, 0),
|
||||||
right: Box::new(Grouping {
|
right: Box::new(Grouping {
|
||||||
expression: Box::new(Literal {
|
expression: Box::new(Literal {
|
||||||
value: Some(LiteralType::number_literal(45.67)),
|
value: LiteralType::number_literal(45.67),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
11
src/token.rs
11
src/token.rs
@ -68,6 +68,17 @@ impl LiteralType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for LiteralType {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
LiteralType::String(v) => write!(f, "{v}"),
|
||||||
|
LiteralType::Number(v) => write!(f, "{v:.2}"),
|
||||||
|
LiteralType::Bool(v) => write!(f, "{v}"),
|
||||||
|
LiteralType::Nil => write!(f, "nil"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Token {
|
pub struct Token {
|
||||||
pub t_type: TokenType,
|
pub t_type: TokenType,
|
||||||
|
Loading…
Reference in New Issue
Block a user