Skip to content

Commit

Permalink
Use a struct to maintain valid state
Browse files Browse the repository at this point in the history
This also lets me pre-set the base input strings as strings instead of
BigDecimals.
  • Loading branch information
spejamchr committed Mar 14, 2024
1 parent 4d98678 commit 8a8164f
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 111 deletions.
102 changes: 95 additions & 7 deletions src/bases.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,97 @@
use std::num::NonZeroU64;
use std::{num::NonZeroU64, str::FromStr};

use bigdecimal::{BigDecimal, ToPrimitive};

#[derive(PartialEq, Clone)]
pub struct BaseConversion {
pub input_string: String,
pub input_base: BigDecimal,
pub input_base_string: String,
pub output_base: BigDecimal,
pub output_base_string: String,
}

static FALLBACK_INPUT_BASE: i32 = 10;
static FALLBACK_OUTPUT_BASE: i32 = 10;

impl BaseConversion {
pub fn new_with_defaults(
input_string: String,
input_base_string: String,
output_base_string: String,
base_conversion: Option<&Self>,
) -> Self {
Self {
input_string,
input_base: BigDecimal::from_str(&input_base_string)
.or_else(|e| val_from_popular_strings(&input_base_string).ok_or(e))
.or_else(|e| base_conversion.map(|c| c.input_base.clone()).ok_or(e))
.unwrap_or_else(|_| BigDecimal::from(FALLBACK_INPUT_BASE)),
input_base_string,
output_base: BigDecimal::from_str(&output_base_string)
.or_else(|e| val_from_popular_strings(&output_base_string).ok_or(e))
.or_else(|e| base_conversion.map(|c| c.output_base.clone()).ok_or(e))
.unwrap_or_else(|_| BigDecimal::from(FALLBACK_OUTPUT_BASE)),
output_base_string,
}
}

fn base_10_value(&self) -> Result<BigDecimal, String> {
val_from_base(&self.input_string, &self.input_base)
}

pub fn base_10_string(&self) -> Result<String, String> {
self.base_10_value()
.and_then(|v| val_to_base(&v, &BigDecimal::from(10)))
}

pub fn output_string(&self) -> Result<String, String> {
self.base_10_value()
.and_then(|v| val_to_base(&v, &self.output_base))
}
}

fn val_from_popular_strings(s: &str) -> Option<BigDecimal> {
match s.to_lowercase().as_str() {
"phi" => Some((BigDecimal::from(5).sqrt().unwrap() + 1) / 2),
"φ" => Some((BigDecimal::from(5).sqrt().unwrap() + 1) / 2),
"pi" => Some(
BigDecimal::from_str("3.14159265358979323846264338327950288419716939937510").unwrap(),
),
"π" => Some(
BigDecimal::from_str("3.14159265358979323846264338327950288419716939937510").unwrap(),
),
"e" => Some(
BigDecimal::from_str("2.71828182845904523536028747135266249775724709369995").unwrap(),
),
"sqrt2" => Some(BigDecimal::from(2).sqrt().unwrap()),
"two" => Some(BigDecimal::from(2)),
"binary" => Some(BigDecimal::from(2)),
"three" => Some(BigDecimal::from(3)),
"ternary" => Some(BigDecimal::from(3)),
"four" => Some(BigDecimal::from(4)),
"quaternary" => Some(BigDecimal::from(4)),
"five" => Some(BigDecimal::from(5)),
"quinary" => Some(BigDecimal::from(5)),
"six" => Some(BigDecimal::from(6)),
"senary" => Some(BigDecimal::from(6)),
"octal" => Some(BigDecimal::from(8)),
"eight" => Some(BigDecimal::from(8)),
"ten" => Some(BigDecimal::from(10)),
"decimal" => Some(BigDecimal::from(10)),
"twelve" => Some(BigDecimal::from(12)),
"duodecimal" => Some(BigDecimal::from(12)),
"dozenal" => Some(BigDecimal::from(12)),
"sixteen" => Some(BigDecimal::from(16)),
"hex" => Some(BigDecimal::from(16)),
"twenty" => Some(BigDecimal::from(20)),
"vigesimal" => Some(BigDecimal::from(20)),
"sixty" => Some(BigDecimal::from(60)),
"sexagesimal" => Some(BigDecimal::from(60)),
_ => None,
}
}

