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::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<Float, CalcError> {
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<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)
}
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<Option<Float>, 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);
}

View File

@ -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,
};

View File

@ -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<Token>,
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 {

View File

@ -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<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) {
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<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) {
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 {

View File

@ -36,6 +36,10 @@ pub fn group(expr: Box<Expr>) -> Box<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 {
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 {
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;
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")
}
}