Merge pull request #390 from schungx/master

Fix f32_feature + serde.
This commit is contained in:
Stephen Chung 2021-04-06 23:36:48 +08:00 committed by GitHub
commit cb596a0fc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 685 additions and 497 deletions

View File

@ -23,10 +23,10 @@ jobs:
- "--features sync" - "--features sync"
- "--features no_optimize" - "--features no_optimize"
- "--features no_float" - "--features no_float"
- "--features f32_float" - "--features f32_float,serde,metadata,internals"
- "--features decimal" - "--features decimal"
- "--features no_float,decimal" - "--features no_float,decimal"
- "--tests --features only_i32" - "--tests --features only_i32,serde,metadata,internals"
- "--features only_i64" - "--features only_i64"
- "--features no_index" - "--features no_index"
- "--features no_object" - "--features no_object"

View File

@ -1,7 +1,7 @@
Rhai Release Notes Rhai Release Notes
================== ==================
This version adds string interpolation. This version adds string interpolation with `` `... ${`` ... ``} ...` `` syntax.
Version 0.19.16 Version 0.19.16
=============== ===============

View File

@ -93,8 +93,8 @@ fn bench_parse_primes(bench: &mut Bencher) {
} }
} }
print("Total " + total_primes_found + " primes."); print(`Total ${total_primes_found} primes.`);
print("Run time = " + now.elapsed + " seconds."); print(`Run time = ${now.elapsed} seconds.`);
"#; "#;
let mut engine = Engine::new(); let mut engine = Engine::new();

View File

@ -40,9 +40,9 @@ fn main() {
loop { loop {
let x = get(); let x = get();
print("Script Read: " + x); print(`Script Read: ${x}`);
x += 1; x += 1;
print("Script Write: " + x); print(`Script Write: ${x}`);
put(x); put(x);
} }
"#, "#,

View File

@ -21,9 +21,9 @@ for n in range(0, 5) {
result = fib(target); result = fib(target);
} }
print("Finished. Run time = " + now.elapsed + " seconds."); print(`Finished. Run time = ${now.elapsed} seconds.`);
print("Fibonacci number #" + target + " = " + result); print(`Fibonacci number #${target} = ${result}`);
if result != 317_811 { if result != 317_811 {
print("The answer is WRONG! Should be 317,811!"); print("The answer is WRONG! Should be 317,811!");

View File

@ -4,7 +4,7 @@ let arr = [1, 2, 3, 4];
for a in arr { for a in arr {
for b in [10, 20] { for b in [10, 20] {
print(a + "," + b); print(`${a}, ${b}`);
} }
if a == 3 { break; } if a == 3 { break; }

View File

@ -1,6 +1,6 @@
const MAX = 1_000_000; const MAX = 1_000_000;
print("Iterating an array with " + MAX + " items..."); print(`Iterating an array with ${MAX} items...`);
print("Ready... Go!"); print("Ready... Go!");
@ -18,5 +18,5 @@ for i in list {
sum += i; sum += i;
} }
print("Sum = " + sum); print(`Sum = ${sum}`);
print("Finished. Run time = " + now.elapsed + " seconds."); print(`Finished. Run time = ${now.elapsed} seconds.`);

View File

@ -68,4 +68,4 @@ for i in range(0, SIZE) {
} }
*/ */
print("Finished. Run time = " + now.elapsed + " seconds."); print(`Finished. Run time = ${now.elapsed} seconds.`);

View File

@ -7,7 +7,7 @@ let last_value = ();
let obj1 = #{ let obj1 = #{
_data: 42, // data field _data: 42, // data field
get_data: || this._data, // property getter get_data: || this._data, // property getter
action: || print("Data=" + this._data), // method action: || print(`Data=${this._data}`), // method
update: |x| { // property setter update: |x| { // property setter
this._data = x; this._data = x;
last_value = this._data; // capture 'last_value' last_value = this._data; // capture 'last_value'
@ -38,4 +38,4 @@ if obj2.get_data() > 0 { // property access
obj2.update(42); // call method obj2.update(42); // call method
} }
print("Should be 84: " + last_value); print(`Should be 84: ${last_value}`);

View File

@ -25,8 +25,8 @@ for p in range(2, MAX_NUMBER_TO_CHECK) {
} }
} }
print("Total " + total_primes_found + " primes <= " + MAX_NUMBER_TO_CHECK); print(`Total ${total_primes_found} primes <= ${MAX_NUMBER_TO_CHECK}`);
print("Run time = " + now.elapsed + " seconds."); print(`Run time = ${now.elapsed} seconds.`);
if total_primes_found != 78_498 { if total_primes_found != 78_498 {
print("The answer is WRONG! Should be 78,498!"); print("The answer is WRONG! Should be 78,498!");

View File

@ -10,4 +10,4 @@ while x > 0 {
x -= 1; x -= 1;
} }
print("Finished. Run time = " + now.elapsed + " seconds."); print(`Finished. Run time = ${now.elapsed} seconds.`);

View File

@ -11,17 +11,18 @@ print("foo" >= "bar"); // string comparison
print("the answer is " + 42); // string building using non-string types print("the answer is " + 42); // string building using non-string types
let s = "\u2764 hello, world! \U0001F603"; // string variable let s = "\u2764 hello, world! \U0001F603"; // string variable
print("length=" + s.len); // should be 17 print(`length=${s.len}`); // should be 17
s[s.len-3] = '?'; // change the string s[s.len-3] = '?'; // change the string
print("Question: " + s); // should print 'Question: hello, world?' print(`Question: ${s}`); // should print 'Question: hello, world?'
// Line continuation: // Line continuation:
let s = "This is a long \ let s = "This is a long \
string constructed using \ string constructed using \
line continuation"; line continuation";
print("One string: " + s); // String interpolation
print(`One string: ${s}`);
// Multi-line literal string: // Multi-line literal string:
let s = ` let s = `

View File

@ -75,7 +75,7 @@ let keys = [];
for animal in animals { for animal in animals {
for adjective in adjectives { for adjective in adjectives {
for adverb in adverbs { for adverb in adverbs {
keys.push(adverb + " " + adjective + " " + animal) keys.push(`${adverb} ${adjective} ${animal}`)
} }
} }
} }
@ -99,5 +99,5 @@ for key in keys {
map.remove(key); map.remove(key);
} }
print("Sum = " + sum); print(`Sum = ${sum}`);
print("Finished. Run time = " + now.elapsed + " seconds."); print(`Finished. Run time = ${now.elapsed} seconds.`);

View File

