Skip to content

Commit

Permalink
Allow using commas as decimal separator
Browse files Browse the repository at this point in the history
Close #302
  • Loading branch information
printfn committed Oct 3, 2024
1 parent 70a9a48 commit ba1cdb1
Show file tree
Hide file tree
Showing 15 changed files with 608 additions and 236 deletions.
26 changes: 26 additions & 0 deletions cli/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use fend_core::DecimalSeparatorStyle;

use crate::{color, custom_units::CustomUnitDefinition};
use std::{env, fmt, fs, io};

Expand All @@ -11,6 +13,7 @@ pub struct Config {
pub enable_internet_access: bool,
pub exchange_rate_source: ExchangeRateSource,
pub custom_units: Vec<CustomUnitDefinition>,
pub decimal_separator: DecimalSeparatorStyle,
unknown_settings: UnknownSettings,
unknown_keys: Vec<String>,
}
Expand Down Expand Up @@ -67,6 +70,7 @@ impl<'de> serde::de::Visitor<'de> for ConfigVisitor {
formatter.write_str("a fend configuration struct")
}

#[allow(clippy::too_many_lines)]
fn visit_map<V: serde::de::MapAccess<'de>>(self, mut map: V) -> Result<Config, V::Error> {
let mut result = Config::default();
let mut seen_prompt = false;
Expand All @@ -77,6 +81,7 @@ impl<'de> serde::de::Visitor<'de> for ConfigVisitor {
let mut seen_enable_internet_access = false;
let mut seen_exchange_rate_source = false;
let mut seen_custom_units = false;
let mut seen_decimal_separator_style = false;
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"prompt" => {
Expand Down Expand Up @@ -157,6 +162,23 @@ impl<'de> serde::de::Visitor<'de> for ConfigVisitor {
result.custom_units = map.next_value()?;
seen_custom_units = true;
}
"decimal-separator-style" => {
if seen_decimal_separator_style {
return Err(serde::de::Error::duplicate_field("decimal-separator-style"));
}
let style: String = map.next_value()?;
result.decimal_separator = match style.as_str() {
"dot" | "default" => DecimalSeparatorStyle::Dot,
"comma" => DecimalSeparatorStyle::Comma,
v => {
return Err(serde::de::Error::invalid_value(
serde::de::Unexpected::Str(v),
&"`default`, `dot` or `comma`",
))
}
};
seen_decimal_separator_style = true;
}
unknown_key => {
// this may occur if the user has multiple fend versions installed
map.next_value::<toml::Value>()?;
Expand All @@ -178,6 +200,9 @@ impl<'de> serde::Deserialize<'de> for Config {
"max-history-size",
"unknown-settings",
"enable-internet-access",
"custom-units",
"decimal-separator-style",
"exchange-rate-source",
];
deserializer.deserialize_struct("Config", FIELDS, ConfigVisitor)
}
Expand All @@ -195,6 +220,7 @@ impl Default for Config {
unknown_settings: UnknownSettings::Warn,
exchange_rate_source: ExchangeRateSource::UnitedNations,
custom_units: vec![],
decimal_separator: DecimalSeparatorStyle::Dot,
unknown_keys: vec![],
}
}
Expand Down
2 changes: 2 additions & 0 deletions cli/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ impl InnerCtx {
&custom_unit.attribute.to_fend_core(),
);
}
res.core_ctx
.set_decimal_separator_style(config.decimal_separator);
res
}
}
Expand Down
82 changes: 52 additions & 30 deletions core/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::result::FResult;
use crate::scope::Scope;
use crate::serialize::{Deserialize, Serialize};
use crate::value::{built_in_function::BuiltInFunction, ApplyMulHandling, Value};
use crate::Attrs;
use crate::{Attrs, Context, DecimalSeparatorStyle};
use std::borrow::Cow;
use std::sync::Arc;
use std::{borrow, cmp, fmt, io};
Expand Down Expand Up @@ -128,38 +128,45 @@ pub(crate) enum Expr {
}

