Added the unit statement (very basic and experimental).

This commit is contained in:
PaddiM8 2020-06-13 16:19:32 +02:00
parent c0628855ae
commit 45adb1b526
5 changed files with 165 additions and 78 deletions

View File

@ -7,6 +7,7 @@ use crate::parser::Unit;
pub enum Stmt {
VarDecl(String, Box<Expr>),
FnDecl(String, Vec<String>, Box<Expr>),
UnitDecl(String, String, Box<Expr>),
/// For simplicity, expressions can be put into statements. This is the form in which expressions are passed to the interpreter.
Expr(Box<Expr>),
}
@ -16,7 +17,7 @@ pub enum Stmt {
pub enum Expr {
Binary(Box<Expr>, TokenKind, Box<Expr>),
Unary(TokenKind, Box<Expr>),
Unit(Box<Expr>, TokenKind),
Unit(String, Box<Expr>),
Var(String),
Group(Box<Expr>),
FnCall(String, Vec<Expr>),

View File

@ -39,18 +39,15 @@ impl<'a> Context<'a> {
fn eval_stmt(context: &mut Context, stmt: &Stmt) -> Result<Float, CalcError> {
match stmt {
Stmt::VarDecl(identifier, _) => eval_var_decl_stmt(context, stmt, identifier),
Stmt::VarDecl(_, _) => eval_var_decl_stmt(context, stmt),
Stmt::FnDecl(_, _, _) => eval_fn_decl_stmt(context),
Stmt::UnitDecl(_, _, _) => eval_unit_decl_stmt(context),
Stmt::Expr(expr) => eval_expr_stmt(context, &expr),
}
}
fn eval_var_decl_stmt(
context: &mut Context,
stmt: &Stmt,
identifier: &str,
) -> Result<Float, CalcError> {
context.symbol_table.insert(&identifier, stmt.clone());
fn eval_var_decl_stmt(context: &mut Context, stmt: &Stmt) -> Result<Float, CalcError> {
context.symbol_table.insert(stmt.clone());
Ok(Float::with_val(context.precision, 1))
}
@ -58,6 +55,10 @@ fn eval_fn_decl_stmt(context: &mut Context) -> Result<Float, CalcError> {
Ok(Float::with_val(context.precision, 1)) // Nothing needs to happen here, since the parser will already have added the FnDecl's to the symbol table.
}
fn eval_unit_decl_stmt(context: &mut Context) -> Result<Float, CalcError> {
Ok(Float::with_val(context.precision, 1))
}
fn eval_expr_stmt(context: &mut Context, expr: &Expr) -> Result<Float, CalcError> {
eval_expr(context, &expr)
}
@ -66,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, kind) => eval_unit_expr(context, expr, kind),
Expr::Unit(_, expr) => eval_unit_expr(context, 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),
@ -78,12 +79,20 @@ fn eval_expr(context: &mut Context, expr: &Expr) -> Result<Float, CalcError> {
fn eval_binary_expr(
context: &mut Context,
left: &Expr,
left_expr: &Expr,
op: &TokenKind,
right: &Expr,
right_expr: &Expr,
) -> Result<Float, CalcError> {
let left = eval_expr(context, &left)?;
let right = eval_expr(context, &right)?;
let left = eval_expr(context, left_expr)?;
let right = if let Expr::Unit(left_unit, _) = left_expr {
if let Expr::Unit(right_unit, right_unit_expr) = right_expr {
convert_unit(context, right_unit_expr, right_unit, &left_unit)?
} else {
eval_expr(context, right_expr)?
}
} else {
eval_expr(context, right_expr)?
};
Ok(match op {
TokenKind::Plus => left + right,
@ -108,26 +117,26 @@ fn eval_unary_expr(context: &mut Context, op: &TokenKind, expr: &Expr) -> Result
}
}
fn eval_unit_expr(
fn eval_unit_expr(context: &mut Context, expr: &Expr) -> Result<Float, CalcError> {
eval_expr(context, expr)
}
fn convert_unit(
context: &mut Context,
expr: &Expr,
kind: &TokenKind,
from_unit: &str,
to_unit: &str,
) -> Result<Float, CalcError> {
let x = eval_expr(context, &expr);
let unit = kind.to_unit()?;
if let Some(Stmt::UnitDecl(_, _, unit_def)) =
context.symbol_table.get_unit(from_unit, to_unit).cloned()
{
context
.symbol_table
.insert(Stmt::VarDecl(String::from("u"), Box::new(expr.clone())));
// Don't do any angle conversions if the defauly angle unit is the same as the unit kind
match unit {
Unit::Degrees | Unit::Radians => {
if context.angle_unit == unit {
return x;
}
}
}
match unit {
Unit::Degrees => Ok(prelude::special_funcs::to_radians(x?)),
Unit::Radians => Ok(prelude::special_funcs::to_degrees(x?)),
eval_expr(context, &unit_def)
} else {
Err(CalcError::InvalidUnit)
}
}
@ -138,7 +147,7 @@ fn eval_var_expr(context: &mut Context, identifier: &str) -> Result<Float, CalcE
}
// Look for the variable in the symbol table
let var_decl = context.symbol_table.get(identifier).cloned();
let var_decl = context.symbol_table.get_var(identifier).cloned();
match var_decl {
Some(Stmt::VarDecl(_, expr)) => eval_expr(context, &expr),
_ => Err(CalcError::UndefinedVar(identifier.into())),
@ -202,7 +211,7 @@ fn eval_fn_call_expr(
// then calculate the expression and add it to the total sum.
context
.symbol_table
.set("n", Stmt::VarDecl(String::from("n"), Box::new(n_expr)));
.set(Stmt::VarDecl(String::from("n"), Box::new(n_expr)));
sum += eval_expr(context, &expressions[2])?;
}
@ -212,10 +221,7 @@ fn eval_fn_call_expr(
}
// Symbol Table
let stmt_definition = context
.symbol_table
.get(&format!("{}()", identifier))
.cloned();
let stmt_definition = context.symbol_table.get_fn(identifier).cloned();
match stmt_definition {
Some(Stmt::FnDecl(_, arguments, fn_body)) => {
@ -294,7 +300,7 @@ mod tests {
assert!(fact_dec_result > 169.406 && fact_dec_result < 169.407);
}
#[test]
/*#[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)));
@ -304,7 +310,7 @@ mod tests {
(interpret(deg).unwrap().unwrap() - Float::with_val(PRECISION, 0.017456)).abs()
< Float::with_val(PRECISION, 0.0001)
);
}
}*/
#[test]
fn test_var() {
@ -312,7 +318,7 @@ mod tests {
// Prepare by inserting a variable declaration in the symbol table.
let mut symbol_table = SymbolTable::new();
symbol_table.insert("x", var_decl("x", literal("1")));
symbol_table.insert(var_decl("x", literal("1")));
let mut context = Context::new(&mut symbol_table, &Unit::Radians, PRECISION);
assert_eq!(context.interpret(vec![stmt]).unwrap().unwrap(), 1);
@ -345,14 +351,11 @@ mod tests {
// Prepare by inserting a variable declaration in the symbol table.
let mut symbol_table = SymbolTable::new();
symbol_table.insert(
"f()",
fn_decl(
"f",
vec![String::from("x")],
binary(var("x"), TokenKind::Plus, literal("2")),
),
);
symbol_table.insert(fn_decl(
"f",
vec![String::from("x")],
binary(var("x"), TokenKind::Plus, literal("2")),
));
let mut context = Context::new(&mut symbol_table, &Unit::Radians, PRECISION);
assert_eq!(context.interpret(vec![stmt]).unwrap().unwrap(), 3);

View File

@ -16,6 +16,8 @@ pub enum TokenKind {
Equals,
Exclamation,
UnitKeyword,
To,
Deg,
Rad,
@ -170,6 +172,8 @@ impl<'a> Lexer<'a> {
let kind = match value.as_ref() {
"deg" | "°" => TokenKind::Deg,
"rad" => TokenKind::Rad,
"unit" => TokenKind::UnitKeyword,
"to" => TokenKind::To,
_ => TokenKind::Identifier,
};

View File

@ -19,6 +19,8 @@ pub struct Context {
pos: usize,
symbol_table: SymbolTable,
angle_unit: Unit,
parsing_unit_decl: bool,
unit_decl_base_unit: Option<String>,
}
impl Context {
@ -28,6 +30,8 @@ impl Context {
pos: 0,
symbol_table: SymbolTable::new(),
angle_unit: Unit::Radians,
parsing_unit_decl: false,
unit_decl_base_unit: None,
}
}
@ -101,6 +105,8 @@ fn parse_stmt(context: &mut Context) -> Result<Stmt, CalcError> {
TokenKind::OpenParenthesis => parse_identifier_stmt(context)?,
_ => Stmt::Expr(Box::new(parse_expr(context)?)),
});
} else if match_token(context, TokenKind::UnitKeyword) {
return parse_unit_decl_stmt(context);
}
Ok(Stmt::Expr(Box::new(parse_expr(context)?)))
@ -132,9 +138,7 @@ fn parse_identifier_stmt(context: &mut Context) -> Result<Stmt, CalcError> {
// Insert the function declaration into the symbol table during parsing
// so that the parser can find out if particular functions exist.
context
.symbol_table
.insert(&format!("{}()", identifier), fn_decl.clone());
context.symbol_table.insert(fn_decl.clone());
return Ok(fn_decl);
}
@ -156,6 +160,29 @@ fn parse_var_decl_stmt(context: &mut Context) -> Result<Stmt, CalcError> {
Ok(Stmt::VarDecl(identifier.value, Box::new(expr)))
}
fn parse_unit_decl_stmt(context: &mut Context) -> Result<Stmt, CalcError> {
advance(context); // Unit keyword
let identifier = advance(context).clone();
consume(context, TokenKind::Equals)?;
// Parse the definition
context.parsing_unit_decl = true;
let def = parse_expr(context)?;
context.parsing_unit_decl = false;
let base_unit = if let Some(base_unit) = &context.unit_decl_base_unit {
base_unit.clone()
} else {
return Err(CalcError::InvalidUnit);
};
let stmt = Stmt::UnitDecl(identifier.value, base_unit, Box::new(def));
context.symbol_table.insert(stmt.clone());
Ok(stmt)
}
fn parse_expr(context: &mut Context) -> Result<Expr, CalcError> {
Ok(parse_sum(context)?)
}
@ -175,7 +202,7 @@ fn parse_sum(context: &mut Context) -> Result<Expr, CalcError> {
}
fn parse_factor(context: &mut Context) -> Result<Expr, CalcError> {
let mut left = parse_unary(context)?;
let mut left = parse_unit(context)?;
while match_token(context, TokenKind::Star)
|| match_token(context, TokenKind::Slash)
@ -188,13 +215,27 @@ fn parse_factor(context: &mut Context) -> Result<Expr, CalcError> {
_ => advance(context).kind.clone(),
};
let right = parse_unary(context)?;
let right = parse_unit(context)?;
left = Expr::Binary(Box::new(left), op, Box::new(right));
}
Ok(left)
}
fn parse_unit(context: &mut Context) -> Result<Expr, CalcError> {
let expr = parse_unary(context)?;
let peek = &peek(&context).value;
if match_token(context, TokenKind::Identifier) && context.symbol_table.contains_unit(&peek) {
return Ok(Expr::Unit(
advance(context).value.to_string(),
Box::new(expr),
));
}
Ok(expr)
}
fn parse_unary(context: &mut Context) -> Result<Expr, CalcError> {
if match_token(context, TokenKind::Minus) {
let op = advance(context).kind.clone();
@ -236,11 +277,7 @@ fn parse_primary(context: &mut Context) -> Result<Expr, CalcError> {
_ => Expr::Literal(advance(context).value.clone()),
};
if !is_at_end(context) && peek(context).kind.is_unit() {
Ok(Expr::Unit(Box::new(expr), advance(context).kind.clone()))
} else {
Ok(expr)
}
Ok(expr)
}
fn parse_group(context: &mut Context) -> Result<Expr, CalcError> {
@ -295,7 +332,10 @@ fn parse_identifier(context: &mut Context) -> Result<Expr, CalcError> {
}
// Eg. x
if context.symbol_table.contains_var(&identifier.value) {
if context.parsing_unit_decl {
context.unit_decl_base_unit = Some(identifier.value);
Ok(Expr::Var(String::from("u")))
} else if context.symbol_table.contains_var(&identifier.value) {
Ok(Expr::Var(identifier.value))
} else {
let mut chars = identifier.value.chars();
@ -315,19 +355,19 @@ fn parse_identifier(context: &mut Context) -> Result<Expr, CalcError> {
}
}
fn peek(context: &mut Context) -> &Token {
fn peek(context: &Context) -> &Token {
&context.tokens[context.pos]
}
fn peek_next(context: &mut Context) -> &Token {
fn peek_next(context: &Context) -> &Token {
&context.tokens[context.pos + 1]
}
fn previous(context: &mut Context) -> &Token {
fn previous(context: &Context) -> &Token {
&context.tokens[context.pos - 1]
}
fn match_token(context: &mut Context, kind: TokenKind) -> bool {
fn match_token(context: &Context, kind: TokenKind) -> bool {
if is_at_end(context) {
return false;
}
@ -348,7 +388,7 @@ fn consume(context: &mut Context, kind: TokenKind) -> Result<&Token, CalcError>
Err(CalcError::UnexpectedToken(kind))
}
fn is_at_end(context: &mut Context) -> bool {
fn is_at_end(context: &Context) -> bool {
context.pos >= context.tokens.len() || peek(context).kind == TokenKind::EOF
}
@ -357,7 +397,6 @@ mod tests {
use super::*;
use crate::lexer::{Token, TokenKind::*};
use crate::test_helpers::*;
use test_case::test_case;
fn parse_with_context(context: &mut Context, tokens: Vec<Token>) -> Result<Stmt, CalcError> {
context.tokens = tokens;
@ -447,7 +486,7 @@ mod tests {
);
}
#[test_case(Deg)]
/*#[test_case(Deg)]
#[test_case(Rad)]
fn test_unary(angle_unit: TokenKind) {
let tokens = vec![
@ -460,7 +499,7 @@ mod tests {
parse(tokens).unwrap(),
Stmt::Expr(unary(Minus, Box::new(Expr::Unit(literal("1"), angle_unit))))
);
}
}*/
#[test]
fn test_var_decl() {
@ -517,10 +556,11 @@ mod tests {
let mut context = Context::new();
// Add the function to the symbol table first, in order to prevent errors.
context.symbol_table.set(
"f()",
Stmt::FnDecl(String::from("f"), vec![String::from("x")], literal("1")),
);
context.symbol_table.set(Stmt::FnDecl(
String::from("f"),
vec![String::from("x")],
literal("1"),
));
assert_eq!(
parse_with_context(&mut context, tokens).unwrap(),

View File

@ -1,40 +1,79 @@
use crate::{ast::Stmt, prelude};
use std::collections::HashMap;
#[derive(Debug)]
pub struct SymbolTable {
hashmap: HashMap<String, Stmt>,
unit_types: HashMap<String, ()>,
}
impl SymbolTable {
pub fn new() -> Self {
SymbolTable {
hashmap: HashMap::new(),
unit_types: HashMap::new(),
}
}
pub fn insert(&mut self, key: &str, value: Stmt) {
self.hashmap.insert(key.into(), value);
pub fn insert(&mut self, value: Stmt) -> Option<Stmt> {
match &value {
Stmt::VarDecl(identifier, _) => {
self.hashmap.insert(format!("var.{}", identifier), value)
}
Stmt::UnitDecl(identifier, to_unit, _) => {
self.unit_types.insert(identifier.to_string(), ());
self.unit_types.insert(to_unit.to_string(), ());
self.hashmap
.insert(format!("unit.{}.{}", identifier, to_unit), value)
}
Stmt::FnDecl(identifier, _, _) => {
self.hashmap.insert(format!("fn.{}", identifier), value)
}
_ => panic!("Can only insert VarDecl, UnitDecl and FnDecl into symbol table."),
}
}
pub fn get(&self, key: &str) -> Option<&Stmt> {
self.hashmap.get(key)
pub fn get_var(&self, key: &str) -> Option<&Stmt> {
self.hashmap.get(&format!("var.{}", key))
}
pub fn set(&mut self, key: &str, value: Stmt) {
if let Some(stmt) = self.hashmap.get_mut(key) {
pub fn get_unit(&self, key: &str, to_unit: &str) -> Option<&Stmt> {
self.hashmap.get(&format!("unit.{}.{}", key, to_unit))
}
pub fn get_fn(&self, key: &str) -> Option<&Stmt> {
self.hashmap.get(&format!("fn.{}", key))
}
pub fn set(&mut self, value: Stmt) {
let existing_item = match &value {
Stmt::VarDecl(identifier, _) => self.hashmap.get_mut(&format!("var.{}", identifier)),
Stmt::UnitDecl(identifier, to_unit, _) => self
.hashmap
.get_mut(&format!("unit.{}.{}", identifier, to_unit)),
Stmt::FnDecl(identifier, _, _) => self.hashmap.get_mut(&format!("fn.{}", identifier)),
_ => panic!("Can only set VarDecl, UnitDecl and FnDecl in symbol table."),
};
if let Some(stmt) = existing_item {
*stmt = value;
} else {
self.insert(key, value);
self.insert(value);
}
}
pub fn contains_var(&self, identifier: &str) -> bool {
prelude::CONSTANTS.contains_key(identifier) || self.hashmap.contains_key(identifier)
prelude::CONSTANTS.contains_key(identifier)
|| self.hashmap.contains_key(&format!("var.{}", identifier))
}
pub fn contains_unit(&self, identifier: &str) -> bool {
self.unit_types.contains_key(identifier)
}
pub fn contains_fn(&self, identifier: &str) -> bool {
prelude::UNARY_FUNCS.contains_key(identifier)
|| prelude::UNARY_FUNCS.contains_key(identifier)
|| self.hashmap.contains_key(&format!("{}()", identifier))
|| self.hashmap.contains_key(&format!("fn.{}", identifier))
}
}