Made parenthesis optional for unary functions

This only applies when the argument is a literal. To do this
efficiently, I changed the `prelude` module. The module now has
compile-time generated hashmaps of functions.
This commit is contained in:
PaddiM8 2020-05-29 21:35:59 +02:00
parent 2cdade5d05
commit 34364dd40e
8 changed files with 292 additions and 171 deletions

149
Cargo.lock generated
View File

@ -113,6 +113,7 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
name = "lek"
version = "0.1.0"
dependencies = [
"phf",
"rustyline",
]
@ -150,6 +151,131 @@ dependencies = [
"void",
]
[[package]]
name = "phf"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
dependencies = [
"phf_macros",
"phf_shared",
"proc-macro-hack",
]
[[package]]
name = "phf_generator"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
dependencies = [
"phf_shared",
"rand",
]
[[package]]
name = "phf_macros"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c"
dependencies = [
"phf_generator",
"phf_shared",
"proc-macro-hack",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "phf_shared"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
dependencies = [
"siphasher",
]
[[package]]
name = "ppv-lite86"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea"
[[package]]
name = "proc-macro-hack"
version = "0.5.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4"
[[package]]
name = "proc-macro2"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1502d12e458c49a4c9cbff560d0fe0060c252bc29799ed94ca2ed4bb665a0101"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom",
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
"rand_pcg",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core",
]
[[package]]
name = "rand_pcg"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
dependencies = [
"rand_core",
]
[[package]]
name = "redox_syscall"
version = "0.1.56"
@ -204,6 +330,23 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "siphasher"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7"
[[package]]
name = "syn"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7e33a62f20d3dc02a1bc9c1d385f92b459bbf35e4dc325eed20c53db5b90c03"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "unicode-segmentation"
version = "1.6.0"
@ -216,6 +359,12 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
[[package]]
name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
[[package]]
name = "utf8parse"
version = "0.2.0"

View File

@ -12,3 +12,4 @@ panic = "abort"
[dependencies]
rustyline = "6.1.2"
phf = { version = "0.8", features = ["macros"] }

View File