impl Expr {
pub(crate) fn compare<I: Interrupt>(&self, other: &Self, int: &I) -> FResult<bool> {
pub(crate) fn compare<I: Interrupt>(
&self,
other: &Self,
ctx: &mut Context,
int: &I,
) -> FResult<bool> {
Ok(match (self, other) {
(Self::Literal(a), Self::Literal(b)) => {
a.compare(b, int)? == Some(cmp::Ordering::Equal)
a.compare(b, ctx, int)? == Some(cmp::Ordering::Equal)
}
(Self::Ident(a), Self::Ident(b)) => a == b,
(Self::Parens(a), Self::Parens(b)) => a.compare(b, int)?,
(Self::UnaryMinus(a), Self::UnaryMinus(b)) => a.compare(b, int)?,
(Self::UnaryPlus(a), Self::UnaryPlus(b)) => a.compare(b, int)?,
(Self::UnaryDiv(a), Self::UnaryDiv(b)) => a.compare(b, int)?,
(Self::Factorial(a), Self::Factorial(b)) => a.compare(b, int)?,
(Self::Parens(a), Self::Parens(b)) => a.compare(b, ctx, int)?,
(Self::UnaryMinus(a), Self::UnaryMinus(b)) => a.compare(b, ctx, int)?,
(Self::UnaryPlus(a), Self::UnaryPlus(b)) => a.compare(b, ctx, int)?,
(Self::UnaryDiv(a), Self::UnaryDiv(b)) => a.compare(b, ctx, int)?,
(Self::Factorial(a), Self::Factorial(b)) => a.compare(b, ctx, int)?,
(Self::Bop(a1, a2, a3), Self::Bop(b1, b2, b3)) => {
a1 == b1 && a2.compare(b2, int)? && a3.compare(b3, int)?
a1 == b1 && a2.compare(b2, ctx, int)? && a3.compare(b3, ctx, int)?
}
(Self::Apply(a1, a2), Self::Apply(b1, b2)) => {
a1.compare(b1, int)? && a2.compare(b2, int)?
a1.compare(b1, ctx, int)? && a2.compare(b2, ctx, int)?
}
(Self::ApplyFunctionCall(a1, a2), Self::ApplyFunctionCall(b1, b2)) => {
a1.compare(b1, int)? && a2.compare(b2, int)?
a1.compare(b1, ctx, int)? && a2.compare(b2, ctx, int)?
}
(Self::ApplyMul(a1, a2), Self::ApplyMul(b1, b2)) => {
a1.compare(b1, int)? && a2.compare(b2, int)?
a1.compare(b1, ctx, int)? && a2.compare(b2, ctx, int)?
}
(Self::As(a1, a2), Self::As(b1, b2)) => {
a1.compare(b1, ctx, int)? && a2.compare(b2, ctx, int)?
}
(Self::As(a1, a2), Self::As(b1, b2)) => a1.compare(b1, int)? && a2.compare(b2, int)?,
(Self::Fn(a1, a2), Self::Fn(b1, b2)) => a1 == b1 && a2.compare(b2, int)?,
(Self::Of(a1, a2), Self::Of(b1, b2)) => a1 == b1 && a2.compare(b2, int)?,
(Self::Assign(a1, a2), Self::Assign(b1, b2)) => a1 == b1 && a2.compare(b2, int)?,
(Self::Fn(a1, a2), Self::Fn(b1, b2)) => a1 == b1 && a2.compare(b2, ctx, int)?,
(Self::Of(a1, a2), Self::Of(b1, b2)) => a1 == b1 && a2.compare(b2, ctx, int)?,
(Self::Assign(a1, a2), Self::Assign(b1, b2)) => a1 == b1 && a2.compare(b2, ctx, int)?,
(Self::Equality(a1, a2, a3), Self::Equality(b1, b2, b3)) => {
a1 == b1 && a2.compare(b2, int)? && a3.compare(b3, int)?
a1 == b1 && a2.compare(b2, ctx, int)? && a3.compare(b3, ctx, int)?
}
(Self::Statements(a1, a2), Self::Statements(b1, b2)) => {
a1.compare(b1, int)? && a2.compare(b2, int)?
a1.compare(b1, ctx, int)? && a2.compare(b2, ctx, int)?
}
_ => false,
})
Expand Down Expand Up @@ -412,14 +419,26 @@ pub(crate) fn evaluate<I: Interrupt>(
Expr::UnaryDiv(x) => {
eval!(*x)?.handle_num(|x| Number::from(1).div(x, int), Expr::UnaryDiv, scope)?
}
Expr::Factorial(x) => {
eval!(*x)?.handle_num(|x| x.factorial(int), Expr::Factorial, scope)?
}
Expr::Bop(Bop::Plus, a, b) => evaluate_add(eval!(*a)?, eval!(*b)?, scope, int)?,
Expr::Factorial(x) => eval!(*x)?.handle_num(
|x| x.factorial(context.decimal_separator, int),
Expr::Factorial,
scope,
)?,
Expr::Bop(Bop::Plus, a, b) => evaluate_add(
eval!(*a)?,
eval!(*b)?,
scope,
context.decimal_separator,
int,
)?,
Expr::Bop(Bop::Minus, a, b) => {
let a = eval!(*a)?;
match a {
Value::Num(a) => Value::Num(Box::new(a.sub(eval!(*b)?.expect_num()?, int)?)),
Value::Num(a) => Value::Num(Box::new(a.sub(
eval!(*b)?.expect_num()?,
context.decimal_separator,
int,
)?)),
Value::Date(a) => a.sub(eval!(*b)?, int)?,
f @ (Value::BuiltInFunction(_) | Value::Fn(_, _, _)) => f.apply(
Expr::UnaryMinus(b),
Expand All @@ -446,7 +465,7 @@ pub(crate) fn evaluate<I: Interrupt>(
}
lhs.handle_two_nums(
eval!(*b)?,
|a, b| a.pow(b, int),
|a, b| a.pow(b, context.decimal_separator, int),
|a| {
|f| {
Expr::Bop(
Expand Down Expand Up @@ -511,7 +530,7 @@ pub(crate) fn evaluate<I: Interrupt>(
Expr::Equality(is_equals, a, b) => {
let lhs = evaluate(*a, scope.clone(), attrs, context, int)?;
let rhs = evaluate(*b, scope, attrs, context, int)?;
Value::Bool(match lhs.compare(&rhs, int)? {
Value::Bool(match lhs.compare(&rhs, context, int)? {
Some(cmp::Ordering::Equal) => is_equals,
Some(cmp::Ordering::Greater | cmp::Ordering::Less) | None => !is_equals,
})
Expand All @@ -523,10 +542,13 @@ fn evaluate_add<I: Interrupt>(
a: Value,
b: Value,
scope: Option<Arc<Scope>>,
decimal_separator: DecimalSeparatorStyle,
int: &I,
) -> FResult<Value> {
Ok(match (a, b) {
(Value::Num(a), Value::Num(b)) => Value::Num(Box::new(a.add(*b, int)?)),
(Value::Num(a), Value::Num(b)) => {
Value::Num(Box::new(a.add(*b, decimal_separator, int)?))
}
(Value::String(a), Value::String(b)) => {
Value::String(format!("{}{}", a.as_ref(), b.as_ref()).into())
}
Expand Down Expand Up @@ -612,7 +634,7 @@ fn evaluate_as<I: Interrupt>(
"char" | "character" => {
let a = evaluate(a, scope, attrs, context, int)?;
if let Value::Num(v) = a {
let n = v.try_as_usize(int)?;
let n = v.try_as_usize(context.decimal_separator, int)?;
let ch = n
.try_into()
.ok()
Expand All @@ -626,7 +648,7 @@ fn evaluate_as<I: Interrupt>(
"roman" | "roman_numeral" => {
let a = evaluate(a, scope, attrs, context, int)?
.expect_num()?
.try_as_usize(int)?;
.try_as_usize(context.decimal_separator, int)?;
if a == 0 {
return Err(FendError::RomanNumeralZero);
}
Expand All @@ -645,7 +667,7 @@ fn evaluate_as<I: Interrupt>(
"words" => {
let uint = evaluate(a, scope, attrs, context, int)?
.expect_num()?
.into_unitless_complex(int)?
.into_unitless_complex(context.decimal_separator, int)?
.try_as_real()?
.try_as_biguint(int)?;
return Ok(Value::String(borrow::Cow::Owned(uint.to_words(int)?)));
Expand All @@ -657,7 +679,7 @@ fn evaluate_as<I: Interrupt>(
Value::Num(b) => Value::Num(Box::new(
evaluate(a, scope, attrs, context, int)?
.expect_num()?
.convert_to(*b, int)?,
.convert_to(*b, context.decimal_separator, int)?,
)),
Value::Format(fmt) => Value::Num(Box::new(
evaluate(a, scope, attrs, context, int)?
Expand Down
2 changes: 1 addition & 1 deletion core/src/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub(crate) fn evaluate_to_value<I: Interrupt>(
context: &mut crate::Context,
int: &I,
) -> FResult<Value> {
let lex = lexer::lex(input, int);
let lex = lexer::lex(input, context, int);
let mut tokens = vec![];
let mut missing_open_parens: i32 = 0;
for token in lex {
Expand Down
Loading

0 comments on commit ba1cdb1

Please sign in to comment.