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.
* 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.
* New `$bool$`, `$int$`, `$float$` and `$string$` expression types for custom syntax.
Version 0.20.2

View File

@ -1819,11 +1819,11 @@ impl fmt::Debug for 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]
pub fn get_constant_value(&self) -> Option<Dynamic> {
pub fn get_literal_value(&self) -> Option<Dynamic> {
Some(match self {
Self::DynamicConstant(x, _) => x.as_ref().clone(),
Self::IntegerConstant(x, _) => (*x).into(),
@ -1838,7 +1838,7 @@ impl Expr {
Self::Array(x, _) if self.is_constant() => {
let mut arr = Array::with_capacity(x.len());
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")
}));
Dynamic::from_array(arr)
@ -1850,7 +1850,7 @@ impl Expr {
x.0.iter().for_each(|(k, v)| {
*map.get_mut(k.name.as_str())
.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")
});
Dynamic::from_map(map)

View File

@ -208,7 +208,7 @@ fn optimize_stmt_block(
state.push_var(
&x.name,
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 { ... }
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();
value.hash(hasher);
let hash = hasher.finish();
@ -841,7 +841,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) {
#[cfg(not(feature = "no_index"))]
Expr::Array(_, _) if expr.is_constant() => {
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 .. ]
#[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"))]
Expr::Map(_, _) if expr.is_constant() => {
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, .. }
#[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 {
Expr::Stack(slot, _) => x.constants[*slot].clone(),
_ => e.get_constant_value().unwrap()
_ => e.get_literal_value().unwrap()
}).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
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();
x.constants.push(value);
*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 {
let mut arg_values: StaticVec<_> = x.args.iter().map(|e| match e {
Expr::Stack(slot, _) => x.constants[*slot].clone(),
_ => e.get_constant_value().unwrap()
_ => e.get_literal_value().unwrap()
}).collect();
// 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);
// Move constant arguments
if let Some(value) = arg.get_constant_value() {
if let Some(value) = arg.get_literal_value() {
state.set_dirty();
x.constants.push(value);
*arg = Expr::Stack(x.constants.len()-1, arg.position());

View File

@ -93,6 +93,8 @@ pub enum ParseErrorType {
UnknownOperator(String),
/// Expecting a particular token but not finding one. Wrapped values are the token and description.
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
/// description (if any).
MalformedCallExpr(String),
@ -196,6 +198,7 @@ impl ParseErrorType {
Self::BadInput(err) => err.desc(),
Self::UnknownOperator(_) => "Unknown operator",
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::MalformedIndexExpr(_) => "Invalid index in indexing 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::MissingSymbol(s) => f.write_str(s),
Self::AssignmentToConstant(s) if s.is_empty() => f.write_str(self.desc()),
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::optimize::optimize_into_ast;
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::{
is_keyword_function, is_valid_identifier, Token, TokenStream, TokenizerControl,
};
@ -27,7 +30,7 @@ use std::{
};
#[cfg(not(feature = "no_float"))]
use crate::FLOAT;
use crate::{syntax::MARKER_FLOAT, FLOAT};
#[cfg(not(feature = "no_function"))]
use crate::FnAccess;
@ -876,7 +879,7 @@ fn parse_switch(
};
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();
value.hash(hasher);
let hash = hasher.finish();
@ -1917,6 +1920,60 @@ fn parse_custom_syntax(
}
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) {
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
(t, _) if t.syntax().as_ref() == s => {

View File

@ -5,8 +5,8 @@ use crate::engine::EvalContext;
use crate::fn_native::SendSync;
use crate::token::{is_valid_identifier, Token};
use crate::{
Engine, Identifier, ImmutableString, LexError, ParseError, Position, RhaiResult, Shared,
StaticVec,
Dynamic, Engine, Identifier, ImmutableString, LexError, ParseError, Position, RhaiResult,
Shared, StaticVec,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -14,6 +14,11 @@ use std::prelude::v1::*;
pub const MARKER_EXPR: &str = "$expr$";
pub const MARKER_BLOCK: &str = "$block$";
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.
#[cfg(not(feature = "sync"))]
@ -58,6 +63,11 @@ impl Expression<'_> {
pub fn position(&self) -> 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<'_, '_, '_, '_, '_, '_, '_> {
@ -132,7 +142,15 @@ impl Engine {
let seg = match s {
// 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
s if !segments.is_empty() && token.is_some() => {
// 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(
&[
"exec", "[", "$ident$", "]", "->", "$block$", "while", "$expr$",
"exec", "[", "$ident$", ";", "$int$", "]", "->", "$block$", "while", "$expr$",
],
true,
|context, inputs| {
let var_name = inputs[0].get_variable_name().unwrap().to_string();
let stmt = inputs.get(1).unwrap();
let condition = inputs.get(2).unwrap();
let max = inputs[1].get_literal_value().unwrap().as_int().unwrap();
let stmt = inputs.get(2).unwrap();
let condition = inputs.get(3).unwrap();
context.scope_mut().push(var_name.clone(), 0 as INT);
let mut count: INT = 0;
loop {
if count >= max {
break;
}
context.eval_expression_tree(stmt)?;
count += 1;
@ -63,17 +68,17 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
engine.eval::<INT>(
"
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
"
)?,
210
150
);
assert_eq!(
engine.eval::<INT>(
"
let x = 0;
exec [x] -> { x += 1 } while x < 42;
exec [x;100] -> { x += 1 } while x < 42;
x
"
)?,
@ -82,7 +87,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<INT>(
"
exec [x] -> { x += 1 } while x < 42;
exec [x;100] -> { x += 1 } while x < 42;
x
"
)?,
@ -92,11 +97,11 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
engine.eval::<INT>(
"
let foo = 123;
exec [x] -> { x += 1 } while x < 42;
exec [x;15] -> { x += 1 } while x < 42;
foo + x + x1 + x2 + x3
"
)?,
171
144
);
// The first symbol must be an identifier