added percentage unit and modulo

This commit is contained in:
PaddiM8 2020-12-09 22:18:00 +01:00
parent 4f655033b9
commit dd1b4d723f
8 changed files with 86 additions and 13 deletions

4
Cargo.lock generated
View File

@ -92,7 +92,7 @@ dependencies = [
[[package]] [[package]]
name = "kalk" name = "kalk"
version = "0.2.2" version = "0.2.3"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
"phf", "phf",
@ -103,7 +103,7 @@ dependencies = [
[[package]] [[package]]
name = "kalk_cli" name = "kalk_cli"
version = "0.3.2" version = "0.3.3"
dependencies = [ dependencies = [
"ansi_term", "ansi_term",
"kalk", "kalk",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "kalk" name = "kalk"
version = "0.2.2" version = "0.2.3"
authors = ["PaddiM8"] authors = ["PaddiM8"]
edition = "2018" edition = "2018"
readme = "README.md" readme = "README.md"

View File

@ -113,7 +113,7 @@ fn eval_binary_expr(
} }
let (left, left_unit) = eval_expr(context, left_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. let (_, right_unit) = eval_expr(context, right_expr, "")?; // TODO: Avoid evaluating this twice.
if right_unit.len() > 0 { if right_unit.len() > 0 {
@ -131,12 +131,17 @@ fn eval_binary_expr(
unit.into() unit.into()
}; };
if let Expr::Unary(TokenKind::Percent, _) = right_expr {
right *= left.clone();
}
Ok(( Ok((
match op { match op {
TokenKind::Plus => left + right, TokenKind::Plus => left + right,
TokenKind::Minus => left - right, TokenKind::Minus => left - right,
TokenKind::Star => left * right, TokenKind::Star => left * right,
TokenKind::Slash => left / right, TokenKind::Slash => left / right,
TokenKind::Percent => left % right,
TokenKind::Power => left.pow(right), TokenKind::Power => left.pow(right),
_ => Float::with_val(1, 1), _ => Float::with_val(1, 1),
}, },
@ -154,6 +159,7 @@ fn eval_unary_expr(
match op { match op {
TokenKind::Minus => Ok((-expr_value, unit)), TokenKind::Minus => Ok((-expr_value, unit)),
TokenKind::Percent => Ok((expr_value * 0.01, unit)),
TokenKind::Exclamation => Ok(( TokenKind::Exclamation => Ok((
Float::with_val( Float::with_val(
context.precision, context.precision,
@ -409,6 +415,17 @@ mod tests {
assert_eq!(interpret(pow).unwrap().unwrap(), 8); 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] #[test]
fn test_unary() { fn test_unary() {
let neg = Stmt::Expr(unary(Minus, literal("1"))); let neg = Stmt::Expr(unary(Minus, literal("1")));

View File

@ -15,6 +15,7 @@ pub enum TokenKind {
Power, Power,
Equals, Equals,
Exclamation, Exclamation,
Percent,
UnitKeyword, UnitKeyword,
ToKeyword, ToKeyword,
@ -112,6 +113,7 @@ impl<'a> Lexer<'a> {
'!' => build(TokenKind::Exclamation, "", span), '!' => build(TokenKind::Exclamation, "", span),
',' => build(TokenKind::Comma, "", span), ',' => build(TokenKind::Comma, "", span),
';' => build(TokenKind::Semicolon, "", span), ';' => build(TokenKind::Semicolon, "", span),
'%' => build(TokenKind::Percent, "", span),
_ => build(TokenKind::Unknown, "", 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 { fn is_valid_identifier(c: Option<&char>) -> bool {
if let Some(c) = c { if let Some(c) = c {
regex::Regex::new(r"[^\s\n\r0-9\+-/\*\^!\(\)=\.,;|⌊⌋⌈⌉]") regex::Regex::new(r"[^\s\n\r0-9\+-/%\*\^!\(\)=\.,;|⌊⌋⌈⌉]")
.unwrap() .unwrap()
.is_match(&c.to_string()) .is_match(&c.to_string())
} else { } else {
@ -230,12 +232,13 @@ mod tests {
#[test] #[test]
fn test_token_kinds() { fn test_token_kinds() {
let tokens = Lexer::lex("+-*/^()|=!,"); let tokens = Lexer::lex("+-*/%^()|=!,");
let expected = vec![ let expected = vec![
TokenKind::Plus, TokenKind::Plus,
TokenKind::Minus, TokenKind::Minus,
TokenKind::Star, TokenKind::Star,
TokenKind::Slash, TokenKind::Slash,
TokenKind::Percent,
TokenKind::Power, TokenKind::Power,
TokenKind::OpenParenthesis, TokenKind::OpenParenthesis,
TokenKind::ClosedParenthesis, TokenKind::ClosedParenthesis,

View File

@ -72,6 +72,7 @@ pub enum CalcError {
UndefinedFn(String), UndefinedFn(String),
UndefinedVar(String), UndefinedVar(String),
UnableToInvert(String), UnableToInvert(String),
UnableToParseExpression,
Unknown, Unknown,
} }
@ -240,18 +241,41 @@ fn parse_sum(context: &mut Context) -> Result<Expr, CalcError> {
fn parse_factor(context: &mut Context) -> Result<Expr, CalcError> { fn parse_factor(context: &mut Context) -> Result<Expr, CalcError> {
let mut left = parse_unit(context)?; 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) while match_token(context, TokenKind::Star)
|| match_token(context, TokenKind::Slash) || match_token(context, TokenKind::Slash)
|| match_token(context, TokenKind::Percent)
|| match_token(context, TokenKind::Identifier) || match_token(context, TokenKind::Identifier)
|| match_token(context, TokenKind::Literal) || 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 { let op = match peek(context).kind {
TokenKind::Identifier | TokenKind::Literal => TokenKind::Star, TokenKind::Identifier | TokenKind::Literal => TokenKind::Star,
_ => advance(context).kind.clone(), _ => 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)); left = Expr::Binary(Box::new(left), op, Box::new(right));
} }
@ -279,7 +303,12 @@ fn parse_unary(context: &mut Context) -> Result<Expr, CalcError> {
return Ok(Expr::Unary(op, expr)); 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<Expr, CalcError> { fn parse_exponent(context: &mut Context) -> Result<Expr, CalcError> {
@ -310,7 +339,8 @@ fn parse_primary(context: &mut Context) -> Result<Expr, CalcError> {
TokenKind::OpenParenthesis => parse_group(context)?, TokenKind::OpenParenthesis => parse_group(context)?,
TokenKind::Pipe | TokenKind::OpenCeil | TokenKind::OpenFloor => parse_group_fn(context)?, TokenKind::Pipe | TokenKind::OpenCeil | TokenKind::OpenFloor => parse_group_fn(context)?,
TokenKind::Identifier => parse_identifier(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) 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] #[test]
fn test_unit() { fn test_unit() {
let tokens = vec![token(Literal, "1"), token(Identifier, "a")]; let tokens = vec![token(Literal, "1"), token(Identifier, "a")];

View File

@ -1,6 +1,6 @@
[package] [package]
name = "kalk_cli" name = "kalk_cli"
version = "0.3.2" version = "0.3.3"
authors = ["PaddiM8"] authors = ["PaddiM8"]
edition = "2018" edition = "2018"
readme = "../README.md" readme = "../README.md"
@ -15,7 +15,7 @@ path = "src/main.rs"
name = "kalk" name = "kalk"
[dependencies] [dependencies]
kalk = { path = "../kalk", version = "^0.2.2" } kalk = { path = "../kalk", version = "^0.2.3" }
rustyline = "6.1.2" rustyline = "6.1.2"
ansi_term = "0.12" ansi_term = "0.12"
regex = "1" regex = "1"

View File

@ -61,6 +61,7 @@ fn print_calc_err(err: CalcError) {
UnableToInvert(msg) => format!("Unable to invert: {}", msg), UnableToInvert(msg) => format!("Unable to invert: {}", msg),
UndefinedFn(name) => format!("Undefined function: '{}'.", name), UndefinedFn(name) => format!("Undefined function: '{}'.", name),
UndefinedVar(name) => format!("Undefined variable: '{}'.", name), UndefinedVar(name) => format!("Undefined variable: '{}'.", name),
UnableToParseExpression => format!("Unable to parse expression."),
Unknown => format!("Unknown error."), Unknown => format!("Unknown error."),
}); });
} }

View File

@ -55,7 +55,7 @@ impl Highlighter for LineHighlighter {
let reg = Regex::new( let reg = Regex::new(
r"(?x) r"(?x)
(?P<identifier>[^!-@\s_|^]+(_\d+)?) | (?P<identifier>[^!-@\s_|^]+(_\d+)?) |
(?P<op>[+\-/*^!])", (?P<op>[+\-/*%^!])",
) )
.unwrap(); .unwrap();