diff --git a/.ci/build.sh b/.ci/build.sh index 20c75afa..fb0e7e04 100755 --- a/.ci/build.sh +++ b/.ci/build.sh @@ -5,9 +5,7 @@ set -ex cargo build --verbose cargo test --verbose -if [ "$TRAVIS_RUST_VERSION" = "nightly" ] -then +if [[ $TRAVIS_RUST_VERSION == "nightly" ]]; then cargo build --verbose --features no_std cargo test --verbose --features no_std fi - diff --git a/Cargo.toml b/Cargo.toml index bf4e0dc2..2f2ab525 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,3 +75,6 @@ optional = true [target.'cfg(target_arch = "wasm32")'.dependencies] instant= { version = "0.1.4", features = ["wasm-bindgen"] } # WASM implementation of std::time::Instant + +[package.metadata.docs.rs] +features = [ "serde", "internals" ] diff --git a/LICENSE.txt b/LICENSE-APACHE.txt similarity index 89% rename from LICENSE.txt rename to LICENSE-APACHE.txt index 261eeb9e..d9a10c0d 100644 --- a/LICENSE.txt +++ b/LICENSE-APACHE.txt @@ -174,28 +174,3 @@ of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/LICENSE-MIT.txt b/LICENSE-MIT.txt new file mode 100644 index 00000000..31aa7938 --- /dev/null +++ b/LICENSE-MIT.txt @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 1f9f5bf8..88d26eae 100644 --- a/README.md +++ b/README.md @@ -64,3 +64,17 @@ Playground An [Online Playground](https://alvinhochun.github.io/rhai-demo/) is available with syntax-highlighting editor. Scripts can be evaluated directly from the editor. + +License +------- + +Licensed under either: + +* [Apache License, Version 2.0](https://github.com/jonathandturner/rhai/blob/master/LICENSE-APACHE.txt), or +* [MIT license](https://github.com/jonathandturner/rhai/blob/master/LICENSE-MIT.txt) + +at your option. + +Unless explicitly stated otherwise, any contribution intentionally submitted +for inclusion in this crate, as defined in the Apache-2.0 license, shall +be dual-licensed as above, without any additional terms or conditions. diff --git a/RELEASES.md b/RELEASES.md index 8ba7d09c..22aae65d 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -18,6 +18,7 @@ New features * Anonymous functions in the syntax of a closure, e.g. `|x, y, z| x + y - z`. * Custom syntax now works even without the `internals` feature. * Currying of function pointers is supported via the `curry` keyword. +* `Module::set_indexer_get_set_fn` is added as a shorthand of both `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn`. Breaking changes ---------------- diff --git a/src/any.rs b/src/any.rs index 7f637187..1d478e62 100644 --- a/src/any.rs +++ b/src/any.rs @@ -30,12 +30,24 @@ use crate::stdlib::time::Instant; #[cfg(target_arch = "wasm32")] use instant::Instant; +mod private { + use crate::fn_native::SendSync; + use crate::stdlib::any::Any; + + /// A sealed trait that prevents other crates from implementing [`Variant`]. + /// + /// [`Variant`]: super::Variant + pub trait Sealed {} + + impl Sealed for T {} +} + /// Trait to represent any type. /// /// Currently, `Variant` is not `Send` nor `Sync`, so it can practically be any type. /// Turn on the `sync` feature to restrict it to only types that implement `Send + Sync`. #[cfg(not(feature = "sync"))] -pub trait Variant: Any { +pub trait Variant: Any + private::Sealed { /// Convert this `Variant` trait object to `&dyn Any`. fn as_any(&self) -> &dyn Any; @@ -53,10 +65,6 @@ pub trait Variant: Any { /// Clone into `Dynamic`. fn clone_into_dynamic(&self) -> Dynamic; - - /// This trait may only be implemented by `rhai`. - #[doc(hidden)] - fn _closed(&self) -> _Private; } /// Trait to represent any type. @@ -64,7 +72,7 @@ pub trait Variant: Any { /// `From<_>` is implemented for `i64` (`i32` if `only_i32`), `f64` (if not `no_float`), /// `bool`, `String`, `char`, `Vec` (into `Array`) and `HashMap` (into `Map`). #[cfg(feature = "sync")] -pub trait Variant: Any + Send + Sync { +pub trait Variant: Any + Send + Sync + private::Sealed { /// Convert this `Variant` trait object to `&dyn Any`. fn as_any(&self) -> &dyn Any; @@ -82,10 +90,6 @@ pub trait Variant: Any + Send + Sync { /// Clone into `Dynamic`. fn clone_into_dynamic(&self) -> Dynamic; - - /// This trait may only be implemented by `rhai`. - #[doc(hidden)] - fn _closed(&self) -> _Private; } impl Variant for T { @@ -107,9 +111,6 @@ impl Variant for T { fn clone_into_dynamic(&self) -> Dynamic { Dynamic::from(self.clone()) } - fn _closed(&self) -> _Private { - _Private - } } impl dyn Variant { @@ -809,8 +810,3 @@ impl From> for Dynamic { Self(Union::FnPtr(value)) } } - -/// Private type which ensures that `rhai::Any` and `rhai::AnyExt` can only -/// be implemented by this crate. -#[doc(hidden)] -pub struct _Private; diff --git a/src/engine.rs b/src/engine.rs index cb7234d3..a852b3fa 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -11,7 +11,7 @@ use crate::parser::{Expr, FnAccess, ImmutableString, ReturnType, ScriptFnDef, St use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; use crate::result::EvalAltResult; use crate::scope::{EntryType as ScopeEntryType, Scope}; -use crate::syntax::{CustomSyntax, EvalContext, Expression}; +use crate::syntax::{CustomSyntax, EvalContext}; use crate::token::Position; use crate::utils::StaticVec; @@ -38,7 +38,12 @@ pub type Array = Vec; #[cfg(not(feature = "no_object"))] pub type Map = HashMap; -/// A stack of imported modules. +/// [INTERNALS] A stack of imported modules. +/// Exported under the `internals` feature only. +/// +/// ## WARNING +/// +/// This type is volatile and may change. pub type Imports<'a> = Vec<(Cow<'a, str>, Module)>; #[cfg(not(feature = "unchecked"))] @@ -189,12 +194,17 @@ impl> From for Target<'_> { } } -/// A type that holds all the current states of the Engine. +/// [INTERNALS] A type that holds all the current states of the Engine. +/// Exported under the `internals` feature only. /// /// # Safety /// /// This type uses some unsafe code, mainly for avoiding cloning of local variable names via /// direct lifetime casting. +/// +/// ## WARNING +/// +/// This type is volatile and may change. #[derive(Debug, Clone, Eq, PartialEq, Hash, Default)] pub struct State { /// Normally, access to variables are parsed with a relative offset into the scope to avoid a lookup. @@ -1020,7 +1030,7 @@ impl Engine { map.entry(index).or_insert(Default::default()).into() } else { let index = idx - .downcast_ref::() + .downcast_ref::() .ok_or_else(|| EvalAltResult::ErrorStringIndexExpr(idx_pos))?; map.get_mut(index.as_str()) @@ -1050,19 +1060,20 @@ impl Engine { } } + #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] _ => { - let type_name = self.map_type_name(val.type_name()); + let type_name = val.type_name(); let args = &mut [val, &mut idx]; self.exec_fn_call( state, lib, FN_IDX_GET, true, 0, args, is_ref, true, None, level, ) .map(|(v, _)| v.into()) - .map_err(|_| { - Box::new(EvalAltResult::ErrorIndexingType( - type_name.into(), - Position::none(), - )) + .map_err(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(_, _) => Box::new( + EvalAltResult::ErrorIndexingType(type_name.into(), Position::none()), + ), + _ => err, }) } diff --git a/src/error.rs b/src/error.rs index 86c49091..b6901c68 100644 --- a/src/error.rs +++ b/src/error.rs @@ -10,7 +10,12 @@ use crate::stdlib::{ string::{String, ToString}, }; -/// Error when tokenizing the script text. +/// [INTERNALS] Error encountered when tokenizing the script text. +/// Exported under the `internals` feature only. +/// +/// ## WARNING +/// +/// This type is volatile and may change. #[derive(Debug, Eq, PartialEq, Clone, Hash)] #[non_exhaustive] pub enum LexError { diff --git a/src/fn_native.rs b/src/fn_native.rs index fc58ef45..2224e2d5 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -10,7 +10,9 @@ use crate::token::{is_valid_identifier, Position}; use crate::utils::{ImmutableString, StaticVec}; use crate::Scope; -use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, mem, rc::Rc, string::String, sync::Arc}; +use crate::stdlib::{ + boxed::Box, convert::TryFrom, fmt, mem, rc::Rc, string::String, sync::Arc, vec::Vec, +}; /// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(feature = "sync")] diff --git a/src/lib.rs b/src/lib.rs index c2e868c0..bd3d7afe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -170,7 +170,7 @@ pub use token::{get_next_token, parse_string_literal, InputStream, Token, Tokeni #[cfg(feature = "internals")] #[deprecated(note = "this type is volatile and may change")] -pub use parser::{CustomExpr, Expr, ReturnType, ScriptFnDef, Stmt}; +pub use parser::{CustomExpr, Expr, FloatWrapper, ReturnType, ScriptFnDef, Stmt}; #[cfg(feature = "internals")] #[deprecated(note = "this type is volatile and may change")] diff --git a/src/module.rs b/src/module.rs index 7216c248..22632c7d 100644 --- a/src/module.rs +++ b/src/module.rs @@ -738,18 +738,18 @@ impl Module { /// }); /// assert!(module.contains_fn(hash)); /// ``` - pub fn set_indexer_set_fn( + pub fn set_indexer_set_fn( &mut self, - func: impl Fn(&mut A, B, A) -> FuncReturn<()> + SendSync + 'static, + func: impl Fn(&mut A, B, C) -> FuncReturn<()> + SendSync + 'static, ) -> u64 { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); - let c = mem::take(args[2]).cast::(); + let c = mem::take(args[2]).cast::(); let a = args[0].downcast_mut::().unwrap(); func(a, b, c).map(Dynamic::from) }; - let arg_types = [TypeId::of::(), TypeId::of::(), TypeId::of::()]; + let arg_types = [TypeId::of::(), TypeId::of::(), TypeId::of::()]; self.set_fn( FN_IDX_SET, Public, @@ -758,6 +758,40 @@ impl Module { ) } + /// Set a pair of Rust index getter and setter functions, returning both hash keys. + /// This is a shorthand for `set_indexer_get_fn` and `set_indexer_set_fn`. + /// + /// If there are similar existing Rust functions, they are replaced. + /// + /// # Examples + /// + /// ``` + /// use rhai::{Module, ImmutableString}; + /// + /// let mut module = Module::new(); + /// let (hash_get, hash_set) = module.set_indexer_get_set_fn( + /// |x: &mut i64, y: ImmutableString| { + /// Ok(*x + y.len() as i64) + /// }, + /// |x: &mut i64, y: ImmutableString, value: i64| { + /// *x = y.len() as i64 + value; + /// Ok(()) + /// } + /// ); + /// assert!(module.contains_fn(hash_get)); + /// assert!(module.contains_fn(hash_set)); + /// ``` + pub fn set_indexer_get_set_fn( + &mut self, + getter: impl Fn(&mut A, B) -> FuncReturn + SendSync + 'static, + setter: impl Fn(&mut A, B, T) -> FuncReturn<()> + SendSync + 'static, + ) -> (u64, u64) { + ( + self.set_indexer_get_fn(getter), + self.set_indexer_set_fn(setter), + ) + } + /// Set a Rust function taking four parameters into the module, returning a hash key. /// /// If there is a similar existing Rust function, it is replaced. @@ -1094,11 +1128,17 @@ impl Module { } } -/// A chain of module names to qualify a variable or function call. -/// A `u64` hash key is kept for quick search purposes. +/// [INTERNALS] A chain of module names to qualify a variable or function call. +/// Exported under the `internals` feature only. +/// +/// A `u64` hash key is cached for quick search purposes. /// /// A `StaticVec` is used because most module-level access contains only one level, /// and it is wasteful to always allocate a `Vec` with one element. +/// +/// ## WARNING +/// +/// This type is volatile and may change. #[derive(Clone, Eq, PartialEq, Default, Hash)] pub struct ModuleRef(StaticVec<(String, Position)>, Option); diff --git a/src/optimize.rs b/src/optimize.rs index eaa8aeb3..738ea300 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -456,7 +456,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // "xxx" in "xxxxx" (Expr::StringConstant(a), Expr::StringConstant(b)) => { state.set_dirty(); - if b.0.contains(a.0.as_ref()) { Expr::True(a.1) } else { Expr::False(a.1) } + if b.0.contains(a.0.as_str()) { Expr::True(a.1) } else { Expr::False(a.1) } } // 'x' in "xxxxx" (Expr::CharConstant(a), Expr::StringConstant(b)) => { @@ -560,7 +560,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { let has_script_fn = state.lib.iter_fn().find(|(_, _, _, f)| { if !f.is_script() { return false; } let fn_def = f.get_fn_def(); - &fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len()) + fn_def.name.as_str() == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len()) }).is_some(); #[cfg(feature = "no_function")] diff --git a/src/parser.rs b/src/parser.rs index b6a3fa08..da0409db 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -342,11 +342,16 @@ impl fmt::Display for FnAccess { } } -/// A scripted function definition. +/// [INTERNALS] A type containing information on a scripted function. +/// Exported under the `internals` feature only. +/// +/// ## WARNING +/// +/// This type is volatile and may change. #[derive(Debug, Clone, Hash)] pub struct ScriptFnDef { /// Function name. - pub name: String, + pub name: ImmutableString, /// Function access mode. pub access: FnAccess, /// Names of function parameters. @@ -376,7 +381,12 @@ impl fmt::Display for ScriptFnDef { } } -/// `return`/`throw` statement. +/// [INTERNALS] A type encapsulating the mode of a `return`/`throw` statement. +/// Exported under the `internals` feature only. +/// +/// ## WARNING +/// +/// This type is volatile and may change. #[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)] pub enum ReturnType { /// `return` statement. @@ -477,7 +487,8 @@ impl ParseSettings { } } -/// A statement. +/// [INTERNALS] A Rhai statement. +/// Exported under the `internals` feature only. /// /// Each variant is at most one pointer in size (for speed), /// with everything being allocated together in one single tuple. @@ -582,6 +593,12 @@ impl Stmt { } } +/// [INTERNALS] A type wrapping a custom syntax definition. +/// Exported under the `internals` feature only. +/// +/// ## WARNING +/// +/// This type is volatile and may change. #[derive(Clone)] pub struct CustomExpr(pub StaticVec, pub Shared); @@ -592,11 +609,20 @@ impl fmt::Debug for CustomExpr { } impl Hash for CustomExpr { - fn hash(&self, state: &mut H) { + fn hash(&self, state: &mut H) { self.0.hash(state); } } +/// [INTERNALS] A type wrapping a floating-point number. +/// Exported under the `internals` feature only. +/// +/// This type is mainly used to provide a standard `Hash` implementation +/// to floating-point numbers, allowing `Expr` to derive `Hash` automatically. +/// +/// ## WARNING +/// +/// This type is volatile and may change. #[cfg(not(feature = "no_float"))] #[derive(Debug, PartialEq, PartialOrd, Clone)] pub struct FloatWrapper(pub FLOAT, pub Position); @@ -609,10 +635,15 @@ impl Hash for FloatWrapper { } } -/// An expression. +/// [INTERNALS] An expression sub-tree. +/// Exported under the `internals` feature only. /// /// Each variant is at most one pointer in size (for speed), /// with everything being allocated together in one single tuple. +/// +/// ## WARNING +/// +/// This type is volatile and may change. #[derive(Debug, Clone, Hash)] pub enum Expr { /// Integer constant. @@ -2852,7 +2883,7 @@ fn parse_fn( let params = params.into_iter().map(|(p, _)| p).collect(); Ok(ScriptFnDef { - name, + name: name.into(), access, params, body, @@ -2940,7 +2971,7 @@ fn parse_anon_fn( let hash = s.finish(); // Create unique function name - let fn_name = format!("{}{}", FN_ANONYMOUS, hash); + let fn_name: ImmutableString = format!("{}{:16x}", FN_ANONYMOUS, hash).into(); let script = ScriptFnDef { name: fn_name.clone(), @@ -2950,7 +2981,7 @@ fn parse_anon_fn( pos: settings.pos, }; - let expr = Expr::FnPointer(Box::new((fn_name.into(), settings.pos))); + let expr = Expr::FnPointer(Box::new((fn_name, settings.pos))); Ok((expr, script)) } diff --git a/src/serde/de.rs b/src/serde/de.rs index 88533718..4ab9919a 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -162,10 +162,12 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { Union::Variant(value) if value.is::() => self.deserialize_i16(visitor), Union::Variant(value) if value.is::() => self.deserialize_i32(visitor), Union::Variant(value) if value.is::() => self.deserialize_i64(visitor), + Union::Variant(value) if value.is::() => self.deserialize_i128(visitor), Union::Variant(value) if value.is::() => self.deserialize_u8(visitor), Union::Variant(value) if value.is::() => self.deserialize_u16(visitor), Union::Variant(value) if value.is::() => self.deserialize_u32(visitor), Union::Variant(value) if value.is::() => self.deserialize_u64(visitor), + Union::Variant(value) if value.is::() => self.deserialize_u128(visitor), Union::Variant(_) => self.type_error(), } @@ -219,6 +221,18 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } } + fn deserialize_i128>(self, visitor: V) -> Result> { + if let Ok(v) = self.value.as_int() { + self.deserialize_int(v, visitor) + } else if cfg!(not(feature = "only_i32")) { + self.type_error() + } else { + self.value + .downcast_ref::() + .map_or_else(|| self.type_error(), |&x| visitor.visit_i128(x)) + } + } + fn deserialize_u8>(self, visitor: V) -> Result> { if let Ok(v) = self.value.as_int() { self.deserialize_int(v, visitor) @@ -259,6 +273,16 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { } } + fn deserialize_u128>(self, visitor: V) -> Result> { + if let Ok(v) = self.value.as_int() { + self.deserialize_int(v, visitor) + } else { + self.value + .downcast_ref::() + .map_or_else(|| self.type_error(), |&x| visitor.visit_u128(x)) + } + } + fn deserialize_f32>(self, visitor: V) -> Result> { #[cfg(not(feature = "no_float"))] return self diff --git a/src/serde/ser.rs b/src/serde/ser.rs index 181573b0..7c3f9321 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -150,6 +150,21 @@ impl Serializer for &mut DynamicSerializer { } } + fn serialize_i128(self, v: i128) -> Result> { + #[cfg(not(feature = "only_i32"))] + if v > i64::MAX as i128 { + return Ok(Dynamic::from(v)); + } else { + return self.serialize_i64(v as i64); + } + #[cfg(feature = "only_i32")] + if v > i32::MAX as i128 { + return Ok(Dynamic::from(v)); + } else { + return self.serialize_i32(v as i32); + } + } + fn serialize_u8(self, v: u8) -> Result> { #[cfg(not(feature = "only_i32"))] return self.serialize_i64(i64::from(v)); @@ -190,6 +205,21 @@ impl Serializer for &mut DynamicSerializer { } } + fn serialize_u128(self, v: u128) -> Result> { + #[cfg(not(feature = "only_i32"))] + if v > i64::MAX as u128 { + return Ok(Dynamic::from(v)); + } else { + return self.serialize_i64(v as i64); + } + #[cfg(feature = "only_i32")] + if v > i32::MAX as u128 { + return Ok(Dynamic::from(v)); + } else { + return self.serialize_i32(v as i32); + } + } + fn serialize_f32(self, v: f32) -> Result> { Ok(Dynamic::from(v)) } diff --git a/src/syntax.rs b/src/syntax.rs index 485e99ea..616833c1 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -12,7 +12,8 @@ use crate::token::{is_valid_identifier, Position, Token}; use crate::utils::StaticVec; use crate::stdlib::{ - fmt, + boxed::Box, + fmt, format, rc::Rc, string::{String, ToString}, sync::Arc, diff --git a/src/token.rs b/src/token.rs index f2719a7a..72e733d3 100644 --- a/src/token.rs +++ b/src/token.rs @@ -136,89 +136,181 @@ impl fmt::Debug for Position { } } -/// Tokens. +/// [INTERNALS] A Rhai language token. +/// Exported under the `internals` feature only. +/// +/// ## WARNING +/// +/// This type is volatile and may change. #[derive(Debug, PartialEq, Clone)] pub enum Token { + /// An `INT` constant. IntegerConstant(INT), + /// A `FLOAT` constaint. + /// + /// Never appears under the `no_float` feature. #[cfg(not(feature = "no_float"))] FloatConstant(FLOAT), + /// An identifier. Identifier(String), + /// A character constant. CharConstant(char), + /// A string constant. StringConstant(String), + /// `{` LeftBrace, + /// `}` RightBrace, + /// `(` LeftParen, + /// `)` RightParen, + /// `[` LeftBracket, + /// `]` RightBracket, + /// `+` Plus, + /// `+` (unary) UnaryPlus, + /// `-` Minus, + /// `-` (unary) UnaryMinus, + /// `*` Multiply, + /// `/` Divide, + /// `%` Modulo, + /// `~` PowerOf, + /// `<<` LeftShift, + /// `>>` RightShift, + /// `;` SemiColon, + /// `:` Colon, + /// `::` DoubleColon, + /// `,` Comma, + /// `.` Period, + /// `#{` MapStart, + /// `=` Equals, + /// `true` True, + /// `false` False, + /// `let` Let, + /// `const` Const, + /// `if` If, + /// `else` Else, + /// `while` While, + /// `loop` Loop, + /// `for` For, + /// `in` In, + /// `<` LessThan, + /// `>` GreaterThan, + /// `<=` LessThanEqualsTo, + /// `>=` GreaterThanEqualsTo, + /// `==` EqualsTo, + /// `!=` NotEqualsTo, + /// `!` Bang, + /// `|` Pipe, + /// `||` Or, + /// `^` XOr, + /// `&` Ampersand, + /// `&&` And, + /// `fn` + /// + /// Never appears under the `no_function` feature. #[cfg(not(feature = "no_function"))] Fn, + /// `continue` Continue, + /// `break` Break, + /// `return` Return, + /// `throw` Throw, + /// `+=` PlusAssign, + /// `-=` MinusAssign, + /// `*=` MultiplyAssign, + /// `/=` DivideAssign, + /// `<<=` LeftShiftAssign, + /// `>>=` RightShiftAssign, + /// `&=` AndAssign, + /// `|=` OrAssign, + /// `^=` XOrAssign, + /// `%=` ModuloAssign, + /// `~=` PowerOfAssign, + /// `private` + /// + /// Never appears under the `no_function` feature. #[cfg(not(feature = "no_function"))] Private, + /// `import` + /// + /// Never appears under the `no_module` feature. #[cfg(not(feature = "no_module"))] Import, + /// `export` + /// + /// Never appears under the `no_module` feature. #[cfg(not(feature = "no_module"))] Export, + /// `as` + /// + /// Never appears under the `no_module` feature. #[cfg(not(feature = "no_module"))] As, + /// A lexer error. LexError(Box), + /// A comment block. Comment(String), + /// A reserved symbol. Reserved(String), + /// A custom keyword. Custom(String), + /// End of the input stream. EOF, } @@ -566,7 +658,7 @@ impl Token { } } - /// Is this token a reserved keyword? + /// Is this token a reserved symbol? pub fn is_reserved(&self) -> bool { match self { Self::Reserved(_) => true, @@ -589,7 +681,12 @@ impl From for String { } } -/// State of the tokenizer. +/// [INTERNALS] State of the tokenizer. +/// Exported under the `internals` feature only. +/// +/// ## WARNING +/// +/// This type is volatile and may change. #[derive(Debug, Clone, Eq, PartialEq, Default)] pub struct TokenizeState { /// Maximum length of a string (0 = unlimited). @@ -604,7 +701,12 @@ pub struct TokenizeState { pub include_comments: bool, } -/// Trait that encapsulates a peekable character input stream. +/// [INTERNALS] Trait that encapsulates a peekable character input stream. +/// Exported under the `internals` feature only. +/// +/// ## WARNING +/// +/// This trait is volatile and may change. pub trait InputStream { /// Get the next character fn get_next(&mut self) -> Option; @@ -628,7 +730,12 @@ pub fn is_valid_identifier(name: impl Iterator) -> bool { first_alphabetic } -/// Parse a string literal wrapped by `enclosing_char`. +/// [INTERNALS] Parse a string literal wrapped by `enclosing_char`. +/// Exported under the `internals` feature only. +/// +/// ## WARNING +/// +/// This type is volatile and may change. pub fn parse_string_literal( stream: &mut impl InputStream, state: &mut TokenizeState, @@ -794,7 +901,12 @@ fn scan_comment( } } -/// Get the next token. +/// [INTERNALS] Get the next token from the `InputStream`. +/// Exported under the `internals` feature only. +/// +/// ## WARNING +/// +/// This type is volatile and may change. pub fn get_next_token( stream: &mut impl InputStream, state: &mut TokenizeState, @@ -810,6 +922,32 @@ pub fn get_next_token( result } +/// Test if the given character is a hex character. +fn is_hex_char(c: char) -> bool { + match c { + 'a'..='f' => true, + 'A'..='F' => true, + '0'..='9' => true, + _ => false, + } +} + +/// Test if the given character is an octal character. +fn is_octal_char(c: char) -> bool { + match c { + '0'..='7' => true, + _ => false, + } +} + +/// Test if the given character is a binary character. +fn is_binary_char(c: char) -> bool { + match c { + '0' | '1' => true, + _ => false, + } +} + /// Get the next token. fn get_next_token_inner( stream: &mut impl InputStream, @@ -872,18 +1010,9 @@ fn get_next_token_inner( eat_next(stream, pos); let valid = match ch { - 'x' | 'X' => [ - 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_', - ], - 'o' | 'O' => [ - '0', '1', '2', '3', '4', '5', '6', '7', '_', '_', '_', '_', - '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', - ], - 'b' | 'B' => [ - '0', '1', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', - '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', - ], + 'x' | 'X' => is_hex_char, + 'o' | 'O' => is_octal_char, + 'b' | 'B' => is_binary_char, _ => unreachable!(), }; @@ -895,7 +1024,7 @@ fn get_next_token_inner( }); while let Some(next_char_in_escape_seq) = stream.peek_next() { - if !valid.contains(&next_char_in_escape_seq) { + if !valid(next_char_in_escape_seq) { break; } diff --git a/src/utils.rs b/src/utils.rs index a6eff859..760c809e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -92,9 +92,12 @@ pub fn calc_fn_spec<'a>( s.finish() } -/// A type to hold a number of values in static storage for no-allocation, quick access. +/// [INTERNALS] An array-like type that holds a number of values in static storage for no-allocation, quick access. +/// Exported under the `internals` feature only. +/// /// If too many items are stored, it converts into using a `Vec`. /// +/// /// This is essentially a knock-off of the [`staticvec`](https://crates.io/crates/staticvec) crate. /// This simplified implementation here is to avoid pulling in another crate. /// @@ -130,6 +133,10 @@ pub fn calc_fn_spec<'a>( /// # Safety /// /// This type uses some unsafe code (mainly for uninitialized/unused array slots) for efficiency. +/// +/// ## WARNING +/// +/// This type is volatile and may change. // // TODO - remove unsafe code pub struct StaticVec { diff --git a/tests/functions.rs b/tests/functions.rs index 3c0a080b..78d20fa3 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -104,5 +104,19 @@ fn test_function_pointers() -> Result<(), Box> { 42 ); + #[cfg(not(feature = "no_object"))] + assert_eq!( + engine.eval::( + r#" + fn foo(x) { this.data += x; } + + let x = #{ data: 40, action: Fn("foo") }; + x.action(2); + x.data + "# + )?, + 42 + ); + Ok(()) } diff --git a/tests/number_literals.rs b/tests/number_literals.rs index 699de19a..ce19b52d 100644 --- a/tests/number_literals.rs +++ b/tests/number_literals.rs @@ -14,6 +14,7 @@ fn test_hex_literal() -> Result<(), Box> { let engine = Engine::new(); assert_eq!(engine.eval::("let x = 0xf; x")?, 15); + assert_eq!(engine.eval::("let x = 0Xf; x")?, 15); assert_eq!(engine.eval::("let x = 0xff; x")?, 255); Ok(()) @@ -24,6 +25,7 @@ fn test_octal_literal() -> Result<(), Box> { let engine = Engine::new(); assert_eq!(engine.eval::("let x = 0o77; x")?, 63); + assert_eq!(engine.eval::("let x = 0O77; x")?, 63); assert_eq!(engine.eval::("let x = 0o1234; x")?, 668); Ok(()) @@ -34,6 +36,7 @@ fn test_binary_literal() -> Result<(), Box> { let engine = Engine::new(); assert_eq!(engine.eval::("let x = 0b1111; x")?, 15); + assert_eq!(engine.eval::("let x = 0B1111; x")?, 15); assert_eq!( engine.eval::("let x = 0b0011_1100_1010_0101; x")?, 15525