@ -9,7 +9,7 @@ use crate::stdlib::{
fmt, fmt,
hash::Hash, hash::Hash,
iter::empty, iter::empty,
num::NonZeroUsize, num::{NonZeroU8, NonZeroUsize},
ops::{Add, AddAssign}, ops::{Add, AddAssign},
string::String, string::String,
vec, vec,
@ -25,6 +25,9 @@ use crate::{
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::{stdlib::str::FromStr, FLOAT}; use crate::{stdlib::str::FromStr, FLOAT};
#[cfg(not(feature = "no_float"))]
use num_traits::Float;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::Array; use crate::Array;
@ -384,7 +387,7 @@ impl AST {
/// "#)?; /// "#)?;
/// ///
/// let ast2 = engine.compile(r#" /// let ast2 = engine.compile(r#"
/// fn foo(n) { "hello" + n } /// fn foo(n) { `hello${n}` }
/// foo("!") /// foo("!")
/// "#)?; /// "#)?;
/// ///
@ -395,7 +398,7 @@ impl AST {
/// ///
/// // 'ast' is essentially: /// // 'ast' is essentially:
/// // /// //
/// // fn foo(n) { "hello" + n } // <- definition of first 'foo' is overwritten /// // fn foo(n) { `hello${n}` } // <- definition of first 'foo' is overwritten
/// // foo(1) // <- notice this will be "hello1" instead of 43, /// // foo(1) // <- notice this will be "hello1" instead of 43,
/// // // but it is no longer the return value /// // // but it is no longer the return value
/// // foo("!") // returns "hello!" /// // foo("!") // returns "hello!"
@ -436,7 +439,7 @@ impl AST {
/// "#)?; /// "#)?;
/// ///
/// let ast2 = engine.compile(r#" /// let ast2 = engine.compile(r#"
/// fn foo(n) { "hello" + n } /// fn foo(n) { `hello${n}` }
/// foo("!") /// foo("!")
/// "#)?; /// "#)?;
/// ///
@ -447,7 +450,7 @@ impl AST {
/// ///
/// // 'ast1' is essentially: /// // 'ast1' is essentially:
/// // /// //
/// // fn foo(n) { "hello" + n } // <- definition of first 'foo' is overwritten /// // fn foo(n) { `hello${n}` } // <- definition of first 'foo' is overwritten
/// // foo(1) // <- notice this will be "hello1" instead of 43, /// // foo(1) // <- notice this will be "hello1" instead of 43,
/// // // but it is no longer the return value /// // // but it is no longer the return value
/// // foo("!") // returns "hello!" /// // foo("!") // returns "hello!"
@ -490,7 +493,7 @@ impl AST {
/// "#)?; /// "#)?;
/// ///
/// let ast2 = engine.compile(r#" /// let ast2 = engine.compile(r#"
/// fn foo(n) { "hello" + n } /// fn foo(n) { `hello${n}` }
/// fn error() { 0 } /// fn error() { 0 }
/// foo("!") /// foo("!")
/// "#)?; /// "#)?;
@ -574,7 +577,7 @@ impl AST {
/// "#)?; /// "#)?;
/// ///
/// let ast2 = engine.compile(r#" /// let ast2 = engine.compile(r#"
/// fn foo(n) { "hello" + n } /// fn foo(n) { `hello${n}` }
/// fn error() { 0 } /// fn error() { 0 }
/// foo("!") /// foo("!")
/// "#)?; /// "#)?;
@ -776,7 +779,7 @@ pub struct Ident {
impl fmt::Debug for Ident { impl fmt::Debug for Ident {
#[inline(always)] #[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Ident({:?} @ {:?})", self.name, self.pos) write!(f, "{:?} @ {:?}", self.name, self.pos)
} }
} }
@ -1440,13 +1443,13 @@ impl FnCallExpr {
} }
} }
/// A type that wraps a [`FLOAT`] and implements [`Hash`]. /// A type that wraps a floating-point number and implements [`Hash`].
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[derive(Clone, Copy, PartialEq, PartialOrd)] #[derive(Clone, Copy, PartialEq, PartialOrd)]
pub struct FloatWrapper(FLOAT); pub struct FloatWrapper<F>(F);
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl Hash for FloatWrapper { impl Hash for FloatWrapper<FLOAT> {
#[inline(always)] #[inline(always)]
fn hash<H: crate::stdlib::hash::Hasher>(&self, state: &mut H) { fn hash<H: crate::stdlib::hash::Hasher>(&self, state: &mut H) {
self.0.to_ne_bytes().hash(state); self.0.to_ne_bytes().hash(state);
@ -1454,24 +1457,24 @@ impl Hash for FloatWrapper {
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl AsRef<FLOAT> for FloatWrapper { impl<F: Float> AsRef<F> for FloatWrapper<F> {
#[inline(always)] #[inline(always)]
fn as_ref(&self) -> &FLOAT { fn as_ref(&self) -> &F {
&self.0 &self.0
} }
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl AsMut<FLOAT> for FloatWrapper { impl<F: Float> AsMut<F> for FloatWrapper<F> {
#[inline(always)] #[inline(always)]
fn as_mut(&mut self) -> &mut FLOAT { fn as_mut(&mut self) -> &mut F {
&mut self.0 &mut self.0
} }
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl crate::stdlib::ops::Deref for FloatWrapper { impl<F: Float> crate::stdlib::ops::Deref for FloatWrapper<F> {
type Target = FLOAT; type Target = F;
#[inline(always)] #[inline(always)]
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@ -1480,7 +1483,7 @@ impl crate::stdlib::ops::Deref for FloatWrapper {
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl crate::stdlib::ops::DerefMut for FloatWrapper { impl<F: Float> crate::stdlib::ops::DerefMut for FloatWrapper<F> {
#[inline(always)] #[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0 &mut self.0
@ -1488,52 +1491,68 @@ impl crate::stdlib::ops::DerefMut for FloatWrapper {
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl fmt::Debug for FloatWrapper { impl<F: Float + fmt::Display> fmt::Debug for FloatWrapper<F> {
#[inline(always)] #[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f) fmt::Display::fmt(&self.0, f)
} }
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl fmt::Display for FloatWrapper { impl<F: Float + fmt::Display + fmt::LowerExp + From<f32>> fmt::Display for FloatWrapper<F> {
#[inline(always)] #[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(feature = "no_std")]
#[cfg(not(feature = "no_float"))]
use num_traits::Float;
let abs = self.0.abs(); let abs = self.0.abs();
if abs > 10000000000000.0 || abs < 0.0000000000001 { if abs > Self::MAX_NATURAL_FLOAT_FOR_DISPLAY.into()
|| abs < Self::MIN_NATURAL_FLOAT_FOR_DISPLAY.into()
{
write!(f, "{:e}", self.0) write!(f, "{:e}", self.0)
} else { } else {
self.0.fmt(f) fmt::Display::fmt(&self.0, f)?;
if abs.fract().is_zero() {
f.write_str(".0")?;
}
Ok(())
} }
} }
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl From<FLOAT> for FloatWrapper { impl<F: Float> From<F> for FloatWrapper<F> {
#[inline(always)] #[inline(always)]
fn from(value: FLOAT) -> Self { fn from(value: F) -> Self {
Self::new(value) Self::new(value)
} }
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl FromStr for FloatWrapper { impl<F: Float + FromStr> FromStr for FloatWrapper<F> {
type Err = <FLOAT as FromStr>::Err; type Err = <F as FromStr>::Err;
#[inline(always)] #[inline(always)]
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
FLOAT::from_str(s).map(Into::<Self>::into) F::from_str(s).map(Into::<Self>::into)
} }
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl FloatWrapper { impl<F: Float> FloatWrapper<F> {
/// Maximum floating-point number for natural display before switching to scientific notation.
pub const MAX_NATURAL_FLOAT_FOR_DISPLAY: f32 = 10000000000000.0;
/// Minimum floating-point number for natural display before switching to scientific notation.
pub const MIN_NATURAL_FLOAT_FOR_DISPLAY: f32 = 0.0000000000001;
#[inline(always)] #[inline(always)]
pub const fn new(value: FLOAT) -> Self { pub fn new(value: F) -> Self {
Self(value)
}
}
#[cfg(not(feature = "no_float"))]
impl FloatWrapper<FLOAT> {
#[inline(always)]
pub(crate) const fn const_new(value: FLOAT) -> Self {
Self(value) Self(value)
} }
} }
@ -1544,7 +1563,7 @@ impl FloatWrapper {
/// # Volatile Data Structure /// # Volatile Data Structure
/// ///
/// This type is volatile and may change. /// This type is volatile and may change.
#[derive(Debug, Clone, Hash)] #[derive(Clone, Hash)]
pub enum Expr { pub enum Expr {
/// Dynamic constant. /// Dynamic constant.
/// Used to hold either an [`Array`] or [`Map`][crate::Map] literal for quick cloning. /// Used to hold either an [`Array`] or [`Map`][crate::Map] literal for quick cloning.
@ -1556,7 +1575,7 @@ pub enum Expr {
IntegerConstant(INT, Position), IntegerConstant(INT, Position),
/// Floating-point constant. /// Floating-point constant.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
FloatConstant(FloatWrapper, Position), FloatConstant(FloatWrapper<FLOAT>, Position),
/// Character constant. /// Character constant.
CharConstant(char, Position), CharConstant(char, Position),
/// [String][ImmutableString] constant. /// [String][ImmutableString] constant.
@ -1574,8 +1593,20 @@ pub enum Expr {
), ),
/// () /// ()
Unit(Position), Unit(Position),
/// Variable access - (optional index, optional (hash, modules), variable name) /// Variable access - optional short index, position, (optional index, optional (hash, modules), variable name)
Variable(Box<(Option<NonZeroUsize>, Option<(u64, NamespaceRef)>, Ident)>), ///
/// The short index is [`u8`] which is used when the index is <= 255, which should be the vast
/// majority of cases (unless there are more than 255 variables defined!).
/// This is to avoid reading a pointer redirection during each variable access.
Variable(
Option<NonZeroU8>,
Position,
Box<(
Option<NonZeroUsize>,
Option<(u64, NamespaceRef)>,
Identifier,
)>,
),
/// Property access - ((getter, hash), (setter, hash), prop) /// Property access - ((getter, hash), (setter, hash), prop)
Property(Box<((Identifier, u64), (Identifier, u64), Ident)>), Property(Box<((Identifier, u64), (Identifier, u64), Ident)>),
/// { [statement][Stmt] ... } /// { [statement][Stmt] ... }
@ -1601,6 +1632,80 @@ impl Default for Expr {
} }
} }
impl fmt::Debug for Expr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DynamicConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos),
Self::BoolConstant(value, pos) => write!(f, "{} @ {:?}", value, pos),
Self::IntegerConstant(value, pos) => write!(f, "{} @ {:?}", value, pos),
#[cfg(not(feature = "no_float"))]
Self::FloatConstant(value, pos) => write!(f, "{} @ {:?}", value, pos),
Self::CharConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos),
Self::StringConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos),
Self::FnPointer(value, pos) => write!(f, "Fn({:?}) @ {:?}", value, pos),
Self::Unit(pos) => write!(f, "() @ {:?}", pos),
Self::InterpolatedString(x) => {
f.write_str("InterpolatedString")?;
f.debug_list().entries(x.iter()).finish()
}
Self::Array(x, pos) => {
f.write_str("Array")?;
f.debug_list().entries(x.iter()).finish()?;
write!(f, " @ {:?}", pos)
}
Self::Map(x, pos) => {
f.write_str("Map")?;
f.debug_map()
.entries(x.0.iter().map(|(k, v)| (k, v)))
.finish()?;
write!(f, " @ {:?}", pos)
}
Self::Variable(i, pos, x) => {
f.write_str("Variable(")?;
match x.1 {
Some((_, ref namespace)) => write!(f, "{}::", namespace)?,
_ => (),
}
write!(f, "{}", x.2)?;
match i.map_or_else(|| x.0, |n| NonZeroUsize::new(n.get() as usize)) {
Some(n) => write!(f, " [{}]", n)?,
_ => (),
}
write!(f, ") @ {:?}", pos)
}
Self::Property(x) => write!(f, "Property({:?} @ {:?})", x.2.name, x.2.pos),
Self::Stmt(x) => {
f.write_str("Stmt")?;
f.debug_list().entries(x.statements.iter()).finish()?;
write!(f, " @ {:?}", x.pos)
}
Self::FnCall(x, pos) => {
f.debug_tuple("FnCall").field(x).finish()?;
write!(f, " @ {:?}", pos)
}
Self::Dot(x, pos) | Self::Index(x, pos) | Self::And(x, pos) | Self::Or(x, pos) => {
let op = match self {
Self::Dot(_, _) => "Dot",
Self::Index(_, _) => "Index",
Self::And(_, _) => "And",
Self::Or(_, _) => "Or",
_ => unreachable!(),
};
f.debug_struct(op)
.field("lhs", &x.lhs)
.field("rhs", &x.rhs)
.finish()?;
write!(f, " @ {:?}", pos)
}
Self::Custom(x, pos) => {
f.debug_tuple("Custom").field(x).finish()?;
write!(f, " @ {:?}", pos)
}
}
}
}
impl Expr { impl Expr {
/// Get the [`Dynamic`] value of a constant expression. /// Get the [`Dynamic`] value of a constant expression.
/// ///
@ -1640,11 +1745,19 @@ impl Expr {
_ => return None, _ => return None,
}) })
} }
/// Is the expression a simple variable access?
#[inline(always)]
pub(crate) fn is_variable_access(&self, non_qualified: bool) -> bool {
match self {
Self::Variable(_, _, x) => !non_qualified || x.1.is_none(),
_ => false,
}
}
/// Return the variable name if the expression a simple variable access. /// Return the variable name if the expression a simple variable access.
#[inline(always)] #[inline(always)]
pub(crate) fn get_variable_access(&self, non_qualified: bool) -> Option<&str> { pub(crate) fn get_variable_name(&self, non_qualified: bool) -> Option<&str> {
match self { match self {
Self::Variable(x) if !non_qualified || x.1.is_none() => Some((x.2).name.as_str()), Self::Variable(_, _, x) if !non_qualified || x.1.is_none() => Some(x.2.as_str()),
_ => None, _ => None,
} }
} }
@ -1666,7 +1779,7 @@ impl Expr {
Self::Map(_, pos) => *pos, Self::Map(_, pos) => *pos,
Self::Property(x) => (x.2).pos, Self::Property(x) => (x.2).pos,
Self::Stmt(x) => x.pos, Self::Stmt(x) => x.pos,
Self::Variable(x) => (x.2).pos, Self::Variable(_, pos, _) => *pos,
Self::FnCall(_, pos) => *pos, Self::FnCall(_, pos) => *pos,
Self::And(x, _) | Self::Or(x, _) => x.lhs.position(), Self::And(x, _) | Self::Or(x, _) => x.lhs.position(),
@ -1696,7 +1809,7 @@ impl Expr {
Self::FnPointer(_, pos) => *pos = new_pos, Self::FnPointer(_, pos) => *pos = new_pos,
Self::Array(_, pos) => *pos = new_pos, Self::Array(_, pos) => *pos = new_pos,
Self::Map(_, pos) => *pos = new_pos, Self::Map(_, pos) => *pos = new_pos,
Self::Variable(x) => (x.2).pos = new_pos, Self::Variable(_, pos, _) => *pos = new_pos,
Self::Property(x) => (x.2).pos = new_pos, Self::Property(x) => (x.2).pos = new_pos,
Self::Stmt(x) => x.pos = new_pos, Self::Stmt(x) => x.pos = new_pos,
Self::FnCall(_, pos) => *pos = new_pos, Self::FnCall(_, pos) => *pos = new_pos,
@ -1724,7 +1837,7 @@ impl Expr {
Self::Stmt(x) => x.statements.iter().all(Stmt::is_pure), Self::Stmt(x) => x.statements.iter().all(Stmt::is_pure),
Self::Variable(_) => true, Self::Variable(_, _, _) => true,
_ => self.is_constant(), _ => self.is_constant(),
} }
@ -1794,7 +1907,7 @@ impl Expr {
_ => false, _ => false,
}, },
Self::Variable(_) => match token { Self::Variable(_, _, _) => match token {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Token::LeftBracket => true, Token::LeftBracket => true,
Token::LeftParen => true, Token::LeftParen => true,
@ -1894,7 +2007,7 @@ mod tests {
assert_eq!(size_of::<Option<ast::Expr>>(), 16); assert_eq!(size_of::<Option<ast::Expr>>(), 16);
assert_eq!(size_of::<ast::Stmt>(), 32); assert_eq!(size_of::<ast::Stmt>(), 32);
assert_eq!(size_of::<Option<ast::Stmt>>(), 32); assert_eq!(size_of::<Option<ast::Stmt>>(), 32);
assert_eq!(size_of::<FnPtr>(), 80); assert_eq!(size_of::<FnPtr>(), 96);
assert_eq!(size_of::<Scope>(), 288); assert_eq!(size_of::<Scope>(), 288);
assert_eq!(size_of::<LexError>(), 56); assert_eq!(size_of::<LexError>(), 56);
assert_eq!(size_of::<ParseError>(), 16); assert_eq!(size_of::<ParseError>(), 16);

View File

@ -159,7 +159,7 @@ pub enum Union {
Int(INT, AccessMode), Int(INT, AccessMode),
/// A floating-point value. /// A floating-point value.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Float(FloatWrapper, AccessMode), Float(FloatWrapper<FLOAT>, AccessMode),
/// A fixed-precision decimal value. /// A fixed-precision decimal value.
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
Decimal(Box<Decimal>, AccessMode), Decimal(Box<Decimal>, AccessMode),
@ -682,16 +682,22 @@ impl Dynamic {
pub const NEGATIVE_ONE: Dynamic = Self(Union::Int(-1, AccessMode::ReadWrite)); pub const NEGATIVE_ONE: Dynamic = Self(Union::Int(-1, AccessMode::ReadWrite));
/// A [`Dynamic`] containing the floating-point zero. /// A [`Dynamic`] containing the floating-point zero.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub const FLOAT_ZERO: Dynamic = pub const FLOAT_ZERO: Dynamic = Self(Union::Float(
Self(Union::Float(FloatWrapper::new(0.0), AccessMode::ReadWrite)); FloatWrapper::const_new(0.0),
AccessMode::ReadWrite,
));
/// A [`Dynamic`] containing the floating-point one. /// A [`Dynamic`] containing the floating-point one.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub const FLOAT_ONE: Dynamic = pub const FLOAT_ONE: Dynamic = Self(Union::Float(
Self(Union::Float(FloatWrapper::new(1.0), AccessMode::ReadWrite)); FloatWrapper::const_new(1.0),
AccessMode::ReadWrite,
));
/// A [`Dynamic`] containing the floating-point negative one. /// A [`Dynamic`] containing the floating-point negative one.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub const FLOAT_NEGATIVE_ONE: Dynamic = pub const FLOAT_NEGATIVE_ONE: Dynamic = Self(Union::Float(
Self(Union::Float(FloatWrapper::new(-1.0), AccessMode::ReadWrite)); FloatWrapper::const_new(-1.0),
AccessMode::ReadWrite,
));
/// Get the [`AccessMode`] for this [`Dynamic`]. /// Get the [`AccessMode`] for this [`Dynamic`].
pub(crate) fn access_mode(&self) -> AccessMode { pub(crate) fn access_mode(&self) -> AccessMode {
@ -1670,9 +1676,9 @@ impl From<FLOAT> for Dynamic {
} }
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl From<FloatWrapper> for Dynamic { impl From<FloatWrapper<FLOAT>> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: FloatWrapper) -> Self { fn from(value: FloatWrapper<FLOAT>) -> Self {
Self(Union::Float(value, AccessMode::ReadWrite)) Self(Union::Float(value, AccessMode::ReadWrite))
} }
} }

View File

@ -52,7 +52,7 @@ pub type Precedence = NonZeroU8;
// We cannot use Cow<str> here because `eval` may load a [module][Module] and // We cannot use Cow<str> here because `eval` may load a [module][Module] and
// the module name will live beyond the AST of the eval script text. // the module name will live beyond the AST of the eval script text.
// The best we can do is a shared reference. // The best we can do is a shared reference.
#[derive(Debug, Clone, Default)] #[derive(Clone, Default)]
pub struct Imports(StaticVec<Identifier>, StaticVec<Shared<Module>>); pub struct Imports(StaticVec<Identifier>, StaticVec<Shared<Module>>);
impl Imports { impl Imports {
@ -147,6 +147,20 @@ impl Imports {
} }
} }
impl fmt::Debug for Imports {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Imports")?;
if self.is_empty() {
f.debug_map().finish()
} else {
f.debug_map()
.entries(self.0.iter().zip(self.1.iter()))
.finish()
}
}
}
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -959,9 +973,14 @@ impl Engine {
expr: &Expr, expr: &Expr,
) -> Result<(Target<'s>, Position), Box<EvalAltResult>> { ) -> Result<(Target<'s>, Position), Box<EvalAltResult>> {
match expr { match expr {
Expr::Variable(v) => match v.as_ref() { Expr::Variable(Some(_), _, _) => {
self.search_scope_only(scope, mods, state, lib, this_ptr, expr)
}
Expr::Variable(None, var_pos, v) => match v.as_ref() {
// Normal variable access
(_, None, _) => self.search_scope_only(scope, mods, state, lib, this_ptr, expr),
// Qualified variable // Qualified variable
(_, Some((hash_var, modules)), Ident { name, pos, .. }) => { (_, Some((hash_var, modules)), var_name) => {
let module = self.search_imports(mods, state, modules).ok_or_else(|| { let module = self.search_imports(mods, state, modules).ok_or_else(|| {
EvalAltResult::ErrorModuleNotFound( EvalAltResult::ErrorModuleNotFound(
modules[0].name.to_string(), modules[0].name.to_string(),
@ -971,20 +990,18 @@ impl Engine {
let target = module.get_qualified_var(*hash_var).map_err(|mut err| { let target = module.get_qualified_var(*hash_var).map_err(|mut err| {
match *err { match *err {
EvalAltResult::ErrorVariableNotFound(ref mut err_name, _) => { EvalAltResult::ErrorVariableNotFound(ref mut err_name, _) => {
*err_name = format!("{}{}", modules, name); *err_name = format!("{}{}", modules, var_name);
} }
_ => (), _ => (),
} }
err.fill_position(*pos) err.fill_position(*var_pos)
})?; })?;
// Module variables are constant // Module variables are constant
let mut target = target.clone(); let mut target = target.clone();
target.set_access_mode(AccessMode::ReadOnly); target.set_access_mode(AccessMode::ReadOnly);
Ok((target.into(), *pos)) Ok((target.into(), *var_pos))
} }
// Normal variable access
_ => self.search_scope_only(scope, mods, state, lib, this_ptr, expr),
}, },
_ => unreachable!("Expr::Variable expected, but gets {:?}", expr), _ => unreachable!("Expr::Variable expected, but gets {:?}", expr),
} }
@ -1000,26 +1017,25 @@ impl Engine {
this_ptr: &'s mut Option<&mut Dynamic>, this_ptr: &'s mut Option<&mut Dynamic>,
expr: &Expr, expr: &Expr,
) -> Result<(Target<'s>, Position), Box<EvalAltResult>> { ) -> Result<(Target<'s>, Position), Box<EvalAltResult>> {
let (index, _, Ident { name, pos, .. }) = match expr { // Make sure that the pointer indirection is taken only when absolutely necessary.
Expr::Variable(v) => v.as_ref(),
let (index, var_pos) = match expr {
// Check if the variable is `this`
Expr::Variable(None, pos, v) if v.0.is_none() && v.2 == KEYWORD_THIS => {
return if let Some(val) = this_ptr {
Ok(((*val).into(), *pos))
} else {
EvalAltResult::ErrorUnboundThis(*pos).into()
}
}
_ if state.always_search => (0, expr.position()),
Expr::Variable(Some(i), pos, _) => (i.get() as usize, *pos),
Expr::Variable(None, pos, v) => (v.0.map(NonZeroUsize::get).unwrap_or(0), *pos),
_ => unreachable!("Expr::Variable expected, but gets {:?}", expr), _ => unreachable!("Expr::Variable expected, but gets {:?}", expr),
}; };
// Check if the variable is `this`
if *name == KEYWORD_THIS {
return if let Some(val) = this_ptr {
Ok(((*val).into(), *pos))
} else {
EvalAltResult::ErrorUnboundThis(*pos).into()
};
}
// Check if it is directly indexed
let index = if state.always_search { &None } else { index };
// Check the variable resolver, if any // Check the variable resolver, if any
if let Some(ref resolve_var) = self.resolve_var { if let Some(ref resolve_var) = self.resolve_var {
let index = index.map(NonZeroUsize::get).unwrap_or(0);
let context = EvalContext { let context = EvalContext {
engine: self, engine: self,
scope, scope,
@ -1030,26 +1046,28 @@ impl Engine {
level: 0, level: 0,
}; };
if let Some(mut result) = if let Some(mut result) =
resolve_var(name, index, &context).map_err(|err| err.fill_position(*pos))? resolve_var(expr.get_variable_name(true).unwrap(), index, &context)
.map_err(|err| err.fill_position(var_pos))?
{ {
result.set_access_mode(AccessMode::ReadOnly); result.set_access_mode(AccessMode::ReadOnly);
return Ok((result.into(), *pos)); return Ok((result.into(), var_pos));
} }
} }
let index = if let Some(index) = index { let index = if index > 0 {
scope.len() - index.get() scope.len() - index
} else { } else {
// Find the variable in the scope // Find the variable in the scope
let var_name = expr.get_variable_name(true).unwrap();
scope scope
.get_index(name) .get_index(var_name)
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(name.to_string(), *pos))? .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(var_name.to_string(), var_pos))?
.0 .0
}; };
let val = scope.get_mut_by_index(index); let val = scope.get_mut_by_index(index);
Ok((val.into(), *pos)) Ok((val.into(), var_pos))
} }
/// Chain-evaluate a dot/index chain. /// Chain-evaluate a dot/index chain.
@ -1401,13 +1419,7 @@ impl Engine {
match lhs { match lhs {
// id.??? or id[???] // id.??? or id[???]
Expr::Variable(x) => { Expr::Variable(_, var_pos, x) => {
let Ident {
name: var_name,
pos: var_pos,
..
} = &x.2;
self.inc_operations(state, *var_pos)?; self.inc_operations(state, *var_pos)?;
let (target, pos) = let (target, pos) =
@ -1415,8 +1427,7 @@ impl Engine {
// Constants cannot be modified // Constants cannot be modified
if target.as_ref().is_read_only() && new_val.is_some() { if target.as_ref().is_read_only() && new_val.is_some() {
return EvalAltResult::ErrorAssignmentToConstant(var_name.to_string(), pos) return EvalAltResult::ErrorAssignmentToConstant(x.2.to_string(), pos).into();
.into();
} }
let obj_ptr = &mut target.into(); let obj_ptr = &mut target.into();
@ -1562,7 +1573,7 @@ impl Engine {
state: &mut State, state: &mut State,
_lib: &[&Module], _lib: &[&Module],
target: &'t mut Dynamic, target: &'t mut Dynamic,
mut idx: Dynamic, mut _idx: Dynamic,
idx_pos: Position, idx_pos: Position,
_create: bool, _create: bool,
_is_ref: bool, _is_ref: bool,
@ -1575,7 +1586,7 @@ impl Engine {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(arr, _)) => { Dynamic(Union::Array(arr, _)) => {
// val_array[idx] // val_array[idx]
let index = idx let index = _idx
.as_int() .as_int()
.map_err(|err| self.make_type_mismatch_err::<crate::INT>(err, idx_pos))?; .map_err(|err| self.make_type_mismatch_err::<crate::INT>(err, idx_pos))?;
@ -1595,8 +1606,8 @@ impl Engine {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Dynamic(Union::Map(map, _)) => { Dynamic(Union::Map(map, _)) => {
// val_map[idx] // val_map[idx]
let index = &*idx.read_lock::<ImmutableString>().ok_or_else(|| { let index = &*_idx.read_lock::<ImmutableString>().ok_or_else(|| {
self.make_type_mismatch_err::<ImmutableString>(idx.type_name(), idx_pos) self.make_type_mismatch_err::<ImmutableString>(_idx.type_name(), idx_pos)
})?; })?;
if _create && !map.contains_key(index.as_str()) { if _create && !map.contains_key(index.as_str()) {
@ -1613,7 +1624,7 @@ impl Engine {
Dynamic(Union::Str(s, _)) => { Dynamic(Union::Str(s, _)) => {
// val_string[idx] // val_string[idx]
let chars_len = s.chars().count(); let chars_len = s.chars().count();
let index = idx let index = _idx
.as_int() .as_int()
.map_err(|err| self.make_type_mismatch_err::<crate::INT>(err, idx_pos))?; .map_err(|err| self.make_type_mismatch_err::<crate::INT>(err, idx_pos))?;
@ -1631,7 +1642,7 @@ impl Engine {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
_ if _indexers => { _ if _indexers => {
let type_name = target.type_name(); let type_name = target.type_name();
let args = &mut [target, &mut idx]; let args = &mut [target, &mut _idx];
let hash_get = FnCallHash::from_native(calc_fn_hash(empty(), FN_IDX_GET, 2)); let hash_get = FnCallHash::from_native(calc_fn_hash(empty(), FN_IDX_GET, 2));
self.exec_fn_call( self.exec_fn_call(
_mods, state, _lib, FN_IDX_GET, hash_get, args, _is_ref, true, idx_pos, None, _mods, state, _lib, FN_IDX_GET, hash_get, args, _is_ref, true, idx_pos, None,
@ -1679,11 +1690,11 @@ impl Engine {
Expr::CharConstant(x, _) => Ok((*x).into()), Expr::CharConstant(x, _) => Ok((*x).into()),
Expr::FnPointer(x, _) => Ok(FnPtr::new_unchecked(x.clone(), Default::default()).into()), Expr::FnPointer(x, _) => Ok(FnPtr::new_unchecked(x.clone(), Default::default()).into()),
Expr::Variable(x) if (x.2).name == KEYWORD_THIS => this_ptr Expr::Variable(None, var_pos, x) if x.0.is_none() && x.2 == KEYWORD_THIS => this_ptr
.as_deref() .as_deref()
.cloned() .cloned()
.ok_or_else(|| EvalAltResult::ErrorUnboundThis((x.2).pos).into()), .ok_or_else(|| EvalAltResult::ErrorUnboundThis(*var_pos).into()),
Expr::Variable(_) => self Expr::Variable(_, _, _) => self
.search_namespace(scope, mods, state, lib, this_ptr, expr) .search_namespace(scope, mods, state, lib, this_ptr, expr)
.map(|(val, _)| val.take_or_clone()), .map(|(val, _)| val.take_or_clone()),
@ -2010,7 +2021,7 @@ impl Engine {
.flatten()), .flatten()),
// var op= rhs // var op= rhs
Stmt::Assignment(x, op_pos) if x.0.get_variable_access(false).is_some() => { Stmt::Assignment(x, op_pos) if x.0.is_variable_access(false) => {
let (lhs_expr, op_info, rhs_expr) = x.as_ref(); let (lhs_expr, op_info, rhs_expr) = x.as_ref();
let rhs_val = self let rhs_val = self
.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)? .eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?
@ -2020,7 +2031,7 @@ impl Engine {
if !lhs_ptr.is_ref() { if !lhs_ptr.is_ref() {
return EvalAltResult::ErrorAssignmentToConstant( return EvalAltResult::ErrorAssignmentToConstant(
lhs_expr.get_variable_access(false).unwrap().to_string(), lhs_expr.get_variable_name(false).unwrap().to_string(),
pos, pos,
) )
.into(); .into();
@ -2031,7 +2042,7 @@ impl Engine {
if lhs_ptr.as_ref().is_read_only() { if lhs_ptr.as_ref().is_read_only() {
// Assignment to constant variable // Assignment to constant variable
EvalAltResult::ErrorAssignmentToConstant( EvalAltResult::ErrorAssignmentToConstant(
lhs_expr.get_variable_access(false).unwrap().to_string(), lhs_expr.get_variable_name(false).unwrap().to_string(),
pos, pos,
) )
.into() .into()
@ -2061,7 +2072,7 @@ impl Engine {
// Must be either `var[index] op= val` or `var.prop op= val` // Must be either `var[index] op= val` or `var.prop op= val`
match lhs_expr { match lhs_expr {
// name op= rhs (handled above) // name op= rhs (handled above)
Expr::Variable(_) => { Expr::Variable(_, _, _) => {
unreachable!("Expr::Variable case should already been handled") unreachable!("Expr::Variable case should already been handled")
} }
// idx_lhs[idx_expr] op= rhs // idx_lhs[idx_expr] op= rhs

View File

@ -9,7 +9,6 @@ use crate::parser::ParseState;
use crate::stdlib::{ use crate::stdlib::{
any::{type_name, TypeId}, any::{type_name, TypeId},
boxed::Box, boxed::Box,
num::NonZeroUsize,
string::String, string::String,
}; };
use crate::{ use crate::{
@ -1158,16 +1157,8 @@ impl Engine {
scripts: &[&str], scripts: &[&str],
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> Result<AST, ParseError> { ) -> Result<AST, ParseError> {
let (stream, buffer) = self.lex_raw(scripts, None); let (stream, tokenizer_control) = self.lex_raw(scripts, None);
let mut state = ParseState::new( let mut state = ParseState::new(self, tokenizer_control);
self,
buffer,
#[cfg(not(feature = "unchecked"))]
NonZeroUsize::new(self.max_expr_depth()),
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_function"))]
NonZeroUsize::new(self.max_function_expr_depth()),
);
self.parse( self.parse(
&mut stream.peekable(), &mut stream.peekable(),
&mut state, &mut state,
@ -1347,7 +1338,7 @@ impl Engine {
.into()); .into());
}; };
let (stream, buffer) = self.lex_raw( let (stream, tokenizer_control) = self.lex_raw(
&scripts, &scripts,
Some(if has_null { Some(if has_null {
|token| match token { |token| match token {
@ -1360,15 +1351,7 @@ impl Engine {
}), }),
); );
let mut state = ParseState::new( let mut state = ParseState::new(self, tokenizer_control);
self,
buffer,
#[cfg(not(feature = "unchecked"))]
NonZeroUsize::new(self.max_expr_depth()),
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_function"))]
NonZeroUsize::new(self.max_function_expr_depth()),
);
let ast = self.parse_global_expr( let ast = self.parse_global_expr(
&mut stream.peekable(), &mut stream.peekable(),
@ -1454,18 +1437,10 @@ impl Engine {
script: &str, script: &str,
) -> Result<AST, ParseError> { ) -> Result<AST, ParseError> {
let scripts = [script]; let scripts = [script];
let (stream, buffer) = self.lex_raw(&scripts, None); let (stream, tokenizer_control) = self.lex_raw(&scripts, None);
let mut peekable = stream.peekable(); let mut peekable = stream.peekable();
let mut state = ParseState::new( let mut state = ParseState::new(self, tokenizer_control);
self,
buffer,
#[cfg(not(feature = "unchecked"))]
NonZeroUsize::new(self.max_expr_depth()),
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_function"))]
NonZeroUsize::new(self.max_function_expr_depth()),
);
self.parse_global_expr(&mut peekable, &mut state, scope, self.optimization_level) self.parse_global_expr(&mut peekable, &mut state, scope, self.optimization_level)
} }
/// Evaluate a script file. /// Evaluate a script file.
@ -1624,16 +1599,8 @@ impl Engine {
script: &str, script: &str,
) -> Result<T, Box<EvalAltResult>> { ) -> Result<T, Box<EvalAltResult>> {
let scripts = [script]; let scripts = [script];
let (stream, buffer) = self.lex_raw(&scripts, None); let (stream, tokenizer_control) = self.lex_raw(&scripts, None);
let mut state = ParseState::new( let mut state = ParseState::new(self, tokenizer_control);
self,
buffer,
#[cfg(not(feature = "unchecked"))]
NonZeroUsize::new(self.max_expr_depth()),
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_function"))]
NonZeroUsize::new(self.max_function_expr_depth()),
);
// No need to optimize a lone expression // No need to optimize a lone expression
let ast = self.parse_global_expr( let ast = self.parse_global_expr(
@ -1779,16 +1746,8 @@ impl Engine {
script: &str, script: &str,
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
let scripts = [script]; let scripts = [script];
let (stream, buffer) = self.lex_raw(&scripts, None); let (stream, tokenizer_control) = self.lex_raw(&scripts, None);
let mut state = ParseState::new( let mut state = ParseState::new(self, tokenizer_control);
self,
buffer,
#[cfg(not(feature = "unchecked"))]
NonZeroUsize::new(self.max_expr_depth()),
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_function"))]
NonZeroUsize::new(self.max_function_expr_depth()),
);
let ast = self.parse( let ast = self.parse(
&mut stream.peekable(), &mut stream.peekable(),

View File

@ -41,7 +41,7 @@ pub trait FuncArgs {
/// ///
/// let ast = engine.compile(r#" /// let ast = engine.compile(r#"
/// fn hello(x, y, z) { /// fn hello(x, y, z) {
/// if x { "hello " + y } else { y + z } /// if x { `hello ${y}` } else { y + z }
/// } /// }
/// "#)?; /// "#)?;
/// ///

View File

@ -750,7 +750,7 @@ impl Engine {
// Method call of script function - map first argument to `this` // Method call of script function - map first argument to `this`
let (first, rest) = args.split_first_mut().unwrap(); let (first, rest) = args.split_first_mut().unwrap();
let orig_source = mem::take(&mut state.source); let orig_source = state.source.take();
state.source = source; state.source = source;
let level = _level + 1; let level = _level + 1;
@ -780,7 +780,7 @@ impl Engine {
backup.as_mut().unwrap().change_first_arg_to_copy(args); backup.as_mut().unwrap().change_first_arg_to_copy(args);
} }
let orig_source = mem::take(&mut state.source); let orig_source = state.source.take();
state.source = source; state.source = source;
let level = _level + 1; let level = _level + 1;
@ -1293,10 +1293,7 @@ impl Engine {
// If the first argument is a variable, and there is no curried arguments, // If the first argument is a variable, and there is no curried arguments,
// convert to method-call style in order to leverage potential &mut first argument and // convert to method-call style in order to leverage potential &mut first argument and
// avoid cloning the value // avoid cloning the value
if curry.is_empty() if curry.is_empty() && !args_expr.is_empty() && args_expr[0].is_variable_access(false) {
&& !args_expr.is_empty()
&& args_expr[0].get_variable_access(false).is_some()
{
// func(x, ...) -> x.func(...) // func(x, ...) -> x.func(...)
arg_values = args_expr arg_values = args_expr
.iter() .iter()
@ -1378,7 +1375,7 @@ impl Engine {
// See if the first argument is a variable (not namespace-qualified). // See if the first argument is a variable (not namespace-qualified).
// If so, convert to method-call style in order to leverage potential // If so, convert to method-call style in order to leverage potential
// &mut first argument and avoid cloning the value // &mut first argument and avoid cloning the value
if !args_expr.is_empty() && args_expr[0].get_variable_access(true).is_some() { if !args_expr.is_empty() && args_expr[0].is_variable_access(true) {
// func(x, ...) -> x.func(...) // func(x, ...) -> x.func(...)
arg_values = args_expr arg_values = args_expr
.iter() .iter()

View File

@ -13,8 +13,8 @@ use crate::stdlib::{
}; };
use crate::token::is_valid_identifier; use crate::token::is_valid_identifier;
use crate::{ use crate::{
calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ImmutableString, Module, Position, calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, Identifier, ImmutableString, Module,
RhaiResult, StaticVec, Position, RhaiResult, StaticVec,
}; };
/// Trait that maps to `Send + Sync` only under the `sync` feature. /// Trait that maps to `Send + Sync` only under the `sync` feature.
@ -252,20 +252,17 @@ pub type FnCallArgs<'a> = [&'a mut Dynamic];
/// A general function pointer, which may carry additional (i.e. curried) argument values /// A general function pointer, which may carry additional (i.e. curried) argument values
/// to be passed onto a function during a call. /// to be passed onto a function during a call.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct FnPtr(ImmutableString, StaticVec<Dynamic>); pub struct FnPtr(Identifier, StaticVec<Dynamic>);
impl FnPtr { impl FnPtr {
/// Create a new function pointer. /// Create a new function pointer.
#[inline(always)] #[inline(always)]
pub fn new(name: impl Into<ImmutableString>) -> Result<Self, Box<EvalAltResult>> { pub fn new(name: impl Into<Identifier>) -> Result<Self, Box<EvalAltResult>> {
name.into().try_into() name.into().try_into()
} }
/// Create a new function pointer without checking its parameters. /// Create a new function pointer without checking its parameters.
#[inline(always)] #[inline(always)]
pub(crate) fn new_unchecked( pub(crate) fn new_unchecked(name: impl Into<Identifier>, curry: StaticVec<Dynamic>) -> Self {
name: impl Into<ImmutableString>,
curry: StaticVec<Dynamic>,
) -> Self {
Self(name.into(), curry) Self(name.into(), curry)
} }
/// Get the name of the function. /// Get the name of the function.
@ -275,12 +272,12 @@ impl FnPtr {
} }
/// Get the name of the function. /// Get the name of the function.
#[inline(always)] #[inline(always)]
pub(crate) fn get_fn_name(&self) -> &ImmutableString { pub(crate) fn get_fn_name(&self) -> &Identifier {
&self.0 &self.0
} }
/// Get the underlying data of the function pointer. /// Get the underlying data of the function pointer.
#[inline(always)] #[inline(always)]
pub(crate) fn take_data(self) -> (ImmutableString, StaticVec<Dynamic>) { pub(crate) fn take_data(self) -> (Identifier, StaticVec<Dynamic>) {
(self.0, self.1) (self.0, self.1)
} }
/// Get the curried arguments. /// Get the curried arguments.
@ -362,11 +359,11 @@ impl fmt::Display for FnPtr {
} }
} }
impl TryFrom<ImmutableString> for FnPtr { impl TryFrom<Identifier> for FnPtr {
type Error = Box<EvalAltResult>; type Error = Box<EvalAltResult>;
#[inline(always)] #[inline(always)]
fn try_from(value: ImmutableString) -> Result<Self, Self::Error> { fn try_from(value: Identifier) -> Result<Self, Self::Error> {
if is_valid_identifier(value.chars()) { if is_valid_identifier(value.chars()) {
Ok(Self(value, Default::default())) Ok(Self(value, Default::default()))
} else { } else {
@ -375,12 +372,22 @@ impl TryFrom<ImmutableString> for FnPtr {
} }
} }
impl TryFrom<ImmutableString> for FnPtr {
type Error = Box<EvalAltResult>;
#[inline(always)]
fn try_from(value: ImmutableString) -> Result<Self, Self::Error> {
let s: Identifier = value.into();
Self::try_from(s)
}
}
impl TryFrom<String> for FnPtr { impl TryFrom<String> for FnPtr {
type Error = Box<EvalAltResult>; type Error = Box<EvalAltResult>;
#[inline(always)] #[inline(always)]
fn try_from(value: String) -> Result<Self, Self::Error> { fn try_from(value: String) -> Result<Self, Self::Error> {
let s: ImmutableString = value.into(); let s: Identifier = value.into();
Self::try_from(s) Self::try_from(s)
} }
} }
@ -390,7 +397,7 @@ impl TryFrom<&str> for FnPtr {
#[inline(always)] #[inline(always)]
fn try_from(value: &str) -> Result<Self, Self::Error> { fn try_from(value: &str) -> Result<Self, Self::Error> {
let s: ImmutableString = value.into(); let s: Identifier = value.into();
Self::try_from(s) Self::try_from(s)
} }
} }

View File

@ -211,7 +211,10 @@ pub use dynamic::Variant;
// Expose internal data structures. // Expose internal data structures.
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[deprecated = "this type is volatile and may change"] #[deprecated = "this type is volatile and may change"]
pub use token::{get_next_token, parse_string_literal, InputStream, Token, TokenizeState}; pub use token::{
get_next_token, parse_string_literal, InputStream, Token, TokenizeState, TokenizerControl,
TokenizerControlBlock,
};
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[deprecated = "this type is volatile and may change"] #[deprecated = "this type is volatile and may change"]

View File

@ -7,8 +7,8 @@ use crate::fn_register::RegisterNativeFunction;
use crate::stdlib::{ use crate::stdlib::{
any::TypeId, any::TypeId,
boxed::Box, boxed::Box,
collections::BTreeMap, collections::{BTreeMap, BTreeSet},
fmt, format, fmt,
iter::empty, iter::empty,
num::NonZeroUsize, num::NonZeroUsize,
ops::{Add, AddAssign, Deref, DerefMut}, ops::{Add, AddAssign, Deref, DerefMut},
@ -173,50 +173,36 @@ impl Default for Module {
impl fmt::Debug for Module { impl fmt::Debug for Module {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!( let mut d = f.debug_struct("Module");
f,
"Module({}\n{}{}{})", if let Some(ref id) = self.id {
self.id d.field("id", id);
.as_ref() }
.map(|id| format!("id: {:?},", id))
.unwrap_or_default(), if !self.modules.is_empty() {
if !self.modules.is_empty() { d.field(
format!( "modules",
" modules: {}\n", &self
self.modules .modules
.keys() .keys()
.map(|m| m.as_str()) .map(|m| m.as_str())
.collect::<Vec<_>>() .collect::<BTreeSet<_>>(),
.join(", ") );
) }
} else { if !self.variables.is_empty() {
Default::default() d.field("vars", &self.variables);
}, }
if !self.variables.is_empty() { if !self.functions.is_empty() {
format!( d.field(
" vars: {}\n", "functions",
self.variables &self
.iter() .functions
.map(|(k, v)| format!("{}={:?}", k, v)) .values()
.collect::<Vec<_>>() .map(|f| crate::stdlib::string::ToString::to_string(&f.func))
.join(", ") .collect::<BTreeSet<_>>(),
) );
} else { }
Default::default() d.finish()
},
if !self.functions.is_empty() {
format!(
" functions: {}\n",
self.functions
.values()
.map(|f| crate::stdlib::string::ToString::to_string(&f.func))
.collect::<Vec<_>>()
.join(", ")
)
} else {
Default::default()
}
)
} }
} }

View File

@ -2,7 +2,7 @@
use crate::ast::{Expr, Stmt, StmtBlock}; use crate::ast::{Expr, Stmt, StmtBlock};
use crate::dynamic::AccessMode; use crate::dynamic::AccessMode;
use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF}; use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF};
use crate::fn_builtin::get_builtin_binary_op_fn; use crate::fn_builtin::get_builtin_binary_op_fn;
use crate::parser::map_dynamic_to_expr; use crate::parser::map_dynamic_to_expr;
use crate::stdlib::{ use crate::stdlib::{
@ -17,8 +17,8 @@ use crate::stdlib::{
}; };
use crate::utils::get_hasher; use crate::utils::get_hasher;
use crate::{ use crate::{
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, Module, Position, Scope, calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, ImmutableString, Module,
StaticVec, AST, Position, Scope, StaticVec, AST,
}; };
/// Level of optimization performed. /// Level of optimization performed.
@ -385,7 +385,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
match stmt { match stmt {
// expr op= expr // expr op= expr
Stmt::Assignment(x, _) => match x.0 { Stmt::Assignment(x, _) => match x.0 {
Expr::Variable(_) => optimize_expr(&mut x.2, state), Expr::Variable(_, _, _) => optimize_expr(&mut x.2, state),
_ => { _ => {
optimize_expr(&mut x.0, state); optimize_expr(&mut x.0, state);
optimize_expr(&mut x.2, state); optimize_expr(&mut x.2, state);
@ -546,19 +546,21 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
Stmt::Import(expr, _, _) => optimize_expr(expr, state), Stmt::Import(expr, _, _) => optimize_expr(expr, state),
// { block } // { block }
Stmt::Block(statements, pos) => { Stmt::Block(statements, pos) => {
let block = mem::take(statements); let mut block =
*stmt = match optimize_stmt_block(block, state, preserve_result, true, false) { optimize_stmt_block(mem::take(statements), state, preserve_result, true, false);
statements if statements.is_empty() => {
match block.as_mut_slice() {
[] => {
state.set_dirty(); state.set_dirty();
Stmt::Noop(*pos) *stmt = Stmt::Noop(*pos);
} }
// Only one statement - promote // Only one statement - promote
mut statements if statements.len() == 1 => { [s] => {
state.set_dirty(); state.set_dirty();
statements.pop().unwrap() *stmt = mem::take(s);
} }
statements => Stmt::Block(statements, *pos), _ => *stmt = Stmt::Block(block, *pos),
}; }
} }
// try { pure try_block } catch ( var ) { catch_block } -> try_block // try { pure try_block } catch ( var ) { catch_block } -> try_block
Stmt::TryCatch(x, _, _) if x.0.statements.iter().all(Stmt::is_pure) => { Stmt::TryCatch(x, _, _) if x.0.statements.iter().all(Stmt::is_pure) => {
@ -609,18 +611,16 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
match expr { match expr {
// {} // {}
Expr::Stmt(x) if x.statements.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.pos) } Expr::Stmt(x) if x.statements.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.pos) }
// { Stmt(Expr) } // { stmt; ... } - do not count promotion as dirty because it gets turned back into an array
Expr::Stmt(x) if x.statements.len() == 1 && x.statements[0].is_pure() && matches!(x.statements[0], Stmt::Expr(_)) => Expr::Stmt(x) => {
{ x.statements = optimize_stmt_block(mem::take(&mut x.statements).into_vec(), state, true, true, false).into();
state.set_dirty();
if let Stmt::Expr(e) = mem::take(&mut x.statements[0]) { // { Stmt(Expr) } - promote
*expr = e; match x.statements.as_mut() {
} else { [ Stmt::Expr(e) ] => { state.set_dirty(); *expr = mem::take(e); }
unreachable!(); _ => ()
} }
} }
// { stmt; ... } - do not count promotion as dirty because it gets turned back into an array
Expr::Stmt(x) => x.statements = optimize_stmt_block(mem::take(&mut x.statements).into_vec(), state, true, true, false).into(),
// lhs.rhs // lhs.rhs
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Dot(x, _) => match (&mut x.lhs, &mut x.rhs) { Expr::Dot(x, _) => match (&mut x.lhs, &mut x.rhs) {
@ -635,7 +635,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
.unwrap_or_else(|| Expr::Unit(*pos)); .unwrap_or_else(|| Expr::Unit(*pos));
} }
// var.rhs // var.rhs
(Expr::Variable(_), rhs) => optimize_expr(rhs, state), (Expr::Variable(_, _, _), rhs) => optimize_expr(rhs, state),
// lhs.rhs // lhs.rhs
(lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); } (lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); }
} }
@ -670,7 +670,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
*expr = Expr::CharConstant(s.chars().nth(*i as usize).unwrap(), *pos); *expr = Expr::CharConstant(s.chars().nth(*i as usize).unwrap(), *pos);
} }
// var[rhs] // var[rhs]
(Expr::Variable(_), rhs) => optimize_expr(rhs, state), (Expr::Variable(_, _, _), rhs) => optimize_expr(rhs, state),
// lhs[rhs] // lhs[rhs]
(lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); } (lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); }
}, },
@ -794,6 +794,19 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
Expr::FnCall(x, _) if x.name == KEYWORD_EVAL => { Expr::FnCall(x, _) if x.name == KEYWORD_EVAL => {
state.propagate_constants = false; state.propagate_constants = false;
} }
// Fn
Expr::FnCall(x, pos)
if x.namespace.is_none() // Non-qualified
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations
&& x.num_args() == 1
&& x.constant_args.len() == 1
&& x.constant_args[0].0.is::<ImmutableString>()
&& x.name == KEYWORD_FN_PTR
=> {
state.set_dirty();
*expr = Expr::FnPointer(mem::take(&mut x.constant_args[0].0).take_immutable_string().unwrap(), *pos);
}
// Do not call some special keywords // Do not call some special keywords
Expr::FnCall(x, _) if DONT_EVAL_KEYWORDS.contains(&x.name.as_ref()) => { Expr::FnCall(x, _) if DONT_EVAL_KEYWORDS.contains(&x.name.as_ref()) => {
x.args.iter_mut().for_each(|a| optimize_expr(a, state)); x.args.iter_mut().for_each(|a| optimize_expr(a, state));
@ -901,12 +914,12 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
} }
// constant-name // constant-name
Expr::Variable(x) if x.1.is_none() && state.find_constant(&x.2.name).is_some() => { Expr::Variable(_, pos, x) if x.1.is_none() && state.find_constant(&x.2).is_some() => {
state.set_dirty(); state.set_dirty();
// Replace constant with value // Replace constant with value
let mut result = state.find_constant(&x.2.name).unwrap().clone(); let mut result = state.find_constant(&x.2).unwrap().clone();
result.set_position(x.2.pos); result.set_position(*pos);
*expr = result; *expr = result;
} }

View File

@ -9,7 +9,7 @@ def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, {
mod fn_ptr_functions { mod fn_ptr_functions {
#[rhai_fn(name = "name", get = "name", pure)] #[rhai_fn(name = "name", get = "name", pure)]
pub fn name(f: &mut FnPtr) -> ImmutableString { pub fn name(f: &mut FnPtr) -> ImmutableString {
f.get_fn_name().clone() f.get_fn_name().as_str().into()
} }
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]

View File

@ -73,31 +73,23 @@ mod print_debug_functions {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use num_traits::Float; use num_traits::Float;
use crate::ast::FloatWrapper;
#[rhai_fn(name = "print", name = "to_string")] #[rhai_fn(name = "print", name = "to_string")]
pub fn print_f64(number: f64) -> ImmutableString { pub fn print_f64(number: f64) -> ImmutableString {
let abs = number.abs(); FloatWrapper::new(number).to_string().into()
if abs > 10000000000000.0 || abs < 0.0000000000001 {
format!("{:e}", number).into()
} else {
number.to_string().into()
}
} }
#[rhai_fn(name = "print", name = "to_string")] #[rhai_fn(name = "print", name = "to_string")]
pub fn print_f32(number: f32) -> ImmutableString { pub fn print_f32(number: f32) -> ImmutableString {
let abs = number.abs(); FloatWrapper::new(number).to_string().into()
if abs > 10000000000000.0 || abs < 0.0000000000001 {
format!("{:e}", number).into()
} else {
number.to_string().into()
}
} }
#[rhai_fn(name = "debug", name = "to_debug")] #[rhai_fn(name = "debug", name = "to_debug")]
pub fn debug_f64(number: f64) -> ImmutableString { pub fn debug_f64(number: f64) -> ImmutableString {
number.to_string().into() format!("{:?}", FloatWrapper::new(number)).into()
} }
#[rhai_fn(name = "debug", name = "to_debug")] #[rhai_fn(name = "debug", name = "to_debug")]
pub fn debug_f32(number: f32) -> ImmutableString { pub fn debug_f32(number: f32) -> ImmutableString {
number.to_string().into() format!("{:?}", FloatWrapper::new(number)).into()
} }
} }

View File

@ -56,6 +56,10 @@ mod string_functions {
pub fn add_append_str(string1: ImmutableString, string2: ImmutableString) -> ImmutableString { pub fn add_append_str(string1: ImmutableString, string2: ImmutableString) -> ImmutableString {
string1 + string2 string1 + string2
} }
#[rhai_fn(name = "+", name = "append")]
pub fn add_append_char(string: ImmutableString, ch: char) -> ImmutableString {
string + ch
}
#[rhai_fn(name = "+", name = "append")] #[rhai_fn(name = "+", name = "append")]
pub fn add_append_unit(string: ImmutableString, _item: ()) -> ImmutableString { pub fn add_append_unit(string: ImmutableString, _item: ()) -> ImmutableString {

View File

@ -11,18 +11,19 @@ use crate::optimize::optimize_into_ast;
use crate::optimize::OptimizationLevel; use crate::optimize::OptimizationLevel;
use crate::stdlib::{ use crate::stdlib::{
boxed::Box, boxed::Box,
cell::Cell,
collections::BTreeMap, collections::BTreeMap,
format, format,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
iter::empty, iter::empty,
num::NonZeroUsize, num::{NonZeroU8, NonZeroUsize},
string::{String, ToString}, string::{String, ToString},
vec, vec,
vec::Vec, vec::Vec,
}; };
use crate::syntax::{CustomSyntax, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; use crate::syntax::{CustomSyntax, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
use crate::token::{is_keyword_function, is_valid_identifier, Token, TokenStream}; use crate::token::{
is_keyword_function, is_valid_identifier, Token, TokenStream, TokenizerControl,
};
use crate::utils::{get_hasher, IdentifierBuilder}; use crate::utils::{get_hasher, IdentifierBuilder};
use crate::{ use crate::{
calc_fn_hash, Dynamic, Engine, Identifier, LexError, ParseError, ParseErrorType, Position, calc_fn_hash, Dynamic, Engine, Identifier, LexError, ParseError, ParseErrorType, Position,
@ -45,7 +46,7 @@ pub struct ParseState<'e> {
/// Reference to the scripting [`Engine`]. /// Reference to the scripting [`Engine`].
engine: &'e Engine, engine: &'e Engine,
/// Input stream buffer containing the next character to read. /// Input stream buffer containing the next character to read.
buffer: Shared<Cell<Option<char>>>, tokenizer_control: TokenizerControl,
/// Interned strings. /// Interned strings.
interned_strings: IdentifierBuilder, interned_strings: IdentifierBuilder,
/// Encapsulates a local stack with variable names to simulate an actual runtime scope. /// Encapsulates a local stack with variable names to simulate an actual runtime scope.
@ -76,22 +77,15 @@ pub struct ParseState<'e> {
impl<'e> ParseState<'e> { impl<'e> ParseState<'e> {
/// Create a new [`ParseState`]. /// Create a new [`ParseState`].
#[inline(always)] #[inline(always)]
pub fn new( pub fn new(engine: &'e Engine, tokenizer_control: TokenizerControl) -> Self {
engine: &'e Engine,
buffer: Shared<Cell<Option<char>>>,
#[cfg(not(feature = "unchecked"))] max_expr_depth: Option<NonZeroUsize>,
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_function"))]
max_function_expr_depth: Option<NonZeroUsize>,
) -> Self {
Self { Self {
engine, engine,
buffer, tokenizer_control,
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
max_expr_depth, max_expr_depth: NonZeroUsize::new(engine.max_expr_depth()),
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
max_function_expr_depth, max_function_expr_depth: NonZeroUsize::new(engine.max_function_expr_depth()),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
external_vars: Default::default(), external_vars: Default::default(),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
@ -231,17 +225,20 @@ impl Expr {
#[inline(always)] #[inline(always)]
fn into_property(self, state: &mut ParseState) -> Self { fn into_property(self, state: &mut ParseState) -> Self {
match self { match self {
Self::Variable(x) if x.1.is_none() => { Self::Variable(_, pos, x) if x.1.is_none() => {
let ident = x.2; let ident = x.2;
let getter = state.get_identifier(crate::engine::make_getter(&ident.name)); let getter = state.get_identifier(crate::engine::make_getter(&ident));
let hash_get = calc_fn_hash(empty(), &getter, 1); let hash_get = calc_fn_hash(empty(), &getter, 1);
let setter = state.get_identifier(crate::engine::make_setter(&ident.name)); let setter = state.get_identifier(crate::engine::make_setter(&ident));
let hash_set = calc_fn_hash(empty(), &setter, 2); let hash_set = calc_fn_hash(empty(), &setter, 2);
Self::Property(Box::new(( Self::Property(Box::new((
(getter, hash_get), (getter, hash_get),
(setter, hash_set), (setter, hash_set),
ident.into(), Ident {
name: state.get_identifier(ident),
pos,
},
))) )))
} }
_ => self, _ => self,
@ -716,6 +713,7 @@ fn parse_map_literal(
} }
(s, pos) (s, pos)
} }
(Token::InterpolatedString(_), pos) => return Err(PERR::PropertyExpected.into_err(pos)),
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
return Err(PERR::Reserved(s).into_err(pos)); return Err(PERR::Reserved(s).into_err(pos));
} }
@ -982,14 +980,12 @@ fn parse_primary(
// | ... // | ...
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Token::Pipe | Token::Or if settings.allow_anonymous_fn => { Token::Pipe | Token::Or if settings.allow_anonymous_fn => {
let mut new_state = ParseState::new( let mut new_state = ParseState::new(state.engine, state.tokenizer_control.clone());
state.engine,
state.buffer.clone(), #[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "unchecked"))] {
state.max_function_expr_depth, new_state.max_expr_depth = new_state.max_function_expr_depth;
#[cfg(not(feature = "unchecked"))] }
state.max_function_expr_depth,
);
let settings = ParseSettings { let settings = ParseSettings {
allow_if_expr: true, allow_if_expr: true,
@ -1034,7 +1030,9 @@ fn parse_primary(
segments.push(expr); segments.push(expr);
// Make sure to parse the following as text // Make sure to parse the following as text
state.buffer.set(Some('`')); let mut control = state.tokenizer_control.get();
control.is_within_text = true;
state.tokenizer_control.set(control);
match input.next().unwrap() { match input.next().unwrap() {
(Token::StringConstant(s), pos) => { (Token::StringConstant(s), pos) => {
@ -1082,11 +1080,11 @@ fn parse_primary(
// Once the identifier consumed we must enable next variables capturing // Once the identifier consumed we must enable next variables capturing
state.allow_capture = true; state.allow_capture = true;
} }
let var_name_def = Ident { Expr::Variable(
name: state.get_identifier(s), None,
pos: settings.pos, settings.pos,
}; Box::new((None, None, state.get_identifier(s))),
Expr::Variable(Box::new((None, None, var_name_def))) )
} }
// Namespace qualification // Namespace qualification
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -1096,20 +1094,27 @@ fn parse_primary(
// Once the identifier consumed we must enable next variables capturing // Once the identifier consumed we must enable next variables capturing
state.allow_capture = true; state.allow_capture = true;
} }
let var_name_def = Ident { Expr::Variable(
name: state.get_identifier(s), None,
pos: settings.pos, settings.pos,
}; Box::new((None, None, state.get_identifier(s))),
Expr::Variable(Box::new((None, None, var_name_def))) )
} }
// Normal variable access // Normal variable access
_ => { _ => {
let index = state.access_var(&s, settings.pos); let index = state.access_var(&s, settings.pos);
let var_name_def = Ident { let short_index = index.and_then(|x| {
name: state.get_identifier(s), if x.get() <= u8::MAX as usize {
pos: settings.pos, NonZeroU8::new(x.get() as u8)
}; } else {
Expr::Variable(Box::new((index, None, var_name_def))) None
}
});
Expr::Variable(
short_index,
settings.pos,
Box::new((index, None, state.get_identifier(s))),
)
} }
} }
} }
@ -1123,21 +1128,17 @@ fn parse_primary(
match input.peek().unwrap().0 { match input.peek().unwrap().0 {
// Function call is allowed to have reserved keyword // Function call is allowed to have reserved keyword
Token::LeftParen | Token::Bang if is_keyword_function(&s) => { Token::LeftParen | Token::Bang if is_keyword_function(&s) => Expr::Variable(
let var_name_def = Ident { None,
name: state.get_identifier(s), settings.pos,
pos: settings.pos, Box::new((None, None, state.get_identifier(s))),
}; ),
Expr::Variable(Box::new((None, None, var_name_def)))
}
// Access to `this` as a variable is OK within a function scope // Access to `this` as a variable is OK within a function scope
_ if s == KEYWORD_THIS && settings.is_function_scope => { _ if s == KEYWORD_THIS && settings.is_function_scope => Expr::Variable(
let var_name_def = Ident { None,
name: state.get_identifier(s), settings.pos,
pos: settings.pos, Box::new((None, None, state.get_identifier(s))),
}; ),
Expr::Variable(Box::new((None, None, var_name_def)))
}
// Cannot access to `this` as a variable not in a function scope // Cannot access to `this` as a variable not in a function scope
_ if s == KEYWORD_THIS => { _ if s == KEYWORD_THIS => {
let msg = format!("'{}' can only be used in functions", s); let msg = format!("'{}' can only be used in functions", s);
@ -1173,7 +1174,7 @@ fn parse_primary(
root_expr = match (root_expr, tail_token) { root_expr = match (root_expr, tail_token) {
// Qualified function call with ! // Qualified function call with !
(Expr::Variable(x), Token::Bang) if x.1.is_some() => { (Expr::Variable(_, _, x), Token::Bang) if x.1.is_some() => {
return Err(if !match_token(input, Token::LeftParen).0 { return Err(if !match_token(input, Token::LeftParen).0 {
LexError::UnexpectedInput(Token::Bang.syntax().to_string()).into_err(tail_pos) LexError::UnexpectedInput(Token::Bang.syntax().to_string()).into_err(tail_pos)
} else { } else {
@ -1185,7 +1186,7 @@ fn parse_primary(
}); });
} }
// Function call with ! // Function call with !
(Expr::Variable(x), Token::Bang) => { (Expr::Variable(_, var_pos, x), Token::Bang) => {
let (matched, pos) = match_token(input, Token::LeftParen); let (matched, pos) = match_token(input, Token::LeftParen);
if !matched { if !matched {
return Err(PERR::MissingToken( return Err(PERR::MissingToken(
@ -1195,37 +1196,40 @@ fn parse_primary(
.into_err(pos)); .into_err(pos));
} }
let (_, namespace, Ident { name, pos, .. }) = *x; let (_, namespace, name) = *x;
settings.pos = pos; settings.pos = var_pos;
let ns = namespace.map(|(_, ns)| ns); let ns = namespace.map(|(_, ns)| ns);
parse_fn_call(input, state, lib, name, true, ns, settings.level_up())? parse_fn_call(input, state, lib, name, true, ns, settings.level_up())?
} }
// Function call // Function call
(Expr::Variable(x), Token::LeftParen) => { (Expr::Variable(_, var_pos, x), Token::LeftParen) => {
let (_, namespace, Ident { name, pos, .. }) = *x; let (_, namespace, name) = *x;
settings.pos = pos; settings.pos = var_pos;
let ns = namespace.map(|(_, ns)| ns); let ns = namespace.map(|(_, ns)| ns);
parse_fn_call(input, state, lib, name, false, ns, settings.level_up())? parse_fn_call(input, state, lib, name, false, ns, settings.level_up())?
} }
// module access // module access
(Expr::Variable(x), Token::DoubleColon) => match input.next().unwrap() { (Expr::Variable(_, var_pos, x), Token::DoubleColon) => match input.next().unwrap() {
(Token::Identifier(id2), pos2) => { (Token::Identifier(id2), pos2) => {
let (index, mut namespace, var_name_def) = *x; let (_, mut namespace, var_name) = *x;
let var_name_def = Ident {
name: var_name,
pos: var_pos,
};
if let Some((_, ref mut namespace)) = namespace { if let Some((_, ref mut namespace)) = namespace {
namespace.push(var_name_def); namespace.push(var_name_def);
} else { } else {
let mut ns: NamespaceRef = Default::default(); let mut ns: NamespaceRef = Default::default();
ns.push(var_name_def); ns.push(var_name_def);
let index = 42; // Dummy namespace = Some((42, ns));
namespace = Some((index, ns));
} }
let var_name_def = Ident { Expr::Variable(
name: state.get_identifier(id2), None,
pos: pos2, pos2,
}; Box::new((None, namespace, state.get_identifier(id2))),
Expr::Variable(Box::new((index, namespace, var_name_def))) )
} }
(Token::Reserved(id2), pos2) if is_valid_identifier(id2.chars()) => { (Token::Reserved(id2), pos2) if is_valid_identifier(id2.chars()) => {
return Err(PERR::Reserved(id2).into_err(pos2)); return Err(PERR::Reserved(id2).into_err(pos2));
@ -1267,16 +1271,16 @@ fn parse_primary(
} }
// Cache the hash key for namespace-qualified variables // Cache the hash key for namespace-qualified variables
match &mut root_expr { match root_expr {
Expr::Variable(x) if x.1.is_some() => Some(x), Expr::Variable(_, _, ref mut x) if x.1.is_some() => Some(x),
Expr::Index(x, _) | Expr::Dot(x, _) => match &mut x.lhs { Expr::Index(ref mut x, _) | Expr::Dot(ref mut x, _) => match &mut x.lhs {
Expr::Variable(x) if x.1.is_some() => Some(x), Expr::Variable(_, _, x) if x.1.is_some() => Some(x),
_ => None, _ => None,
}, },
_ => None, _ => None,
} }
.map(|x| match x.as_mut() { .map(|x| match x.as_mut() {
(_, Some((ref mut hash, ref mut namespace)), Ident { name, .. }) => { (_, Some((hash, namespace)), name) => {
*hash = calc_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0); *hash = calc_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0);
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -1428,19 +1432,20 @@ fn make_assignment_stmt<'a>(
Err(PERR::AssignmentToConstant("".into()).into_err(lhs.position())) Err(PERR::AssignmentToConstant("".into()).into_err(lhs.position()))
} }
// var (non-indexed) = rhs // var (non-indexed) = rhs
Expr::Variable(x) if x.0.is_none() => { Expr::Variable(None, _, x) if x.0.is_none() => {
Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos)) Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos))
} }
// var (indexed) = rhs // var (indexed) = rhs
Expr::Variable(x) => { Expr::Variable(i, var_pos, x) => {
let (index, _, Ident { name, pos, .. }) = x.as_ref(); let (index, _, name) = x.as_ref();
match state.stack[(state.stack.len() - index.unwrap().get())].1 { let index = i.map_or_else(|| index.unwrap().get(), |n| n.get() as usize);
match state.stack[state.stack.len() - index].1 {
AccessMode::ReadWrite => { AccessMode::ReadWrite => {
Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos)) Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos))
} }
// Constant values cannot be assigned to // Constant values cannot be assigned to
AccessMode::ReadOnly => { AccessMode::ReadOnly => {
Err(PERR::AssignmentToConstant(name.to_string()).into_err(*pos)) Err(PERR::AssignmentToConstant(name.to_string()).into_err(*var_pos))
} }
} }
} }
@ -1449,19 +1454,20 @@ fn make_assignment_stmt<'a>(
match check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _))) { match check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _))) {
Position::NONE => match &x.lhs { Position::NONE => match &x.lhs {
// var[???] (non-indexed) = rhs, var.??? (non-indexed) = rhs // var[???] (non-indexed) = rhs, var.??? (non-indexed) = rhs
Expr::Variable(x) if x.0.is_none() => { Expr::Variable(None, _, x) if x.0.is_none() => {
Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos)) Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos))
} }
// var[???] (indexed) = rhs, var.??? (indexed) = rhs // var[???] (indexed) = rhs, var.??? (indexed) = rhs
Expr::Variable(x) => { Expr::Variable(i, var_pos, x) => {
let (index, _, Ident { name, pos, .. }) = x.as_ref(); let (index, _, name) = x.as_ref();
match state.stack[(state.stack.len() - index.unwrap().get())].1 { let index = i.map_or_else(|| index.unwrap().get(), |n| n.get() as usize);
match state.stack[state.stack.len() - index].1 {
AccessMode::ReadWrite => { AccessMode::ReadWrite => {
Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos)) Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos))
} }
// Constant values cannot be assigned to // Constant values cannot be assigned to
AccessMode::ReadOnly => { AccessMode::ReadOnly => {
Err(PERR::AssignmentToConstant(name.to_string()).into_err(*pos)) Err(PERR::AssignmentToConstant(name.to_string()).into_err(*var_pos))
} }
} }
} }
@ -1537,19 +1543,26 @@ fn make_dot_expr(
Expr::Index(x, pos) Expr::Index(x, pos)
} }
// lhs.id // lhs.id
(lhs, Expr::Variable(x)) if x.1.is_none() => { (lhs, Expr::Variable(_, var_pos, x)) if x.1.is_none() => {
let ident = x.2; let ident = x.2;
let getter = state.get_identifier(crate::engine::make_getter(&ident.name)); let getter = state.get_identifier(crate::engine::make_getter(&ident));
let hash_get = calc_fn_hash(empty(), &getter, 1); let hash_get = calc_fn_hash(empty(), &getter, 1);
let setter = state.get_identifier(crate::engine::make_setter(&ident.name)); let setter = state.get_identifier(crate::engine::make_setter(&ident));
let hash_set = calc_fn_hash(empty(), &setter, 2); let hash_set = calc_fn_hash(empty(), &setter, 2);
let rhs = Expr::Property(Box::new(((getter, hash_get), (setter, hash_set), ident))); let rhs = Expr::Property(Box::new((
(getter, hash_get),
(setter, hash_set),
Ident {
name: state.get_identifier(ident),
pos: var_pos,
},
)));
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos) Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
} }
// lhs.module::id - syntax error // lhs.module::id - syntax error
(_, Expr::Variable(x)) if x.1.is_some() => { (_, Expr::Variable(_, _, x)) if x.1.is_some() => {
return Err(PERR::PropertyExpected.into_err(x.1.unwrap().1[0].pos)) return Err(PERR::PropertyExpected.into_err(x.1.unwrap().1[0].pos))
} }
// lhs.prop // lhs.prop
@ -1558,7 +1571,7 @@ fn make_dot_expr(
} }
// lhs.dot_lhs.dot_rhs // lhs.dot_lhs.dot_rhs
(lhs, Expr::Dot(x, pos)) => match x.lhs { (lhs, Expr::Dot(x, pos)) => match x.lhs {
Expr::Variable(_) | Expr::Property(_) => { Expr::Variable(_, _, _) | Expr::Property(_) => {
let rhs = Expr::Dot( let rhs = Expr::Dot(
Box::new(BinaryExpr { Box::new(BinaryExpr {
lhs: x.lhs.into_property(state), lhs: x.lhs.into_property(state),
@ -1873,8 +1886,7 @@ fn parse_custom_syntax(
let name = state.get_identifier(s); let name = state.get_identifier(s);
segments.push(name.clone().into()); segments.push(name.clone().into());
tokens.push(state.get_identifier(MARKER_IDENT)); tokens.push(state.get_identifier(MARKER_IDENT));
let var_name_def = Ident { name, pos }; keywords.push(Expr::Variable(None, pos, Box::new((None, None, name))));
keywords.push(Expr::Variable(Box::new((None, None, var_name_def))));
} }
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
return Err(PERR::Reserved(s).into_err(pos)); return Err(PERR::Reserved(s).into_err(pos));
@ -2540,14 +2552,13 @@ fn parse_stmt(
match input.next().unwrap() { match input.next().unwrap() {
(Token::Fn, pos) => { (Token::Fn, pos) => {
let mut new_state = ParseState::new( let mut new_state =
state.engine, ParseState::new(state.engine, state.tokenizer_control.clone());
state.buffer.clone(),
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
state.max_function_expr_depth, {
#[cfg(not(feature = "unchecked"))] new_state.max_expr_depth = new_state.max_function_expr_depth;
state.max_function_expr_depth, }
);
let settings = ParseSettings { let settings = ParseSettings {
allow_if_expr: true, allow_if_expr: true,
@ -2827,11 +2838,11 @@ fn make_curry_from_externals(
args.push(fn_expr); args.push(fn_expr);
externals.iter().for_each(|x| { externals.iter().for_each(|x| {
let var_def = Ident { args.push(Expr::Variable(
name: x.clone(), None,
pos: Position::NONE, Position::NONE,
}; Box::new((None, None, x.clone())),
args.push(Expr::Variable(Box::new((None, None, var_def)))); ));
}); });
let expr = Expr::FnCall( let expr = Expr::FnCall(

View File

@ -26,7 +26,7 @@ impl Serialize for Dynamic {
Union::Float(x, _) => ser.serialize_f64(**x), Union::Float(x, _) => ser.serialize_f64(**x),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[cfg(feature = "f32_float")] #[cfg(feature = "f32_float")]
Union::Float(x, _) => ser.serialize_f32(*x), Union::Float(x, _) => ser.serialize_f32(**x),
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
#[cfg(not(feature = "f32_float"))] #[cfg(not(feature = "f32_float"))]

View File

@ -45,7 +45,7 @@ impl Expression<'_> {
/// If this expression is a variable name, return it. Otherwise [`None`]. /// If this expression is a variable name, return it. Otherwise [`None`].
#[inline(always)] #[inline(always)]
pub fn get_variable_name(&self) -> Option<&str> { pub fn get_variable_name(&self) -> Option<&str> {
self.0.get_variable_access(true) self.0.get_variable_name(true)
} }
/// Get the expression. /// Get the expression.
#[inline(always)] #[inline(always)]

View File

@ -11,13 +11,14 @@ use crate::stdlib::{
iter::{FusedIterator, Peekable}, iter::{FusedIterator, Peekable},
num::NonZeroUsize, num::NonZeroUsize,
ops::{Add, AddAssign}, ops::{Add, AddAssign},
rc::Rc,
str::{Chars, FromStr}, str::{Chars, FromStr},
string::{String, ToString}, string::{String, ToString},
}; };
use crate::{Engine, LexError, Shared, StaticVec, INT}; use crate::{Engine, LexError, StaticVec, INT};
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::ast::FloatWrapper; use crate::{ast::FloatWrapper, FLOAT};
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
use rust_decimal::Decimal; use rust_decimal::Decimal;
@ -25,6 +26,17 @@ use rust_decimal::Decimal;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
use crate::engine::KEYWORD_IS_DEF_FN; use crate::engine::KEYWORD_IS_DEF_FN;
/// _(INTERNALS)_ A type containing commands to control the tokenizer.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Copy, Default)]
pub struct TokenizerControlBlock {
/// Is the current tokenizer position within an interpolated text string?
/// This flag allows switching the tokenizer back to _text_ parsing after an interpolation stream.
pub is_within_text: bool,
}
/// _(INTERNALS)_ A shared object that allows control of the tokenizer from outside.
pub type TokenizerControl = Rc<Cell<TokenizerControlBlock>>;
type LERR = LexError; type LERR = LexError;
/// Separator character for numbers. /// Separator character for numbers.
@ -198,7 +210,7 @@ pub enum Token {
/// ///
/// Reserved under the `no_float` feature. /// Reserved under the `no_float` feature.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
FloatConstant(FloatWrapper), FloatConstant(FloatWrapper<FLOAT>),
/// A [`Decimal`] constant. /// A [`Decimal`] constant.
/// ///
/// Requires the `decimal` feature. /// Requires the `decimal` feature.
@ -815,7 +827,7 @@ impl From<Token> for String {
/// This type is volatile and may change. /// This type is volatile and may change.
#[derive(Debug, Clone, Eq, PartialEq, Default)] #[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct TokenizeState { pub struct TokenizeState {
/// Maximum length of a string (0 = unlimited). /// Maximum length of a string.
pub max_string_size: Option<NonZeroUsize>, pub max_string_size: Option<NonZeroUsize>,
/// Can the next token be a unary operator? /// Can the next token be a unary operator?
pub non_unary: bool, pub non_unary: bool,
@ -827,6 +839,8 @@ pub struct TokenizeState {
pub include_comments: bool, pub include_comments: bool,
/// Disable doc-comments? /// Disable doc-comments?
pub disable_doc_comments: bool, pub disable_doc_comments: bool,
/// Is the current tokenizer position within the text stream of an interpolated string?
pub is_within_text_terminated_by: Option<char>,
} }
/// _(INTERNALS)_ Trait that encapsulates a peekable character input stream. /// _(INTERNALS)_ Trait that encapsulates a peekable character input stream.
@ -849,6 +863,9 @@ pub trait InputStream {
/// _(INTERNALS)_ Parse a string literal ended by `termination_char`. /// _(INTERNALS)_ Parse a string literal ended by `termination_char`.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
/// ///
/// Returns the parsed string and a boolean indicating whether the string is
/// terminated by an interpolation `${`.
///
/// # Volatile API /// # Volatile API
/// ///
/// This function is volatile and may change. /// This function is volatile and may change.
@ -859,6 +876,7 @@ pub fn parse_string_literal(
termination_char: char, termination_char: char,
continuation: bool, continuation: bool,
verbatim: bool, verbatim: bool,
skip_first_new_line: bool,
allow_interpolation: bool, allow_interpolation: bool,
) -> Result<(String, bool), (LexError, Position)> { ) -> Result<(String, bool), (LexError, Position)> {
let mut result: smallvec::SmallVec<[char; 16]> = Default::default(); let mut result: smallvec::SmallVec<[char; 16]> = Default::default();
@ -868,6 +886,25 @@ pub fn parse_string_literal(
let mut skip_whitespace_until = 0; let mut skip_whitespace_until = 0;
let mut interpolated = false; let mut interpolated = false;
if skip_first_new_line {
// Start from the next line if at the end of line
match stream.peek_next() {
// `\r - start from next line
Some('\r') => {
eat_next(stream, pos);
// `\r\n
if stream.peek_next().map(|ch| ch == '\n').unwrap_or(false) {
eat_next(stream, pos);
}
}
// `\n - start from next line
Some('\n') => {
eat_next(stream, pos);
}
_ => (),
}
}
loop { loop {
let next_char = stream.get_next().ok_or((LERR::UnterminatedString, start))?; let next_char = stream.get_next().ok_or((LERR::UnterminatedString, start))?;
@ -1148,6 +1185,22 @@ fn get_next_token_inner(
} }
} }
// Within text?
if let Some(ch) = state.is_within_text_terminated_by.take() {
let start_pos = *pos;
return parse_string_literal(stream, state, pos, ch, false, true, true, true).map_or_else(
|(err, err_pos)| Some((Token::LexError(err), err_pos)),
|(result, interpolated)| {
if interpolated {
Some((Token::InterpolatedString(result), start_pos))
} else {
Some((Token::StringConstant(result), start_pos))
}
},
);
}
let mut negated = false; let mut negated = false;
while let Some(c) = stream.get_next() { while let Some(c) = stream.get_next() {
@ -1262,42 +1315,42 @@ fn get_next_token_inner(
} }
// Parse number // Parse number
if let Some(radix) = radix_base { return Some((
let out: String = result.iter().skip(2).filter(|&&c| c != NUM_SEP).collect(); if let Some(radix) = radix_base {
let out: String =
result.iter().skip(2).filter(|&&c| c != NUM_SEP).collect();
return Some((
INT::from_str_radix(&out, radix) INT::from_str_radix(&out, radix)
.map(Token::IntegerConstant) .map(Token::IntegerConstant)
.unwrap_or_else(|_| { .unwrap_or_else(|_| {
Token::LexError(LERR::MalformedNumber(result.into_iter().collect())) Token::LexError(LERR::MalformedNumber(result.into_iter().collect()))
}), })
start_pos, } else {
)); let out: String = result.iter().filter(|&&c| c != NUM_SEP).collect();
} else { let num = INT::from_str(&out).map(Token::IntegerConstant);
let out: String = result.iter().filter(|&&c| c != NUM_SEP).collect();
let num = INT::from_str(&out).map(Token::IntegerConstant);
// If integer parsing is unnecessary, try float instead // If integer parsing is unnecessary, try float instead
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
let num = let num =
num.or_else(|_| FloatWrapper::from_str(&out).map(Token::FloatConstant)); num.or_else(|_| FloatWrapper::from_str(&out).map(Token::FloatConstant));
// Then try decimal // Then try decimal
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
let num = num.or_else(|_| Decimal::from_str(&out).map(Token::DecimalConstant)); let num =
num.or_else(|_| Decimal::from_str(&out).map(Token::DecimalConstant));
// Then try decimal in scientific notation // Then try decimal in scientific notation
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
let num = let num = num.or_else(|_| {
num.or_else(|_| Decimal::from_scientific(&out).map(Token::DecimalConstant)); Decimal::from_scientific(&out).map(Token::DecimalConstant)
});
return Some((
num.unwrap_or_else(|_| { num.unwrap_or_else(|_| {
Token::LexError(LERR::MalformedNumber(result.into_iter().collect())) Token::LexError(LERR::MalformedNumber(result.into_iter().collect()))
}), })
start_pos, },
)); start_pos,
} ));
} }
// letter or underscore ... // letter or underscore ...
@ -1312,41 +1365,25 @@ fn get_next_token_inner(
// " - string literal // " - string literal
('"', _) => { ('"', _) => {
return parse_string_literal(stream, state, pos, c, true, false, false) return parse_string_literal(stream, state, pos, c, true, false, false, false)
.map_or_else( .map_or_else(
|err| Some((Token::LexError(err.0), err.1)), |(err, err_pos)| Some((Token::LexError(err), err_pos)),
|(result, _)| Some((Token::StringConstant(result), start_pos)), |(result, _)| Some((Token::StringConstant(result), start_pos)),
); );
} }
// ` - string literal // ` - string literal
('`', _) => { ('`', _) => {
// Start from the next line if ` at the end of line return parse_string_literal(stream, state, pos, c, false, true, true, true)
match stream.peek_next() { .map_or_else(
// `\r - start from next line |(err, err_pos)| Some((Token::LexError(err), err_pos)),
Some('\r') => { |(result, interpolated)| {
eat_next(stream, pos); if interpolated {
// `\r\n Some((Token::InterpolatedString(result), start_pos))
if stream.peek_next().map(|ch| ch == '\n').unwrap_or(false) { } else {
eat_next(stream, pos); Some((Token::StringConstant(result), start_pos))
} }
} },
// `\n - start from next line );
Some('\n') => {
eat_next(stream, pos);
}
_ => (),
}
return parse_string_literal(stream, state, pos, c, false, true, true).map_or_else(
|err| Some((Token::LexError(err.0), err.1)),
|(result, interpolated)| {
if interpolated {
Some((Token::InterpolatedString(result), start_pos))
} else {
Some((Token::StringConstant(result), start_pos))
}
},
);
} }
// ' - character literal // ' - character literal
@ -1358,19 +1395,20 @@ fn get_next_token_inner(
} }
('\'', _) => { ('\'', _) => {
return Some( return Some(
parse_string_literal(stream, state, pos, c, false, false, false).map_or_else( parse_string_literal(stream, state, pos, c, false, false, false, false)
|err| (Token::LexError(err.0), err.1), .map_or_else(
|(result, _)| { |(err, err_pos)| (Token::LexError(err), err_pos),
let mut chars = result.chars(); |(result, _)| {
let first = chars.next().unwrap(); let mut chars = result.chars();
let first = chars.next().unwrap();
if chars.next().is_some() { if chars.next().is_some() {
(Token::LexError(LERR::MalformedChar(result)), start_pos) (Token::LexError(LERR::MalformedChar(result)), start_pos)
} else { } else {
(Token::CharConstant(first), start_pos) (Token::CharConstant(first), start_pos)
} }
}, },
), ),
) )
} }
@ -1840,8 +1878,8 @@ pub struct TokenIterator<'a> {
state: TokenizeState, state: TokenizeState,
/// Current position. /// Current position.
pos: Position, pos: Position,
/// Buffer containing the next character to read, if any. /// External buffer containing the next character to read, if any.
buffer: Shared<Cell<Option<char>>>, tokenizer_control: TokenizerControl,
/// Input character stream. /// Input character stream.
stream: MultiInputsStream<'a>, stream: MultiInputsStream<'a>,
/// A processor function that maps a token to another. /// A processor function that maps a token to another.
@ -1852,9 +1890,14 @@ impl<'a> Iterator for TokenIterator<'a> {
type Item = (Token, Position); type Item = (Token, Position);
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
if let Some(ch) = self.buffer.take() { let mut control = self.tokenizer_control.get();
self.stream.unget(ch);
self.pos.rewind(); if control.is_within_text {
// Switch to text mode terminated by back-tick
self.state.is_within_text_terminated_by = Some('`');
// Reset it
control.is_within_text = false;
self.tokenizer_control.set(control);
} }
let (token, pos) = match get_next_token(&mut self.stream, &mut self.state, &mut self.pos) { let (token, pos) = match get_next_token(&mut self.stream, &mut self.state, &mut self.pos) {
@ -1945,7 +1988,7 @@ impl Engine {
pub fn lex<'a>( pub fn lex<'a>(
&'a self, &'a self,
input: impl IntoIterator<Item = &'a &'a str>, input: impl IntoIterator<Item = &'a &'a str>,
) -> (TokenIterator<'a>, Shared<Cell<Option<char>>>) { ) -> (TokenIterator<'a>, TokenizerControl) {
self.lex_raw(input, None) self.lex_raw(input, None)
} }
/// _(INTERNALS)_ Tokenize an input text stream with a mapping function. /// _(INTERNALS)_ Tokenize an input text stream with a mapping function.
@ -1956,7 +1999,7 @@ impl Engine {
&'a self, &'a self,
input: impl IntoIterator<Item = &'a &'a str>, input: impl IntoIterator<Item = &'a &'a str>,
map: fn(Token) -> Token, map: fn(Token) -> Token,
) -> (TokenIterator<'a>, Shared<Cell<Option<char>>>) { ) -> (TokenIterator<'a>, TokenizerControl) {
self.lex_raw(input, Some(map)) self.lex_raw(input, Some(map))
} }
/// Tokenize an input text stream with an optional mapping function. /// Tokenize an input text stream with an optional mapping function.
@ -1965,8 +2008,8 @@ impl Engine {
&'a self, &'a self,
input: impl IntoIterator<Item = &'a &'a str>, input: impl IntoIterator<Item = &'a &'a str>,
map: Option<fn(Token) -> Token>, map: Option<fn(Token) -> Token>,
) -> (TokenIterator<'a>, Shared<Cell<Option<char>>>) { ) -> (TokenIterator<'a>, TokenizerControl) {
let buffer: Shared<Cell<Option<char>>> = Cell::new(None).into(); let buffer: TokenizerControl = Default::default();
let buffer2 = buffer.clone(); let buffer2 = buffer.clone();
( (
@ -1982,9 +2025,10 @@ impl Engine {
end_with_none: false, end_with_none: false,
include_comments: false, include_comments: false,
disable_doc_comments: self.disable_doc_comments, disable_doc_comments: self.disable_doc_comments,
is_within_text_terminated_by: None,
}, },
pos: Position::new(1, 0), pos: Position::new(1, 0),
buffer, tokenizer_control: buffer,
stream: MultiInputsStream { stream: MultiInputsStream {
buf: None, buf: None,
streams: input.into_iter().map(|s| s.chars().peekable()).collect(), streams: input.into_iter().map(|s| s.chars().peekable()).collect(),

View File

@ -81,7 +81,7 @@ fn test_call_fn_args() -> Result<(), Box<EvalAltResult>> {
let ast = engine.compile( let ast = engine.compile(
r#" r#"
fn hello(x, y, z) { fn hello(x, y, z) {
if x { "hello " + y } else { y + z } if x { `hello ${y}` } else { y + z }
} }
"#, "#,
)?; )?;

View File

@ -1,5 +1,5 @@
#![cfg(not(feature = "no_function"))] #![cfg(not(feature = "no_function"))]
use rhai::{Engine, EvalAltResult, FnNamespace, Module, ParseErrorType, Shared, INT}; use rhai::{Engine, EvalAltResult, FnNamespace, Module, Shared, INT};
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[test] #[test]

View File

@ -35,8 +35,27 @@ fn test_map_indexing() -> Result<(), Box<EvalAltResult>> {
engine.eval::<INT>("let y = #{a: 1, b: 2, c: 3}; y.a = 5; y.a")?, engine.eval::<INT>("let y = #{a: 1, b: 2, c: 3}; y.a = 5; y.a")?,
5 5
); );
engine.eval::<()>("let y = #{a: 1, b: 2, c: 3}; y.z")?; engine.eval::<()>("let y = #{a: 1, b: 2, c: 3}; y.z")?;
#[cfg(not(feature = "no_index"))]
assert_eq!(
engine.eval::<INT>(
r#"
let y = #{`a
b`: 1}; y["a\nb"]
"#
)?,
1
);
assert!(matches!(
*engine
.eval::<INT>("let y = #{`a${1}`: 1}; y.a1")
.expect_err("should error"),
EvalAltResult::ErrorParsing(ParseErrorType::PropertyExpected, _)
));
assert!(engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "c" in y"#)?); assert!(engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "c" in y"#)?);
assert!(engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "b" in y"#)?); assert!(engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "b" in y"#)?);
assert!(!engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "z" in y"#)?); assert!(!engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "z" in y"#)?);

View File

@ -315,7 +315,7 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
// Final variable values become constant module variable values // Final variable values become constant module variable values
foo = calc(foo); foo = calc(foo);
hello = "hello, " + foo + " worlds!"; hello = `hello, ${foo} worlds!`;
export export
x as abc, x as abc,

View File

@ -48,6 +48,7 @@ fn test_optimizer_run() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[cfg(not(feature = "no_module"))]
#[test] #[test]
fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> { fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
@ -55,24 +56,33 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?; let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?;
assert!(format!("{:?}", ast).starts_with( assert_eq!(
r#"AST { source: None, body: [Expr(IntegerConstant(123, 1:53))], functions: Module("# format!("{:?}", ast),
)); "AST { source: None, body: [Expr(123 @ 1:53)], functions: Module, resolver: None }"
);
let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?; let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?;
assert!(format!("{:?}", ast).starts_with(r#"AST { source: None, body: [Const(BoolConstant(false, 1:18), Ident("DECISION" @ 1:7), false, 1:1), Expr(IntegerConstant(123, 1:51))], functions: Module("#)); assert_eq!(
format!("{:?}", ast),
r#"AST { source: None, body: [Const(false @ 1:18, "DECISION" @ 1:7, false, 1:1), Expr(123 @ 1:51)], functions: Module, resolver: None }"#
);
let ast = engine.compile("if 1 == 2 { 42 }")?; let ast = engine.compile("if 1 == 2 { 42 }")?;
assert!(format!("{:?}", ast).starts_with("AST { source: None, body: [], functions: Module(")); assert_eq!(
format!("{:?}", ast),
"AST { source: None, body: [], functions: Module, resolver: None }"
);
engine.set_optimization_level(OptimizationLevel::Full); engine.set_optimization_level(OptimizationLevel::Full);
let ast = engine.compile("abs(-42)")?; let ast = engine.compile("abs(-42)")?;
assert!(format!("{:?}", ast) assert_eq!(
.starts_with(r"AST { source: None, body: [Expr(IntegerConstant(42, 1:1))]")); format!("{:?}", ast),
"AST { source: None, body: [Expr(42 @ 1:1)], functions: Module, resolver: None }"
);
Ok(()) Ok(())
} }

View File

@ -10,6 +10,8 @@ use serde::{Deserialize, Serialize};
use rhai::Array; use rhai::Array;
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use rhai::Map; use rhai::Map;
#[cfg(not(feature = "no_float"))]
use rhai::FLOAT;
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
use rust_decimal::Decimal; use rust_decimal::Decimal;
@ -358,7 +360,7 @@ fn test_serde_de_primary_types() -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{ {
assert_eq!(123.456, from_dynamic::<f64>(&123.456_f64.into())?); assert_eq!(123.456, from_dynamic::<FLOAT>(&123.456.into())?);
assert_eq!(123.456, from_dynamic::<f32>(&Dynamic::from(123.456_f32))?); assert_eq!(123.456, from_dynamic::<f32>(&Dynamic::from(123.456_f32))?);
} }
@ -447,8 +449,8 @@ fn test_serde_de_struct() -> Result<(), Box<EvalAltResult>> {
fn test_serde_de_script() -> Result<(), Box<EvalAltResult>> { fn test_serde_de_script() -> Result<(), Box<EvalAltResult>> {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct Point { struct Point {
x: f64, x: FLOAT,
y: f64, y: FLOAT,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]

View File

@ -325,6 +325,16 @@ fn test_string_interpolated() -> Result<(), Box<EvalAltResult>> {
"hello 42 worlds!" "hello 42 worlds!"
); );
assert_eq!(
engine.eval::<String>(
r#"
let x = 40;
"hello ${x+2} worlds!"
"#
)?,
"hello ${x+2} worlds!"
);
assert_eq!( assert_eq!(
engine.eval::<String>( engine.eval::<String>(
r" r"