Integrated the angle unit system with then new dynamic unit system.

This commit is contained in:
PaddiM8 2020-06-15 19:10:55 +02:00
parent 1d19f40e9f
commit 83668bbb84
6 changed files with 118 additions and 83 deletions

View File

@ -1,7 +1,6 @@
use crate::ast::{Expr, Stmt}; use crate::ast::{Expr, Stmt};
use crate::lexer::TokenKind; use crate::lexer::TokenKind;
use crate::parser::CalcError; use crate::parser::CalcError;
use crate::parser::Unit;
use crate::parser::DECL_UNIT; use crate::parser::DECL_UNIT;
use crate::prelude; use crate::prelude;
use crate::symbol_table::SymbolTable; use crate::symbol_table::SymbolTable;
@ -10,14 +9,14 @@ use rug::Float;
pub struct Context<'a> { pub struct Context<'a> {
symbol_table: &'a mut SymbolTable, symbol_table: &'a mut SymbolTable,
angle_unit: Unit, angle_unit: String,
precision: u32, precision: u32,
} }
impl<'a> Context<'a> { 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 { Context {
angle_unit: angle_unit.clone(), angle_unit: angle_unit.into(),
symbol_table, symbol_table,
precision, precision,
} }
@ -68,7 +67,7 @@ fn eval_expr(context: &mut Context, expr: &Expr) -> Result<Float, CalcError> {
match expr { match expr {
Expr::Binary(left, op, right) => eval_binary_expr(context, &left, op, &right), Expr::Binary(left, op, right) => eval_binary_expr(context, &left, op, &right),
Expr::Unary(op, expr) => eval_unary_expr(context, op, expr), 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::Var(identifier) => eval_var_expr(context, identifier),
Expr::Literal(value) => eval_literal_expr(context, value), Expr::Literal(value) => eval_literal_expr(context, value),
Expr::Group(expr) => eval_group_expr(context, &expr), 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<Float, CalcError> { fn eval_unit_expr(
context: &mut Context,
identifier: &str,
expr: &Expr,
) -> Result<Float, CalcError> {
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) eval_expr(context, expr)
} }
fn convert_unit( pub fn convert_unit(
context: &mut Context, context: &mut Context,
expr: &Expr, expr: &Expr,
from_unit: &str, from_unit: &str,
@ -175,12 +183,12 @@ fn eval_fn_call_expr(
let prelude_func = match expressions.len() { let prelude_func = match expressions.len() {
1 => { 1 => {
let x = eval_expr(context, &expressions[0])?; 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 => { 2 => {
let x = eval_expr(context, &expressions[0])?; let x = eval_expr(context, &expressions[0])?;
let y = eval_expr(context, &expressions[1])?; 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, _ => None,
}; };
@ -259,10 +267,16 @@ mod tests {
fn interpret(stmt: Stmt) -> Result<Option<Float>, CalcError> { fn interpret(stmt: Stmt) -> Result<Option<Float>, CalcError> {
let mut symbol_table = SymbolTable::new(); 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]) context.interpret(vec![stmt])
} }
fn cmp(x: Float, y: f64) -> bool {
println!("{} = {}", x.to_f64(), y);
(x.to_f64() - y).abs() < 0.0001
}
#[test] #[test]
fn test_literal() { fn test_literal() {
let stmt = Stmt::Expr(literal("1")); let stmt = Stmt::Expr(literal("1"));
@ -292,26 +306,38 @@ mod tests {
fn test_unary() { fn test_unary() {
let neg = Stmt::Expr(unary(Minus, literal("1"))); let neg = Stmt::Expr(unary(Minus, literal("1")));
let fact = Stmt::Expr(unary(Exclamation, literal("5"))); 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(neg).unwrap().unwrap(), -1);
assert_eq!(interpret(fact).unwrap().unwrap(), 120); 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] #[test]
fn test_unit() { fn test_angle_units() {
let rad = Stmt::Expr(Box::new(Expr::Unit(literal("1"), Rad))); let rad_explicit = Stmt::Expr(fn_call("sin", vec![*unit("rad", literal("1"))]));
let deg = Stmt::Expr(Box::new(Expr::Unit(literal("1"), Deg))); 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!(cmp(interpret(rad_explicit).unwrap().unwrap(), 0.84147098));
assert!( assert!(cmp(interpret(deg_explicit).unwrap().unwrap(), 0.01745240));
(interpret(deg).unwrap().unwrap() - Float::with_val(PRECISION, 0.017456)).abs()
< Float::with_val(PRECISION, 0.0001) // 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] #[test]
fn test_var() { fn test_var() {
@ -321,7 +347,7 @@ mod tests {
let mut symbol_table = SymbolTable::new(); let mut symbol_table = SymbolTable::new();
symbol_table.insert(var_decl("x", literal("1"))); 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); assert_eq!(context.interpret(vec![stmt]).unwrap().unwrap(), 1);
} }
@ -339,7 +365,7 @@ mod tests {
fn test_var_decl() { fn test_var_decl() {
let stmt = var_decl("x", literal("1")); let stmt = var_decl("x", literal("1"));
let mut symbol_table = SymbolTable::new(); let mut symbol_table = SymbolTable::new();
Context::new(&mut symbol_table, &Unit::Radians, PRECISION) Context::new(&mut symbol_table, "rad", PRECISION)
.interpret(vec![stmt]) .interpret(vec![stmt])
.unwrap(); .unwrap();
@ -358,7 +384,7 @@ mod tests {
binary(var("x"), TokenKind::Plus, literal("2")), 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); assert_eq!(context.interpret(vec![stmt]).unwrap().unwrap(), 3);
} }

View File

@ -17,9 +17,7 @@ pub enum TokenKind {
Exclamation, Exclamation,
UnitKeyword, UnitKeyword,
To, ToKeyword,
Deg,
Rad,
Pipe, Pipe,
OpenCeil, OpenCeil,
@ -170,10 +168,8 @@ impl<'a> Lexer<'a> {
} }
let kind = match value.as_ref() { let kind = match value.as_ref() {
"deg" | "°" => TokenKind::Deg,
"rad" => TokenKind::Rad,
"unit" => TokenKind::UnitKeyword, "unit" => TokenKind::UnitKeyword,
"to" => TokenKind::To, "to" => TokenKind::ToKeyword,
_ => TokenKind::Identifier, _ => TokenKind::Identifier,
}; };

View File

@ -7,6 +7,7 @@ use crate::{
use rug::Float; use rug::Float;
pub const DECL_UNIT: &'static str = ".u"; 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. /// Struct containing the current state of the parser. It stores user-defined functions and variables.
/// # Examples /// # Examples
@ -20,7 +21,7 @@ pub struct Context {
tokens: Vec<Token>, tokens: Vec<Token>,
pos: usize, pos: usize,
symbol_table: SymbolTable, symbol_table: SymbolTable,
angle_unit: Unit, angle_unit: String,
/// This is true whenever the parser is currently parsing a unit declaration. /// 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. /// 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. /// Unit names are instead treated as variables.
@ -32,18 +33,22 @@ pub struct Context {
impl Context { impl Context {
pub fn new() -> Self { pub fn new() -> Self {
Context { let mut context = Self {
tokens: Vec::new(), tokens: Vec::new(),
pos: 0, pos: 0,
symbol_table: SymbolTable::new(), symbol_table: SymbolTable::new(),
angle_unit: Unit::Radians, angle_unit: DEFAULT_ANGLE_UNIT.into(),
parsing_unit_decl: false, parsing_unit_decl: false,
unit_decl_base_unit: None, unit_decl_base_unit: None,
} };
parse(&mut context, crate::prelude::INIT).unwrap();
context
} }
pub fn set_angle_unit(mut self, unit: Unit) -> Self { pub fn set_angle_unit(mut self, unit: &str) -> Self {
self.angle_unit = unit; self.angle_unit = unit.into();
self 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. /// Error that occured during parsing or evaluation.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum CalcError { pub enum CalcError {

View File

@ -1,6 +1,10 @@
use crate::ast::Expr;
use crate::interpreter;
use rug::Float; use rug::Float;
use FuncType::*; use FuncType::*;
pub const INIT: &'static str = "unit deg = (rad*180)/pi";
pub const CONSTANTS: phf::Map<&'static str, &'static str> = phf::phf_map! { pub const CONSTANTS: phf::Map<&'static str, &'static str> = phf::phf_map! {
"pi" => "3.14159265", "pi" => "3.14159265",
"π" => "3.14159265", "π" => "3.14159265",
@ -11,7 +15,6 @@ pub const CONSTANTS: phf::Map<&'static str, &'static str> = phf::phf_map! {
"ϕ" => "1.61803398", "ϕ" => "1.61803398",
}; };
use crate::parser::Unit;
use funcs::*; use funcs::*;
pub const UNARY_FUNCS: phf::Map<&'static str, UnaryFuncInfo> = phf::phf_map! { pub const UNARY_FUNCS: phf::Map<&'static str, UnaryFuncInfo> = phf::phf_map! {
"cos" => UnaryFuncInfo(cos, Trig), "cos" => UnaryFuncInfo(cos, Trig),
@ -75,57 +78,76 @@ pub struct UnaryFuncInfo(fn(Float) -> Float, FuncType);
pub struct BinaryFuncInfo(fn(Float, Float) -> Float, FuncType); pub struct BinaryFuncInfo(fn(Float, Float) -> Float, FuncType);
impl UnaryFuncInfo { 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; let func = self.0;
match self.1 { match self.1 {
FuncType::Trig => func(from_angle_unit(x, angle_unit)), FuncType::Trig => func(from_angle_unit(context, x, angle_unit)),
FuncType::InverseTrig => to_angle_unit(func(x), angle_unit), FuncType::InverseTrig => to_angle_unit(context, func(x), angle_unit),
FuncType::Other => func(x), FuncType::Other => func(x),
} }
} }
} }
impl BinaryFuncInfo { 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; let func = self.0;
match self.1 { match self.1 {
FuncType::Trig => func( FuncType::Trig => func(
from_angle_unit(x, angle_unit), from_angle_unit(context, x, angle_unit),
from_angle_unit(y, 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), FuncType::Other => func(x, y),
} }
} }
} }
pub fn call_unary_func(name: &str, x: Float, angle_unit: &Unit) -> Option<Float> { pub fn call_unary_func(
context: &mut interpreter::Context,
name: &str,
x: Float,
angle_unit: &str,
) -> Option<Float> {
if let Some(func_info) = UNARY_FUNCS.get(name) { 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 { } else {
None None
} }
} }
pub fn call_binary_func(name: &str, x: Float, y: Float, angle_unit: &Unit) -> Option<Float> { pub fn call_binary_func(
context: &mut interpreter::Context,
name: &str,
x: Float,
y: Float,
angle_unit: &str,
) -> Option<Float> {
if let Some(func_info) = BINARY_FUNCS.get(name) { 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 { } else {
None 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 { match angle_unit {
Unit::Radians => x, "rad" => x,
Unit::Degrees => special_funcs::to_degrees(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 { match angle_unit {
Unit::Radians => x, "rad" => x,
Unit::Degrees => special_funcs::to_radians(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 { pub fn factorial(x: Float) -> Float {
((x + 1) as Float).gamma() ((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 { mod funcs {

View File

@ -36,6 +36,10 @@ pub fn group(expr: Box<Expr>) -> Box<Expr> {
Box::new(Expr::Group(expr)) Box::new(Expr::Group(expr))
} }
pub fn unit(identifier: &str, expr: Box<Expr>) -> Box<Expr> {
Box::new(Expr::Unit(identifier.into(), expr))
}
pub fn var_decl(identifier: &str, value: Box<Expr>) -> Stmt { pub fn var_decl(identifier: &str, value: Box<Expr>) -> Stmt {
Stmt::VarDecl(identifier.into(), value) Stmt::VarDecl(identifier.into(), value)
} }
@ -43,3 +47,7 @@ pub fn var_decl(identifier: &str, value: Box<Expr>) -> Stmt {
pub fn fn_decl(identifier: &str, parameters: Vec<String>, value: Box<Expr>) -> Stmt { pub fn fn_decl(identifier: &str, parameters: Vec<String>, value: Box<Expr>) -> Stmt {
Stmt::FnDecl(identifier.into(), parameters, value) Stmt::FnDecl(identifier.into(), parameters, value)
} }
pub fn unit_decl(unit: &str, base_unit: &str, expr: Box<Expr>) -> Stmt {
Stmt::UnitDecl(unit.into(), base_unit.into(), expr)
}

View File

@ -2,13 +2,12 @@ mod output;
mod repl; mod repl;
use kalk::parser; use kalk::parser;
use kalk::parser::Unit;
use std::env; use std::env;
use std::fs::File; use std::fs::File;
use std::io::Read; use std::io::Read;
fn main() { 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. // Command line argument input, execute it and exit.
let mut args = env::args().skip(1); 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") { if let Ok(angle_unit_var) = env::var("ANGLE_UNIT") {
match angle_unit_var.as_ref() { angle_unit_var
"radians" => Unit::Radians,
"degrees" => Unit::Degrees,
_ => {
panic!("Unexpected angle unit: {}.", angle_unit_var);
}
}
} else { } else {
Unit::Radians String::from("rad")
} }
} }