mirror of
https://github.com/PaddiM8/kalker.git
synced 2025-06-24 19:51:45 +02:00
added percentage unit and modulo
This commit is contained in:
parent
4f655033b9
commit
dd1b4d723f
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -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",
|
||||||
|
@ -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"
|
||||||
|
@ -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")));
|
||||||
|
@ -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,
|
||||||
|
@ -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")];
|
||||||
|
@ -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"
|
||||||
|
@ -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."),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user