Allow AST optimization based on external Scope.
This commit is contained in:
parent
9844ae8665
commit
b3a22d942a
@ -73,7 +73,7 @@ fn main() {
|
||||
}
|
||||
|
||||
if let Err(err) = engine
|
||||
.compile(&input)
|
||||
.compile_with_scope(&scope, &input)
|
||||
.map_err(EvalAltResult::ErrorParsing)
|
||||
.and_then(|r| {
|
||||
ast = Some(r);
|
||||
|
@ -1,6 +1,6 @@
|
||||
// This is a script to calculate prime numbers.
|
||||
|
||||
let MAX_NUMBER_TO_CHECK = 10000; // 1229 primes
|
||||
const MAX_NUMBER_TO_CHECK = 10000; // 1229 primes
|
||||
|
||||
let prime_mask = [];
|
||||
prime_mask.pad(MAX_NUMBER_TO_CHECK, true);
|
||||
|
26
src/api.rs
26
src/api.rs
@ -100,8 +100,14 @@ impl<'e> Engine<'e> {
|
||||
|
||||
/// Compile a string into an AST.
|
||||
pub fn compile(&self, input: &str) -> Result<AST, ParseError> {
|
||||
self.compile_with_scope(&Scope::new(), input)
|
||||
}
|
||||
|
||||
/// Compile a string into an AST using own scope.
|
||||
/// The scope is useful for passing constants into the script for optimization.
|
||||
pub fn compile_with_scope(&self, scope: &Scope, input: &str) -> Result<AST, ParseError> {
|
||||
let tokens_stream = lex(input);
|
||||
parse(&mut tokens_stream.peekable(), self.optimize)
|
||||
parse(&mut tokens_stream.peekable(), scope, self.optimize)
|
||||
}
|
||||
|
||||
fn read_file(filename: &str) -> Result<String, EvalAltResult> {
|
||||
@ -117,8 +123,20 @@ impl<'e> Engine<'e> {
|
||||
|
||||
/// Compile a file into an AST.
|
||||
pub fn compile_file(&self, filename: &str) -> Result<AST, EvalAltResult> {
|
||||
Self::read_file(filename)
|
||||
.and_then(|contents| self.compile(&contents).map_err(|err| err.into()))
|
||||
self.compile_file_with_scope(&Scope::new(), filename)
|
||||
}
|
||||
|
||||
/// Compile a file into an AST using own scope.
|
||||
/// The scope is useful for passing constants into the script for optimization.
|
||||
pub fn compile_file_with_scope(
|
||||
&self,
|
||||
scope: &Scope,
|
||||
filename: &str,
|
||||
) -> Result<AST, EvalAltResult> {
|
||||
Self::read_file(filename).and_then(|contents| {
|
||||
self.compile_with_scope(scope, &contents)
|
||||
.map_err(|err| err.into())
|
||||
})
|
||||
}
|
||||
|
||||
/// Evaluate a file.
|
||||
@ -241,7 +259,7 @@ impl<'e> Engine<'e> {
|
||||
) -> Result<(), EvalAltResult> {
|
||||
let tokens_stream = lex(input);
|
||||
|
||||
let ast = parse(&mut tokens_stream.peekable(), self.optimize)
|
||||
let ast = parse(&mut tokens_stream.peekable(), scope, self.optimize)
|
||||
.map_err(EvalAltResult::ErrorParsing)?;
|
||||
|
||||
self.consume_ast_with_scope(scope, retain_functions, &ast)
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::engine::KEYWORD_DUMP_AST;
|
||||
use crate::parser::{Expr, Stmt};
|
||||
use crate::scope::{Scope, ScopeEntry, VariableType};
|
||||
|
||||
struct State {
|
||||
changed: bool,
|
||||
@ -330,13 +331,27 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn optimize(statements: Vec<Stmt>) -> Vec<Stmt> {
|
||||
pub(crate) fn optimize(statements: Vec<Stmt>, scope: &Scope) -> Vec<Stmt> {
|
||||
let mut result = statements;
|
||||
|
||||
loop {
|
||||
let mut state = State::new();
|
||||
let num_statements = result.len();
|
||||
|
||||
scope
|
||||
.iter()
|
||||
.filter(|ScopeEntry { var_type, expr, .. }| {
|
||||
// Get all the constants with definite constant expressions
|
||||
*var_type == VariableType::Constant
|
||||
&& expr.as_ref().map(|e| e.is_constant()).unwrap_or(false)
|
||||
})
|
||||
.for_each(|ScopeEntry { name, expr, .. }| {
|
||||
state.push_constant(
|
||||
name.as_ref(),
|
||||
expr.as_ref().expect("should be Some(expr)").clone(),
|
||||
)
|
||||
});
|
||||
|
||||
result = result
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
|
@ -2,7 +2,8 @@
|
||||
|
||||
use crate::any::Dynamic;
|
||||
use crate::error::{LexError, ParseError, ParseErrorType};
|
||||
use crate::{optimize::optimize, scope::VariableType};
|
||||
use crate::optimize::optimize;
|
||||
use crate::scope::{Scope, VariableType};
|
||||
|
||||
use std::{
|
||||
borrow::Cow, char, cmp::Ordering, fmt, iter::Peekable, str::Chars, str::FromStr, sync::Arc,
|
||||
@ -148,6 +149,42 @@ pub struct AST(
|
||||
#[cfg(not(feature = "no_function"))] pub(crate) Vec<Arc<FnDef>>,
|
||||
);
|
||||
|
||||
impl AST {
|
||||
/// Optimize the AST with constants defined in an external Scope.
|
||||
///
|
||||
/// Although optimization is performed by default during compilation, sometimes it is necessary to
|
||||
/// "re"-optimize an AST. For example, when working with constants that are passed in via an
|
||||
/// external scope, it will be more efficient to optimize the AST once again to take advantage
|
||||
/// of the new constants.
|
||||
///
|
||||
/// With this method, it is no longer necessary to regenerate a large script with hard-coded
|
||||
/// constant values. The script AST can be compiled once and stored. During actually evaluation,
|
||||
/// constants are passed into the Engine via an external scope (i.e. with `scope.push_constant(...)`).
|
||||
/// Then, the AST is cloned and the copy re-optimized before running.
|
||||
pub fn optimize(self, scope: &Scope) -> Self {
|
||||
AST(
|
||||
crate::optimize::optimize(self.0, scope),
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
self.1
|
||||
.into_iter()
|
||||
.map(|fn_def| {
|
||||
let pos = fn_def.body.position();
|
||||
let body = optimize(vec![fn_def.body.clone()], scope)
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap_or_else(|| Stmt::Noop(pos));
|
||||
Arc::new(FnDef {
|
||||
name: fn_def.name.clone(),
|
||||
params: fn_def.params.clone(),
|
||||
body,
|
||||
pos: fn_def.pos,
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)] // Do not derive Clone because it is expensive
|
||||
pub struct FnDef {
|
||||
pub name: String,
|
||||
@ -2043,6 +2080,7 @@ fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseE
|
||||
|
||||
fn parse_top_level<'a>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
scope: &Scope,
|
||||
optimize_ast: bool,
|
||||
) -> Result<AST, ParseError> {
|
||||
let mut statements = Vec::<Stmt>::new();
|
||||
@ -2073,7 +2111,7 @@ fn parse_top_level<'a>(
|
||||
|
||||
return Ok(AST(
|
||||
if optimize_ast {
|
||||
optimize(statements)
|
||||
optimize(statements, &scope)
|
||||
} else {
|
||||
statements
|
||||
},
|
||||
@ -2083,7 +2121,7 @@ fn parse_top_level<'a>(
|
||||
.map(|mut fn_def| {
|
||||
if optimize_ast {
|
||||
let pos = fn_def.body.position();
|
||||
let mut body = optimize(vec![fn_def.body]);
|
||||
let mut body = optimize(vec![fn_def.body], &scope);
|
||||
fn_def.body = body.pop().unwrap_or_else(|| Stmt::Noop(pos));
|
||||
}
|
||||
Arc::new(fn_def)
|
||||
@ -2094,7 +2132,8 @@ fn parse_top_level<'a>(
|
||||
|
||||
pub fn parse<'a>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
scope: &Scope,
|
||||
optimize_ast: bool,
|
||||
) -> Result<AST, ParseError> {
|
||||
parse_top_level(input, optimize_ast)
|
||||
parse_top_level(input, scope, optimize_ast)
|
||||
}
|
||||
|
183
src/scope.rs
183
src/scope.rs
@ -1,6 +1,10 @@
|
||||
//! Module that defines the `Scope` type representing a function call-stack scope.
|
||||
|
||||
use crate::any::{Any, Dynamic};
|
||||
use crate::any::{Any, AnyExt, Dynamic};
|
||||
use crate::parser::{Expr, Position, INT};
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
use crate::parser::FLOAT;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
@ -10,6 +14,13 @@ pub enum VariableType {
|
||||
Constant,
|
||||
}
|
||||
|
||||
pub struct ScopeEntry<'a> {
|
||||
pub name: Cow<'a, str>,
|
||||
pub var_type: VariableType,
|
||||
pub value: Dynamic,
|
||||
pub expr: Option<Expr>,
|
||||
}
|
||||
|
||||
/// A type containing information about current scope.
|
||||
/// Useful for keeping state between `Engine` runs.
|
||||
///
|
||||
@ -31,7 +42,7 @@ pub enum VariableType {
|
||||
///
|
||||
/// When searching for variables, newly-added variables are found before similarly-named but older variables,
|
||||
/// allowing for automatic _shadowing_ of variables.
|
||||
pub struct Scope<'a>(Vec<(Cow<'a, str>, VariableType, Dynamic)>);
|
||||
pub struct Scope<'a>(Vec<ScopeEntry<'a>>);
|
||||
|
||||
impl<'a> Scope<'a> {
|
||||
/// Create a new Scope.
|
||||
@ -50,32 +61,62 @@ impl<'a> Scope<'a> {
|
||||
}
|
||||
|
||||
/// Add (push) a new variable to the Scope.
|
||||
pub fn push<K: Into<Cow<'a, str>>, T: Any>(&mut self, key: K, value: T) {
|
||||
self.0
|
||||
.push((key.into(), VariableType::Normal, Box::new(value)));
|
||||
pub fn push<K: Into<Cow<'a, str>>, T: Any>(&mut self, name: K, value: T) {
|
||||
let value = value.into_dynamic();
|
||||
|
||||
// Map into constant expressions
|
||||
let (expr, value) = map_dynamic_to_expr(value);
|
||||
|
||||
self.0.push(ScopeEntry {
|
||||
name: name.into(),
|
||||
var_type: VariableType::Normal,
|
||||
value,
|
||||
expr,
|
||||
});
|
||||
}
|
||||
|
||||
/// Add (push) a new constant to the Scope.
|
||||
pub fn push_constant<K: Into<Cow<'a, str>>, T: Any>(&mut self, key: K, value: T) {
|
||||
self.0
|
||||
.push((key.into(), VariableType::Constant, Box::new(value)));
|
||||
pub fn push_constant<K: Into<Cow<'a, str>>, T: Any>(&mut self, name: K, value: T) {
|
||||
let value = value.into_dynamic();
|
||||
|
||||
// Map into constant expressions
|
||||
let (expr, value) = map_dynamic_to_expr(value);
|
||||
|
||||
self.0.push(ScopeEntry {
|
||||
name: name.into(),
|
||||
var_type: VariableType::Constant,
|
||||
value,
|
||||
expr,
|
||||
});
|
||||
}
|
||||
|
||||
/// Add (push) a new variable with a `Dynamic` value to the Scope.
|
||||
pub(crate) fn push_dynamic<K: Into<Cow<'a, str>>>(
|
||||
&mut self,
|
||||
key: K,
|
||||
val_type: VariableType,
|
||||
name: K,
|
||||
var_type: VariableType,
|
||||
value: Dynamic,
|
||||
) {
|
||||
self.0.push((key.into(), val_type, value));
|
||||
let (expr, value) = map_dynamic_to_expr(value);
|
||||
|
||||
self.0.push(ScopeEntry {
|
||||
name: name.into(),
|
||||
var_type,
|
||||
value,
|
||||
expr,
|
||||
});
|
||||
}
|
||||
|
||||
/// Remove (pop) the last variable from the Scope.
|
||||
pub fn pop(&mut self) -> Option<(String, VariableType, Dynamic)> {
|
||||
self.0
|
||||
.pop()
|
||||
.map(|(key, var_type, value)| (key.to_string(), var_type, value))
|
||||
self.0.pop().map(
|
||||
|ScopeEntry {
|
||||
name,
|
||||
var_type,
|
||||
value,
|
||||
..
|
||||
}| (name.to_string(), var_type, value),
|
||||
)
|
||||
}
|
||||
|
||||
/// Truncate (rewind) the Scope to a previous size.
|
||||
@ -89,8 +130,18 @@ impl<'a> Scope<'a> {
|
||||
.iter()
|
||||
.enumerate()
|
||||
.rev() // Always search a Scope in reverse order
|
||||
.find(|(_, (name, _, _))| name == key)
|
||||
.map(|(i, (name, var_type, value))| (i, name.as_ref(), *var_type, value.clone()))
|
||||
.find(|(_, ScopeEntry { name, .. })| name == key)
|
||||
.map(
|
||||
|(
|
||||
i,
|
||||
ScopeEntry {
|
||||
name,
|
||||
var_type,
|
||||
value,
|
||||
..
|
||||
},
|
||||
)| (i, name.as_ref(), *var_type, value.clone()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the value of a variable in the Scope, starting from the last.
|
||||
@ -99,50 +150,37 @@ impl<'a> Scope<'a> {
|
||||
.iter()
|
||||
.enumerate()
|
||||
.rev() // Always search a Scope in reverse order
|
||||
.find(|(_, (name, _, _))| name == key)
|
||||
.and_then(|(_, (_, _, value))| value.downcast_ref::<T>())
|
||||
.map(|value| value.clone())
|
||||
.find(|(_, ScopeEntry { name, .. })| name == key)
|
||||
.and_then(|(_, ScopeEntry { value, .. })| value.downcast_ref::<T>())
|
||||
.map(T::clone)
|
||||
}
|
||||
|
||||
/// Get a mutable reference to a variable in the Scope.
|
||||
pub(crate) fn get_mut(&mut self, key: &str, index: usize) -> &mut Dynamic {
|
||||
pub(crate) fn get_mut(&mut self, name: &str, index: usize) -> &mut Dynamic {
|
||||
let entry = self.0.get_mut(index).expect("invalid index in Scope");
|
||||
|
||||
assert_ne!(
|
||||
entry.1,
|
||||
entry.var_type,
|
||||
VariableType::Constant,
|
||||
"get mut of constant variable"
|
||||
);
|
||||
assert_eq!(entry.0, key, "incorrect key at Scope entry");
|
||||
assert_eq!(entry.name, name, "incorrect key at Scope entry");
|
||||
|
||||
&mut entry.2
|
||||
&mut entry.value
|
||||
}
|
||||
|
||||
/// Get a mutable reference to a variable in the Scope and downcast it to a specific type
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub(crate) fn get_mut_by_type<T: Any + Clone>(&mut self, key: &str, index: usize) -> &mut T {
|
||||
self.get_mut(key, index)
|
||||
pub(crate) fn get_mut_by_type<T: Any + Clone>(&mut self, name: &str, index: usize) -> &mut T {
|
||||
self.get_mut(name, index)
|
||||
.downcast_mut::<T>()
|
||||
.expect("wrong type cast")
|
||||
}
|
||||
|
||||
/// Get an iterator to variables in the Scope.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&str, VariableType, &Dynamic)> {
|
||||
self.0
|
||||
.iter()
|
||||
.rev() // Always search a Scope in reverse order
|
||||
.map(|(key, var_type, value)| (key.as_ref(), *var_type, value))
|
||||
pub fn iter(&self) -> impl Iterator<Item = &ScopeEntry> {
|
||||
self.0.iter().rev() // Always search a Scope in reverse order
|
||||
}
|
||||
|
||||
/*
|
||||
/// Get a mutable iterator to variables in the Scope.
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut Dynamic)> {
|
||||
self.0
|
||||
.iter_mut()
|
||||
.rev() // Always search a Scope in reverse order
|
||||
.map(|(key, value)| (key.as_ref(), value))
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
impl<'a, K> std::iter::Extend<(K, VariableType, Dynamic)> for Scope<'a>
|
||||
@ -150,9 +188,66 @@ where
|
||||
K: Into<Cow<'a, str>>,
|
||||
{
|
||||
fn extend<T: IntoIterator<Item = (K, VariableType, Dynamic)>>(&mut self, iter: T) {
|
||||
self.0.extend(
|
||||
iter.into_iter()
|
||||
.map(|(key, var_type, value)| (key.into(), var_type, value)),
|
||||
);
|
||||
self.0
|
||||
.extend(iter.into_iter().map(|(name, var_type, value)| ScopeEntry {
|
||||
name: name.into(),
|
||||
var_type,
|
||||
value,
|
||||
expr: None,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
fn map_dynamic_to_expr(value: Dynamic) -> (Option<Expr>, Dynamic) {
|
||||
if value.is::<INT>() {
|
||||
let value2 = value.clone();
|
||||
(
|
||||
Some(Expr::IntegerConstant(
|
||||
*value.downcast::<INT>().expect("value should be INT"),
|
||||
Position::none(),
|
||||
)),
|
||||
value2,
|
||||
)
|
||||
} else if value.is::<FLOAT>() {
|
||||
let value2 = value.clone();
|
||||
(
|
||||
Some(Expr::FloatConstant(
|
||||
*value.downcast::<FLOAT>().expect("value should be FLOAT"),
|
||||
Position::none(),
|
||||
)),
|
||||
value2,
|
||||
)
|
||||
} else if value.is::<char>() {
|
||||
let value2 = value.clone();
|
||||
(
|
||||
Some(Expr::CharConstant(
|
||||
*value.downcast::<char>().expect("value should be char"),
|
||||
Position::none(),
|
||||
)),
|
||||
value2,
|
||||
)
|
||||
} else if value.is::<String>() {
|
||||
let value2 = value.clone();
|
||||
(
|
||||
Some(Expr::StringConstant(
|
||||
*value.downcast::<String>().expect("value should be String"),
|
||||
Position::none(),
|
||||
)),
|
||||
value2,
|
||||
)
|
||||
} else if value.is::<bool>() {
|
||||
let value2 = value.clone();
|
||||
(
|
||||
Some(
|
||||
if *value.downcast::<bool>().expect("value should be bool") {
|
||||
Expr::True(Position::none())
|
||||
} else {
|
||||
Expr::False(Position::none())
|
||||
},
|
||||
),
|
||||
value2,
|
||||
)
|
||||
} else {
|
||||
(None, value)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user