diff --git a/kalk/src/interpreter.rs b/kalk/src/interpreter.rs index bf38a38..6959929 100644 --- a/kalk/src/interpreter.rs +++ b/kalk/src/interpreter.rs @@ -1,7 +1,6 @@ use crate::ast::{Expr, Stmt}; use crate::lexer::TokenKind; use crate::parser::CalcError; -use crate::parser::Unit; use crate::parser::DECL_UNIT; use crate::prelude; use crate::symbol_table::SymbolTable; @@ -10,14 +9,14 @@ use rug::Float; pub struct Context<'a> { symbol_table: &'a mut SymbolTable, - angle_unit: Unit, + angle_unit: String, precision: u32, } impl<'a> Context<'a> { - pub fn new(symbol_table: &'a mut SymbolTable, angle_unit: &Unit, precision: u32) -> Self { + pub fn new(symbol_table: &'a mut SymbolTable, angle_unit: &str, precision: u32) -> Self { Context { - angle_unit: angle_unit.clone(), + angle_unit: angle_unit.into(), symbol_table, precision, } @@ -68,7 +67,7 @@ fn eval_expr(context: &mut Context, expr: &Expr) -> Result { match expr { Expr::Binary(left, op, right) => eval_binary_expr(context, &left, op, &right), Expr::Unary(op, expr) => eval_unary_expr(context, op, expr), - Expr::Unit(_, expr) => eval_unit_expr(context, expr), + Expr::Unit(identifier, expr) => eval_unit_expr(context, identifier, expr), Expr::Var(identifier) => eval_var_expr(context, identifier), Expr::Literal(value) => eval_literal_expr(context, value), Expr::Group(expr) => eval_group_expr(context, &expr), @@ -118,11 +117,20 @@ fn eval_unary_expr(context: &mut Context, op: &TokenKind, expr: &Expr) -> Result } } -fn eval_unit_expr(context: &mut Context, expr: &Expr) -> Result { +fn eval_unit_expr( + context: &mut Context, + identifier: &str, + expr: &Expr, +) -> Result { + let angle_unit = &context.angle_unit.clone(); + if (identifier == "rad" || identifier == "deg") && angle_unit != identifier { + return convert_unit(context, expr, identifier, angle_unit); + } + eval_expr(context, expr) } -fn convert_unit( +pub fn convert_unit( context: &mut Context, expr: &Expr, from_unit: &str, @@ -175,12 +183,12 @@ fn eval_fn_call_expr( let prelude_func = match expressions.len() { 1 => { let x = eval_expr(context, &expressions[0])?; - prelude::call_unary_func(identifier, x, &context.angle_unit) + prelude::call_unary_func(context, identifier, x, &context.angle_unit.clone()) } 2 => { let x = eval_expr(context, &expressions[0])?; let y = eval_expr(context, &expressions[1])?; - prelude::call_binary_func(identifier, x, y, &context.angle_unit) + prelude::call_binary_func(context, identifier, x, y, &context.angle_unit.clone()) } _ => None, }; @@ -259,10 +267,16 @@ mod tests { fn interpret(stmt: Stmt) -> Result, CalcError> { let mut symbol_table = SymbolTable::new(); - let mut context = Context::new(&mut symbol_table, &Unit::Radians, PRECISION); + let mut context = Context::new(&mut symbol_table, "rad", PRECISION); + context.interpret(vec![stmt]) } + fn cmp(x: Float, y: f64) -> bool { + println!("{} = {}", x.to_f64(), y); + (x.to_f64() - y).abs() < 0.0001 + } + #[test] fn test_literal() { let stmt = Stmt::Expr(literal("1")); @@ -292,26 +306,38 @@ mod tests { fn test_unary() { let neg = Stmt::Expr(unary(Minus, literal("1"))); let fact = Stmt::Expr(unary(Exclamation, literal("5"))); - let fact_dec = Stmt::Expr(unary(Exclamation, literal("5.2"))); assert_eq!(interpret(neg).unwrap().unwrap(), -1); assert_eq!(interpret(fact).unwrap().unwrap(), 120); - - let fact_dec_result = interpret(fact_dec).unwrap().unwrap(); - assert!(fact_dec_result > 169.406 && fact_dec_result < 169.407); } - /*#[test] - fn test_unit() { - let rad = Stmt::Expr(Box::new(Expr::Unit(literal("1"), Rad))); - let deg = Stmt::Expr(Box::new(Expr::Unit(literal("1"), Deg))); + #[test] + fn test_angle_units() { + let rad_explicit = Stmt::Expr(fn_call("sin", vec![*unit("rad", literal("1"))])); + let deg_explicit = Stmt::Expr(fn_call("sin", vec![*unit("deg", literal("1"))])); + //let implicit = Stmt::Expr(fn_call("sin", vec![*literal("1")])); - assert_eq!(interpret(rad).unwrap().unwrap(), 1); - assert!( - (interpret(deg).unwrap().unwrap() - Float::with_val(PRECISION, 0.017456)).abs() - < Float::with_val(PRECISION, 0.0001) - ); - }*/ + assert!(cmp(interpret(rad_explicit).unwrap().unwrap(), 0.84147098)); + assert!(cmp(interpret(deg_explicit).unwrap().unwrap(), 0.01745240)); + + // TODO: Get this to work. + /*let mut rad_symbol_table = SymbolTable::new(); + let mut deg_symbol_table = SymbolTable::new(); + let mut rad_context = Context::new(&mut rad_symbol_table, "rad", PRECISION); + let mut deg_context = Context::new(&mut deg_symbol_table, "deg", PRECISION); + + assert!(cmp( + rad_context + .interpret(vec![implicit.clone()]) + .unwrap() + .unwrap(), + 0.84147098 + )); + assert!(cmp( + deg_context.interpret(vec![implicit]).unwrap().unwrap(), + 0.01745240 + ));*/ + } #[test] fn test_var() { @@ -321,7 +347,7 @@ mod tests { let mut symbol_table = SymbolTable::new(); symbol_table.insert(var_decl("x", literal("1"))); - let mut context = Context::new(&mut symbol_table, &Unit::Radians, PRECISION); + let mut context = Context::new(&mut symbol_table, "rad", PRECISION); assert_eq!(context.interpret(vec![stmt]).unwrap().unwrap(), 1); } @@ -339,7 +365,7 @@ mod tests { fn test_var_decl() { let stmt = var_decl("x", literal("1")); let mut symbol_table = SymbolTable::new(); - Context::new(&mut symbol_table, &Unit::Radians, PRECISION) + Context::new(&mut symbol_table, "rad", PRECISION) .interpret(vec![stmt]) .unwrap(); @@ -358,7 +384,7 @@ mod tests { binary(var("x"), TokenKind::Plus, literal("2")), )); - let mut context = Context::new(&mut symbol_table, &Unit::Radians, PRECISION); + let mut context = Context::new(&mut symbol_table, "rad", PRECISION); assert_eq!(context.interpret(vec![stmt]).unwrap().unwrap(), 3); } diff --git a/kalk/src/lexer.rs b/kalk/src/lexer.rs index 6bc91c5..5c590c9 100644 --- a/kalk/src/lexer.rs +++ b/kalk/src/lexer.rs @@ -17,9 +17,7 @@ pub enum TokenKind { Exclamation, UnitKeyword, - To, - Deg, - Rad, + ToKeyword, Pipe, OpenCeil, @@ -170,10 +168,8 @@ impl<'a> Lexer<'a> { } let kind = match value.as_ref() { - "deg" | "°" => TokenKind::Deg, - "rad" => TokenKind::Rad, "unit" => TokenKind::UnitKeyword, - "to" => TokenKind::To, + "to" => TokenKind::ToKeyword, _ => TokenKind::Identifier, }; diff --git a/kalk/src/parser.rs b/kalk/src/parser.rs index 6c1b84b..5858316 100644 --- a/kalk/src/parser.rs +++ b/kalk/src/parser.rs @@ -7,6 +7,7 @@ use crate::{ use rug::Float; pub const DECL_UNIT: &'static str = ".u"; +pub const DEFAULT_ANGLE_UNIT: &'static str = "rad"; /// Struct containing the current state of the parser. It stores user-defined functions and variables. /// # Examples @@ -20,7 +21,7 @@ pub struct Context { tokens: Vec, pos: usize, symbol_table: SymbolTable, - angle_unit: Unit, + angle_unit: String, /// This is true whenever the parser is currently parsing a unit declaration. /// It is necessary to keep track of this in order to know when to find (figure out) units that haven't been defined yet. /// Unit names are instead treated as variables. @@ -32,18 +33,22 @@ pub struct Context { impl Context { pub fn new() -> Self { - Context { + let mut context = Self { tokens: Vec::new(), pos: 0, symbol_table: SymbolTable::new(), - angle_unit: Unit::Radians, + angle_unit: DEFAULT_ANGLE_UNIT.into(), parsing_unit_decl: false, unit_decl_base_unit: None, - } + }; + + parse(&mut context, crate::prelude::INIT).unwrap(); + + context } - pub fn set_angle_unit(mut self, unit: Unit) -> Self { - self.angle_unit = unit; + pub fn set_angle_unit(mut self, unit: &str) -> Self { + self.angle_unit = unit.into(); self } @@ -55,13 +60,6 @@ impl Default for Context { } } -/// Mathematical unit used in calculations. -#[derive(Debug, Clone, PartialEq)] -pub enum Unit { - Radians, - Degrees, -} - /// Error that occured during parsing or evaluation. #[derive(Debug, Clone, PartialEq)] pub enum CalcError { diff --git a/kalk/src/prelude.rs b/kalk/src/prelude.rs index 2b6d8a6..c9f73b4 100644 --- a/kalk/src/prelude.rs +++ b/kalk/src/prelude.rs @@ -1,6 +1,10 @@ +use crate::ast::Expr; +use crate::interpreter; use rug::Float; use FuncType::*; +pub const INIT: &'static str = "unit deg = (rad*180)/pi"; + pub const CONSTANTS: phf::Map<&'static str, &'static str> = phf::phf_map! { "pi" => "3.14159265", "π" => "3.14159265", @@ -11,7 +15,6 @@ pub const CONSTANTS: phf::Map<&'static str, &'static str> = phf::phf_map! { "ϕ" => "1.61803398", }; -use crate::parser::Unit; use funcs::*; pub const UNARY_FUNCS: phf::Map<&'static str, UnaryFuncInfo> = phf::phf_map! { "cos" => UnaryFuncInfo(cos, Trig), @@ -75,57 +78,76 @@ pub struct UnaryFuncInfo(fn(Float) -> Float, FuncType); pub struct BinaryFuncInfo(fn(Float, Float) -> Float, FuncType); impl UnaryFuncInfo { - fn call(&self, x: Float, angle_unit: &Unit) -> Float { + fn call(&self, context: &mut interpreter::Context, x: Float, angle_unit: &str) -> Float { let func = self.0; match self.1 { - FuncType::Trig => func(from_angle_unit(x, angle_unit)), - FuncType::InverseTrig => to_angle_unit(func(x), angle_unit), + FuncType::Trig => func(from_angle_unit(context, x, angle_unit)), + FuncType::InverseTrig => to_angle_unit(context, func(x), angle_unit), FuncType::Other => func(x), } } } impl BinaryFuncInfo { - fn call(&self, x: Float, y: Float, angle_unit: &Unit) -> Float { + fn call( + &self, + context: &mut interpreter::Context, + x: Float, + y: Float, + angle_unit: &str, + ) -> Float { let func = self.0; match self.1 { FuncType::Trig => func( - from_angle_unit(x, angle_unit), - from_angle_unit(y, angle_unit), + from_angle_unit(context, x, angle_unit), + from_angle_unit(context, y, angle_unit), ), - FuncType::InverseTrig => to_angle_unit(func(x, y), angle_unit), + FuncType::InverseTrig => to_angle_unit(context, func(x, y), angle_unit), FuncType::Other => func(x, y), } } } -pub fn call_unary_func(name: &str, x: Float, angle_unit: &Unit) -> Option { +pub fn call_unary_func( + context: &mut interpreter::Context, + name: &str, + x: Float, + angle_unit: &str, +) -> Option { if let Some(func_info) = UNARY_FUNCS.get(name) { - Some(func_info.call(x, &angle_unit)) + Some(func_info.call(context, x, &angle_unit)) } else { None } } -pub fn call_binary_func(name: &str, x: Float, y: Float, angle_unit: &Unit) -> Option { +pub fn call_binary_func( + context: &mut interpreter::Context, + name: &str, + x: Float, + y: Float, + angle_unit: &str, +) -> Option { if let Some(func_info) = BINARY_FUNCS.get(name) { - Some(func_info.call(x, y, angle_unit)) + Some(func_info.call(context, x, y, angle_unit)) } else { None } } -fn to_angle_unit(x: Float, angle_unit: &Unit) -> Float { +fn to_angle_unit(context: &mut interpreter::Context, x: Float, angle_unit: &str) -> Float { match angle_unit { - Unit::Radians => x, - Unit::Degrees => special_funcs::to_degrees(x), + "rad" => x, + _ => interpreter::convert_unit(context, &Expr::Literal(x.to_string()), "rad", angle_unit) + .unwrap(), } } -fn from_angle_unit(x: Float, angle_unit: &Unit) -> Float { +fn from_angle_unit(context: &mut interpreter::Context, x: Float, angle_unit: &str) -> Float { match angle_unit { - Unit::Radians => x, - Unit::Degrees => special_funcs::to_radians(x), + "rad" => x, + _ => interpreter::convert_unit(context, &Expr::Literal(x.to_string()), angle_unit, "rad") + .unwrap(), } } @@ -135,14 +157,6 @@ pub mod special_funcs { pub fn factorial(x: Float) -> Float { ((x + 1) as Float).gamma() } - - pub fn to_degrees(x: Float) -> Float { - Float::with_val(53, x.to_f64().to_degrees()) - } - - pub fn to_radians(x: Float) -> Float { - Float::with_val(53, x.to_f64().to_radians()) - } } mod funcs { diff --git a/kalk/src/test_helpers.rs b/kalk/src/test_helpers.rs index 89d8d9f..5aa202d 100644 --- a/kalk/src/test_helpers.rs +++ b/kalk/src/test_helpers.rs @@ -36,6 +36,10 @@ pub fn group(expr: Box) -> Box { Box::new(Expr::Group(expr)) } +pub fn unit(identifier: &str, expr: Box) -> Box { + Box::new(Expr::Unit(identifier.into(), expr)) +} + pub fn var_decl(identifier: &str, value: Box) -> Stmt { Stmt::VarDecl(identifier.into(), value) } @@ -43,3 +47,7 @@ pub fn var_decl(identifier: &str, value: Box) -> Stmt { pub fn fn_decl(identifier: &str, parameters: Vec, value: Box) -> Stmt { Stmt::FnDecl(identifier.into(), parameters, value) } + +pub fn unit_decl(unit: &str, base_unit: &str, expr: Box) -> Stmt { + Stmt::UnitDecl(unit.into(), base_unit.into(), expr) +} diff --git a/kalk_cli/src/main.rs b/kalk_cli/src/main.rs index 5440718..4f8d379 100644 --- a/kalk_cli/src/main.rs +++ b/kalk_cli/src/main.rs @@ -2,13 +2,12 @@ mod output; mod repl; use kalk::parser; -use kalk::parser::Unit; use std::env; use std::fs::File; use std::io::Read; fn main() { - let mut parser_context = parser::Context::new().set_angle_unit(get_angle_unit()); + let mut parser_context = parser::Context::new().set_angle_unit(&get_angle_unit()); // Command line argument input, execute it and exit. let mut args = env::args().skip(1); @@ -64,16 +63,10 @@ kalk [OPTIONS] [INPUT] } } -fn get_angle_unit() -> Unit { +fn get_angle_unit() -> String { if let Ok(angle_unit_var) = env::var("ANGLE_UNIT") { - match angle_unit_var.as_ref() { - "radians" => Unit::Radians, - "degrees" => Unit::Degrees, - _ => { - panic!("Unexpected angle unit: {}.", angle_unit_var); - } - } + angle_unit_var } else { - Unit::Radians + String::from("rad") } }