Number bases

This commit is contained in:
PaddiM8 2021-12-30 02:05:04 +01:00
parent 179579de61
commit cb36f69260
6 changed files with 130 additions and 31 deletions

View File

@ -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))
}

View File

@ -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;

View File

@ -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::*;

View File

@ -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
View 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);
}

View File

@ -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