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]]
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",

View File

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

View File

@ -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")));

View File

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

View File

@ -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<Expr, CalcError> {
fn parse_factor(context: &mut Context) -> Result<Expr, CalcError> {
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<Expr, CalcError> {
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> {
@ -310,7 +339,8 @@ fn parse_primary(context: &mut Context) -> Result<Expr, CalcError> {
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")];

View File

@ -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"

View File

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

View File

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