diff --git a/Cargo.lock b/Cargo.lock index ba05e06..64301cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,7 +92,7 @@ dependencies = [ [[package]] name = "kalk" -version = "0.2.2" +version = "0.2.3" dependencies = [ "lazy_static", "phf", @@ -103,7 +103,7 @@ dependencies = [ [[package]] name = "kalk_cli" -version = "0.3.2" +version = "0.3.3" dependencies = [ "ansi_term", "kalk", diff --git a/kalk/Cargo.toml b/kalk/Cargo.toml index 1ac72c7..505ece8 100644 --- a/kalk/Cargo.toml +++ b/kalk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kalk" -version = "0.2.2" +version = "0.2.3" authors = ["PaddiM8"] edition = "2018" readme = "README.md" diff --git a/kalk/src/interpreter.rs b/kalk/src/interpreter.rs index f238e8b..2e2f37c 100644 --- a/kalk/src/interpreter.rs +++ b/kalk/src/interpreter.rs @@ -113,7 +113,7 @@ fn eval_binary_expr( } let (left, left_unit) = eval_expr(context, left_expr, "")?; - let (right, _) = if left_unit.len() > 0 { + let (mut right, _) = if left_unit.len() > 0 { let (_, right_unit) = eval_expr(context, right_expr, "")?; // TODO: Avoid evaluating this twice. if right_unit.len() > 0 { @@ -131,12 +131,17 @@ fn eval_binary_expr( unit.into() }; + if let Expr::Unary(TokenKind::Percent, _) = right_expr { + right *= left.clone(); + } + Ok(( match op { TokenKind::Plus => left + right, TokenKind::Minus => left - right, TokenKind::Star => left * right, TokenKind::Slash => left / right, + TokenKind::Percent => left % right, TokenKind::Power => left.pow(right), _ => Float::with_val(1, 1), }, @@ -154,6 +159,7 @@ fn eval_unary_expr( match op { TokenKind::Minus => Ok((-expr_value, unit)), + TokenKind::Percent => Ok((expr_value * 0.01, unit)), TokenKind::Exclamation => Ok(( Float::with_val( context.precision, @@ -409,6 +415,17 @@ mod tests { assert_eq!(interpret(pow).unwrap().unwrap(), 8); } + #[test] + fn test_percent() { + let stmt = Stmt::Expr(binary( + literal("5"), + Percent, + group(binary(literal("3"), Star, unary(Percent, literal("2")))), + )); + + assert!(cmp(interpret(stmt).unwrap().unwrap(), 0.14)); + } + #[test] fn test_unary() { let neg = Stmt::Expr(unary(Minus, literal("1"))); diff --git a/kalk/src/lexer.rs b/kalk/src/lexer.rs index 590c2b6..35c856a 100644 --- a/kalk/src/lexer.rs +++ b/kalk/src/lexer.rs @@ -15,6 +15,7 @@ pub enum TokenKind { Power, Equals, Exclamation, + Percent, UnitKeyword, ToKeyword, @@ -112,6 +113,7 @@ impl<'a> Lexer<'a> { '!' => build(TokenKind::Exclamation, "", span), ',' => build(TokenKind::Comma, "", span), ';' => build(TokenKind::Semicolon, "", span), + '%' => build(TokenKind::Percent, "", span), _ => build(TokenKind::Unknown, "", span), }; @@ -207,7 +209,7 @@ fn build(kind: TokenKind, value: &str, span: (usize, usize)) -> Token { fn is_valid_identifier(c: Option<&char>) -> bool { if let Some(c) = c { - regex::Regex::new(r"[^\s\n\r0-9\+-/\*\^!\(\)=\.,;|⌊⌋⌈⌉]") + regex::Regex::new(r"[^\s\n\r0-9\+-/%\*\^!\(\)=\.,;|⌊⌋⌈⌉]") .unwrap() .is_match(&c.to_string()) } else { @@ -230,12 +232,13 @@ mod tests { #[test] fn test_token_kinds() { - let tokens = Lexer::lex("+-*/^()|=!,"); + let tokens = Lexer::lex("+-*/%^()|=!,"); let expected = vec![ TokenKind::Plus, TokenKind::Minus, TokenKind::Star, TokenKind::Slash, + TokenKind::Percent, TokenKind::Power, TokenKind::OpenParenthesis, TokenKind::ClosedParenthesis, diff --git a/kalk/src/parser.rs b/kalk/src/parser.rs index 6c94e0f..f897ce2 100644 --- a/kalk/src/parser.rs +++ b/kalk/src/parser.rs @@ -72,6 +72,7 @@ pub enum CalcError { UndefinedFn(String), UndefinedVar(String), UnableToInvert(String), + UnableToParseExpression, Unknown, } @@ -240,18 +241,41 @@ fn parse_sum(context: &mut Context) -> Result { fn parse_factor(context: &mut Context) -> Result { let mut left = parse_unit(context)?; + if let Expr::Unary(TokenKind::Percent, percent_left) = left.clone() { + let try_parse = parse_factor(context); + if !try_parse.is_err() { + left = Expr::Binary( + percent_left, + TokenKind::Percent, + Box::new(try_parse.unwrap()), + ); + } + } + while match_token(context, TokenKind::Star) || match_token(context, TokenKind::Slash) + || match_token(context, TokenKind::Percent) || match_token(context, TokenKind::Identifier) || match_token(context, TokenKind::Literal) { - // If the next token is an identifier, assume it's multiplication. Eg. 3y + // If the token is an identifier, assume it's multiplication. Eg. 3y let op = match peek(context).kind { TokenKind::Identifier | TokenKind::Literal => TokenKind::Star, _ => advance(context).kind.clone(), }; - let right = parse_unit(context)?; + let parse_next = parse_unit(context); + let right = if let Ok(right) = parse_next { + right + /*} else if let Err(CalcError::UnableToParseExpression) = parse_next { + // If it failed to parse further, + // try to parse it as something else. + // Eg. percent unary + break;*/ + } else { + return parse_next; + }; + left = Expr::Binary(Box::new(left), op, Box::new(right)); } @@ -279,7 +303,12 @@ fn parse_unary(context: &mut Context) -> Result { return Ok(Expr::Unary(op, expr)); } - Ok(parse_exponent(context)?) + let expr = parse_exponent(context)?; + if match_token(context, TokenKind::Percent) { + Ok(Expr::Unary(advance(context).kind.clone(), Box::new(expr))) + } else { + Ok(expr) + } } fn parse_exponent(context: &mut Context) -> Result { @@ -310,7 +339,8 @@ fn parse_primary(context: &mut Context) -> Result { TokenKind::OpenParenthesis => parse_group(context)?, TokenKind::Pipe | TokenKind::OpenCeil | TokenKind::OpenFloor => parse_group_fn(context)?, TokenKind::Identifier => parse_identifier(context)?, - _ => Expr::Literal(advance(context).value.clone()), + TokenKind::Literal => Expr::Literal(advance(context).value.clone()), + _ => return Err(CalcError::UnableToParseExpression), }; Ok(expr) @@ -526,6 +556,28 @@ mod tests { ); } + #[test] + fn test_percent() { + let tokens = vec![ + token(Literal, "1"), + token(Percent, ""), + token(Literal, "1"), + token(Plus, ""), + token(Literal, "5"), + token(Percent, ""), + token(EOF, ""), + ]; + + assert_eq!( + parse(tokens).unwrap(), + Stmt::Expr(binary( + binary(literal("1"), Percent, literal("1"),), + Plus, + unary(Percent, literal("5")) + )) + ); + } + #[test] fn test_unit() { let tokens = vec![token(Literal, "1"), token(Identifier, "a")]; diff --git a/kalk_cli/Cargo.toml b/kalk_cli/Cargo.toml index 185914c..db5f2d5 100644 --- a/kalk_cli/Cargo.toml +++ b/kalk_cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kalk_cli" -version = "0.3.2" +version = "0.3.3" authors = ["PaddiM8"] edition = "2018" readme = "../README.md" @@ -15,7 +15,7 @@ path = "src/main.rs" name = "kalk" [dependencies] -kalk = { path = "../kalk", version = "^0.2.2" } +kalk = { path = "../kalk", version = "^0.2.3" } rustyline = "6.1.2" ansi_term = "0.12" regex = "1" diff --git a/kalk_cli/src/output.rs b/kalk_cli/src/output.rs index 37649dd..de44d2b 100644 --- a/kalk_cli/src/output.rs +++ b/kalk_cli/src/output.rs @@ -61,6 +61,7 @@ fn print_calc_err(err: CalcError) { UnableToInvert(msg) => format!("Unable to invert: {}", msg), UndefinedFn(name) => format!("Undefined function: '{}'.", name), UndefinedVar(name) => format!("Undefined variable: '{}'.", name), + UnableToParseExpression => format!("Unable to parse expression."), Unknown => format!("Unknown error."), }); } diff --git a/kalk_cli/src/repl.rs b/kalk_cli/src/repl.rs index 490836b..e37709a 100644 --- a/kalk_cli/src/repl.rs +++ b/kalk_cli/src/repl.rs @@ -55,7 +55,7 @@ impl Highlighter for LineHighlighter { let reg = Regex::new( r"(?x) (?P[^!-@\s_|^⌊⌋⌈⌉]+(_\d+)?) | - (?P[+\-/*^!])", + (?P[+\-/*%^!])", ) .unwrap();