@ -1,22 +1,21 @@
use std::{collections::HashMap, mem};
use std::mem;
use crate::lexer::TokenKind;
use crate::parser::{Expr, Stmt, Unit};
use crate::prelude::{self, Prelude};
use crate::visitor::Visitor;
use crate::prelude::{self};
use crate::{symbol_table::SymbolTable, visitor::Visitor};
pub struct Interpreter<'a> {
symbol_table: &'a mut HashMap<String, Stmt>,
symbol_table: &'a mut SymbolTable,
angle_unit: Unit,
prelude: Prelude,
}
impl<'a> Interpreter<'a> {
pub fn new(angle_unit: Unit, symbol_table: &'a mut HashMap<String, Stmt>) -> Self {
pub fn new(angle_unit: Unit, symbol_table: &'a mut SymbolTable) -> Self {
//let mut hashmap: HashMap<String, Stmt> = HashMap::new();
for constant in prelude::CONSTANTS {
symbol_table.insert(
constant.0.to_string(),
constant.0,
Stmt::VarDecl(
constant.0.to_string(),
Box::new(Expr::Literal(constant.1.to_string())),
@ -27,7 +26,6 @@ impl<'a> Interpreter<'a> {
Interpreter {
angle_unit: angle_unit.clone(),
symbol_table,
prelude: Prelude::new(angle_unit),
}
}
@ -67,23 +65,10 @@ impl<'a> Visitor<f64, f64> for Interpreter<'a> {
fn visit_stmt(&mut self, stmt: &Stmt) -> f64 {
match stmt {
Stmt::VarDecl(identifier, _) => {
self.symbol_table.insert(identifier.clone(), stmt.clone());
0f64
}
Stmt::FnDecl(identifier, arguments, _) => {
// Initialise each of the arguments as their own variable.
for argument in arguments {
self.visit_stmt(&Stmt::VarDecl(
argument.clone(),
Box::new(Expr::Literal(String::from("0"))),
));
}
// Add the function to the symbol table.
self.symbol_table
.insert(format!("{}()", identifier.clone()), stmt.clone());
self.symbol_table.insert(&identifier, stmt.clone());
0f64
}
Stmt::FnDecl(_, _, _) => 0f64, // Nothing needs to happen here, since the parser will already have added the FnDecl's to the symbol table.
Stmt::Expr(expr) => self.visit_expr(&expr),
}
}
@ -138,12 +123,12 @@ impl<'a> Visitor<f64, f64> for Interpreter<'a> {
let prelude_func = match expressions.len() {
1 => {
let x = self.visit_expr(&expressions[0]);
self.prelude.call_unary_func(identifier, x)
prelude::call_unary_func(identifier, x, &self.angle_unit)
}
2 => {
let x = self.visit_expr(&expressions[0]);
let y = self.visit_expr(&expressions[1]);
self.prelude.call_binary_func(identifier, x, y)
prelude::call_binary_func(identifier, x, y, &self.angle_unit)
}
_ => None,
};

View File

@ -184,5 +184,5 @@ impl<'a> Lexer<'a> {
}
fn is_valid_identifier(c: char) -> bool {
c.is_alphabetic() || c == '°' || c == '√' || c == '\''
c.is_alphabetic() || c == '°' || c == '√' || c == '\'' || c == '¨'
}

View File

@ -4,6 +4,7 @@ mod interpreter;
mod lexer;
mod parser;
mod prelude;
mod symbol_table;
mod visitor;
use parser::{Parser, Unit};

View File

@ -1,9 +1,10 @@
use std::{collections::HashMap, mem};
use std::mem;
use crate::{
interpreter::Interpreter,
lexer::{Lexer, Token, TokenKind},
prelude,
symbol_table::SymbolTable,
};
#[derive(Debug, Clone)]
@ -34,7 +35,7 @@ pub struct Parser {
pub angle_unit: Unit,
tokens: Vec<Token>,
pos: usize,
symbol_table: HashMap<String, Stmt>,
symbol_table: SymbolTable,
}
impl TokenKind {
@ -55,7 +56,7 @@ impl Parser {
Parser {
tokens: Vec::new(),
pos: 0,
symbol_table: HashMap::new(),
symbol_table: SymbolTable::new(),
angle_unit: prelude::DEFAULT_ANGLE_UNIT,
}
}
@ -106,7 +107,15 @@ impl Parser {
}
}
return Stmt::FnDecl(identifier, parameter_identifiers, Box::new(expr));
let fn_decl =
Stmt::FnDecl(identifier.clone(), parameter_identifiers, Box::new(expr));
// Insert the function declaration into the symbol table during parsing
// so that the parser can find out if particular functions exist.
self.symbol_table
.insert(&format!("{}()", identifier), fn_decl.clone());
return fn_decl;
}
panic!("Unexpected error.");
@ -221,6 +230,16 @@ impl Parser {
fn parse_identifier(&mut self) -> Expr {
let identifier = self.advance().clone();
// Eg. sqrt64
if self.match_token(TokenKind::Literal) {
// If there is a function with this name, parse it as a function, with the next token as the argument.
if self.symbol_table.contains_func(&identifier.value) {
let parameter = Expr::Literal(self.advance().value.clone());
return Expr::FnCall(identifier.value, vec![parameter]);
}
}
// Eg. sqrt(64)
if self.match_token(TokenKind::OpenParenthesis) {
self.advance();
@ -234,10 +253,11 @@ impl Parser {
self.consume(TokenKind::ClosedParenthesis);
Expr::FnCall(identifier.value, parameters)
} else {
Expr::Var(identifier.value)
return Expr::FnCall(identifier.value, parameters);
}
// Eg. x
Expr::Var(identifier.value)
}
fn peek(&self) -> &Token {

View File

@ -1,5 +1,5 @@
use crate::parser::Unit;
use std::collections::HashMap;
use FuncType::*;
pub const DEFAULT_ANGLE_UNIT: Unit = Unit::Radians;
pub const CONSTANTS: &[(&str, &str)] = &[
@ -12,36 +12,77 @@ pub const CONSTANTS: &[(&str, &str)] = &[
("ϕ", "1.61803398"),
];
use funcs::*;
pub const UNARY_FUNCS: phf::Map<&'static str, UnaryFuncInfo> = phf::phf_map! {
"cos" => UnaryFuncInfo(cos, Trig),
"cosec" => UnaryFuncInfo(cosec, Trig),
"cosech" => UnaryFuncInfo(cosech, Trig),
"cosh" => UnaryFuncInfo(cosh, Trig),
"cot" => UnaryFuncInfo(cot, Trig),
"coth" => UnaryFuncInfo(coth, Trig),
"sec" => UnaryFuncInfo(sec, Trig),
"sech" => UnaryFuncInfo(sech, Trig),
"sin" => UnaryFuncInfo(sin, Trig),
"sinh" => UnaryFuncInfo(sinh, Trig),
"tan" => UnaryFuncInfo(tan, Trig),
"tanh" => UnaryFuncInfo(tanh, Trig),
"acos" => UnaryFuncInfo(acos, InverseTrig),
"acosec" => UnaryFuncInfo(acosec, InverseTrig),
"acosech" => UnaryFuncInfo(acosech, InverseTrig),
"acosh" => UnaryFuncInfo(acosh, InverseTrig),
"acot" => UnaryFuncInfo(acot, InverseTrig),
"acoth" => UnaryFuncInfo(acoth, InverseTrig),
"asec" => UnaryFuncInfo(asec, InverseTrig),
"asech" => UnaryFuncInfo(asech, InverseTrig),
"asin" => UnaryFuncInfo(asin, InverseTrig),
"asinh" => UnaryFuncInfo(asinh, InverseTrig),
"atan" => UnaryFuncInfo(atan, InverseTrig),
"atanh" => UnaryFuncInfo(atanh, InverseTrig),
"abs" => UnaryFuncInfo(abs, Other),
"cbrt" => UnaryFuncInfo(cbrt, Other),
"ceil" => UnaryFuncInfo(ceil, Other),
"exp" => UnaryFuncInfo(exp, Other),
"floor" => UnaryFuncInfo(floor, Other),
"frac" => UnaryFuncInfo(frac, Other),
"log" => UnaryFuncInfo(log, Other),
"ln" => UnaryFuncInfo(ln, Other),
"round" => UnaryFuncInfo(round, Other),
"sqrt" => UnaryFuncInfo(sqrt, Other),
"trunc" => UnaryFuncInfo(trunc, Other),
};
pub const BINARY_FUNCS: phf::Map<&'static str, BinaryFuncInfo> = phf::phf_map! {
"max" => BinaryFuncInfo(max, Other),
"min" => BinaryFuncInfo(min, Other),
};
enum FuncType {
Trig,
InverseTrig,
Other,
}
struct UnaryFuncInfo {
func: Box<fn(f64) -> f64>,
func_type: FuncType,
}
// Unary functions
pub struct UnaryFuncInfo(fn(f64) -> f64, FuncType);
pub struct BinaryFuncInfo(fn(f64, f64) -> f64, FuncType);
impl UnaryFuncInfo {
fn call(&self, x: f64, angle_unit: &Unit) -> f64 {
let func = *self.func;
match self.func_type {
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::Other => func(x),
}
}
}
struct BinaryFuncInfo {
func: Box<fn(f64, f64) -> f64>,
func_type: FuncType,
}
impl BinaryFuncInfo {
fn call(&self, x: f64, y: f64, angle_unit: &Unit) -> f64 {
let func = *self.func;
match self.func_type {
let func = self.0;
match self.1 {
FuncType::Trig => func(
from_angle_unit(x, angle_unit),
from_angle_unit(y, angle_unit),
@ -52,6 +93,22 @@ impl BinaryFuncInfo {
}
}
pub fn call_unary_func(name: &str, x: f64, angle_unit: &Unit) -> Option<f64> {
if let Some(func_info) = UNARY_FUNCS.get(name) {
Some(func_info.call(x, &angle_unit))
} else {
None
}
}
pub fn call_binary_func(name: &str, x: f64, y: f64, angle_unit: &Unit) -> Option<f64> {
if let Some(func_info) = BINARY_FUNCS.get(name) {
Some(func_info.call(x, y, angle_unit))
} else {
None
}
}
fn to_angle_unit(x: f64, angle_unit: &Unit) -> f64 {
match angle_unit {
Unit::Radians => x,
@ -66,130 +123,6 @@ fn from_angle_unit(x: f64, angle_unit: &Unit) -> f64 {
}
}
pub struct Prelude {
angle_unit: Unit,
unary: HashMap<String, UnaryFuncInfo>,
binary: HashMap<String, BinaryFuncInfo>,
}
impl Prelude {
pub fn new(angle_unit: Unit) -> Self {
Prelude {
angle_unit,
unary: HashMap::new(),
binary: HashMap::new(),
}
}
pub fn call_unary_func(&mut self, name: &str, x: f64) -> Option<f64> {
if let Some(func_info) = self.unary.get(name) {
Some(func_info.call(x, &self.angle_unit))
} else {
let trig_func: Option<fn(f64) -> f64> = match name {
"cos" => Some(funcs::cos),
"cosec" => Some(funcs::cosec),
"cosech" => Some(funcs::cosech),
"cosh" => Some(funcs::cosh),
"cot" => Some(funcs::cot),
"coth" => Some(funcs::coth),
"sec" => Some(funcs::sec),
"sech" => Some(funcs::sech),
"sin" => Some(funcs::sin),
"sinh" => Some(funcs::sinh),
"tan" => Some(funcs::tan),
"tanh" => Some(funcs::tanh),
_ => None,
};
if let Some(func) = trig_func {
let func_info = UnaryFuncInfo {
func: Box::new(func),
func_type: FuncType::Trig,
};
let value = func_info.call(x, &self.angle_unit);
self.unary.insert(name.to_string(), func_info);
return Some(value);
}
let inv_trig_func: Option<fn(f64) -> f64> = match name {
"acos" => Some(funcs::acos),
"acosh" => Some(funcs::acosh),
"acot" => Some(funcs::acot),
"acoth" => Some(funcs::acoth),
"acosec" => Some(funcs::acosec),
"asec" => Some(funcs::asec),
"asech" => Some(funcs::asech),
"asin" => Some(funcs::asin),
"asinh" => Some(funcs::asinh),
"atan" => Some(funcs::atan),
"atanh" => Some(funcs::atanh),
_ => None,
};
if let Some(func) = inv_trig_func {
let func_info = UnaryFuncInfo {
func: Box::new(func),
func_type: FuncType::InverseTrig,
};
let value = func_info.call(x, &self.angle_unit);
self.unary.insert(name.to_string(), func_info);
return Some(value);
}
let misc_func: Option<fn(f64) -> f64> = match name {
"abs" => Some(funcs::abs),
"cbrt" => Some(funcs::cbrt),
"ceil" => Some(funcs::ceil),
"exp" => Some(funcs::exp),
"floor" => Some(funcs::floor),
"frac" => Some(funcs::frac),
"log" => Some(funcs::log),
"ln" => Some(funcs::ln),
"round" => Some(funcs::round),
"sqrt" => Some(funcs::sqrt),
"trunc" => Some(funcs::trunc),
_ => None,
};
if let Some(func) = misc_func {
let func_info = UnaryFuncInfo {
func: Box::new(func),
func_type: FuncType::Other,
};
let value = func_info.call(x, &self.angle_unit);
self.unary.insert(name.to_string(), func_info);
return Some(value);
} else {
None
}
}
}
pub fn call_binary_func(&mut self, name: &str, x: f64, y: f64) -> Option<f64> {
let misc_func: Option<fn(f64, f64) -> f64> = match name {
"max" => Some(funcs::max),
"min" => Some(funcs::min),
_ => None,
};
if let Some(func) = misc_func {
let func_info = BinaryFuncInfo {
func: Box::new(func),
func_type: FuncType::Other,
};
let value = func_info.call(x, y, &self.angle_unit);
self.binary.insert(name.to_string(), func_info);
return Some(value);
} else {
None
}
}
}
mod funcs {
pub fn abs(x: f64) -> f64 {
x.abs()
@ -211,7 +144,11 @@ mod funcs {
}
pub fn acosec(x: f64) -> f64 {
(1f64 / x).sinh()
(1f64 / x).asin()
}
pub fn acosech(x: f64) -> f64 {
(1f64 / x).asinh()
}
pub fn asec(x: f64) -> f64 {

28
src/symbol_table.rs Normal file
View File

@ -0,0 +1,28 @@
use crate::{parser::Stmt, prelude};
use std::collections::HashMap;
pub struct SymbolTable {
hashmap: HashMap<String, Stmt>,
}
impl SymbolTable {
pub fn new() -> Self {
SymbolTable {
hashmap: HashMap::new(),
}
}
pub fn insert(&mut self, key: &str, value: Stmt) {
self.hashmap.insert(key.into(), value);
}
pub fn get(&self, key: &str) -> Option<&Stmt> {
self.hashmap.get(key)
}
pub fn contains_func(&self, key: &str) -> bool {
prelude::UNARY_FUNCS.contains_key(key)
|| prelude::UNARY_FUNCS.contains_key(key)
|| self.hashmap.contains_key(&format!("{}()", key))
}
}