Allow empty statements.
This commit is contained in:
parent
03b2e9ad69
commit
019e73bc7e
12
src/api.rs
12
src/api.rs
@ -10,7 +10,7 @@ use crate::result::EvalAltResult;
|
|||||||
use crate::scope::Scope;
|
use crate::scope::Scope;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
use crate::optimize::optimize_ast;
|
use crate::optimize::optimize_into_ast;
|
||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
any::{type_name, TypeId},
|
any::{type_name, TypeId},
|
||||||
@ -415,12 +415,10 @@ impl<'e> Engine<'e> {
|
|||||||
/// (i.e. with `scope.push_constant(...)`). Then, the AST is cloned and the copy re-optimized before running.
|
/// (i.e. with `scope.push_constant(...)`). Then, the AST is cloned and the copy re-optimized before running.
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
pub fn optimize_ast(&self, scope: &Scope, ast: &AST) -> AST {
|
pub fn optimize_ast(&self, scope: &Scope, ast: &AST) -> AST {
|
||||||
optimize_ast(
|
let statements = ast.0.clone();
|
||||||
self,
|
let functions = ast.1.iter().map(|f| (**f).clone()).collect();
|
||||||
scope,
|
|
||||||
ast.0.clone(),
|
optimize_into_ast(self, scope, statements, functions)
|
||||||
ast.1.iter().map(|f| (**f).clone()).collect(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Override default action of `print` (print to stdout using `println!`)
|
/// Override default action of `print` (print to stdout using `println!`)
|
||||||
|
@ -8,9 +8,11 @@ use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST};
|
|||||||
use crate::scope::{Scope, ScopeEntry, VariableType};
|
use crate::scope::{Scope, ScopeEntry, VariableType};
|
||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
|
boxed::Box,
|
||||||
|
string::{String, ToString},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
vec::Vec, string::{String, ToString},
|
vec,
|
||||||
boxed::Box, vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Level of optimization performed.
|
/// Level of optimization performed.
|
||||||
@ -549,17 +551,20 @@ pub(crate) fn optimize<'a>(statements: Vec<Stmt>, engine: &Engine<'a>, scope: &S
|
|||||||
let last_stmt = result.pop();
|
let last_stmt = result.pop();
|
||||||
|
|
||||||
// Remove all pure statements at global level
|
// Remove all pure statements at global level
|
||||||
result.retain(|stmt| !matches!(stmt, Stmt::Expr(expr) if expr.is_pure()));
|
result.retain(|stmt| !stmt.is_pure());
|
||||||
|
|
||||||
|
// Add back the last statement unless it is a lone No-op
|
||||||
if let Some(stmt) = last_stmt {
|
if let Some(stmt) = last_stmt {
|
||||||
result.push(stmt); // Add back the last statement
|
if result.len() > 0 || !matches!(stmt, Stmt::Noop(_)) {
|
||||||
|
result.push(stmt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Optimize an AST.
|
/// Optimize an AST.
|
||||||
pub fn optimize_ast(
|
pub fn optimize_into_ast(
|
||||||
engine: &Engine,
|
engine: &Engine,
|
||||||
scope: &Scope,
|
scope: &Scope,
|
||||||
statements: Vec<Stmt>,
|
statements: Vec<Stmt>,
|
||||||
|
@ -6,7 +6,7 @@ use crate::error::{LexError, ParseError, ParseErrorType};
|
|||||||
use crate::scope::{Scope, VariableType};
|
use crate::scope::{Scope, VariableType};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
use crate::optimize::optimize_ast;
|
use crate::optimize::optimize_into_ast;
|
||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
@ -243,13 +243,15 @@ impl Stmt {
|
|||||||
/// Is this statement self-terminated (i.e. no need for a semicolon terminator)?
|
/// Is this statement self-terminated (i.e. no need for a semicolon terminator)?
|
||||||
pub fn is_self_terminated(&self) -> bool {
|
pub fn is_self_terminated(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Stmt::Noop(_)
|
Stmt::IfElse(_, _, _)
|
||||||
| Stmt::IfElse(_, _, _)
|
|
||||||
| Stmt::While(_, _)
|
| Stmt::While(_, _)
|
||||||
| Stmt::Loop(_)
|
| Stmt::Loop(_)
|
||||||
| Stmt::For(_, _, _)
|
| Stmt::For(_, _, _)
|
||||||
| Stmt::Block(_, _) => true,
|
| Stmt::Block(_, _) => true,
|
||||||
|
|
||||||
|
// A No-op requires a semicolon in order to know it is an empty statement!
|
||||||
|
Stmt::Noop(_) => false,
|
||||||
|
|
||||||
Stmt::Let(_, _, _)
|
Stmt::Let(_, _, _)
|
||||||
| Stmt::Const(_, _, _)
|
| Stmt::Const(_, _, _)
|
||||||
| Stmt::Expr(_)
|
| Stmt::Expr(_)
|
||||||
@ -1717,7 +1719,7 @@ fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pars
|
|||||||
|
|
||||||
/// Parse an assignment.
|
/// Parse an assignment.
|
||||||
fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseError> {
|
fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseError> {
|
||||||
// Is the LHS in a valid format?
|
// Is the LHS in a valid format for an assignment target?
|
||||||
fn valid_assignment_chain(expr: &Expr, is_top: bool) -> Option<ParseError> {
|
fn valid_assignment_chain(expr: &Expr, is_top: bool) -> Option<ParseError> {
|
||||||
match expr {
|
match expr {
|
||||||
// var
|
// var
|
||||||
@ -1798,17 +1800,13 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseEr
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse an operator-assignment expression.
|
/// Parse an operator-assignment expression.
|
||||||
fn parse_op_assignment(
|
fn parse_op_assignment(op: &str, lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseError> {
|
||||||
function: &str,
|
|
||||||
lhs: Expr,
|
|
||||||
rhs: Expr,
|
|
||||||
pos: Position,
|
|
||||||
) -> Result<Expr, ParseError> {
|
|
||||||
let lhs_copy = lhs.clone();
|
let lhs_copy = lhs.clone();
|
||||||
|
|
||||||
|
// lhs op= rhs -> lhs = op(lhs, rhs)
|
||||||
parse_assignment(
|
parse_assignment(
|
||||||
lhs,
|
lhs,
|
||||||
Expr::FunctionCall(function.into(), vec![lhs_copy, rhs], None, pos),
|
Expr::FunctionCall(op.into(), vec![lhs_copy, rhs], None, pos),
|
||||||
pos,
|
pos,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1978,17 +1976,22 @@ fn parse_if<'a>(
|
|||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
breakable: bool,
|
breakable: bool,
|
||||||
) -> Result<Stmt, ParseError> {
|
) -> Result<Stmt, ParseError> {
|
||||||
|
// if ...
|
||||||
input.next();
|
input.next();
|
||||||
|
|
||||||
|
// if guard { body }
|
||||||
let guard = parse_expr(input)?;
|
let guard = parse_expr(input)?;
|
||||||
let if_body = parse_block(input, breakable)?;
|
let if_body = parse_block(input, breakable)?;
|
||||||
|
|
||||||
|
// if guard { body } else ...
|
||||||
let else_body = if matches!(input.peek(), Some((Token::Else, _))) {
|
let else_body = if matches!(input.peek(), Some((Token::Else, _))) {
|
||||||
input.next();
|
input.next();
|
||||||
|
|
||||||
Some(Box::new(if matches!(input.peek(), Some((Token::If, _))) {
|
Some(Box::new(if matches!(input.peek(), Some((Token::If, _))) {
|
||||||
|
// if guard { body } else if ...
|
||||||
parse_if(input, breakable)?
|
parse_if(input, breakable)?
|
||||||
} else {
|
} else {
|
||||||
|
// if guard { body } else { else-body }
|
||||||
parse_block(input, breakable)?
|
parse_block(input, breakable)?
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
@ -2000,8 +2003,10 @@ fn parse_if<'a>(
|
|||||||
|
|
||||||
/// Parse a while loop.
|
/// Parse a while loop.
|
||||||
fn parse_while<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
fn parse_while<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
||||||
|
// while ...
|
||||||
input.next();
|
input.next();
|
||||||
|
|
||||||
|
// while guard { body }
|
||||||
let guard = parse_expr(input)?;
|
let guard = parse_expr(input)?;
|
||||||
let body = parse_block(input, true)?;
|
let body = parse_block(input, true)?;
|
||||||
|
|
||||||
@ -2010,8 +2015,10 @@ fn parse_while<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Pars
|
|||||||
|
|
||||||
/// Parse a loop statement.
|
/// Parse a loop statement.
|
||||||
fn parse_loop<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
fn parse_loop<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
||||||
|
// loop ...
|
||||||
input.next();
|
input.next();
|
||||||
|
|
||||||
|
// loop { body }
|
||||||
let body = parse_block(input, true)?;
|
let body = parse_block(input, true)?;
|
||||||
|
|
||||||
Ok(Stmt::Loop(Box::new(body)))
|
Ok(Stmt::Loop(Box::new(body)))
|
||||||
@ -2019,19 +2026,25 @@ fn parse_loop<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
|
|||||||
|
|
||||||
/// Parse a for loop.
|
/// Parse a for loop.
|
||||||
fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
||||||
|
// for ...
|
||||||
input.next();
|
input.next();
|
||||||
|
|
||||||
|
// for name ...
|
||||||
let name = match input
|
let name = match input
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| ParseError::new(PERR::VariableExpected, Position::eof()))?
|
.ok_or_else(|| ParseError::new(PERR::VariableExpected, Position::eof()))?
|
||||||
{
|
{
|
||||||
|
// Variable name
|
||||||
(Token::Identifier(s), _) => s,
|
(Token::Identifier(s), _) => s,
|
||||||
|
// Bad identifier
|
||||||
(Token::LexError(err), pos) => {
|
(Token::LexError(err), pos) => {
|
||||||
return Err(ParseError::new(PERR::BadInput(err.to_string()), pos))
|
return Err(ParseError::new(PERR::BadInput(err.to_string()), pos))
|
||||||
}
|
}
|
||||||
|
// Not a variable name
|
||||||
(_, pos) => return Err(ParseError::new(PERR::VariableExpected, pos)),
|
(_, pos) => return Err(ParseError::new(PERR::VariableExpected, pos)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// for name in ...
|
||||||
match input
|
match input
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| ParseError::new(PERR::MissingIn, Position::eof()))?
|
.ok_or_else(|| ParseError::new(PERR::MissingIn, Position::eof()))?
|
||||||
@ -2040,6 +2053,7 @@ fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
|
|||||||
(_, pos) => return Err(ParseError::new(PERR::MissingIn, pos)),
|
(_, pos) => return Err(ParseError::new(PERR::MissingIn, pos)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for name in expr { body }
|
||||||
let expr = parse_expr(input)?;
|
let expr = parse_expr(input)?;
|
||||||
let body = parse_block(input, true)?;
|
let body = parse_block(input, true)?;
|
||||||
|
|
||||||
@ -2051,39 +2065,43 @@ fn parse_let<'a>(
|
|||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
var_type: VariableType,
|
var_type: VariableType,
|
||||||
) -> Result<Stmt, ParseError> {
|
) -> Result<Stmt, ParseError> {
|
||||||
let pos = input
|
// let/const... (specified in `var_type`)
|
||||||
.next()
|
input.next();
|
||||||
.ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))?
|
|
||||||
.1;
|
|
||||||
|
|
||||||
let name = match input
|
// let name ...
|
||||||
|
let (name, pos) = match input
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| ParseError::new(PERR::VariableExpected, Position::eof()))?
|
.ok_or_else(|| ParseError::new(PERR::VariableExpected, Position::eof()))?
|
||||||
{
|
{
|
||||||
(Token::Identifier(s), _) => s,
|
(Token::Identifier(s), pos) => (s, pos),
|
||||||
(Token::LexError(err), pos) => {
|
(Token::LexError(err), pos) => {
|
||||||
return Err(ParseError::new(PERR::BadInput(err.to_string()), pos))
|
return Err(ParseError::new(PERR::BadInput(err.to_string()), pos))
|
||||||
}
|
}
|
||||||
(_, pos) => return Err(ParseError::new(PERR::VariableExpected, pos)),
|
(_, pos) => return Err(ParseError::new(PERR::VariableExpected, pos)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// let name = ...
|
||||||
if matches!(input.peek(), Some((Token::Equals, _))) {
|
if matches!(input.peek(), Some((Token::Equals, _))) {
|
||||||
input.next();
|
input.next();
|
||||||
|
|
||||||
|
// let name = expr
|
||||||
let init_value = parse_expr(input)?;
|
let init_value = parse_expr(input)?;
|
||||||
|
|
||||||
match var_type {
|
match var_type {
|
||||||
|
// let name = expr
|
||||||
VariableType::Normal => Ok(Stmt::Let(name, Some(Box::new(init_value)), pos)),
|
VariableType::Normal => Ok(Stmt::Let(name, Some(Box::new(init_value)), pos)),
|
||||||
|
// const name = { expr:constant }
|
||||||
VariableType::Constant if init_value.is_constant() => {
|
VariableType::Constant if init_value.is_constant() => {
|
||||||
Ok(Stmt::Const(name, Box::new(init_value), pos))
|
Ok(Stmt::Const(name, Box::new(init_value), pos))
|
||||||
}
|
}
|
||||||
// Constants require a constant expression
|
// const name = expr - error
|
||||||
VariableType::Constant => Err(ParseError(
|
VariableType::Constant => Err(ParseError(
|
||||||
PERR::ForbiddenConstantExpr(name.to_string()),
|
PERR::ForbiddenConstantExpr(name.to_string()),
|
||||||
init_value.position(),
|
init_value.position(),
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// let name
|
||||||
Ok(Stmt::Let(name, None, pos))
|
Ok(Stmt::Let(name, None, pos))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2093,6 +2111,7 @@ fn parse_block<'a>(
|
|||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
breakable: bool,
|
breakable: bool,
|
||||||
) -> Result<Stmt, ParseError> {
|
) -> Result<Stmt, ParseError> {
|
||||||
|
// Must start with {
|
||||||
let pos = match input
|
let pos = match input
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| ParseError::new(PERR::MissingLeftBrace, Position::eof()))?
|
.ok_or_else(|| ParseError::new(PERR::MissingLeftBrace, Position::eof()))?
|
||||||
@ -2113,15 +2132,19 @@ fn parse_block<'a>(
|
|||||||
statements.push(stmt);
|
statements.push(stmt);
|
||||||
|
|
||||||
match input.peek() {
|
match input.peek() {
|
||||||
|
// EOF
|
||||||
None => break,
|
None => break,
|
||||||
|
// { ... stmt }
|
||||||
Some((Token::RightBrace, _)) => break,
|
Some((Token::RightBrace, _)) => break,
|
||||||
|
// { ... stmt;
|
||||||
Some((Token::SemiColon, _)) => {
|
Some((Token::SemiColon, _)) if need_semicolon => {
|
||||||
input.next();
|
input.next();
|
||||||
}
|
}
|
||||||
|
// { ... { stmt } ;
|
||||||
|
Some((Token::SemiColon, _)) if !need_semicolon => (),
|
||||||
|
// { ... { stmt } ???
|
||||||
Some((_, _)) if !need_semicolon => (),
|
Some((_, _)) if !need_semicolon => (),
|
||||||
|
// { ... stmt ??? - error
|
||||||
Some((_, pos)) => {
|
Some((_, pos)) => {
|
||||||
// Semicolons are not optional between statements
|
// Semicolons are not optional between statements
|
||||||
return Err(ParseError::new(
|
return Err(ParseError::new(
|
||||||
@ -2163,6 +2186,10 @@ fn parse_stmt<'a>(
|
|||||||
.peek()
|
.peek()
|
||||||
.ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))?
|
.ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))?
|
||||||
{
|
{
|
||||||
|
// Semicolon - empty statement
|
||||||
|
(Token::SemiColon, pos) => Ok(Stmt::Noop(*pos)),
|
||||||
|
|
||||||
|
// fn ...
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
(Token::Fn, pos) => return Err(ParseError::new(PERR::WrongFnDefinition, *pos)),
|
(Token::Fn, pos) => return Err(ParseError::new(PERR::WrongFnDefinition, *pos)),
|
||||||
|
|
||||||
@ -2323,6 +2350,7 @@ fn parse_global_level<'a, 'e>(
|
|||||||
while input.peek().is_some() {
|
while input.peek().is_some() {
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
{
|
{
|
||||||
|
// Collect all the function definitions
|
||||||
if matches!(input.peek().expect("should not be None"), (Token::Fn, _)) {
|
if matches!(input.peek().expect("should not be None"), (Token::Fn, _)) {
|
||||||
let f = parse_fn(input)?;
|
let f = parse_fn(input)?;
|
||||||
|
|
||||||
@ -2336,6 +2364,7 @@ fn parse_global_level<'a, 'e>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Actual statement
|
||||||
let stmt = parse_stmt(input, false)?;
|
let stmt = parse_stmt(input, false)?;
|
||||||
|
|
||||||
let need_semicolon = !stmt.is_self_terminated();
|
let need_semicolon = !stmt.is_self_terminated();
|
||||||
@ -2343,12 +2372,17 @@ fn parse_global_level<'a, 'e>(
|
|||||||
statements.push(stmt);
|
statements.push(stmt);
|
||||||
|
|
||||||
match input.peek() {
|
match input.peek() {
|
||||||
|
// EOF
|
||||||
None => break,
|
None => break,
|
||||||
Some((Token::SemiColon, _)) => {
|
// stmt ;
|
||||||
|
Some((Token::SemiColon, _)) if need_semicolon => {
|
||||||
input.next();
|
input.next();
|
||||||
}
|
}
|
||||||
|
// stmt ;
|
||||||
|
Some((Token::SemiColon, _)) if !need_semicolon => (),
|
||||||
|
// { stmt } ???
|
||||||
Some((_, _)) if !need_semicolon => (),
|
Some((_, _)) if !need_semicolon => (),
|
||||||
|
// stmt ??? - error
|
||||||
Some((_, pos)) => {
|
Some((_, pos)) => {
|
||||||
// Semicolons are not optional between statements
|
// Semicolons are not optional between statements
|
||||||
return Err(ParseError::new(
|
return Err(ParseError::new(
|
||||||
@ -2371,8 +2405,11 @@ pub fn parse<'a, 'e>(
|
|||||||
let (statements, functions) = parse_global_level(input)?;
|
let (statements, functions) = parse_global_level(input)?;
|
||||||
|
|
||||||
Ok(
|
Ok(
|
||||||
|
// Optimize AST
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
optimize_ast(engine, scope, statements, functions),
|
optimize_into_ast(engine, scope, statements, functions),
|
||||||
|
//
|
||||||
|
// Do not optimize AST if `no_optimize`
|
||||||
#[cfg(feature = "no_optimize")]
|
#[cfg(feature = "no_optimize")]
|
||||||
AST(statements, functions.into_iter().map(Arc::new).collect()),
|
AST(statements, functions.into_iter().map(Arc::new).collect()),
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user