diff --git a/Cargo.lock b/Cargo.lock index 9d5686a..9e0316c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -536,6 +536,7 @@ dependencies = [ "bevy_egui", "chrono", "console_error_panic_hook", + "kinded", "logos", "tracing-log 0.2.0", "tracing-subscriber", @@ -1422,6 +1423,15 @@ dependencies = [ "const_soft_float", ] +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -2313,6 +2323,27 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "kinded" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce4bdbb2f423660b19f0e9f7115182214732d8dd5f840cd0a3aee3e22562f34c" +dependencies = [ + "kinded_macros", +] + +[[package]] +name = "kinded_macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13b4ddc5dcb32f45dac3d6f606da2a52fdb9964a18427e63cd5ef6c0d13288d" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn 2.0.41", +] + [[package]] name = "ktx2" version = "0.3.0" @@ -3586,6 +3617,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "unicode-width" version = "0.1.11" diff --git a/Cargo.toml b/Cargo.toml index beb3694..56f5aea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" [features] default = ["builtin-parser"] -builtin-parser = ["dep:logos"] +builtin-parser = ["dep:logos", "dep:kinded"] [dependencies] bevy = "0.12.1" @@ -20,6 +20,7 @@ web-time = "0.2.4" # built-in parser logos = { version = "0.13.0", optional = true } +kinded = { version = "0.3.0", optional = true } [target.'cfg(target_os = "android")'.dependencies] android_log-sys = "0.3.0" diff --git a/src/builtin_parser.rs b/src/builtin_parser.rs index 9726416..2e225cd 100644 --- a/src/builtin_parser.rs +++ b/src/builtin_parser.rs @@ -14,6 +14,7 @@ use self::{lexer::TokenStream, parser::parse}; pub(crate) mod lexer; pub(crate) mod parser; pub(crate) mod runner; +pub(crate) mod number; pub use runner::{environment::Environment, error::RunError, unique_rc::*, Value}; diff --git a/src/builtin_parser/lexer.rs b/src/builtin_parser/lexer.rs index ac6f808..90b8a6f 100644 --- a/src/builtin_parser/lexer.rs +++ b/src/builtin_parser/lexer.rs @@ -67,8 +67,22 @@ pub enum Token { #[regex("[a-zA-Z_][a-zA-Z0-9_]*")] Identifer, - #[regex("[0-9]+")] - Number, + #[regex(r#"[0-9]+"#)] + IntegerNumber, + #[regex(r#"[0-9]*\.?[0-9]*"#)] + FloatNumber, + + #[token("i8")] + #[token("i16")] + #[token("i32")] + #[token("i64")] + #[token("u8")] + #[token("u16")] + #[token("u32")] + #[token("u64")] + #[token("f32")] + #[token("f64")] + NumberType, } /// A wrapper for the lexer which provides token peeking and other helper functions @@ -138,7 +152,7 @@ impl<'a> TokenStream<'a> { } /// Get a [`str`] slice of the next [`Token`]. - pub fn _peek_slice(&self) -> &str { + pub fn peek_slice(&self) -> &str { self.lexer.slice() } @@ -171,7 +185,7 @@ mod tests { #[test] fn var_assign() { - let mut lexer = TokenStream::new("x = 1 + 2 - 30"); + let mut lexer = TokenStream::new("x = 1 + 2 - 30.6"); assert_eq!(lexer.next(), Some(Ok(Token::Identifer))); assert_eq!(lexer.slice(), "x"); @@ -179,19 +193,19 @@ mod tests { assert_eq!(lexer.next(), Some(Ok(Token::Equals))); assert_eq!(lexer.slice(), "="); - assert_eq!(lexer.next(), Some(Ok(Token::Number))); + assert_eq!(lexer.next(), Some(Ok(Token::IntegerNumber))); assert_eq!(lexer.slice(), "1"); assert_eq!(lexer.next(), Some(Ok(Token::Plus))); assert_eq!(lexer.slice(), "+"); - assert_eq!(lexer.next(), Some(Ok(Token::Number))); + assert_eq!(lexer.next(), Some(Ok(Token::IntegerNumber))); assert_eq!(lexer.slice(), "2"); assert_eq!(lexer.next(), Some(Ok(Token::Minus))); assert_eq!(lexer.slice(), "-"); - assert_eq!(lexer.next(), Some(Ok(Token::Number))); - assert_eq!(lexer.slice(), "30"); + assert_eq!(lexer.next(), Some(Ok(Token::FloatNumber))); + assert_eq!(lexer.slice(), "30.6"); } } diff --git a/src/builtin_parser/number.rs b/src/builtin_parser/number.rs new file mode 100644 index 0000000..46f33fa --- /dev/null +++ b/src/builtin_parser/number.rs @@ -0,0 +1,187 @@ +#![allow(non_camel_case_types)] + +use std::{fmt::Display, ops::*, str::FromStr}; + +use bevy::reflect::Reflect; +use logos::Span; + +use super::{RunError, Spanned}; + +#[derive(Debug, Clone, Copy)] +pub enum Number { + /// Generic integer that can get downcasted. + Integer(i128), + /// Generic float that can get downcasted to a [`f64`] and [`f32`] + Float(f64), + + u8(u8), + u16(u16), + u32(u32), + u64(u64), + i8(i8), + i16(i16), + i32(i32), + i64(i64), + f32(f32), + f64(f64), +} + +impl Number { + pub fn reflect(self) -> Box { + match self { + Number::u8(number) => Box::new(number), + Number::u16(number) => Box::new(number), + Number::u32(number) => Box::new(number), + Number::u64(number) => Box::new(number), + Number::i8(number) => Box::new(number), + Number::i16(number) => Box::new(number), + Number::i32(number) => Box::new(number), + Number::i64(number) => Box::new(number), + Number::f32(number) => Box::new(number), + Number::f64(number) => Box::new(number), + Number::Integer(_) => todo!(), + Number::Float(_) => todo!(), + } + } + + pub fn kind(&self) -> &'static str { + match self { + Number::Float(_) => "{float}", + Number::Integer(_) => "{integer}", + Number::u8(_) => "u8", + Number::u16(_) => "u16", + Number::u32(_) => "u32", + Number::u64(_) => "u64", + Number::i8(_) => "i8", + Number::i16(_) => "i16", + Number::i32(_) => "i32", + Number::i64(_) => "i64", + Number::f32(_) => "f32", + Number::f64(_) => "f64", + } + } +} + +impl Display for Number { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Number::Float(number) => write!(f, "{number} (float)"), + Number::Integer(number) => write!(f, "{number} (integer)"), + Number::u8(number) => write!(f, "{number} (u8)"), + Number::u16(number) => write!(f, "{number} (u16)"), + Number::u32(number) => write!(f, "{number} (u32)"), + Number::u64(number) => write!(f, "{number} (u64)"), + Number::i8(number) => write!(f, "{number} (i8)"), + Number::i16(number) => write!(f, "{number} (i16)"), + Number::i32(number) => write!(f, "{number} (i32)"), + Number::i64(number) => write!(f, "{number} (i64)"), + Number::f32(number) => write!(f, "{number} (f32)"), + Number::f64(number) => write!(f, "{number} (f64)"), + } + } +} + +macro_rules! impl_op { + ($trait:ident, $fn:ident, $op:tt) => { + impl $trait for Number { + type Output = Result; + fn $fn(self, rhs: Number) -> Self::Output { + match (self, rhs) { + (Number::u8(left), Number::u8(right)) => Ok(Number::u8(left $op right)), + (Number::u16(left), Number::u16(right)) => Ok(Number::u16(left $op right)), + (Number::u32(left), Number::u32(right)) => Ok(Number::u32(left $op right)), + (Number::u64(left), Number::u64(right)) => Ok(Number::u64(left $op right)), + (Number::i8(left), Number::i8(right)) => Ok(Number::i8(left $op right)), + (Number::i16(left), Number::i16(right)) => Ok(Number::i16(left $op right)), + (Number::i32(left), Number::i32(right)) => Ok(Number::i32(left $op right)), + (Number::i64(left), Number::i64(right)) => Ok(Number::i64(left $op right)), + (Number::f32(left), Number::f32(right)) => Ok(Number::f32(left $op right)), + (Number::f64(left), Number::f64(right)) => Ok(Number::f64(left $op right)), + + (Number::Integer(left), Number::u8(right)) => Ok(Number::u8(left as u8 $op right)), + (Number::Integer(left), Number::u16(right)) => Ok(Number::u16(left as u16 $op right)), + (Number::Integer(left), Number::u32(right)) => Ok(Number::u32(left as u32 $op right)), + (Number::Integer(left), Number::u64(right)) => Ok(Number::u64(left as u64 $op right)), + (Number::Integer(left), Number::i8(right)) => Ok(Number::i8(left as i8 $op right)), + (Number::Integer(left), Number::i16(right)) => Ok(Number::i16(left as i16 $op right)), + (Number::Integer(left), Number::i32(right)) => Ok(Number::i32(left as i32 $op right)), + (Number::Integer(left), Number::i64(right)) => Ok(Number::i64(left as i64 $op right)), + (Number::Integer(left), Number::Integer(right)) => Ok(Number::Integer(left $op right)), + (Number::u8(left), Number::Integer(right)) => Ok(Number::u8(left $op right as u8)), + (Number::u16(left), Number::Integer(right)) => Ok(Number::u16(left $op right as u16)), + (Number::u32(left), Number::Integer(right)) => Ok(Number::u32(left $op right as u32)), + (Number::u64(left), Number::Integer(right)) => Ok(Number::u64(left $op right as u64)), + (Number::i8(left), Number::Integer(right)) => Ok(Number::i8(left $op right as i8)), + (Number::i16(left), Number::Integer(right)) => Ok(Number::i16(left $op right as i16)), + (Number::i32(left), Number::Integer(right)) => Ok(Number::i32(left $op right as i32)), + (Number::i64(left), Number::Integer(right)) => Ok(Number::i64(left $op right as i64)), + + (Number::Float(left), Number::f32(right)) => Ok(Number::f32(left as f32 $op right)), + (Number::Float(left), Number::f64(right)) => Ok(Number::f64(left as f64 $op right)), + (Number::Float(left), Number::Float(right)) => Ok(Number::Float(left $op right)), + (Number::f32(left), Number::Float(right)) => Ok(Number::f32(left $op right as f32)), + (Number::f64(left), Number::Float(right)) => Ok(Number::f64(left $op right as f64)), + _ => todo!() + } + } + } + }; +} + +impl_op!(Add, add, +); +impl_op!(Sub, sub, -); +impl_op!(Mul, mul, *); +impl_op!(Div, div, /); +impl_op!(Rem, rem, %); + +impl Number { + pub fn neg(self, span: Span) -> Result { + match self { + Number::u8(_) => Err(RunError::CannotNegateUnsignedInteger(Spanned { + span, + value: self, + })), + Number::u16(_) => Err(RunError::CannotNegateUnsignedInteger(Spanned { + span, + value: self, + })), + Number::u32(_) => Err(RunError::CannotNegateUnsignedInteger(Spanned { + span, + value: self, + })), + Number::u64(_) => Err(RunError::CannotNegateUnsignedInteger(Spanned { + span, + value: self, + })), + Number::i8(number) => Ok(Number::i8(-number)), + Number::i16(number) => Ok(Number::i16(-number)), + Number::i32(number) => Ok(Number::i32(-number)), + Number::i64(number) => Ok(Number::i64(-number)), + Number::f32(number) => Ok(Number::f32(-number)), + Number::f64(number) => Ok(Number::f64(-number)), + Number::Float(number) => Ok(Number::Float(-number)), + Number::Integer(number) => Ok(Number::Integer(-number)), + } + } +} + +macro_rules! from_primitive { + ($primitive:ident) => { + impl From<$primitive> for Number { + fn from(value: $primitive) -> Self { + Number::$primitive(value) + } + } + }; +} + +from_primitive!(u8); +from_primitive!(u16); +from_primitive!(u32); +from_primitive!(u64); +from_primitive!(i8); +from_primitive!(i16); +from_primitive!(i32); +from_primitive!(i64); +from_primitive!(f32); +from_primitive!(f64); diff --git a/src/builtin_parser/parser.rs b/src/builtin_parser/parser.rs index 01ecdd8..c574b76 100644 --- a/src/builtin_parser/parser.rs +++ b/src/builtin_parser/parser.rs @@ -1,10 +1,11 @@ //! Generates an abstract syntax tree from a list of tokens. use logos::Span; -use std::collections::HashMap; +use std::{collections::HashMap, num::IntErrorKind}; use super::{ lexer::{FailedToLexCharacter, Token, TokenStream}, + number::Number, runner::environment::Function, Environment, Spanned, }; @@ -39,7 +40,7 @@ pub enum Expression { name: Box>, value: Box>, }, - Number(f64), + Number(Number), Variable(String), BinaryOp { left: Box>, @@ -92,6 +93,9 @@ pub enum ParseError { }, ExpectedEndline(Spanned), UnexpectedToken(Spanned), + InvalidSuffixForDecimal(std::ops::Range), + NegativeIntOverflow(Span), + PositiveIntOverflow(Span), } pub fn parse(tokens: &mut TokenStream, environment: &Environment) -> Result { @@ -129,7 +133,14 @@ fn parse_expression( } let loop_count = match parse_additive(tokens, environment)?.value { - Expression::Number(number) => number as u64, + Expression::Number(Number::u8(number)) => number as u64, + Expression::Number(Number::u16(number)) => number as u64, + Expression::Number(Number::u32(number)) => number as u64, + Expression::Number(Number::u64(number)) => number, + Expression::Number(Number::i8(number)) => number as u64, + Expression::Number(Number::i16(number)) => number as u64, + Expression::Number(Number::i32(number)) => number as u64, + Expression::Number(Number::i64(number)) => number as u64, t => todo!("{t:?}"), }; @@ -296,14 +307,79 @@ fn parse_primary( let expr = parse_primary(tokens, environment)?; Ok(tokens.wrap_span(Expression::Dereference(Box::new(expr)))) } - Some(Ok(Token::Number)) => { - Ok(tokens.wrap_span(Expression::Number(tokens.slice().parse().unwrap()))) + Some(Ok(Token::IntegerNumber)) => { + if let Some(Ok(Token::NumberType)) = tokens.peek() { + let err_map = |error: std::num::ParseIntError| match error.kind() { + IntErrorKind::PosOverflow => ParseError::PositiveIntOverflow(tokens.span()), + IntErrorKind::NegOverflow => ParseError::NegativeIntOverflow(tokens.span()), + _ => unreachable!("lexer makes sure other errors arent possible") + }; + let number: Number = match tokens.peek_slice() { + "u8" => Number::u8(tokens.slice().parse().map_err(err_map)?), + "u16" => Number::u16(tokens.slice().parse().map_err(err_map)?), + "u32" => Number::u32(tokens.slice().parse().map_err(err_map)?), + "u64" => Number::u64(tokens.slice().parse().map_err(err_map)?), + "i8" => Number::i8(tokens.slice().parse().map_err(err_map)?), + "i16" => Number::i16(tokens.slice().parse().map_err(err_map)?), + "i32" => Number::i32(tokens.slice().parse().map_err(err_map)?), + "i64" => Number::i64(tokens.slice().parse().map_err(err_map)?), + "f32" => Number::f32(tokens.slice().parse().unwrap()), + "f64" => Number::f64(tokens.slice().parse().unwrap()), + _ => unreachable!(), + }; + let start_span = tokens.span().end; + tokens.next(); + + Ok(Spanned { + span: start_span..tokens.span().end, + value: Expression::Number(number), + }) + } else { + let number = Number::Integer(tokens.slice().parse().unwrap()); + + Ok(Spanned { + span: tokens.span(), + value: Expression::Number(number), + }) + } + } + Some(Ok(Token::FloatNumber)) => { + if let Some(Ok(Token::NumberType)) = tokens.peek() { + let number: Number = match tokens.peek_slice() { + "u8" => Err(ParseError::InvalidSuffixForDecimal(tokens.span()))?, + "u16" => Err(ParseError::InvalidSuffixForDecimal(tokens.span()))?, + "u32" => Err(ParseError::InvalidSuffixForDecimal(tokens.span()))?, + "u64" => Err(ParseError::InvalidSuffixForDecimal(tokens.span()))?, + "i8" => Err(ParseError::InvalidSuffixForDecimal(tokens.span()))?, + "i16" => Err(ParseError::InvalidSuffixForDecimal(tokens.span()))?, + "i32" => Err(ParseError::InvalidSuffixForDecimal(tokens.span()))?, + "i64" => Err(ParseError::InvalidSuffixForDecimal(tokens.span()))?, + "f32" => Number::f32(tokens.slice().parse().unwrap()), + "f64" => Number::f64(tokens.slice().parse().unwrap()), + _ => unreachable!(), + }; + let start_span = tokens.span().end; + + tokens.next(); + + Ok(Spanned { + span: start_span..tokens.span().end, + value: Expression::Number(number), + }) + } else { + let number = Number::Float(tokens.slice().parse().unwrap()); + + Ok(Spanned { + span: tokens.span(), + value: Expression::Number(number), + }) + } } Some(Ok(Token::True)) => Ok(tokens.wrap_span(Expression::Boolean(true))), Some(Ok(Token::False)) => Ok(tokens.wrap_span(Expression::Boolean(false))), Some(Ok(token)) => Err(ParseError::UnexpectedToken(tokens.wrap_span(token))), Some(Err(FailedToLexCharacter)) => Err(ParseError::FailedToLexCharacter(tokens.span())), - None => unreachable!("oh fuck what have i done to cause this to happen"), + None => todo!(), }?; // If theres a dot after the expression, do a member expression: while let Some(Ok(Token::Dot)) = tokens.peek() { diff --git a/src/builtin_parser/runner.rs b/src/builtin_parser/runner.rs index c2ae2a6..a2e53dc 100644 --- a/src/builtin_parser/runner.rs +++ b/src/builtin_parser/runner.rs @@ -291,11 +291,11 @@ fn eval_expression( match (left, right) { (Value::Number(left), Value::Number(right)) => Ok(Value::Number(match operator { - Operator::Add => left + right, - Operator::Sub => left - right, - Operator::Mul => left * right, - Operator::Div => left / right, - Operator::Mod => left % right, + Operator::Add => (left + right)?, + Operator::Sub => (left - right)?, + Operator::Mul => (left * right)?, + Operator::Div => (left / right)?, + Operator::Mod => (left % right)?, })), (left, right) => todo!("{left:#?}, {right:#?}"), } @@ -325,12 +325,13 @@ fn eval_expression( }, )?; - match value { - Value::Number(number) => Ok(Value::Number(-number)), - _ => Err(RunError::ExpectedNumberAfterUnaryOperator(Spanned { + if let Value::Number(number) = value { + Ok(Value::Number(number.neg(span)?)) + } else { + Err(RunError::ExpectedNumberAfterUnaryOperator(Spanned { span, value, - })), + })) } } Expression::StructObject { name, map } => { diff --git a/src/builtin_parser/runner/error.rs b/src/builtin_parser/runner/error.rs index 166730a..0d05178 100644 --- a/src/builtin_parser/runner/error.rs +++ b/src/builtin_parser/runner/error.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use logos::Span; use crate::{ - builtin_parser::Spanned, + builtin_parser::{number::Number, Spanned}, command::{CommandHint, CommandHintColor}, }; @@ -37,6 +37,7 @@ pub enum RunError { span: Span, }, CannotMoveOutOfResource(Spanned), + CannotNegateUnsignedInteger(Spanned), } impl RunError { @@ -57,12 +58,13 @@ impl RunError { IncompatibleReflectTypes { span, .. } => vec![span.clone()], EnumVariantNotFound { span, .. } => vec![span.clone()], CannotMoveOutOfResource(Spanned { span, .. }) => vec![span.clone()], + CannotNegateUnsignedInteger(Spanned { span, .. }) => vec![span.clone()] } } pub fn hints(&self) -> Vec { self.spans() .into_iter() - .map(|span| CommandHint::new(span, CommandHintColor::Error, "todo")) + .map(|span| CommandHint::new(span, CommandHintColor::Error, self.message())) .collect() } pub fn message(&self) -> Cow<'static, str> { @@ -92,6 +94,11 @@ impl RunError { CannotMoveOutOfResource(Spanned { value, .. }) => { format!("Cannot move out of resource `{value}`, try borrowing it instead.").into() } + CannotNegateUnsignedInteger(Spanned { value, .. }) => format!( + "Unsigned integers cannot be negated. (Type: {})", + value.kind() + ) + .into(), } } } diff --git a/src/builtin_parser/runner/value.rs b/src/builtin_parser/runner/value.rs index 558f89e..6c1d5ee 100644 --- a/src/builtin_parser/runner/value.rs +++ b/src/builtin_parser/runner/value.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::fmt::Debug; use std::{cell::RefCell, rc::Rc}; +use crate::builtin_parser::number::Number; use crate::builtin_parser::{Environment, StrongRef}; use super::environment::FunctionParam; @@ -22,8 +23,8 @@ use logos::Span; pub enum Value { /// Nothing at all None, - /// A number, for simplicity only f64s are used. (However this will probably change in the future) - Number(f64), + /// A number. + Number(Number), /// `true` or `false`. Thats it... Boolean(bool), /// A string... there isn't much to say about this one. @@ -54,7 +55,7 @@ impl Value { pub fn reflect(self) -> Box { match self { Value::None => Box::new(()), - Value::Number(number) => Box::new(number), + Value::Number(number) => number.reflect(), Value::Boolean(boolean) => Box::new(boolean), Value::String(string) => Box::new(string), Value::Reference(reference) => todo!(), @@ -241,11 +242,31 @@ impl From<()> for Value { Value::None } } -impl From for Value { - fn from(number: f64) -> Self { - Value::Number(number) - } +macro_rules! from_number { + ($($number:ident),*$(,)?) => { + $( + impl From<$number> for Value { + fn from(number: $number) -> Self { + Value::Number(Number::$number(number)) + } + } + )* + }; } + +from_number!( + u8, + u16, + u32, + u64, + i8, + i16, + i32, + i64, + f32, + f64, +); + impl From for Value { fn from(string: String) -> Self { Value::String(string) @@ -349,7 +370,27 @@ macro_rules! impl_function_param_for_value { } }; } -impl_function_param_for_value!(impl f64: Value::Number(number) => number); +macro_rules! impl_function_param_for_numbers { + ($($number:ident),*$(,)?) => { + $( + impl_function_param_for_value!(impl $number: Value::Number(Number::$number(number)) => number); + )* + }; +} + +impl_function_param_for_numbers!( + u8, + u16, + u32, + u64, + i8, + i16, + i32, + i64, + f32, + f64, +); + impl_function_param_for_value!(impl bool: Value::Boolean(boolean) => boolean); impl_function_param_for_value!(impl String: Value::String(string) => string); impl_function_param_for_value!(impl HashMap>>: Value::Object(object) => object);