diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 87bf6dc..dbc336a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -27,7 +27,7 @@ jobs: target/ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - uses: actions-rs/toolchain@v1 + - uses: actions-rs/toolchain@v1.0.6 with: toolchain: nightly diff --git a/Cargo.toml b/Cargo.toml index 530e106..53c88ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,17 @@ name = "luxya" version = "0.1.0" authors = ["franek "] edition = "2018" +description = "Programming language with a tree-walking interpreter" +license-file = "LICENSE" +repository = "https://github.com/franeklubi/luxya/" +keywords = [ + "programming-language", + "interpreter", + "programming-languages", + "interpreted-programming-language", + "tree-walk-interpreter", +] +categories = ["compilers"] [dependencies] exitcode = "1.1.2" diff --git a/src/ast/expr.rs b/src/ast/expr.rs index be2da73..a363855 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -1,4 +1,4 @@ -use crate::{ast::stmt::*, parser::types::Property, token::Token}; +use crate::{ast::stmt::Stmt, parser::types::Property, token::Token}; use std::{cell::Cell, rc::Rc}; #[derive(Clone)] diff --git a/src/interpreter/interpreter_env.rs b/src/interpreter/env.rs similarity index 87% rename from src/interpreter/interpreter_env.rs rename to src/interpreter/env.rs index 543c664..f0bf9cf 100644 --- a/src/interpreter/interpreter_env.rs +++ b/src/interpreter/env.rs @@ -1,9 +1,10 @@ -use super::{helpers::*, types::*}; +use super::{ + helpers::assume_identifier, + types::{InterpreterValue, RuntimeError}, +}; use crate::{ - env::*, - token::*, - // unwrap_enclosing, - // unwrap_scope, + env::{DeclaredValue, EnvironmentBase, EnvironmentWrapper}, + token::Token, unwrap_scope_mut, }; @@ -30,15 +31,13 @@ impl PartialEq for InterpreterEnvironment { impl EnvironmentWrapper for InterpreterEnvironment { fn new() -> Self { - InterpreterEnvironment(Rc::new(RefCell::new(EnvironmentBase::new( - None, - )))) + Self(Rc::new(RefCell::new(EnvironmentBase::new(None)))) } fn fork(&self) -> Self { - InterpreterEnvironment(Rc::new(RefCell::new(EnvironmentBase::new( - Some(self.clone()), - )))) + Self(Rc::new(RefCell::new(EnvironmentBase::new(Some( + self.clone(), + ))))) } fn read( diff --git a/src/interpreter/expressions.rs b/src/interpreter/expressions.rs index 1a63a5e..433965d 100644 --- a/src/interpreter/expressions.rs +++ b/src/interpreter/expressions.rs @@ -1,18 +1,52 @@ -use super::{helpers::*, interpret::*, interpreter_env::*, types::*}; -use crate::{ast::expr::*, env::*, token::*}; +use super::{ + env::InterpreterEnvironment, + helpers::{ + assume_identifier, + bind_function, + confirm_arity, + construct_lox_defined_function, + extract_subscription_index, + guard_function, + map_arguments, + unwrap_list, + }, + interpret::{eval_expression, eval_statements}, + types::{InterpreterFunction, InterpreterValue, RuntimeError}, +}; +use crate::{ + ast::expr::{ + AssignmentValue, + BinaryValue, + CallValue, + Expr, + FunctionValue, + GetAccessor, + GetValue, + IdentifierValue, + LiteralValue, + ObjectValue, + SetValue, + SuperAccessor, + SuperValue, + ThisValue, + UnaryValue, + }, + env::{DeclaredValue, EnvironmentWrapper}, + token::{Location, Token, TokenType}, +}; use std::{cell::RefCell, collections::HashMap, rc::Rc}; // inlining because it's used only once, but i wanted to take it // out of the context, to make it less cluttery -#[inline(always)] +#[inline] pub fn literal_expression( v: &LiteralValue, env: &InterpreterEnvironment, ) -> Result { match v { - LiteralValue::String(s) => Ok(InterpreterValue::String(Rc::clone(&s))), + LiteralValue::String(s) => Ok(InterpreterValue::String(Rc::clone(s))), LiteralValue::Number(n) => Ok(InterpreterValue::Number(*n)), LiteralValue::True => Ok(InterpreterValue::True), LiteralValue::False => Ok(InterpreterValue::False), @@ -29,7 +63,7 @@ pub fn literal_expression( } } -#[inline(always)] +#[inline] pub fn identifier_expression( v: &IdentifierValue, env: &E, @@ -40,7 +74,7 @@ where Ok(env.read(v.env_distance.get(), &v.name)?.value) } -#[inline(always)] +#[inline] pub fn assignment_expression( expr_evaluator: fn(&Expr, &E) -> Result, v: &AssignmentValue, @@ -56,7 +90,7 @@ where ) } -#[inline(always)] +#[inline] pub fn call_expression( v: &CallValue, env: &InterpreterEnvironment, @@ -90,7 +124,7 @@ pub fn execute_call( let fun_env = &enclosing_env.fork(); if let Some(params) = &fv.params { - map_arguments(params, &arguments, fun_env) + map_arguments(params, &arguments, fun_env); } if let Some(statements) = &fv.body { @@ -128,11 +162,11 @@ pub fn execute_call( } } -#[inline(always)] +#[inline] pub fn function_expression( v: &FunctionValue, env: &InterpreterEnvironment, -) -> Result { +) -> InterpreterValue { let fun = construct_lox_defined_function(v, env); if let Some(t) = &v.name { @@ -147,7 +181,7 @@ pub fn function_expression( ); } - Ok(fun) + fun } pub fn unary_expression( @@ -228,7 +262,7 @@ pub fn binary_experssion( TokenType::LessEqual => Ok((n1 <= n2).into()), TokenType::Modulo => Ok(InterpreterValue::Number(n1 % n2)), - _ => unreachable!("Scanner did a bad job 😎."), + _ => unreachable!("Scanner did a bad job \u{1f60e}."), } } (InterpreterValue::String(s1), InterpreterValue::String(s2)) => { @@ -273,7 +307,7 @@ fn find_method( { (methods, superclass) } else { - unreachable!("Class is not a class? 🤔") + unreachable!("Class is not a class? \u{1f914}") }; if let Some(method) = methods.get(key) { @@ -297,7 +331,7 @@ fn get_dot( ) -> Result { // auxiliary function used only once down below, that's why inlining is // completely justified 🥺 - #[inline(always)] + #[inline] fn get_property( key: &str, properties: &HashMap, @@ -336,18 +370,12 @@ fn get_dot( match &v.key { GetAccessor::DotName(iden) => { - get_property(iden, &borrowed_props, &class, &getee, &v.blame) + get_property(iden, &borrowed_props, class, &getee, &v.blame) } GetAccessor::DotEval(expr) => { let key = eval_expression(expr, env)?.to_string(); - get_property( - &key.as_str(), - &borrowed_props, - &class, - &getee, - &v.blame, - ) + get_property(key.as_str(), &borrowed_props, class, &getee, &v.blame) } _ => unreachable!("Wrong accessor in dot"), } @@ -365,9 +393,9 @@ fn get_subscription( extract_subscription_index(&v.key, &v.blame, s.len(), env)?; unsafe { - Ok(InterpreterValue::Char( - *s.as_bytes().get_unchecked(index) as char - )) + Ok(InterpreterValue::Char(char::from( + *s.as_bytes().get_unchecked(index), + ))) } } InterpreterValue::List(l) => { @@ -389,7 +417,7 @@ fn get_subscription( } } -#[inline(always)] +#[inline] pub fn get_expression( v: &GetValue, env: &InterpreterEnvironment, @@ -463,7 +491,7 @@ fn set_subscription( Ok(value) } -#[inline(always)] +#[inline] pub fn set_expression( v: &SetValue, env: &InterpreterEnvironment, @@ -475,7 +503,7 @@ pub fn set_expression( } } -#[inline(always)] +#[inline] pub fn this_expression(v: &ThisValue, env: &E) -> Result where E: EnvironmentWrapper, @@ -518,7 +546,9 @@ pub fn super_expression( { constructor } else { - unreachable!("Superclass should be a class like come on 🤦") + unreachable!( + "Superclass should be a class like come on \u{1f926}" + ) }; let constructor = constructor.ok_or_else(|| RuntimeError { @@ -528,7 +558,7 @@ pub fn super_expression( let constructor = bind_function(&constructor, instance); - execute_call(&constructor, &args, &v.blame, env) + execute_call(&constructor, args, &v.blame, env) } } } diff --git a/src/interpreter/helpers.rs b/src/interpreter/helpers.rs index 4a4da80..74983cd 100644 --- a/src/interpreter/helpers.rs +++ b/src/interpreter/helpers.rs @@ -1,13 +1,33 @@ -use super::{interpret::eval_expression, interpreter_env::*, types::*}; +use super::{ + env::InterpreterEnvironment, + interpret::eval_expression, + types::{InterpreterFunction, InterpreterValue, RuntimeError, StmtResult}, +}; use crate::{ ast::expr::{FunctionValue, GetAccessor}, - env::*, - token::*, + env::{DeclaredValue, EnvironmentWrapper}, + token::{Token, TokenType}, }; use std::{cell::RefMut, rc::Rc}; +#[macro_export] +macro_rules! try_exact_convert { + ($from:expr, $from_t:ty, $to_t:ty) => {{ + #[allow(clippy::as_conversions)] + let converted = $from as $to_t; + + #[allow(clippy::float_cmp, clippy::as_conversions)] + if converted as $from_t == $from { + Ok(converted) + } else { + Err("Cannot convert") + } + }}; +} + + // A shorthand way to extract identifier's name pub fn assume_identifier(t: &Token) -> &str { match &t.token_type { @@ -35,13 +55,15 @@ pub fn guard_function( } } -#[inline(always)] +#[inline] pub fn confirm_arity( target: usize, value: usize, blame: &Token, ) -> Result<(), RuntimeError> { - if target != value { + if target == value { + Ok(()) + } else { Err(RuntimeError { message: format!( "{} arguments", @@ -53,12 +75,10 @@ pub fn confirm_arity( ), token: blame.clone(), }) - } else { - Ok(()) } } -#[inline(always)] +#[inline] pub fn map_arguments( parameters: &[Token], arguments: &[InterpreterValue], @@ -74,10 +94,10 @@ pub fn map_arguments( value: arg.clone(), }, ); - }) + }); } -#[inline(always)] +#[inline] pub fn construct_lox_defined_function( fv: &FunctionValue, env: &InterpreterEnvironment, @@ -118,7 +138,7 @@ pub fn bind_function( } } -#[inline(always)] +#[inline] pub fn unwrap_list<'a>( value: &'a InterpreterValue, blame: &Token, @@ -163,14 +183,15 @@ pub fn extract_subscription_index( _ => unreachable!("Wrong accessor in subscription"), }?; - if extracted_n.fract() != 0.0 || extracted_n < 0.0 { - return Err(RuntimeError { - message: format!("Cannot access element on index {}", extracted_n), + let index = try_exact_convert!(extracted_n, f64, usize).map_err(|_| { + RuntimeError { + message: format!( + "Cannot access element on erroneous index {}", + extracted_n + ), token: blame.clone(), - }); - } - - let index = extracted_n as usize; + } + })?; if index >= max_len { Err(RuntimeError { @@ -178,6 +199,6 @@ pub fn extract_subscription_index( token: blame.clone(), }) } else { - Ok(extracted_n as usize) + Ok(index) } } diff --git a/src/interpreter/interpret.rs b/src/interpreter/interpret.rs index 3d7c894..d91d93a 100644 --- a/src/interpreter/interpret.rs +++ b/src/interpreter/interpret.rs @@ -1,20 +1,44 @@ use super::{ - expressions::*, - interpreter_env::*, - native_functions::declare_native_functions, - statements::*, - types::*, + env::InterpreterEnvironment, + expressions::{ + assignment_expression, + binary_experssion, + call_expression, + function_expression, + get_expression, + identifier_expression, + literal_expression, + object_expression, + set_expression, + super_expression, + this_expression, + unary_expression, + }, + native_functions, + statements::{ + block_statement, + break_statement, + class_statement, + continue_statement, + declaration_statement, + expression_statement, + for_statement, + if_statement, + print_statement, + return_statement, + }, + types::{InterpreterValue, RuntimeError, StmtResult}, }; use crate::{ - ast::{expr::*, stmt::*}, - env::*, + ast::{expr::Expr, stmt::Stmt}, + env::EnvironmentWrapper, }; pub fn interpret(statements: &[Stmt]) -> Result<(), RuntimeError> { let env = InterpreterEnvironment::new(); - declare_native_functions(&env); + native_functions::declare(&env); match eval_statements(statements, &env)? { StmtResult::Noop => Ok(()), @@ -38,7 +62,7 @@ pub fn eval_statements( env: &InterpreterEnvironment, ) -> Result, RuntimeError> { for stmt in statements { - let res = eval_statement(&stmt, env)?; + let res = eval_statement(stmt, env)?; if !matches!(res, StmtResult::Noop) { return Ok(res); @@ -60,8 +84,8 @@ fn eval_statement( Stmt::If(v) => if_statement(eval_expression, eval_statement, v, env), Stmt::For(v) => for_statement(eval_expression, eval_statement, v, env), Stmt::Return(v) => return_statement(eval_expression, v, env), - Stmt::Break(v) => break_statement(v), - Stmt::Continue(v) => continue_statement(v), + Stmt::Break(v) => Ok(break_statement(v)), + Stmt::Continue(v) => Ok(continue_statement(v)), Stmt::Class(v) => class_statement(v, env), } } @@ -78,7 +102,7 @@ pub fn eval_expression( Expr::Identifier(v) => identifier_expression(v, env), Expr::Assignment(v) => assignment_expression(eval_expression, v, env), Expr::Call(v) => call_expression(v, env), - Expr::Function(v) => function_expression(v, env), + Expr::Function(v) => Ok(function_expression(v, env)), Expr::Get(v) => get_expression(v, env), Expr::Set(v) => set_expression(v, env), Expr::This(v) => this_expression(v, env), diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 31dd04d..8463375 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -1,9 +1,9 @@ mod interpret; mod pn; +pub mod env; pub mod expressions; pub mod helpers; -pub mod interpreter_env; pub mod native_functions; pub mod statements; pub mod types; diff --git a/src/interpreter/native_functions.rs b/src/interpreter/native_functions.rs index 71df8c2..45ef594 100644 --- a/src/interpreter/native_functions.rs +++ b/src/interpreter/native_functions.rs @@ -1,9 +1,18 @@ use super::{ + env::InterpreterEnvironment, helpers::unwrap_list, - interpreter_env::InterpreterEnvironment, - types::*, + types::{ + InterpreterFunction, + InterpreterValue, + NativeFunctionSignature, + RuntimeError, + }, +}; +use crate::{ + env::{DeclaredValue, EnvironmentWrapper}, + token::Token, + try_exact_convert, }; -use crate::{env::*, token::*}; use std::{ cell::RefCell, @@ -87,13 +96,24 @@ fn native_len( args: &[InterpreterValue], ) -> Result { match &args[0] { - InterpreterValue::String(s) => { - Ok(InterpreterValue::Number(s.len() as f64)) - } + InterpreterValue::String(s) => try_exact_convert!(s.len(), usize, f64) + .map_err(|_| RuntimeError { + message: format!("Cannot conver from {}_usize to f64", s.len(),), + token: keyword.clone(), + }) + .map(InterpreterValue::Number), InterpreterValue::List(l) => { let l_borrow = l.borrow(); - Ok(InterpreterValue::Number(l_borrow.len() as f64)) + try_exact_convert!(l_borrow.len(), usize, f64) + .map_err(|_| RuntimeError { + message: format!( + "Cannot conver from {}_usize to f64", + l_borrow.len(), + ), + token: keyword.clone(), + }) + .map(InterpreterValue::Number) } _ => Err(RuntimeError { message: format!("Can't get length of {}", &args[0].human_type()), @@ -135,7 +155,7 @@ fn native_push( _env: &InterpreterEnvironment, args: &[InterpreterValue], ) -> Result { - let mut l_borrow = unwrap_list(&args[0], &keyword, 0, None)?; + let mut l_borrow = unwrap_list(&args[0], keyword, 0, None)?; l_borrow.push(args[1].clone()); @@ -369,36 +389,36 @@ fn native_read( } fn declarator(env: &InterpreterEnvironment, funs: &[FunctionDefinition]) { - funs.iter().for_each(|fd| { + for definition in funs { env.declare( - fd.name.to_owned(), + definition.name.to_owned(), DeclaredValue { mutable: true, value: InterpreterValue::Function { fun: Rc::new(InterpreterFunction::Native { - arity: fd.arity, - fun: fd.fun, + arity: definition.arity, + fun: definition.fun, }), enclosing_env: env.clone(), }, }, ); - }) + } } -pub fn declare_native_functions(env: &InterpreterEnvironment) { +pub fn declare(env: &InterpreterEnvironment) { declarator( env, &[ FunctionDefinition { name: NATIVE_FUNCTION_NAMES[0], arity: 1, - fun: |_k, _e, args| Ok(native_str(_k, _e, args)), + fun: |keyword, env, args| Ok(native_str(keyword, env, args)), }, FunctionDefinition { name: NATIVE_FUNCTION_NAMES[1], arity: 1, - fun: |_k, _e, args| Ok(native_typeof(_k, _e, args)), + fun: |keyword, env, args| Ok(native_typeof(keyword, env, args)), }, FunctionDefinition { name: NATIVE_FUNCTION_NAMES[2], diff --git a/src/interpreter/pn.rs b/src/interpreter/pn.rs index dba92f2..3cd9444 100644 --- a/src/interpreter/pn.rs +++ b/src/interpreter/pn.rs @@ -1,8 +1,8 @@ -use crate::ast::expr::*; +use crate::ast::expr::{Expr, LiteralValue}; #[allow(dead_code)] -pub fn pn_stringify_tree(expr: &Expr) -> String { +pub fn stringify_tree(expr: &Expr) -> String { match expr { Expr::Binary(v) => { pn_gen(&v.operator.token_type.to_string(), &[&v.left, &v.right]) @@ -23,7 +23,7 @@ pub fn pn_stringify_tree(expr: &Expr) -> String { Expr::Identifier(v) => v.name.token_type.to_string(), Expr::Assignment(v) => pn_gen(&format!("= {}", v.name), &[&v.value]), Expr::Call(v) => pn_gen( - &format!("call {}", pn_stringify_tree(&v.calee)), + &format!("call {}", stringify_tree(&v.calee)), v.arguments.iter().collect::>().as_slice(), ), // TODO: implement these XD @@ -39,10 +39,10 @@ pub fn pn_stringify_tree(expr: &Expr) -> String { fn pn_gen(name: &str, exprs: &[&Expr]) -> String { let mut res = format!("({}", name); - exprs.iter().for_each(|expr| { + for expr in exprs.iter() { res += " "; - res += &pn_stringify_tree(expr); - }); + res += &stringify_tree(expr); + } res + ")" } diff --git a/src/interpreter/statements.rs b/src/interpreter/statements.rs index f0806e1..81843db 100644 --- a/src/interpreter/statements.rs +++ b/src/interpreter/statements.rs @@ -1,18 +1,33 @@ use super::{ - helpers::*, + env::InterpreterEnvironment, + helpers::{assume_identifier, construct_lox_defined_function}, interpret::eval_expression, - interpreter_env::InterpreterEnvironment, - types::*, + types::{InterpreterValue, RuntimeError, StmtResult}, }; use crate::{ - ast::{expr::*, stmt::*}, - env::*, + ast::{ + expr::Expr, + stmt::{ + BlockValue, + BreakValue, + ClassValue, + ContinueValue, + DeclarationValue, + ExpressionValue, + ForValue, + IfValue, + PrintValue, + ReturnValue, + Stmt, + }, + }, + env::{DeclaredValue, EnvironmentWrapper}, }; use std::{collections::HashMap, rc::Rc}; -#[inline(always)] +#[inline] pub fn expression_statement( expr_evaluator: fn(&Expr, &E) -> Result, v: &ExpressionValue, @@ -26,7 +41,7 @@ where Ok(StmtResult::Noop) } -#[inline(always)] +#[inline] pub fn print_statement( expr_evaluator: fn(&Expr, &E) -> Result, v: &PrintValue, @@ -55,7 +70,7 @@ where .initializer .as_ref() .map_or(Ok(InterpreterValue::Nil), |initializer| { - expr_evaluator(&initializer, env) + expr_evaluator(initializer, env) })?; env.declare( @@ -69,7 +84,7 @@ where Ok(StmtResult::Noop) } -#[inline(always)] +#[inline] pub fn block_statement( stmts_evaluator: fn(&[Stmt], &E) -> Result, RuntimeError>, v: &BlockValue, @@ -83,7 +98,7 @@ where stmts_evaluator(&v.statements, &new_scope) } -#[inline(always)] +#[inline] pub fn if_statement( expr_evaluator: fn(&Expr, &E) -> Result, stmt_evaluator: fn( @@ -97,11 +112,9 @@ where E: EnvironmentWrapper, { if expr_evaluator(&v.condition, env)? == InterpreterValue::True { - if let Some(then) = &v.then { - stmt_evaluator(then, env) - } else { - Ok(StmtResult::Noop) - } + v.then + .as_ref() + .map_or(Ok(StmtResult::Noop), |then| stmt_evaluator(then, env)) } else if let Some(otherwise) = &v.otherwise { stmt_evaluator(otherwise, env) } else { @@ -171,7 +184,7 @@ where Ok(StmtResult::Noop) } -#[inline(always)] +#[inline] pub fn return_statement( expr_evaluator: fn(&Expr, &E) -> Result, v: &ReturnValue, @@ -189,18 +202,14 @@ where }) } -#[inline(always)] -pub fn break_statement( - v: &BreakValue, -) -> Result, RuntimeError> { - Ok(StmtResult::Break(v.keyword.clone())) +#[inline] +pub fn break_statement(v: &BreakValue) -> StmtResult { + StmtResult::Break(v.keyword.clone()) } -#[inline(always)] -pub fn continue_statement( - v: &ContinueValue, -) -> Result, RuntimeError> { - Ok(StmtResult::Continue(v.keyword.clone())) +#[inline] +pub fn continue_statement(v: &ContinueValue) -> StmtResult { + StmtResult::Continue(v.keyword.clone()) } pub fn class_statement( diff --git a/src/interpreter/types.rs b/src/interpreter/types.rs index 69c86df..2d2a590 100644 --- a/src/interpreter/types.rs +++ b/src/interpreter/types.rs @@ -1,5 +1,9 @@ -use super::interpreter_env::*; -use crate::{ast::expr::*, runner::DescribableError, token::*}; +use super::env::InterpreterEnvironment; +use crate::{ + ast::expr::FunctionValue, + runner::DescribableError, + token::{Location, Token}, +}; use std::{cell::RefCell, collections::HashMap, fmt, rc::Rc}; @@ -47,17 +51,16 @@ pub enum InterpreterValue { } impl InterpreterValue { - pub fn human_type(&self) -> &str { + pub const fn human_type(&self) -> &str { match self { + InterpreterValue::True | InterpreterValue::False => "boolean", InterpreterValue::Instance { .. } => "class instance", InterpreterValue::Function { .. } => "function", InterpreterValue::Class { .. } => "class", InterpreterValue::String(_) => "string", InterpreterValue::Number(_) => "number", - InterpreterValue::False => "boolean", InterpreterValue::List(_) => "list", InterpreterValue::Char(_) => "char", - InterpreterValue::True => "boolean", InterpreterValue::Nil => "nil", } } @@ -181,9 +184,9 @@ impl PartialEq for InterpreterFunction { impl From for InterpreterValue { fn from(v: bool) -> Self { if v { - InterpreterValue::True + Self::True } else { - InterpreterValue::False + Self::False } } } diff --git a/src/lib.rs b/src/lib.rs index 9e46b92..1abf010 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,11 @@ #![feature(option_result_unwrap_unchecked)] +#![warn( + clippy::all, + clippy::pedantic, + clippy::nursery, + clippy::unnecessary_wraps, + clippy::semicolon_if_nothing_returned +)] mod ast; mod env; @@ -10,3 +17,4 @@ mod scanner; mod token; pub use runner::*; +pub use runner::{run_file, run_repl}; diff --git a/src/main.rs b/src/main.rs index 49eb0fc..0a798af 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ fn main() { if args.len() > 1 { for arg in args.iter().skip(1) { - match luxya::run_file(&arg) { + match luxya::run_file(arg) { Err(luxya::RunError::Io(err)) => { println!("{}", err); process::exit(exitcode::IOERR); @@ -18,7 +18,7 @@ fn main() { _ => (), } } - } else if let Err(err) = luxya::run_prompt() { + } else if let Err(err) = luxya::run_repl() { println!("{}", err); process::exit(exitcode::OSERR); } diff --git a/src/parser/expression.rs b/src/parser/expressions.rs similarity index 61% rename from src/parser/expression.rs rename to src/parser/expressions.rs index f49422d..aeb7ae8 100644 --- a/src/parser/expression.rs +++ b/src/parser/expressions.rs @@ -1,12 +1,35 @@ -use super::{statements::*, types::*}; +use super::{ + statements::block_statement, + types::{ParseError, ParserIter, Property}, +}; use crate::{ - ast::{expr::*, stmt::*}, + ast::{ + expr::{ + AssignmentValue, + BinaryValue, + CallValue, + Expr, + FunctionValue, + GetAccessor, + GetValue, + GroupingValue, + IdentifierValue, + LiteralValue, + ObjectValue, + SetValue, + SuperAccessor, + SuperValue, + ThisValue, + UnaryValue, + }, + stmt::Stmt, + }, build_binary_expr, expect, expect_one, match_then_consume, peek_matches, - token::*, + token::{Token, TokenType}, }; use std::{cell::Cell, rc::Rc}; @@ -87,7 +110,7 @@ fn factor(tokens: ParserIter) -> Result { fn unary(tokens: ParserIter) -> Result { if matches!( tokens.peek().map(|t| &t.token_type), - Some(TokenType::Bang) | Some(TokenType::Minus) + Some(TokenType::Bang | TokenType::Minus) ) { let operator = tokens.next().unwrap(); @@ -189,13 +212,15 @@ fn finish_call(tokens: ParserIter, calee: Expr) -> Result { } fn finish_get(tokens: ParserIter, getee: Expr) -> Result { - let consumed = tokens.next(); - let consumed_token_type = consumed.as_ref().map(|c| c.token_type.clone()); + let peek = tokens.peek(); + let peek_token_type = peek.as_ref().map(|c| c.token_type.clone()); - match consumed_token_type { + // Consuming the next token in following match arms + // (except for the error one), because I want to advance the iterator + match peek_token_type { Some(TokenType::Identifier(i)) => { - // unwrap_unchecked because we just matched peek 😇 - let blame = unsafe { consumed.unwrap_unchecked() }; + // unwrap_unchecked because we just matched the type of peek 😇 + let blame = unsafe { tokens.next().unwrap_unchecked() }; Ok(Expr::Get(GetValue { getee: Box::new(getee), @@ -205,7 +230,7 @@ fn finish_get(tokens: ParserIter, getee: Expr) -> Result { } Some(TokenType::LeftParen) => { // same here, unwrapping what we already matched - let blame = unsafe { tokens.peek().unwrap_unchecked().clone() }; + let blame = unsafe { tokens.next().unwrap_unchecked() }; let eval = expression(tokens)?; @@ -218,7 +243,7 @@ fn finish_get(tokens: ParserIter, getee: Expr) -> Result { })) } _ => Err(ParseError { - token: tokens.peek().cloned(), + token: peek.cloned(), message: "Expected identifier or a parenthesized expression to \ evaluate" .into(), @@ -227,32 +252,34 @@ fn finish_get(tokens: ParserIter, getee: Expr) -> Result { } fn finish_sub(tokens: ParserIter, getee: Expr) -> Result { - let peek_type = tokens.peek().as_ref().map(|p| p.token_type.clone()); - - let accessor = match peek_type { - Some(TokenType::Number(n)) => { - // unwrap_unchecked because we just matched peek 😇 - let blame = unsafe { tokens.next().unwrap_unchecked() }; + let peek = tokens.peek(); + let peek_type = peek.as_ref().map(|p| p.token_type.clone()); + + let accessor = if let Some(TokenType::Number(n)) = peek_type { + // unwrap_unchecked because we just matched peek 😇 + // + // Consuming the next token here, because I want to advance the + // iterator + let blame = unsafe { tokens.next().unwrap_unchecked() }; + + Expr::Get(GetValue { + getee: Box::new(getee), + key: GetAccessor::SubscriptionNumber(n), + blame, + }) + } else { + // unwrapping what we already matched + let blame = unsafe { peek.unwrap_unchecked().clone() }; - Ok(Expr::Get(GetValue { - getee: Box::new(getee), - key: GetAccessor::SubscriptionNumber(n), - blame, - })) - } - _ => { - // same here, unwrapping what we already matched - let blame = unsafe { tokens.peek().unwrap_unchecked().clone() }; + let eval = expression(tokens)?; - let eval = expression(tokens)?; + Expr::Get(GetValue { + getee: Box::new(getee), + key: GetAccessor::SubscriptionEval(Box::new(eval)), + blame, + }) + }; - Ok(Expr::Get(GetValue { - getee: Box::new(getee), - key: GetAccessor::SubscriptionEval(Box::new(eval)), - blame, - })) - } - }?; expect_one!(tokens, TokenType::RightSquareBracket)?; @@ -322,120 +349,128 @@ fn primary(tokens: ParserIter) -> Result { env_distance: Cell::new(0), })), + // Lists + TokenType::LeftSquareBracket => parse_list(tokens), + + // Objects + TokenType::LeftBrace => parse_object(tokens, token), + // Super - TokenType::Super => { - let dummy_expr = Expr::Literal(LiteralValue::Nil); - - let accessor = match tokens.next().map(|next| next.token_type) { - Some(TokenType::LeftParen) => { - let call_expr = finish_call(tokens, dummy_expr)?; - - let arguments = if let Expr::Call(cv) = call_expr { - cv.arguments - } else { - unreachable!("Call is not a call? Weird 😳") - }; - - SuperAccessor::Call(arguments) - } - Some(TokenType::Dot) => { - let name = expect!( - tokens, - TokenType::Identifier(_), - "Expected a superclass method name", - )?; - - SuperAccessor::Method(name) - } - _ => { - return Err(ParseError { - token: Some(token), - message: "Expected `.` or `(...args)` (constructor \ - call) after `super`" - .into(), - }) - } - }; + TokenType::Super => parse_super(tokens, token), - Ok(Expr::Super(SuperValue { - blame: token, - accessor, - env_distance: Cell::new(0), - })) - } + _ => Err(ParseError { + token: Some(token), + message: "Expected expression".into(), + }), + } +} - // Block - TokenType::LeftSquareBracket => { - let mut values = Vec::new(); +// Singular-primary parsing functions down there 👇 - while !peek_matches!(tokens, TokenType::RightSquareBracket) { - values.push(expression(tokens)?); +pub fn parse_object( + tokens: ParserIter, + blame: Token, +) -> Result { + let mut properties: Vec = Vec::new(); + + while !peek_matches!(tokens, TokenType::RightBrace) { + let key_token = expect!( + tokens, + TokenType::Identifier(_) | TokenType::String(_), + "Expected property name", + )?; + + let key = match &key_token.token_type { + TokenType::Identifier(s) | TokenType::String(s) => s, + _ => unreachable!("Hi!! Welcome to my kitchen"), + }; - if match_then_consume!(tokens, TokenType::Comma).is_none() { - break; - } - } + let value = if match_then_consume!(tokens, TokenType::Colon).is_some() { + expression(tokens)? + } else if let TokenType::Identifier(_) = key_token.token_type { + Expr::Identifier(IdentifierValue { + name: key_token.clone(), + env_distance: Cell::default(), + }) + } else { + return Err(ParseError { + token: Some(key_token), + message: "Cannot use short property declaration with string" + .into(), + }); + }; - expect_one!(tokens, TokenType::RightSquareBracket)?; + properties.push(Property { + key: key.clone(), + value, + }); - Ok(Expr::Literal(LiteralValue::List(Rc::new(values)))) + if match_then_consume!(tokens, TokenType::Comma).is_none() { + break; } + } - // Objects - TokenType::LeftBrace => { - let mut properties: Vec = Vec::new(); - - while !peek_matches!(tokens, TokenType::RightBrace) { - let key_token = expect!( - tokens, - TokenType::Identifier(_) | TokenType::String(_), - "Expected property name", - )?; - - let key = match &key_token.token_type { - TokenType::Identifier(s) | TokenType::String(s) => s, - _ => unreachable!("Hi!! Welcome to my kitchen"), - }; - - let value = if match_then_consume!(tokens, TokenType::Colon) - .is_some() - { - expression(tokens)? - } else if let TokenType::Identifier(_) = key_token.token_type { - Expr::Identifier(IdentifierValue { - name: key_token.clone(), - env_distance: Default::default(), - }) - } else { - return Err(ParseError { - token: Some(key_token), - message: "Cannot use short property declaration with \ - string" - .into(), - }); - }; - - properties.push(Property { - key: key.clone(), - value, - }); - - if match_then_consume!(tokens, TokenType::Comma).is_none() { - break; - } - } + expect_one!(tokens, TokenType::RightBrace)?; - expect_one!(tokens, TokenType::RightBrace)?; + Ok(Expr::Object(ObjectValue { blame, properties })) +} - Ok(Expr::Object(ObjectValue { - blame: token, - properties, - })) +pub fn parse_super( + tokens: ParserIter, + blame: Token, +) -> Result { + let dummy_expr = Expr::Literal(LiteralValue::Nil); + + let accessor = match tokens.next().map(|next| next.token_type) { + Some(TokenType::LeftParen) => { + let call_expr = finish_call(tokens, dummy_expr)?; + + let arguments = if let Expr::Call(cv) = call_expr { + cv.arguments + } else { + unreachable!("Call is not a call? Weird \u{1f633}") + }; + + SuperAccessor::Call(arguments) } + Some(TokenType::Dot) => { + let name = expect!( + tokens, + TokenType::Identifier(_), + "Expected a superclass method name", + )?; - _ => Err(ParseError { - token: Some(token), - message: "Expected expression".into(), - }), + SuperAccessor::Method(name) + } + _ => { + return Err(ParseError { + token: Some(blame), + message: "Expected `.` or `(...args)` (constructor call) \ + after `super`" + .into(), + }) + } + }; + + Ok(Expr::Super(SuperValue { + blame, + accessor, + env_distance: Cell::new(0), + })) +} + +pub fn parse_list(tokens: ParserIter) -> Result { + let mut values = Vec::new(); + + while !peek_matches!(tokens, TokenType::RightSquareBracket) { + values.push(expression(tokens)?); + + if match_then_consume!(tokens, TokenType::Comma).is_none() { + break; + } } + + expect_one!(tokens, TokenType::RightSquareBracket)?; + + Ok(Expr::Literal(LiteralValue::List(Rc::new(values)))) } diff --git a/src/parser/helpers.rs b/src/parser/helpers.rs index 7c24105..f3c6c43 100644 --- a/src/parser/helpers.rs +++ b/src/parser/helpers.rs @@ -1,8 +1,11 @@ -use super::types::*; -use crate::{expect_one, token::*}; +use super::types::{ParseError, ParserIter}; +use crate::{ + expect_one, + token::{Token, TokenType}, +}; -#[inline(always)] +#[inline] pub fn expect_semicolon(tokens: ParserIter) -> Result { expect_one!(tokens, TokenType::Semicolon) } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 468f2d6..ddbfe48 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,4 +1,4 @@ -mod expression; +mod expressions; mod helpers; mod parse; mod statements; diff --git a/src/parser/parse.rs b/src/parser/parse.rs index d226ea8..ee34ea0 100644 --- a/src/parser/parse.rs +++ b/src/parser/parse.rs @@ -1,5 +1,25 @@ -use super::{expression::expression, helpers::*, statements::*, types::*}; -use crate::{ast::stmt::*, expect, match_then_consume, token::*}; +use super::{ + expressions::expression, + helpers::{expect_semicolon, synchronize}, + statements::{ + block_statement, + break_statement, + class_statement, + continue_statement, + expression_statement, + for_statement, + if_statement, + print_statement, + return_statement, + }, + types::{ParseError, ParserIter}, +}; +use crate::{ + ast::stmt::{DeclarationValue, Stmt}, + expect, + match_then_consume, + token::{Token, TokenType}, +}; pub fn parse(tokens: Vec) -> (Vec, Vec) { diff --git a/src/parser/statements.rs b/src/parser/statements.rs index 4b8e591..29e90fb 100644 --- a/src/parser/statements.rs +++ b/src/parser/statements.rs @@ -1,18 +1,37 @@ -use super::{expression::*, helpers::*, parse::*, types::*}; +use super::{ + expressions::{expression, function_declaration}, + helpers::expect_semicolon, + parse::{declaration, statement}, + types::{ParseError, ParserIter}, +}; use crate::{ - ast::{expr::*, stmt::*}, + ast::{ + expr::{Expr, IdentifierValue}, + stmt::{ + BlockValue, + BreakValue, + ClassValue, + ContinueValue, + ExpressionValue, + ForValue, + IfValue, + PrintValue, + ReturnValue, + Stmt, + }, + }, expect, expect_one, match_then_consume, match_then_consume_stmt, peek_matches, - token::*, + token::{Token, TokenType}, }; -use std::vec; +use std::{cell::Cell, vec}; -#[inline(always)] +#[inline] pub fn print_statement(tokens: ParserIter) -> Result, ParseError> { let stmt = Stmt::Print(PrintValue { expression: expression(tokens)?, @@ -179,10 +198,10 @@ pub fn return_statement( tokens: ParserIter, keyword: Token, ) -> Result, ParseError> { - let expression = if !peek_matches!(tokens, TokenType::Semicolon) { - Some(expression(tokens)?) - } else { + let expression = if peek_matches!(tokens, TokenType::Semicolon) { None + } else { + Some(expression(tokens)?) }; expect_semicolon(tokens)?; @@ -193,7 +212,7 @@ pub fn return_statement( }))) } -#[inline(always)] +#[inline] pub fn break_statement( tokens: ParserIter, keyword: Token, @@ -203,7 +222,7 @@ pub fn break_statement( Ok(Some(Stmt::Break(BreakValue { keyword }))) } -#[inline(always)] +#[inline] pub fn continue_statement( tokens: ParserIter, keyword: Token, @@ -227,7 +246,7 @@ pub fn class_statement(tokens: ParserIter) -> Result, ParseError> { Some(Expr::Identifier(IdentifierValue { name: superclass_name, - env_distance: Default::default(), + env_distance: Cell::default(), })) } else { None diff --git a/src/parser/types.rs b/src/parser/types.rs index 71d78ca..f9f0f13 100644 --- a/src/parser/types.rs +++ b/src/parser/types.rs @@ -1,4 +1,8 @@ -use crate::{ast::expr::*, runner::DescribableError, token::*}; +use crate::{ + ast::expr::Expr, + runner::DescribableError, + token::{Location, Token}, +}; use std::{iter, rc::Rc, vec}; @@ -11,14 +15,13 @@ pub struct ParseError { impl DescribableError for ParseError { fn location(&self) -> Location { - if let Some(token) = &self.token { - token.location - } else { + self.token.as_ref().map_or( Location { byte_offset: usize::MAX, byte_length: 1, - } - } + }, + |token| token.location, + ) } fn description(&self) -> &str { @@ -27,7 +30,7 @@ impl DescribableError for ParseError { } impl Expr { - pub fn human_type(&self) -> &str { + pub const fn human_type(&self) -> &str { match self { Expr::Assignment(_) => "an assignment", Expr::Binary(_) => "a binary expression", diff --git a/src/resolver/resolver_env.rs b/src/resolver/env.rs similarity index 94% rename from src/resolver/resolver_env.rs rename to src/resolver/env.rs index 41e2710..5a920af 100644 --- a/src/resolver/resolver_env.rs +++ b/src/resolver/env.rs @@ -1,7 +1,7 @@ use super::helpers::assume_resolvable_expr; use crate::{ ast::expr::Expr, - env::*, + env::{DeclaredValue, EnvironmentBase, EnvironmentWrapper}, interpreter::{ helpers::assume_identifier, types::{InterpreterValue, RuntimeError}, @@ -31,11 +31,11 @@ pub struct ResolverEnvironment( // I'll always supply Nil here impl EnvironmentWrapper for ResolverEnvironment { fn new() -> Self { - ResolverEnvironment(Rc::new(RefCell::new(EnvironmentBase::new(None)))) + Self(Rc::new(RefCell::new(EnvironmentBase::new(None)))) } fn fork(&self) -> Self { - ResolverEnvironment(Rc::new(RefCell::new(EnvironmentBase::new(Some( + Self(Rc::new(RefCell::new(EnvironmentBase::new(Some( self.clone(), ))))) } @@ -93,15 +93,15 @@ impl EnvironmentWrapper for ResolverEnvironment { ) -> Result { let entry = self.read(steps, identifier)?; - if !entry.mutable { + if entry.mutable { + Ok(InterpreterValue::Nil) + } else { let name = assume_identifier(identifier); Err(RuntimeError { message: format!("Cannot reassign a const `{}`", name), token: identifier.clone(), }) - } else { - Ok(InterpreterValue::Nil) } } } diff --git a/src/resolver/expressions.rs b/src/resolver/expressions.rs index e17c564..c625973 100644 --- a/src/resolver/expressions.rs +++ b/src/resolver/expressions.rs @@ -1,15 +1,29 @@ -use super::{resolve, resolver_env::ResolverEnvironment}; +use super::{env::ResolverEnvironment, resolve}; use crate::{ - ast::expr::*, - env::*, + ast::expr::{ + AssignmentValue, + BinaryValue, + CallValue, + Expr, + FunctionValue, + GetAccessor, + GetValue, + IdentifierValue, + ObjectValue, + SetValue, + SuperAccessor, + SuperValue, + ThisValue, + }, + env::{DeclaredValue, EnvironmentWrapper}, interpreter::{ - helpers::*, + helpers::{assume_identifier, guard_function}, types::{InterpreterValue, RuntimeError}, }, }; -#[inline(always)] +#[inline] pub fn identifier_expression( expr: &Expr, v: &IdentifierValue, @@ -26,7 +40,7 @@ pub fn assignment_expression( env: &ResolverEnvironment, ) -> Result { // that takes care on the variables on the right - resolve::resolve_expression(&v.value, env)?; + resolve::expression(&v.value, env)?; // and this one manages the ones on the left 😎 env.resolve_nest_level(expr, &v.name)?; @@ -82,20 +96,20 @@ pub fn function_expression( // evaluating function body if let Some(statements) = &v.body { - let e = resolve::resolve_statements(statements, &new_scope)?; + let e = resolve::statements(statements, &new_scope)?; Ok(guard_function(e)?) } else { Ok(InterpreterValue::Nil) } } -#[inline(always)] +#[inline] pub fn binary_expression( v: &BinaryValue, env: &ResolverEnvironment, ) -> Result { - resolve::resolve_expression(&v.left, env)?; - resolve::resolve_expression(&v.right, env)?; + resolve::expression(&v.left, env)?; + resolve::expression(&v.right, env)?; Ok(InterpreterValue::Nil) } @@ -104,28 +118,28 @@ pub fn call_expression( v: &CallValue, env: &ResolverEnvironment, ) -> Result { - resolve::resolve_expression(&v.calee, env)?; + resolve::expression(&v.calee, env)?; for arg in &v.arguments { - resolve::resolve_expression(arg, env)?; + resolve::expression(arg, env)?; } Ok(InterpreterValue::Nil) } -#[inline(always)] +#[inline] pub fn get_expression( v: &GetValue, env: &ResolverEnvironment, ) -> Result { - resolve::resolve_expression(&v.getee, env)?; + resolve::expression(&v.getee, env)?; match &v.key { GetAccessor::DotEval(key) => { - resolve::resolve_expression(key, env)?; + resolve::expression(key, env)?; } GetAccessor::SubscriptionEval(expr) => { - resolve::resolve_expression(expr, env)?; + resolve::expression(expr, env)?; } _ => (), } @@ -137,17 +151,17 @@ pub fn set_expression( v: &SetValue, env: &ResolverEnvironment, ) -> Result { - resolve::resolve_expression(&v.setee, env)?; - resolve::resolve_expression(&v.value, env)?; + resolve::expression(&v.setee, env)?; + resolve::expression(&v.value, env)?; if let GetAccessor::DotEval(key) = &v.key { - resolve::resolve_expression(key, env)?; + resolve::expression(key, env)?; } Ok(InterpreterValue::Nil) } -#[inline(always)] +#[inline] pub fn this_expression( expr: &Expr, v: &ThisValue, @@ -176,20 +190,20 @@ pub fn super_expression( if let SuperAccessor::Call(args) = &v.accessor { for arg in args.iter() { - resolve::resolve_expression(arg, env)?; + resolve::expression(arg, env)?; } } Ok(InterpreterValue::Nil) } -#[inline(always)] +#[inline] pub fn object_expression( v: &ObjectValue, env: &ResolverEnvironment, ) -> Result { for value in v.properties.iter().map(|p| &p.value) { - resolve::resolve_expression(&value, env)?; + resolve::expression(value, env)?; } Ok(InterpreterValue::Nil) diff --git a/src/resolver/mod.rs b/src/resolver/mod.rs index b417e63..a0de8e5 100644 --- a/src/resolver/mod.rs +++ b/src/resolver/mod.rs @@ -1,7 +1,7 @@ +mod env; mod expressions; mod helpers; mod resolve; -mod resolver_env; mod statements; pub use resolve::resolve; diff --git a/src/resolver/resolve.rs b/src/resolver/resolve.rs index 4650d29..135a853 100644 --- a/src/resolver/resolve.rs +++ b/src/resolver/resolve.rs @@ -1,7 +1,28 @@ -use super::{expressions::*, resolver_env::*, statements::*}; +use super::{ + env::ResolverEnvironment, + expressions::{ + assignment_expression, + binary_expression, + call_expression, + function_expression, + get_expression, + identifier_expression, + object_expression, + set_expression, + super_expression, + this_expression, + }, + statements::{ + class_statement, + declaration_statement, + for_statement, + if_statement, + print_statement, + }, +}; use crate::{ - ast::{expr::*, stmt::*}, - env::*, + ast::{expr::Expr, stmt::Stmt}, + env::EnvironmentWrapper, interpreter::{ native_functions::NATIVE_FUNCTION_NAMES, statements as interpreter_stmts, @@ -11,18 +32,19 @@ use crate::{ }; -pub fn resolve(statements: &[Stmt]) -> Result<(), RuntimeError> { +pub fn resolve(stmts: &[Stmt]) -> Result<(), RuntimeError> { let scope = ResolverEnvironment::new(); + // Declaring native functions { let scope_map = unwrap_scope_mut!(scope); - NATIVE_FUNCTION_NAMES.iter().for_each(|k| { - scope_map.insert(k.to_string(), true); - }); + for k in &NATIVE_FUNCTION_NAMES { + scope_map.insert((*k).to_string(), true); + } } - match resolve_statements(statements, &scope)? { + match statements(stmts, &scope)? { StmtResult::Noop => Ok(()), StmtResult::Break(token) => Err(RuntimeError { message: "Cannot use `break` outside of a loop".into(), @@ -39,12 +61,12 @@ pub fn resolve(statements: &[Stmt]) -> Result<(), RuntimeError> { } } -pub fn resolve_statements( +pub fn statements( statements: &[Stmt], env: &ResolverEnvironment, ) -> Result, RuntimeError> { for stmt in statements { - let res = resolve_statement(&stmt, env)?; + let res = statement(stmt, env)?; if !matches!(res, StmtResult::Noop) { return Ok(res); @@ -54,21 +76,21 @@ pub fn resolve_statements( Ok(StmtResult::Noop) } -pub fn resolve_statement( +pub fn statement( stmt: &Stmt, env: &ResolverEnvironment, ) -> Result, RuntimeError> { match stmt { Stmt::Block(v) => { - interpreter_stmts::block_statement(resolve_statements, v, env) + interpreter_stmts::block_statement(statements, v, env) } Stmt::Expression(v) => { - interpreter_stmts::expression_statement(resolve_expression, v, env) + interpreter_stmts::expression_statement(expression, v, env) } - Stmt::Break(v) => interpreter_stmts::break_statement(v), - Stmt::Continue(v) => interpreter_stmts::continue_statement(v), + Stmt::Break(v) => Ok(interpreter_stmts::break_statement(v)), + Stmt::Continue(v) => Ok(interpreter_stmts::continue_statement(v)), Stmt::Return(v) => { - interpreter_stmts::return_statement(resolve_expression, v, env) + interpreter_stmts::return_statement(expression, v, env) } // custom resolver statement handlers @@ -80,18 +102,18 @@ pub fn resolve_statement( } } -pub fn resolve_expression( +pub fn expression( expr: &Expr, env: &ResolverEnvironment, ) -> Result { match expr { - Expr::Grouping(v) => resolve_expression(&v.expression, env), + Expr::Grouping(v) => expression(&v.expression, env), Expr::Literal(_v) => Ok(InterpreterValue::Nil), // custom resolver expression handlers Expr::Identifier(v) => identifier_expression(expr, v, env), Expr::Assignment(v) => assignment_expression(expr, v, env), - Expr::Unary(v) => resolve_expression(&v.right, env), + Expr::Unary(v) => expression(&v.right, env), Expr::Function(v) => function_expression(v, env), Expr::Super(v) => super_expression(expr, v, env), Expr::This(v) => this_expression(expr, v, env), diff --git a/src/resolver/statements.rs b/src/resolver/statements.rs index 5bce4b3..9191ae1 100644 --- a/src/resolver/statements.rs +++ b/src/resolver/statements.rs @@ -1,7 +1,10 @@ -use super::{resolve, resolver_env::*}; +use super::{env::ResolverEnvironment, resolve}; use crate::{ - ast::{expr::*, stmt::*}, - env::*, + ast::{ + expr::Expr, + stmt::{ClassValue, DeclarationValue, ForValue, IfValue, PrintValue}, + }, + env::{DeclaredValue, EnvironmentWrapper}, interpreter::{ helpers::assume_identifier, types::{InterpreterValue, RuntimeError, StmtResult}, @@ -9,12 +12,12 @@ use crate::{ }; -#[inline(always)] +#[inline] pub fn print_statement( v: &PrintValue, env: &ResolverEnvironment, ) -> Result, RuntimeError> { - resolve::resolve_expression(&v.expression, env)?; + resolve::expression(&v.expression, env)?; Ok(StmtResult::Noop) } @@ -24,7 +27,7 @@ pub fn declaration_statement( env: &ResolverEnvironment, ) -> Result, RuntimeError> { if let Some(i) = &v.initializer { - resolve::resolve_expression(i, env)?; + resolve::expression(i, env)?; } env.declare( @@ -42,14 +45,14 @@ pub fn if_statement( v: &IfValue, env: &ResolverEnvironment, ) -> Result, RuntimeError> { - resolve::resolve_expression(&v.condition, env)?; + resolve::expression(&v.condition, env)?; if let Some(then) = &v.then { - resolve::resolve_statement(then, env)?; + resolve::statement(then, env)?; } if let Some(otherwise) = &v.otherwise { - resolve::resolve_statement(otherwise, env)?; + resolve::statement(otherwise, env)?; } Ok(StmtResult::Noop) @@ -59,14 +62,14 @@ pub fn for_statement( v: &ForValue, env: &ResolverEnvironment, ) -> Result, RuntimeError> { - resolve::resolve_statement(&v.body, env)?; + resolve::statement(&v.body, env)?; if let Some(condition) = &v.condition { - resolve::resolve_expression(condition, env)?; + resolve::expression(condition, env)?; } if let Some(closer) = &v.closer { - resolve::resolve_statement(closer, env)?; + resolve::statement(closer, env)?; } Ok(StmtResult::Noop) @@ -102,7 +105,7 @@ pub fn class_statement( }); } - resolve::resolve_expression(expr, env)?; + resolve::expression(expr, env)?; let superclass_env = env.fork(); @@ -130,8 +133,8 @@ pub fn class_statement( ); for method in &v.methods { - // resolve_expression wires the method to function_expression - resolve::resolve_expression(method, &class_env)?; + // expression wires the method to function_expression + resolve::expression(method, &class_env)?; } Ok(StmtResult::Noop) diff --git a/src/runner/errors.rs b/src/runner/errors.rs index 7e48876..cc877da 100644 --- a/src/runner/errors.rs +++ b/src/runner/errors.rs @@ -11,7 +11,7 @@ pub trait DescribableError { } -pub fn report_errors(source: &str, category: &str, errors: &[T]) +pub fn report(source: &str, category: &str, errors: &[T]) where T: DescribableError, { diff --git a/src/runner/mod.rs b/src/runner/mod.rs index 0a74eba..b5342d9 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -4,5 +4,5 @@ mod run; mod types; pub use errors::DescribableError; -pub use run::{run_file, run_prompt}; +pub use run::{file as run_file, repl as run_repl}; pub use types::RunError; diff --git a/src/runner/run.rs b/src/runner/run.rs index e67e5a1..b2fc94b 100644 --- a/src/runner/run.rs +++ b/src/runner/run.rs @@ -1,4 +1,4 @@ -use super::{errors, types::*}; +use super::{errors, types::RunError}; use crate::{interpreter, parser, resolver, scanner}; use std::{ @@ -7,20 +7,29 @@ use std::{ }; -pub fn run_file(path: &str) -> Result<(), RunError> { +/// # Errors +/// +/// Will return `RunError::Io` if `path` +/// does not exist or the user does not have permission to read it. +// +/// Will return `RunError::Exec` if any execution errors occur. +pub fn file(path: &str) -> Result<(), RunError> { let mut f = fs::File::open(path)?; let mut buffer = String::new(); f.read_to_string(&mut buffer)?; - if let true = run(buffer) { + if let true = run(&buffer) { return Err(RunError::Exec); }; Ok(()) } -pub fn run_prompt() -> Result<(), io::Error> { +/// # Errors +/// +/// Will return `Err` if there are any errors during reading from command line. +pub fn repl() -> Result<(), io::Error> { loop { print!(">>> "); io::stdout().flush()?; @@ -37,8 +46,8 @@ pub fn run_prompt() -> Result<(), io::Error> { buffer += ";"; // TODO: merge envs when doing REPL - if run(buffer) { - eprintln!("Errors occurred") + if run(&buffer) { + eprintln!("Errors occurred"); } } @@ -50,12 +59,12 @@ pub fn run_prompt() -> Result<(), io::Error> { // things to be persistent dont we? (COMBAK: when implementing a better repl) // // bool indicates if any error(s) occurred -fn run(source: String) -> bool { +fn run(source: &str) -> bool { // Scanning - let (tokens, errors) = scanner::scan(&source); + let (tokens, errors) = scanner::scan(source); if !errors.is_empty() { - errors::report_errors(&source, "Scan", &errors); + errors::report(source, "Scan", &errors); return true; } @@ -64,20 +73,20 @@ fn run(source: String) -> bool { let (statements, errors) = parser::parse(tokens); if !errors.is_empty() { - errors::report_errors(&source, "Parse", &errors); + errors::report(source, "Parse", &errors); return true; } // Resolving if let Err(error) = resolver::resolve(&statements) { - errors::report_errors(&source, "Resolve", &[error]); + errors::report(source, "Resolve", &[error]); true // Interpreting 😇 } else if let Err(error) = interpreter::interpret(&statements) { - errors::report_errors(&source, "Runtime", &[error]); + errors::report(source, "Runtime", &[error]); true } else { diff --git a/src/runner/types.rs b/src/runner/types.rs index f142d8d..dd3f93d 100644 --- a/src/runner/types.rs +++ b/src/runner/types.rs @@ -11,7 +11,7 @@ pub enum RunError { impl From for RunError { fn from(e: io::Error) -> Self { - RunError::Io(e) + Self::Io(e) } } diff --git a/src/scanner/helpers.rs b/src/scanner/helpers.rs index d962d0d..89eec1c 100644 --- a/src/scanner/helpers.rs +++ b/src/scanner/helpers.rs @@ -1,4 +1,5 @@ use super::types::{ConsumptionResult, ScanError, ScannerIter}; +use crate::token::TokenType; /// will consume chars while peek matches the predicate @@ -7,7 +8,7 @@ use super::types::{ConsumptionResult, ScanError, ScannerIter}; /// the next char would be /// (regardless of it being there or the iterator ending) /// -/// sets hit_eof when the scanning has reached eof +/// sets `hit_eof` when the scanning has reached eof pub fn consume_while_peek( chars: ScannerIter, predicate: impl Fn(&char) -> bool, @@ -57,3 +58,28 @@ pub fn expect_char( }) } } + +pub fn tokenize_identifier(identifier: &str) -> TokenType { + match identifier { + "and" => TokenType::And, + "class" => TokenType::Class, + "else" => TokenType::Else, + "false" => TokenType::False, + "for" => TokenType::For, + "fun" => TokenType::Fun, + "if" => TokenType::If, + "nil" => TokenType::Nil, + "or" => TokenType::Or, + "print" => TokenType::Print, + "return" => TokenType::Return, + "super" => TokenType::Super, + "this" => TokenType::This, + "true" => TokenType::True, + "let" => TokenType::Let, + "const" => TokenType::Const, + "break" => TokenType::Break, + "continue" => TokenType::Continue, + "extends" => TokenType::Extends, + _ => TokenType::Identifier(identifier.into()), + } +} diff --git a/src/scanner/scan.rs b/src/scanner/scan.rs index 599ba4a..b3cb256 100644 --- a/src/scanner/scan.rs +++ b/src/scanner/scan.rs @@ -1,33 +1,30 @@ -use super::{helpers::*, types::*}; +use super::{ + helpers::{consume_while_peek, expect_char, tokenize_identifier}, + types::{ScanError, ScannerIter}, +}; use crate::token::{self, Location, TokenType}; -fn resolve_identifier(identifier: &str) -> TokenType { - match identifier { - "and" => TokenType::And, - "class" => TokenType::Class, - "else" => TokenType::Else, - "false" => TokenType::False, - "for" => TokenType::For, - "fun" => TokenType::Fun, - "if" => TokenType::If, - "nil" => TokenType::Nil, - "or" => TokenType::Or, - "print" => TokenType::Print, - "return" => TokenType::Return, - "super" => TokenType::Super, - "this" => TokenType::This, - "true" => TokenType::True, - "let" => TokenType::Let, - "const" => TokenType::Const, - "break" => TokenType::Break, - "continue" => TokenType::Continue, - "extends" => TokenType::Extends, - _ => TokenType::Identifier(identifier.into()), +pub fn scan(source: &str) -> (Vec, Vec) { + let mut tokens = vec![]; + let mut errors = vec![]; + + let mut chars = source.char_indices().peekable(); + + while let Some(_peek) = chars.peek() { + // We should be at the beginning of the next lexeme + match scan_token(&mut chars, source) { + Ok(Some(token)) => tokens.push(token), + Ok(None) => break, // iterator is exhausted + Err(err) => errors.push(err), + } } + + (tokens, errors) } -// consumes the next token's chars +/// Consumes the next token's chars +#[allow(clippy::too_many_lines)] fn scan_token( chars: ScannerIter, source: &str, @@ -102,9 +99,9 @@ fn scan_token( chars.take_while(|(_, c)| *c != '\n').for_each(drop); continue; - } else { - TokenType::Slash } + + TokenType::Slash } '"' => { let res = consume_while_peek(chars, |c| *c != '"'); @@ -158,7 +155,7 @@ fn scan_token( token_len = identifier_end - i; - resolve_identifier(&source[i..identifier_end]) + tokenize_identifier(&source[i..identifier_end]) } c if c.is_whitespace() => { continue; @@ -182,21 +179,3 @@ fn scan_token( Ok(None) } - -pub fn scan(source: &str) -> (Vec, Vec) { - let mut tokens = vec![]; - let mut errors = vec![]; - - let mut chars = source.char_indices().peekable(); - - while let Some(_peek) = chars.peek() { - // We should be at the beginning of the next lexeme - match scan_token(&mut chars, source) { - Ok(Some(token)) => tokens.push(token), - Ok(None) => break, // iterator is exhausted - Err(err) => errors.push(err), - } - } - - (tokens, errors) -} diff --git a/src/token.rs b/src/token.rs index e9c2f1f..989ef27 100644 --- a/src/token.rs +++ b/src/token.rs @@ -2,6 +2,7 @@ use std::{fmt, mem, rc::Rc}; #[derive(Clone, Debug)] +#[allow(clippy::module_name_repetitions)] pub enum TokenType { // Single-character tokens LeftParen, @@ -69,7 +70,7 @@ impl TokenType { } } - pub fn human_type(&self) -> &str { + pub const fn human_type(&self) -> &str { match self { TokenType::String(_) => "string", TokenType::Identifier(_) => "identifier",