New custom syntax expression types.
This commit is contained in:
parent
c3eb6d65f6
commit
a5031969ca
@ -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
|
||||
|
10
src/ast.rs
10
src/ast.rs
@ -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)
|
||||
|
@ -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());
|
||||
|
@ -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),
|
||||
|
@ -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 => {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user