mirror of
https://github.com/PaddiM8/kalker.git
synced 2024-12-12 17:40:52 +01:00
Number bases
This commit is contained in:
parent
179579de61
commit
cb36f69260
@ -175,9 +175,11 @@ impl<'a> Lexer<'a> {
|
||||
}
|
||||
|
||||
fn next_number_literal(&mut self) -> Token {
|
||||
let start = self.index;
|
||||
let mut start = self.index;
|
||||
let mut end = start;
|
||||
let mut value = String::new();
|
||||
let mut leading_zero = self.peek().unwrap_or(&'\0') == &'0';
|
||||
let mut base = 10u32;
|
||||
|
||||
loop {
|
||||
let c = if let Some(c) = self.peek() {
|
||||
@ -186,7 +188,30 @@ impl<'a> Lexer<'a> {
|
||||
break;
|
||||
};
|
||||
|
||||
if !c.is_digit(10) && c != '.' && !c.is_whitespace() || c == '\n' || c == '\r' {
|
||||
// If at the second character and
|
||||
// the first character is a zero,
|
||||
// allow a letter
|
||||
if end - start == 1 && leading_zero {
|
||||
base = match c {
|
||||
'b' => 2,
|
||||
'o' => 8,
|
||||
'x' => 16,
|
||||
_ => 10,
|
||||
};
|
||||
|
||||
// Don't include eg. 0x in the value
|
||||
start += 2;
|
||||
end += 1;
|
||||
self.advance();
|
||||
value.clear();
|
||||
leading_zero = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if !c.is_digit(base) && c != '.' && c != '_' && !c.is_whitespace()
|
||||
|| c == '\n'
|
||||
|| c == '\r'
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
@ -195,6 +220,23 @@ impl<'a> Lexer<'a> {
|
||||
self.advance();
|
||||
}
|
||||
|
||||
// Subscript unicode symbols after the literal, eg. 11₂
|
||||
let mut base_str = String::new();
|
||||
while crate::text_utils::is_subscript(self.peek().unwrap_or(&'\0')) {
|
||||
base_str.push(*self.peek().unwrap());
|
||||
self.advance();
|
||||
}
|
||||
|
||||
if base_str != "" {
|
||||
base = crate::text_utils::subscript_to_digits(base_str.chars())
|
||||
.parse::<u32>()
|
||||
.unwrap_or(10);
|
||||
}
|
||||
|
||||
if base != 10 {
|
||||
value.push_str(&format!("_{}", base));
|
||||
}
|
||||
|
||||
build(TokenKind::Literal, &value, (start, end))
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ pub mod kalk_num;
|
||||
mod lexer;
|
||||
pub mod parser;
|
||||
mod prelude;
|
||||
mod radix;
|
||||
mod symbol_table;
|
||||
mod test_helpers;
|
||||
pub mod text_utils;
|
||||
|
@ -114,7 +114,7 @@ pub enum CalcError {
|
||||
UnableToSolveEquation,
|
||||
UnableToOverrideConstant(String),
|
||||
UnableToParseExpression,
|
||||
UnrecognizedLogBase,
|
||||
UnrecognizedBase,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
@ -143,7 +143,7 @@ impl ToString for CalcError {
|
||||
CalcError::UnableToParseExpression => format!("Unable to parse expression."),
|
||||
CalcError::UnableToSolveEquation => format!("Unable to solve equation."),
|
||||
CalcError::UnableToOverrideConstant(name) => format!("Unable to override constant: '{}'.", name),
|
||||
CalcError::UnrecognizedLogBase => format!("Unrecognized log base."),
|
||||
CalcError::UnrecognizedBase => format!("Unrecognized base."),
|
||||
CalcError::Unknown => format!("Unknown error."),
|
||||
}
|
||||
}
|
||||
@ -596,7 +596,7 @@ fn parse_identifier(context: &mut Context) -> Result<Expr, CalcError> {
|
||||
if identifier.full_name.starts_with("log") {
|
||||
if let Some(c) = identifier.full_name.chars().nth(3) {
|
||||
if crate::text_utils::is_subscript(&c) {
|
||||
log_base = Some(parse_log_base(&identifier)?);
|
||||
log_base = Some(Expr::Literal(get_base(&identifier.full_name)? as f64));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -739,15 +739,6 @@ fn parse_identifier(context: &mut Context) -> Result<Expr, CalcError> {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_log_base(identifier: &Identifier) -> Result<Expr, CalcError> {
|
||||
let subscript = identifier.full_name.chars().skip(3);
|
||||
if let Some(base) = crate::text_utils::parse_subscript(subscript) {
|
||||
Ok(Expr::Literal(base))
|
||||
} else {
|
||||
Err(CalcError::UnrecognizedLogBase)
|
||||
}
|
||||
}
|
||||
|
||||
fn split_into_variables(context: &mut Context, identifier: &Identifier) -> Result<Expr, CalcError> {
|
||||
let mut chars: Vec<char> = identifier.pure_name.chars().collect();
|
||||
let mut left = Expr::Var(Identifier::from_full_name(&chars[0].to_string()));
|
||||
@ -864,13 +855,29 @@ fn is_at_end(context: &Context) -> bool {
|
||||
}
|
||||
|
||||
fn string_to_num(value: &str) -> Result<f64, CalcError> {
|
||||
if let Ok(result) = value.replace(" ", "").parse::<f64>() {
|
||||
let base = get_base(value)?;
|
||||
if let Some(result) = crate::radix::parse_float_radix(value.replace(" ", ""), base) {
|
||||
Ok(result)
|
||||
} else {
|
||||
Err(CalcError::InvalidNumberLiteral(value.into()))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_base(value: &str) -> Result<u8, CalcError> {
|
||||
let underscore_pos = if let Some(i) = value.find('_') {
|
||||
i
|
||||
} else {
|
||||
return Ok(10);
|
||||
};
|
||||
|
||||
let subscript = value.chars().skip(underscore_pos + 1usize);
|
||||
if let Some(base) = crate::text_utils::parse_subscript(subscript) {
|
||||
Ok(base)
|
||||
} else {
|
||||
Err(CalcError::UnrecognizedBase)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -503,12 +503,18 @@ pub mod funcs {
|
||||
pub fn gcd(x: KalkNum, y: KalkNum) -> KalkNum {
|
||||
// Find the norm of a Gaussian integer
|
||||
fn norm(x: KalkNum) -> KalkNum {
|
||||
KalkNum::new((x.value.clone() * x.value) + (x.imaginary_value.clone() * x.imaginary_value), &x.unit)
|
||||
KalkNum::new(
|
||||
(x.value.clone() * x.value) + (x.imaginary_value.clone() * x.imaginary_value),
|
||||
&x.unit,
|
||||
)
|
||||
}
|
||||
|
||||
if x.has_imaginary() || y.has_imaginary() {
|
||||
if x.value.clone().fract() != 0f64 || y.value.clone().fract() != 0f64
|
||||
|| x.imaginary_value.clone().fract() != 0f64 || y.imaginary_value.clone().fract() != 0f64 {
|
||||
if x.value.clone().fract() != 0f64
|
||||
|| y.value.clone().fract() != 0f64
|
||||
|| x.imaginary_value.clone().fract() != 0f64
|
||||
|| y.imaginary_value.clone().fract() != 0f64
|
||||
{
|
||||
// Not a Gaussian integer!
|
||||
// TODO: throw an actual error instead of returning NaN
|
||||
return KalkNum::from(f64::NAN);
|
||||
@ -539,7 +545,10 @@ pub mod funcs {
|
||||
}
|
||||
} else {
|
||||
if x.value < 0f64 || y.value < 0f64 {
|
||||
return gcd(KalkNum::new(x.value.abs(), &x.unit), KalkNum::new(y.value.abs(), &y.unit));
|
||||
return gcd(
|
||||
KalkNum::new(x.value.abs(), &x.unit),
|
||||
KalkNum::new(y.value.abs(), &y.unit),
|
||||
);
|
||||
}
|
||||
|
||||
// Euclidean GCD algorithm, but with modulus
|
||||
@ -747,20 +756,32 @@ mod tests {
|
||||
#[test]
|
||||
fn test_binary_funcs() {
|
||||
let in_out = vec![
|
||||
(gcd as fn(KalkNum, KalkNum) -> KalkNum, ((12f64, 0f64), (18f64, 0f64)), ( 6f64, 0f64)),
|
||||
(gcd, ((30f64, 0f64), (18f64, 0f64)), ( 6f64, 0f64)),
|
||||
(gcd, (( 5f64, 0f64), ( 2f64, 1f64)), ( 2f64, 1f64)),
|
||||
(gcd, ((18f64, 4f64), (30f64, 0f64)), ( 4f64, 2f64)),
|
||||
(gcd, (( 3f64, 1f64), ( 1f64, -1f64)), ( 1f64, -1f64)),
|
||||
(gcd, ((12f64, -8f64), ( 6f64, 4f64)), ( 2f64, 0f64)),
|
||||
(lcm, ((12f64, -8f64), ( 6f64, 4f64)), (52f64, 0f64)),
|
||||
(lcm, (( 1f64, -2f64), ( 3f64, 1f64)), ( 5f64, -5f64)),
|
||||
(
|
||||
gcd as fn(KalkNum, KalkNum) -> KalkNum,
|
||||
((12f64, 0f64), (18f64, 0f64)),
|
||||
(6f64, 0f64),
|
||||
),
|
||||
(gcd, ((30f64, 0f64), (18f64, 0f64)), (6f64, 0f64)),
|
||||
(gcd, ((5f64, 0f64), (2f64, 1f64)), (2f64, 1f64)),
|
||||
(gcd, ((18f64, 4f64), (30f64, 0f64)), (4f64, 2f64)),
|
||||
(gcd, ((3f64, 1f64), (1f64, -1f64)), (1f64, -1f64)),
|
||||
(gcd, ((12f64, -8f64), (6f64, 4f64)), (2f64, 0f64)),
|
||||
(lcm, ((12f64, -8f64), (6f64, 4f64)), (52f64, 0f64)),
|
||||
(lcm, ((1f64, -2f64), (3f64, 1f64)), (5f64, -5f64)),
|
||||
];
|
||||
|
||||
for (i, (func, input, expected_output)) in in_out.iter().enumerate() {
|
||||
let actual_output = func(
|
||||
KalkNum::new_with_imaginary(KalkNum::from(input.0.0).value, "", KalkNum::from(input.0.1).value),
|
||||
KalkNum::new_with_imaginary(KalkNum::from(input.1.0).value, "", KalkNum::from(input.1.1).value),
|
||||
KalkNum::new_with_imaginary(
|
||||
KalkNum::from(input.0 .0).value,
|
||||
"",
|
||||
KalkNum::from(input.0 .1).value,
|
||||
),
|
||||
KalkNum::new_with_imaginary(
|
||||
KalkNum::from(input.1 .0).value,
|
||||
"",
|
||||
KalkNum::from(input.1 .1).value,
|
||||
),
|
||||
);
|
||||
|
||||
println!(
|
||||
|
28
kalk/src/radix.rs
Normal file
28
kalk/src/radix.rs
Normal file
@ -0,0 +1,28 @@
|
||||
pub fn parse_float_radix(value: String, radix: u8) -> Option<f64> {
|
||||
if radix == 10 {
|
||||
return if let Ok(result) = value.parse::<f64>() {
|
||||
Some(result)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
|
||||
let mut sum = 0f64;
|
||||
let length = value.find('_').unwrap_or(value.len());
|
||||
let mut i = (value.find('.').unwrap_or(length) - 1) as i32;
|
||||
for c in value.chars() {
|
||||
if c == '_' {
|
||||
break;
|
||||
}
|
||||
|
||||
if c == '.' {
|
||||
continue;
|
||||
}
|
||||
|
||||
let digit = c.to_digit(radix as u32)? as f64;
|
||||
sum += digit * (radix as f64).powi(i as i32);
|
||||
i -= 1;
|
||||
}
|
||||
|
||||
return Some(sum);
|
||||
}
|
@ -16,8 +16,8 @@ pub fn is_subscript(c: &char) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_subscript(chars: impl Iterator<Item = char>) -> Option<f64> {
|
||||
if let Ok(result) = subscript_to_digits(chars).parse::<f64>() {
|
||||
pub fn parse_subscript(chars: impl Iterator<Item = char>) -> Option<u8> {
|
||||
if let Ok(result) = subscript_to_digits(chars).parse::<u8>() {
|
||||
Some(result)
|
||||
} else {
|
||||
None
|
||||
|
Loading…
Reference in New Issue
Block a user