mirror of
https://github.com/PaddiM8/kalker.git
synced 2024-12-12 17:40:52 +01:00
Comparison operators and Iverson brackets
This commit is contained in:
parent
2402d444f5
commit
fe50789cbd
@ -156,6 +156,12 @@ fn eval_binary_expr(
|
||||
TokenKind::Slash => left.div(context, right),
|
||||
TokenKind::Percent => left.rem(context, right),
|
||||
TokenKind::Power => left.pow(context, right),
|
||||
TokenKind::Equals => left.eq(context, right),
|
||||
TokenKind::NotEquals => left.not_eq(context, right),
|
||||
TokenKind::GreaterThan => left.greater_than(context, right),
|
||||
TokenKind::LessThan => left.less_than(context, right),
|
||||
TokenKind::GreaterOrEquals => left.greater_or_equals(context, right),
|
||||
TokenKind::LessOrEquals => left.less_or_equals(context, right),
|
||||
_ => KalkNum::from(1),
|
||||
};
|
||||
|
||||
@ -468,12 +474,54 @@ mod tests {
|
||||
let mul = Stmt::Expr(binary(literal(2f64), Star, literal(3f64)));
|
||||
let div = Stmt::Expr(binary(literal(2f64), Slash, literal(4f64)));
|
||||
let pow = Stmt::Expr(binary(literal(2f64), Power, literal(3f64)));
|
||||
let equals = Stmt::Expr(binary(literal(2f64), Equals, literal(3f64)));
|
||||
let not_equals = Stmt::Expr(binary(literal(2f64), NotEquals, literal(3f64)));
|
||||
let greater_than = Stmt::Expr(binary(literal(2f64), GreaterThan, literal(3f64)));
|
||||
let less_than = Stmt::Expr(binary(literal(2f64), LessThan, literal(3f64)));
|
||||
let greater_or_equals = Stmt::Expr(binary(literal(2f64), GreaterOrEquals, literal(3f64)));
|
||||
let less_or_equals = Stmt::Expr(binary(literal(2f64), LessOrEquals, literal(3f64)));
|
||||
|
||||
assert_eq!(interpret(add).unwrap().unwrap().to_f64(), 5f64);
|
||||
assert_eq!(interpret(sub).unwrap().unwrap().to_f64(), -1f64);
|
||||
assert_eq!(interpret(mul).unwrap().unwrap().to_f64(), 6f64);
|
||||
assert_eq!(interpret(div).unwrap().unwrap().to_f64(), 0.5f64);
|
||||
assert_eq!(interpret(pow).unwrap().unwrap().to_f64(), 8f64);
|
||||
|
||||
let result = interpret(equals).unwrap().unwrap();
|
||||
assert_eq!(
|
||||
(result.to_f64(), result.boolean_value.unwrap()),
|
||||
(3f64, false)
|
||||
);
|
||||
|
||||
let result = interpret(not_equals).unwrap().unwrap();
|
||||
assert_eq!(
|
||||
(result.to_f64(), result.boolean_value.unwrap()),
|
||||
(3f64, true)
|
||||
);
|
||||
|
||||
let result = interpret(greater_than).unwrap().unwrap();
|
||||
assert_eq!(
|
||||
(result.to_f64(), result.boolean_value.unwrap()),
|
||||
(3f64, false)
|
||||
);
|
||||
|
||||
let result = interpret(less_than).unwrap().unwrap();
|
||||
assert_eq!(
|
||||
(result.to_f64(), result.boolean_value.unwrap()),
|
||||
(3f64, true)
|
||||
);
|
||||
|
||||
let result = interpret(greater_or_equals).unwrap().unwrap();
|
||||
assert_eq!(
|
||||
(result.to_f64(), result.boolean_value.unwrap()),
|
||||
(3f64, false)
|
||||
);
|
||||
|
||||
let result = interpret(less_or_equals).unwrap().unwrap();
|
||||
assert_eq!(
|
||||
(result.to_f64(), result.boolean_value.unwrap()),
|
||||
(3f64, true)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -186,6 +186,10 @@ impl KalkNum {
|
||||
}
|
||||
|
||||
pub fn to_string_pretty(&self) -> String {
|
||||
if let Some(boolean_value) = self.boolean_value {
|
||||
return boolean_value.to_string();
|
||||
}
|
||||
|
||||
let real_f64 = self.to_f64();
|
||||
let imaginary_f64 = self.imaginary_to_f64();
|
||||
if real_f64.is_nan() || imaginary_f64.is_nan() {
|
||||
@ -314,6 +318,58 @@ impl KalkNum {
|
||||
KalkNum::new(self.value % right.value, &right.unit)
|
||||
}
|
||||
|
||||
pub(crate) fn eq(self, context: &mut crate::interpreter::Context, rhs: KalkNum) -> KalkNum {
|
||||
let mut right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs);
|
||||
right.boolean_value = Some(self.value == right.value);
|
||||
right
|
||||
}
|
||||
|
||||
pub(crate) fn not_eq(self, context: &mut crate::interpreter::Context, rhs: KalkNum) -> KalkNum {
|
||||
let mut right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs);
|
||||
right.boolean_value = Some(self.value != right.value);
|
||||
right
|
||||
}
|
||||
|
||||
pub(crate) fn greater_than(
|
||||
self,
|
||||
context: &mut crate::interpreter::Context,
|
||||
rhs: KalkNum,
|
||||
) -> KalkNum {
|
||||
let mut right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs);
|
||||
right.boolean_value = Some(self.value > right.value);
|
||||
right
|
||||
}
|
||||
|
||||
pub(crate) fn less_than(
|
||||
self,
|
||||
context: &mut crate::interpreter::Context,
|
||||
rhs: KalkNum,
|
||||
) -> KalkNum {
|
||||
let mut right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs);
|
||||
right.boolean_value = Some(self.value < right.value);
|
||||
right
|
||||
}
|
||||
|
||||
pub(crate) fn greater_or_equals(
|
||||
self,
|
||||
context: &mut crate::interpreter::Context,
|
||||
rhs: KalkNum,
|
||||
) -> KalkNum {
|
||||
let mut right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs);
|
||||
right.boolean_value = Some(self.value >= right.value);
|
||||
right
|
||||
}
|
||||
|
||||
pub(crate) fn less_or_equals(
|
||||
self,
|
||||
context: &mut crate::interpreter::Context,
|
||||
rhs: KalkNum,
|
||||
) -> KalkNum {
|
||||
let mut right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs);
|
||||
right.boolean_value = Some(self.value <= right.value);
|
||||
right
|
||||
}
|
||||
|
||||
pub(crate) fn add_without_unit(self, rhs: KalkNum) -> KalkNum {
|
||||
KalkNum::new_with_imaginary(
|
||||
self.value + rhs.value,
|
||||
|
@ -7,6 +7,7 @@ pub struct KalkNum {
|
||||
pub(crate) value: f64,
|
||||
pub(crate) unit: String,
|
||||
pub(crate) imaginary_value: f64,
|
||||
pub(crate) boolean_value: Option<bool>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
@ -16,6 +17,7 @@ impl KalkNum {
|
||||
value,
|
||||
unit: unit.to_string(),
|
||||
imaginary_value: 0f64,
|
||||
boolean_value: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,6 +30,7 @@ impl KalkNum {
|
||||
} else {
|
||||
imaginary_value
|
||||
},
|
||||
boolean_value: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,6 +39,16 @@ impl KalkNum {
|
||||
value: 0f64,
|
||||
unit: String::new(),
|
||||
imaginary_value: value,
|
||||
boolean_value: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_bool(value: bool) -> Self {
|
||||
Self {
|
||||
value: 0f64,
|
||||
unit: String::new(),
|
||||
imaginary_value: 0f64,
|
||||
boolean_value: Some(value),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ pub struct KalkNum {
|
||||
pub(crate) value: Float,
|
||||
pub(crate) unit: String,
|
||||
pub(crate) imaginary_value: Float,
|
||||
pub(crate) boolean_value: Option<bool>,
|
||||
}
|
||||
|
||||
impl KalkNum {
|
||||
@ -19,6 +20,7 @@ impl KalkNum {
|
||||
value,
|
||||
unit: unit.to_string(),
|
||||
imaginary_value: Float::with_val(63, 0),
|
||||
boolean_value: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,6 +33,7 @@ impl KalkNum {
|
||||
} else {
|
||||
imaginary_value
|
||||
},
|
||||
boolean_value: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,6 +42,16 @@ impl KalkNum {
|
||||
value: Float::with_val(63, 0),
|
||||
unit: String::new(),
|
||||
imaginary_value: value,
|
||||
boolean_value: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_bool(value: bool) -> Self {
|
||||
Self {
|
||||
value: Float::with_val(63, 0),
|
||||
unit: String::new(),
|
||||
imaginary_value: Float::with_val(63, 0),
|
||||
boolean_value: Some(value),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,10 +13,15 @@ pub enum TokenKind {
|
||||
Star,
|
||||
Slash,
|
||||
Power,
|
||||
Equals,
|
||||
Exclamation,
|
||||
Percent,
|
||||
Tick,
|
||||
GreaterThan,
|
||||
LessThan,
|
||||
Equals,
|
||||
NotEquals,
|
||||
GreaterOrEquals,
|
||||
LessOrEquals,
|
||||
|
||||
UnitKeyword,
|
||||
ToKeyword,
|
||||
@ -28,6 +33,8 @@ pub enum TokenKind {
|
||||
ClosedFloor,
|
||||
OpenParenthesis,
|
||||
ClosedParenthesis,
|
||||
OpenBracket,
|
||||
ClosedBracket,
|
||||
Comma,
|
||||
Semicolon,
|
||||
|
||||
@ -110,12 +117,19 @@ impl<'a> Lexer<'a> {
|
||||
'⌋' => build(TokenKind::ClosedFloor, "", span),
|
||||
'(' => build(TokenKind::OpenParenthesis, "", span),
|
||||
')' => build(TokenKind::ClosedParenthesis, "", span),
|
||||
'=' => build(TokenKind::Equals, "", span),
|
||||
'[' => build(TokenKind::OpenBracket, "", span),
|
||||
']' => build(TokenKind::ClosedBracket, "", span),
|
||||
'!' => build(TokenKind::Exclamation, "", span),
|
||||
'=' => build(TokenKind::Equals, "", span),
|
||||
'>' => build(TokenKind::GreaterThan, "", span),
|
||||
'<' => build(TokenKind::LessThan, "", span),
|
||||
',' => build(TokenKind::Comma, "", span),
|
||||
';' => build(TokenKind::Semicolon, "", span),
|
||||
'%' => build(TokenKind::Percent, "", span),
|
||||
'\'' => build(TokenKind::Tick, "", span),
|
||||
'≠' => build(TokenKind::NotEquals, "", span),
|
||||
'≥' => build(TokenKind::GreaterOrEquals, "", span),
|
||||
'≤' => build(TokenKind::LessOrEquals, "", span),
|
||||
// Some of the special symbols will be lexed here,
|
||||
// so that they don't merge with other symbols.
|
||||
'π' => build(TokenKind::Identifier, "π", span),
|
||||
@ -128,13 +142,25 @@ impl<'a> Lexer<'a> {
|
||||
|
||||
self.advance();
|
||||
|
||||
// Handle **
|
||||
if let (TokenKind::Star, Some(c)) = (token.kind, self.peek()) {
|
||||
if *c == '*' {
|
||||
// Handle tokens with two characters
|
||||
match (token.kind, self.peek()) {
|
||||
(TokenKind::Star, Some('*')) => {
|
||||
self.advance();
|
||||
|
||||
return build(TokenKind::Power, "", span);
|
||||
}
|
||||
(TokenKind::Exclamation, Some('=')) => {
|
||||
self.advance();
|
||||
return build(TokenKind::NotEquals, "", span);
|
||||
}
|
||||
(TokenKind::GreaterThan, Some('=')) => {
|
||||
self.advance();
|
||||
return build(TokenKind::GreaterOrEquals, "", span);
|
||||
}
|
||||
(TokenKind::LessThan, Some('=')) => {
|
||||
self.advance();
|
||||
return build(TokenKind::LessOrEquals, "", span);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
token
|
||||
@ -228,7 +254,8 @@ fn is_valid_identifier(c: Option<&char>) -> bool {
|
||||
if let Some(c) = c {
|
||||
match c {
|
||||
'+' | '-' | '/' | '*' | '%' | '^' | '!' | '(' | ')' | '=' | '.' | ',' | ';' | '|'
|
||||
| '⌊' | '⌋' | '⌈' | '⌉' | ']' | 'π' | '√' | 'τ' | 'ϕ' | 'Γ' => false,
|
||||
| '⌊' | '⌋' | '⌈' | '⌉' | '[' | ']' | 'π' | '√' | 'τ' | 'ϕ' | 'Γ' | '<' | '>' | '≠'
|
||||
| '≥' | '≤' => false,
|
||||
_ => !c.is_digit(10),
|
||||
}
|
||||
} else {
|
||||
@ -274,6 +301,22 @@ mod tests {
|
||||
match_tokens(tokens, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[wasm_bindgen_test]
|
||||
fn test_brackets() {
|
||||
let tokens = Lexer::lex("[1 < 2]");
|
||||
let expected = vec![
|
||||
TokenKind::OpenBracket,
|
||||
TokenKind::Literal,
|
||||
TokenKind::LessThan,
|
||||
TokenKind::Literal,
|
||||
TokenKind::ClosedBracket,
|
||||
TokenKind::EOF,
|
||||
];
|
||||
|
||||
match_tokens(tokens, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[wasm_bindgen_test]
|
||||
fn test_empty() {
|
||||
|
@ -149,7 +149,9 @@ pub fn eval(
|
||||
) -> Result<Option<KalkNum>, CalcError> {
|
||||
// Variable and function declaration parsers will set this to false
|
||||
// if the equal sign is for one of those instead.
|
||||
context.contains_equation_equal_sign = input.contains("=");
|
||||
// It also should not contain an iverson bracket, since equal signs in there
|
||||
// mean something else. This is not super reliable, and should probably be improved in the future.
|
||||
context.contains_equation_equal_sign = input.contains("=") && !input.contains("[");
|
||||
let statements = parse(context, input)?;
|
||||
|
||||
let mut interpreter = interpreter::Context::new(
|
||||
@ -302,13 +304,14 @@ fn parse_unit_decl_stmt(context: &mut Context) -> Result<Stmt, CalcError> {
|
||||
}
|
||||
|
||||
fn parse_expr(context: &mut Context) -> Result<Expr, CalcError> {
|
||||
Ok(parse_equation(context)?)
|
||||
Ok(parse_equality(context)?)
|
||||
}
|
||||
|
||||
fn parse_equation(context: &mut Context) -> Result<Expr, CalcError> {
|
||||
let left = parse_to(context)?;
|
||||
fn parse_equality(context: &mut Context) -> Result<Expr, CalcError> {
|
||||
let mut left = parse_to(context)?;
|
||||
|
||||
if match_token(context, TokenKind::Equals) {
|
||||
// Equation
|
||||
if match_token(context, TokenKind::Equals) && context.contains_equation_equal_sign {
|
||||
advance(context);
|
||||
let right = parse_to(context)?;
|
||||
let var_name = if let Some(var_name) = &context.equation_variable {
|
||||
@ -333,9 +336,25 @@ fn parse_equation(context: &mut Context) -> Result<Expr, CalcError> {
|
||||
Identifier::from_full_name(var_name),
|
||||
Box::new(inverted.clone()),
|
||||
));
|
||||
|
||||
return Ok(inverted);
|
||||
}
|
||||
|
||||
// Equality check
|
||||
while match_token(context, TokenKind::Equals)
|
||||
|| match_token(context, TokenKind::NotEquals)
|
||||
|| match_token(context, TokenKind::GreaterThan)
|
||||
|| match_token(context, TokenKind::LessThan)
|
||||
|| match_token(context, TokenKind::GreaterOrEquals)
|
||||
|| match_token(context, TokenKind::LessOrEquals)
|
||||
{
|
||||
let op = peek(context).kind;
|
||||
advance(context);
|
||||
let right = parse_to(context)?;
|
||||
|
||||
left = Expr::Binary(Box::new(left), op, Box::new(right));
|
||||
}
|
||||
|
||||
Ok(left)
|
||||
}
|
||||
|
||||
@ -462,7 +481,9 @@ fn parse_factorial(context: &mut Context) -> Result<Expr, CalcError> {
|
||||
fn parse_primary(context: &mut Context) -> Result<Expr, CalcError> {
|
||||
let expr = match peek(context).kind {
|
||||
TokenKind::OpenParenthesis => parse_group(context)?,
|
||||
TokenKind::Pipe | TokenKind::OpenCeil | TokenKind::OpenFloor => parse_group_fn(context)?,
|
||||
TokenKind::Pipe | TokenKind::OpenCeil | TokenKind::OpenFloor | TokenKind::OpenBracket => {
|
||||
parse_group_fn(context)?
|
||||
}
|
||||
TokenKind::Identifier => parse_identifier(context)?,
|
||||
TokenKind::Literal => Expr::Literal(string_to_num(&advance(context).value)?),
|
||||
_ => return Err(CalcError::UnableToParseExpression),
|
||||
@ -484,6 +505,7 @@ fn parse_group_fn(context: &mut Context) -> Result<Expr, CalcError> {
|
||||
TokenKind::Pipe => "abs",
|
||||
TokenKind::OpenCeil => "ceil",
|
||||
TokenKind::OpenFloor => "floor",
|
||||
TokenKind::OpenBracket => "iverson",
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
|
@ -89,6 +89,7 @@ lazy_static! {
|
||||
m.insert("abs", (UnaryFuncInfo(abs, Other), ""));
|
||||
m.insert("cbrt", (UnaryFuncInfo(cbrt, Other), ""));
|
||||
m.insert("ceil", (UnaryFuncInfo(ceil, Other), ""));
|
||||
m.insert("iverson", (UnaryFuncInfo(inverson, Other), ""));
|
||||
m.insert("exp", (UnaryFuncInfo(exp, Other), ""));
|
||||
m.insert("floor", (UnaryFuncInfo(floor, Other), ""));
|
||||
m.insert("frac", (UnaryFuncInfo(frac, Other), ""));
|
||||
@ -506,6 +507,18 @@ pub mod funcs {
|
||||
KalkNum::new_with_imaginary(x.value, "", KalkNum::default().value)
|
||||
}
|
||||
|
||||
pub fn inverson(x: KalkNum) -> KalkNum {
|
||||
KalkNum::from(if let Some(boolean_value) = x.boolean_value {
|
||||
if boolean_value {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
} else {
|
||||
1
|
||||
})
|
||||
}
|
||||
|
||||
pub fn log(x: KalkNum) -> KalkNum {
|
||||
if x.has_imaginary() || x.value < 0f64 {
|
||||
// ln(z) / ln(10)
|
||||
|
@ -76,7 +76,7 @@ impl Highlighter for LineHighlighter {
|
||||
|
||||
let reg = Regex::new(
|
||||
r"(?x)
|
||||
(?P<identifier>[^!-@\s_|^⌊⌋⌈⌉]+(_\d+)?) |
|
||||
(?P<identifier>[^!-@\s_|^⌊⌋⌈⌉\[\]≠≥≤]+(_\d+)?) |
|
||||
(?P<op>[+\-/*%^!])",
|
||||
)
|
||||
.unwrap();
|
||||
@ -122,6 +122,9 @@ lazy_static! {
|
||||
m.insert("sqrt", "√");
|
||||
m.insert("tau", "τ");
|
||||
m.insert("(", "()");
|
||||
m.insert("!=", "≠");
|
||||
m.insert(">=", "≥");
|
||||
m.insert("<=", "≤");
|
||||
m
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user