pub fn rounded_string(num: BigDecimal, hard_limit: Option<NonZeroU64>) -> String {
if let Some(hl) = hard_limit {
if num.digits() > hl.get() {
Expand Down Expand Up @@ -61,7 +151,7 @@ fn base_digits_to_val(digits: &str, base: &BigDecimal) -> Result<BigDecimal, Str
.map(|n| n.round(32).normalized())
}

pub fn val_from_base(input: &str, base: &BigDecimal) -> Result<BigDecimal, String> {
fn val_from_base(input: &str, base: &BigDecimal) -> Result<BigDecimal, String> {
if base <= &bigdecimal::One::one() {
return Err("Input base must be greater than 1".to_string());
}
Expand All @@ -82,7 +172,7 @@ pub fn val_from_base(input: &str, base: &BigDecimal) -> Result<BigDecimal, Strin
}
}

fn digit_to_str(digit: usize) -> String {
fn digit_to_string(digit: usize) -> String {
static DIGITS: [&str; 36] = [
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H",
"I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
Expand All @@ -93,7 +183,7 @@ fn digit_to_str(digit: usize) -> String {
}
}

pub fn val_to_base(value: &BigDecimal, base: &BigDecimal) -> Result<String, String> {
fn val_to_base(value: &BigDecimal, base: &BigDecimal) -> Result<String, String> {
let mut value = value.clone();
if base <= &bigdecimal::One::one() {
return Err("Output base must be greater than 1".to_string());
Expand Down Expand Up @@ -129,7 +219,7 @@ pub fn val_to_base(value: &BigDecimal, base: &BigDecimal) -> Result<String, Stri
output.push('.')
}
let dusize = digit.to_usize().unwrap();
output.push_str(&digit_to_str(dusize));
output.push_str(&digit_to_string(dusize));
exp -= 1;
power = power / base;
}
Expand Down Expand Up @@ -169,8 +259,6 @@ pub fn rep_to_digit_exponent_pairs(rep: &str) -> Vec<(String, isize)> {

#[cfg(test)]
mod tests {
use std::str::FromStr;

use super::*;

#[test]
Expand Down
103 changes: 31 additions & 72 deletions src/components/home_inputs.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
use bigdecimal::BigDecimal;
use core::str::FromStr;
use leptos::{html::*, *};
use web_sys::Event;

use crate::components::value_in_base::value_in_base;
use crate::bases::BaseConversion;

use super::value_in_base::value_in_base;

#[component]
pub fn HomeInputs(
base10_representation: Memo<Result<String, String>>,
output_representation: Memo<Result<String, String>>,
#[prop(into)] input_string: Signal<String>,
base_conversion: Memo<BaseConversion>,
#[prop(into)] set_input_string: WriteSignal<String>,
#[prop(into)] input_base: Signal<BigDecimal>,
#[prop(into)] set_input_base: WriteSignal<BigDecimal>,
#[prop(into)] output_base: Signal<BigDecimal>,
#[prop(into)] set_output_base: WriteSignal<BigDecimal>,
#[prop(into)] set_input_base_string: WriteSignal<String>,
#[prop(into)] set_output_base_string: WriteSignal<String>,
) -> impl IntoView {
move || {
table()
Expand All @@ -23,16 +20,18 @@ pub fn HomeInputs(
thead().child(
tr().child(th().child("Value in Base-10:"))
.child(th().child(value_in_base(
base10_representation,
(|| BigDecimal::from(10)).into(),
create_memo(move |_| base_conversion().base_10_string()),
create_memo(move |_| BigDecimal::from(10)),
))),
),
)
.child(
tfoot().child(
tr().child(th().child("Output Value:"))
.child(th().child(value_in_base(output_representation, output_base))),
),
tfoot().child(tr().child(th().child("Output Value:")).child(th().child(
value_in_base(
create_memo(move |_| base_conversion().output_string()),
create_memo(move |_| base_conversion().output_base),
),
))),
)
.child(
tbody()
Expand All @@ -45,31 +44,38 @@ pub fn HomeInputs(
input()
.id("InputValue")
.style("width", move || {
format!("{}ch", input_string().chars().count().max(18) + 2)
format!(
"{}ch",
2 + base_conversion()
.input_string
.chars()
.count()
.max(18)
)
})
.attr("type", "text")
.attr("value", input_string)
.attr("value", move || base_conversion().input_string)
.on(ev::input, move |ev| {
set_input_string(event_target_value(&ev))
}),
),
),
)
.child(
tr().attr("title", move || input_base().to_string())
tr().attr("title", move || base_conversion().input_base.to_string())
.child(td().child(label().attr("for", "InputBase").child("Input Base")))
.child(
td().child(
input()
.id("InputBase")
.attr("type", "text")
.attr("value", move || input_base().to_string())
.on(ev::input, update_base(set_input_base)),
.attr("value", move || base_conversion().input_base_string)
.on(ev::input, update_base(set_input_base_string)),
),
),
)
.child(
tr().attr("title", move || output_base().to_string())
tr().attr("title", move || base_conversion().output_base.to_string())
.child(
td().child(label().attr("for", "OutputBase").child("Output Base")),
)
Expand All @@ -78,67 +84,20 @@ pub fn HomeInputs(
input()
.id("OutputBase")
.attr("type", "text")
.attr("value", move || output_base().to_string())
.on(ev::input, update_base(set_output_base)),
.attr("value", move || base_conversion().output_base_string)
.on(ev::input, update_base(set_output_base_string)),
),
),
),
)
}
}

fn val_from_popular_strings(s: &str) -> Option<BigDecimal> {
match s.to_lowercase().as_str() {
"phi" => Some((BigDecimal::from(5).sqrt().unwrap() + 1) / 2),
"φ" => Some((BigDecimal::from(5).sqrt().unwrap() + 1) / 2),
"pi" => Some(
BigDecimal::from_str("3.14159265358979323846264338327950288419716939937510").unwrap(),
),
"π" => Some(
BigDecimal::from_str("3.14159265358979323846264338327950288419716939937510").unwrap(),
),
"e" => Some(
BigDecimal::from_str("2.71828182845904523536028747135266249775724709369995").unwrap(),
),
"sqrt2" => Some(BigDecimal::from(2).sqrt().unwrap()),
"two" => Some(BigDecimal::from(2)),
"binary" => Some(BigDecimal::from(2)),
"three" => Some(BigDecimal::from(3)),
"ternary" => Some(BigDecimal::from(3)),
"four" => Some(BigDecimal::from(4)),
"quaternary" => Some(BigDecimal::from(4)),
"five" => Some(BigDecimal::from(5)),
"quinary" => Some(BigDecimal::from(5)),
"six" => Some(BigDecimal::from(6)),
"senary" => Some(BigDecimal::from(6)),
"octal" => Some(BigDecimal::from(8)),
"eight" => Some(BigDecimal::from(8)),
"ten" => Some(BigDecimal::from(10)),
"decimal" => Some(BigDecimal::from(10)),
"twelve" => Some(BigDecimal::from(12)),
"duodecimal" => Some(BigDecimal::from(12)),
"dozenal" => Some(BigDecimal::from(12)),
"sixteen" => Some(BigDecimal::from(16)),
"hex" => Some(BigDecimal::from(16)),
"twenty" => Some(BigDecimal::from(20)),
"vigesimal" => Some(BigDecimal::from(20)),
"sixty" => Some(BigDecimal::from(60)),
"sexagesimal" => Some(BigDecimal::from(60)),
_ => None,
}
}

fn update_base<SF>(setter: SF) -> impl Fn(Event)
where
SF: Fn(BigDecimal),
SF: Fn(String),
{
move |ev| {
let s = event_target_value(&ev);
if let Some(n) = BigDecimal::from_str(&s)
.ok()
.or_else(|| val_from_popular_strings(&s))
{
setter(n)
}
setter(event_target_value(&ev));
}
}
18 changes: 9 additions & 9 deletions src/components/output_details.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
mod content;

use bigdecimal::BigDecimal;
use content::content;
use leptos::{html::*, *};

use crate::bases::BaseConversion;

#[derive(Clone, Debug)]
enum OpenState {
Open,
Closed,
}

#[component]
pub fn OutputDetails(
output: Memo<Result<String, String>>,
#[prop(into)] base: Signal<BigDecimal>,
) -> impl IntoView {
pub fn OutputDetails(base_conversion: Memo<BaseConversion>) -> impl IntoView {
let (is_open, set_is_open) = create_signal(OpenState::Closed);
let close = move || set_is_open(OpenState::Closed);
let open = move |_| set_is_open(OpenState::Open);

move || match (output(), is_open()) {
(Ok(o), OpenState::Open) => {
div().child(content(create_memo(move |_| o.clone()), base, close))
}
move || match (base_conversion().output_string(), is_open()) {
(Ok(o), OpenState::Open) => div().child(content(
create_memo(move |_| o.clone()),
create_memo(move |_| base_conversion().output_base).into(),
close,
)),
(Ok(_), OpenState::Closed) => {
div().child(button().on(ev::click, open).child("Show Output Details"))
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/value_in_base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use leptos::{html::*, *};

use crate::bases::rounded_string;

pub fn value_in_base(val: Memo<Result<String, String>>, base: Signal<BigDecimal>) -> impl IntoView {
pub fn value_in_base(val: Memo<Result<String, String>>, base: Memo<BigDecimal>) -> impl IntoView {
move || {
div().classes("value").child(match val() {
Ok(v) => span().child(
Expand Down
Loading

0 comments on commit 8a8164f

Please sign in to comment.