New custom syntax expression types.

This commit is contained in:
Stephen Chung 2021-06-10 10:16:39 +08:00
parent c3eb6d65f6
commit a5031969ca
7 changed files with 113 additions and 28 deletions

View File

@ -20,6 +20,7 @@ New features
* New syntax for `for` statement to include counter variable. * New syntax for `for` statement to include counter variable.
* An integer value can now be indexed to get/set a single bit. * An integer value can now be indexed to get/set a single bit.
* The `bits` method of an integer can be used to iterate through its bits. * The `bits` method of an integer can be used to iterate through its bits.
* New `$bool$`, `$int$`, `$float$` and `$string$` expression types for custom syntax.
Version 0.20.2 Version 0.20.2

View File

@ -1819,11 +1819,11 @@ impl fmt::Debug for Expr {
} }
impl Expr { impl Expr {
/// Get the [`Dynamic`] value of a constant expression. /// Get the [`Dynamic`] value of a literal constant expression.
/// ///
/// Returns [`None`] if the expression is not constant. /// Returns [`None`] if the expression is not a literal constant.
#[inline] #[inline]
pub fn get_constant_value(&self) -> Option<Dynamic> { pub fn get_literal_value(&self) -> Option<Dynamic> {
Some(match self { Some(match self {
Self::DynamicConstant(x, _) => x.as_ref().clone(), Self::DynamicConstant(x, _) => x.as_ref().clone(),
Self::IntegerConstant(x, _) => (*x).into(), Self::IntegerConstant(x, _) => (*x).into(),
@ -1838,7 +1838,7 @@ impl Expr {
Self::Array(x, _) if self.is_constant() => { Self::Array(x, _) if self.is_constant() => {
let mut arr = Array::with_capacity(x.len()); let mut arr = Array::with_capacity(x.len());
arr.extend(x.iter().map(|v| { arr.extend(x.iter().map(|v| {
v.get_constant_value() v.get_literal_value()
.expect("never fails because a constant array always has a constant value") .expect("never fails because a constant array always has a constant value")
})); }));
Dynamic::from_array(arr) Dynamic::from_array(arr)
@ -1850,7 +1850,7 @@ impl Expr {
x.0.iter().for_each(|(k, v)| { x.0.iter().for_each(|(k, v)| {
*map.get_mut(k.name.as_str()) *map.get_mut(k.name.as_str())
.expect("never fails because the template should contain all the keys") = v .expect("never fails because the template should contain all the keys") = v
.get_constant_value() .get_literal_value()
.expect("never fails because a constant map always has a constant value") .expect("never fails because a constant map always has a constant value")
}); });
Dynamic::from_map(map) Dynamic::from_map(map)

View File

@ -208,7 +208,7 @@ fn optimize_stmt_block(
state.push_var( state.push_var(
&x.name, &x.name,
AccessMode::ReadOnly, AccessMode::ReadOnly,
value_expr.get_constant_value(), value_expr.get_literal_value(),
); );
} }
} }
@ -454,7 +454,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
// switch const { ... } // switch const { ... }
Stmt::Switch(match_expr, x, pos) if match_expr.is_constant() => { Stmt::Switch(match_expr, x, pos) if match_expr.is_constant() => {
let value = match_expr.get_constant_value().unwrap(); let value = match_expr.get_literal_value().unwrap();
let hasher = &mut get_hasher(); let hasher = &mut get_hasher();
value.hash(hasher); value.hash(hasher);
let hash = hasher.finish(); let hash = hasher.finish();
@ -841,7 +841,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Array(_, _) if expr.is_constant() => { Expr::Array(_, _) if expr.is_constant() => {
state.set_dirty(); state.set_dirty();
*expr = Expr::DynamicConstant(Box::new(expr.get_constant_value().unwrap()), expr.position()); *expr = Expr::DynamicConstant(Box::new(expr.get_literal_value().unwrap()), expr.position());
} }
// [ items .. ] // [ items .. ]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -850,7 +850,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Map(_, _) if expr.is_constant() => { Expr::Map(_, _) if expr.is_constant() => {
state.set_dirty(); state.set_dirty();
*expr = Expr::DynamicConstant(Box::new(expr.get_constant_value().unwrap()), expr.position()); *expr = Expr::DynamicConstant(Box::new(expr.get_literal_value().unwrap()), expr.position());
} }
// #{ key:value, .. } // #{ key:value, .. }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -942,7 +942,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) {
=> { => {
let mut arg_values: StaticVec<_> = x.args.iter().map(|e| match e { let mut arg_values: StaticVec<_> = x.args.iter().map(|e| match e {
Expr::Stack(slot, _) => x.constants[*slot].clone(), Expr::Stack(slot, _) => x.constants[*slot].clone(),
_ => e.get_constant_value().unwrap() _ => e.get_literal_value().unwrap()
}).collect(); }).collect();
let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect(); let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect();
@ -968,7 +968,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) {
// Move constant arguments // Move constant arguments
for arg in x.args.iter_mut() { for arg in x.args.iter_mut() {
if let Some(value) = arg.get_constant_value() { if let Some(value) = arg.get_literal_value() {
state.set_dirty(); state.set_dirty();
x.constants.push(value); x.constants.push(value);
*arg = Expr::Stack(x.constants.len()-1, arg.position()); *arg = Expr::Stack(x.constants.len()-1, arg.position());
@ -991,7 +991,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) {
if !has_script_fn { if !has_script_fn {
let mut arg_values: StaticVec<_> = x.args.iter().map(|e| match e { let mut arg_values: StaticVec<_> = x.args.iter().map(|e| match e {
Expr::Stack(slot, _) => x.constants[*slot].clone(), Expr::Stack(slot, _) => x.constants[*slot].clone(),
_ => e.get_constant_value().unwrap() _ => e.get_literal_value().unwrap()
}).collect(); }).collect();
// Save the typename of the first argument if it is `type_of()` // Save the typename of the first argument if it is `type_of()`
@ -1025,7 +1025,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) {
optimize_expr(arg, state, false); optimize_expr(arg, state, false);
// Move constant arguments // Move constant arguments
if let Some(value) = arg.get_constant_value() { if let Some(value) = arg.get_literal_value() {
state.set_dirty(); state.set_dirty();
x.constants.push(value); x.constants.push(value);
*arg = Expr::Stack(x.constants.len()-1, arg.position()); *arg = Expr::Stack(x.constants.len()-1, arg.position());

View File

@ -93,6 +93,8 @@ pub enum ParseErrorType {
UnknownOperator(String), UnknownOperator(String),
/// Expecting a particular token but not finding one. Wrapped values are the token and description. /// Expecting a particular token but not finding one. Wrapped values are the token and description.
MissingToken(String, String), MissingToken(String, String),
/// Expecting a particular symbol but not finding one. Wrapped value is the description.
MissingSymbol(String),
/// An expression in function call arguments `()` has syntax error. Wrapped value is the error /// An expression in function call arguments `()` has syntax error. Wrapped value is the error
/// description (if any). /// description (if any).
MalformedCallExpr(String), MalformedCallExpr(String),
@ -196,6 +198,7 @@ impl ParseErrorType {
Self::BadInput(err) => err.desc(), Self::BadInput(err) => err.desc(),
Self::UnknownOperator(_) => "Unknown operator", Self::UnknownOperator(_) => "Unknown operator",
Self::MissingToken(_, _) => "Expecting a certain token that is missing", Self::MissingToken(_, _) => "Expecting a certain token that is missing",
Self::MissingSymbol(_) => "Expecting a certain symbol that is missing",
Self::MalformedCallExpr(_) => "Invalid expression in function call arguments", Self::MalformedCallExpr(_) => "Invalid expression in function call arguments",
Self::MalformedIndexExpr(_) => "Invalid index in indexing expression", Self::MalformedIndexExpr(_) => "Invalid index in indexing expression",
Self::MalformedInExpr(_) => "Invalid 'in' expression", Self::MalformedInExpr(_) => "Invalid 'in' expression",
@ -268,6 +271,7 @@ impl fmt::Display for ParseErrorType {
} }
Self::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s), Self::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s),
Self::MissingSymbol(s) => f.write_str(s),
Self::AssignmentToConstant(s) if s.is_empty() => f.write_str(self.desc()), Self::AssignmentToConstant(s) if s.is_empty() => f.write_str(self.desc()),
Self::AssignmentToConstant(s) => write!(f, "Cannot assign to constant '{}'", s), Self::AssignmentToConstant(s) => write!(f, "Cannot assign to constant '{}'", s),

View File

@ -9,7 +9,10 @@ use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
use crate::module::NamespaceRef; use crate::module::NamespaceRef;
use crate::optimize::optimize_into_ast; use crate::optimize::optimize_into_ast;
use crate::optimize::OptimizationLevel; use crate::optimize::OptimizationLevel;
use crate::syntax::{CustomSyntax, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; use crate::syntax::{
CustomSyntax, MARKER_BLOCK, MARKER_BOOL, MARKER_EXPR, MARKER_IDENT, MARKER_INT, MARKER_STRING,
};
use crate::token::{ use crate::token::{
is_keyword_function, is_valid_identifier, Token, TokenStream, TokenizerControl, is_keyword_function, is_valid_identifier, Token, TokenStream, TokenizerControl,
}; };
@ -27,7 +30,7 @@ use std::{
}; };
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::FLOAT; use crate::{syntax::MARKER_FLOAT, FLOAT};
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
use crate::FnAccess; use crate::FnAccess;
@ -876,7 +879,7 @@ fn parse_switch(
}; };
let hash = if let Some(expr) = expr { let hash = if let Some(expr) = expr {
if let Some(value) = expr.get_constant_value() { if let Some(value) = expr.get_literal_value() {
let hasher = &mut get_hasher(); let hasher = &mut get_hasher();
value.hash(hasher); value.hash(hasher);
let hash = hasher.finish(); let hash = hasher.finish();
@ -1917,6 +1920,60 @@ fn parse_custom_syntax(
} }
stmt => unreachable!("expecting Stmt::Block, but gets {:?}", stmt), stmt => unreachable!("expecting Stmt::Block, but gets {:?}", stmt),
}, },
MARKER_BOOL => match input.next().expect(NEVER_ENDS) {
(b @ Token::True, pos) | (b @ Token::False, pos) => {
keywords.push(Expr::BoolConstant(b == Token::True, pos));
let keyword = state.get_identifier(MARKER_BOOL);
segments.push(keyword.clone().into());
tokens.push(keyword);
}
(_, pos) => {
return Err(
PERR::MissingSymbol("Expecting 'true' or 'false'".to_string())
.into_err(pos),
)
}
},
MARKER_INT => match input.next().expect(NEVER_ENDS) {
(Token::IntegerConstant(i), pos) => {
keywords.push(Expr::IntegerConstant(i, pos));
let keyword = state.get_identifier(MARKER_INT);
segments.push(keyword.clone().into());
tokens.push(keyword);
}
(_, pos) => {
return Err(
PERR::MissingSymbol("Expecting an integer number".to_string())
.into_err(pos),
)
}
},
#[cfg(not(feature = "no_float"))]
MARKER_FLOAT => match input.next().expect(NEVER_ENDS) {
(Token::FloatConstant(f), pos) => {
keywords.push(Expr::FloatConstant(f, pos));
let keyword = state.get_identifier(MARKER_FLOAT);
segments.push(keyword.clone().into());
tokens.push(keyword);
}
(_, pos) => {
return Err(PERR::MissingSymbol(
"Expecting a floating-point number".to_string(),
)
.into_err(pos))
}
},
MARKER_STRING => match input.next().expect(NEVER_ENDS) {
(Token::StringConstant(s), pos) => {
keywords.push(Expr::StringConstant(state.get_identifier(s).into(), pos));
let keyword = state.get_identifier(MARKER_STRING);
segments.push(keyword.clone().into());
tokens.push(keyword);
}
(_, pos) => {
return Err(PERR::MissingSymbol("Expecting a string".to_string()).into_err(pos))
}
},
s => match input.next().expect(NEVER_ENDS) { s => match input.next().expect(NEVER_ENDS) {
(Token::LexError(err), pos) => return Err(err.into_err(pos)), (Token::LexError(err), pos) => return Err(err.into_err(pos)),
(t, _) if t.syntax().as_ref() == s => { (t, _) if t.syntax().as_ref() == s => {

View File

@ -5,8 +5,8 @@ use crate::engine::EvalContext;
use crate::fn_native::SendSync; use crate::fn_native::SendSync;
use crate::token::{is_valid_identifier, Token}; use crate::token::{is_valid_identifier, Token};
use crate::{ use crate::{
Engine, Identifier, ImmutableString, LexError, ParseError, Position, RhaiResult, Shared, Dynamic, Engine, Identifier, ImmutableString, LexError, ParseError, Position, RhaiResult,
StaticVec, Shared, StaticVec,
}; };
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -14,6 +14,11 @@ use std::prelude::v1::*;
pub const MARKER_EXPR: &str = "$expr$"; pub const MARKER_EXPR: &str = "$expr$";
pub const MARKER_BLOCK: &str = "$block$"; pub const MARKER_BLOCK: &str = "$block$";
pub const MARKER_IDENT: &str = "$ident$"; pub const MARKER_IDENT: &str = "$ident$";
pub const MARKER_STRING: &str = "$string$";
pub const MARKER_INT: &str = "$int$";
#[cfg(not(feature = "no_float"))]
pub const MARKER_FLOAT: &str = "$float$";
pub const MARKER_BOOL: &str = "$bool$";
/// A general expression evaluation trait object. /// A general expression evaluation trait object.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
@ -58,6 +63,11 @@ impl Expression<'_> {
pub fn position(&self) -> Position { pub fn position(&self) -> Position {
self.0.position() self.0.position()
} }
/// Get the value of this expression if it is a literal constant.
#[inline(always)]
pub fn get_literal_value(&self) -> Option<Dynamic> {
self.0.get_literal_value()
}
} }
impl EvalContext<'_, '_, '_, '_, '_, '_, '_> { impl EvalContext<'_, '_, '_, '_, '_, '_, '_> {
@ -132,7 +142,15 @@ impl Engine {
let seg = match s { let seg = match s {
// Markers not in first position // Markers not in first position
MARKER_IDENT | MARKER_EXPR | MARKER_BLOCK if !segments.is_empty() => s.into(), MARKER_IDENT | MARKER_EXPR | MARKER_BLOCK | MARKER_BOOL | MARKER_INT
| MARKER_STRING
if !segments.is_empty() =>
{
s.into()
}
// Markers not in first position
#[cfg(not(feature = "no_float"))]
MARKER_FLOAT if !segments.is_empty() => s.into(),
// Standard or reserved keyword/symbol not in first position // Standard or reserved keyword/symbol not in first position
s if !segments.is_empty() && token.is_some() => { s if !segments.is_empty() && token.is_some() => {
// Make it a custom keyword/symbol if it is disabled or reserved // Make it a custom keyword/symbol if it is disabled or reserved

View File

@ -19,19 +19,24 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
engine.register_custom_syntax( engine.register_custom_syntax(
&[ &[
"exec", "[", "$ident$", "]", "->", "$block$", "while", "$expr$", "exec", "[", "$ident$", ";", "$int$", "]", "->", "$block$", "while", "$expr$",
], ],
true, true,
|context, inputs| { |context, inputs| {
let var_name = inputs[0].get_variable_name().unwrap().to_string(); let var_name = inputs[0].get_variable_name().unwrap().to_string();
let stmt = inputs.get(1).unwrap(); let max = inputs[1].get_literal_value().unwrap().as_int().unwrap();
let condition = inputs.get(2).unwrap(); let stmt = inputs.get(2).unwrap();
let condition = inputs.get(3).unwrap();
context.scope_mut().push(var_name.clone(), 0 as INT); context.scope_mut().push(var_name.clone(), 0 as INT);
let mut count: INT = 0; let mut count: INT = 0;
loop { loop {
if count >= max {
break;
}
context.eval_expression_tree(stmt)?; context.eval_expression_tree(stmt)?;
count += 1; count += 1;
@ -63,17 +68,17 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
engine.eval::<INT>( engine.eval::<INT>(
" "
let x = 0; let x = 0;
let foo = (exec [x] -> { x += 2 } while x < 42) * 10; let foo = (exec [x;15] -> { x += 2 } while x < 42) * 10;
foo foo
" "
)?, )?,
210 150
); );
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(
" "
let x = 0; let x = 0;
exec [x] -> { x += 1 } while x < 42; exec [x;100] -> { x += 1 } while x < 42;
x x
" "
)?, )?,
@ -82,7 +87,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(
" "
exec [x] -> { x += 1 } while x < 42; exec [x;100] -> { x += 1 } while x < 42;
x x
" "
)?, )?,
@ -92,11 +97,11 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
engine.eval::<INT>( engine.eval::<INT>(
" "
let foo = 123; let foo = 123;
exec [x] -> { x += 1 } while x < 42; exec [x;15] -> { x += 1 } while x < 42;
foo + x + x1 + x2 + x3 foo + x + x1 + x2 + x3
" "
)?, )?,
171 144
); );
// The first symbol must be an identifier // The first symbol must be an identifier