From a5031969ca7a9d7c33029674990306ca323b14aa Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 10 Jun 2021 10:16:39 +0800 Subject: [PATCH] New custom syntax expression types. --- CHANGELOG.md | 1 + src/ast.rs | 10 ++++---- src/optimize.rs | 16 ++++++------ src/parse_error.rs | 4 +++ src/parser.rs | 63 +++++++++++++++++++++++++++++++++++++++++++--- src/syntax.rs | 24 +++++++++++++++--- tests/syntax.rs | 23 ++++++++++------- 7 files changed, 113 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e4cfb75..844eefc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/ast.rs b/src/ast.rs index a6b3fdb7..1834a965 100644 --- a/src/ast.rs +++ b/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 { + pub fn get_literal_value(&self) -> Option { 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) diff --git a/src/optimize.rs b/src/optimize.rs index 2ce023fa..7e1d6d40 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -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()); diff --git a/src/parse_error.rs b/src/parse_error.rs index ea32e35e..468cbd79 100644 --- a/src/parse_error.rs +++ b/src/parse_error.rs @@ -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), diff --git a/src/parser.rs b/src/parser.rs index 0d1e4f74..998dec80 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -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 => { diff --git a/src/syntax.rs b/src/syntax.rs index 34632f9e..be664bcf 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -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 { + 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 diff --git a/tests/syntax.rs b/tests/syntax.rs index 1b8857e9..8fbeedb5 100644 --- a/tests/syntax.rs +++ b/tests/syntax.rs @@ -19,19 +19,24 @@ fn test_custom_syntax() -> Result<(), Box> { 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> { engine.eval::( " 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::( " 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> { assert_eq!( engine.eval::( " - 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> { engine.eval::( " 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