Allow empty statements.

This commit is contained in:
Stephen Chung 2020-03-18 18:41:18 +08:00
parent 03b2e9ad69
commit 019e73bc7e
3 changed files with 78 additions and 38 deletions

View File

@ -10,7 +10,7 @@ use crate::result::EvalAltResult;
use crate::scope::Scope;
#[cfg(not(feature = "no_optimize"))]
use crate::optimize::optimize_ast;
use crate::optimize::optimize_into_ast;
use crate::stdlib::{
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.
#[cfg(not(feature = "no_optimize"))]
pub fn optimize_ast(&self, scope: &Scope, ast: &AST) -> AST {
optimize_ast(
self,
scope,
ast.0.clone(),
ast.1.iter().map(|f| (**f).clone()).collect(),
)
let statements = ast.0.clone();
let functions = ast.1.iter().map(|f| (**f).clone()).collect();
optimize_into_ast(self, scope, statements, functions)
}
/// Override default action of `print` (print to stdout using `println!`)

View File

@ -8,9 +8,11 @@ use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST};
use crate::scope::{Scope, ScopeEntry, VariableType};
use crate::stdlib::{
boxed::Box,
string::{String, ToString},
sync::Arc,
vec::Vec, string::{String, ToString},
boxed::Box, vec,
vec,
vec::Vec,
};
/// 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();
// 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 {
result.push(stmt); // Add back the last statement
if result.len() > 0 || !matches!(stmt, Stmt::Noop(_)) {
result.push(stmt);
}
}
result
}
/// Optimize an AST.
pub fn optimize_ast(
pub fn optimize_into_ast(
engine: &Engine,
scope: &Scope,
statements: Vec<Stmt>,

View File

@ -6,7 +6,7 @@ use crate::error::{LexError, ParseError, ParseErrorType};
use crate::scope::{Scope, VariableType};
#[cfg(not(feature = "no_optimize"))]
use crate::optimize::optimize_ast;
use crate::optimize::optimize_into_ast;
use crate::stdlib::{
borrow::Cow,
@ -243,13 +243,15 @@ impl Stmt {
/// Is this statement self-terminated (i.e. no need for a semicolon terminator)?
pub fn is_self_terminated(&self) -> bool {
match self {
Stmt::Noop(_)
| Stmt::IfElse(_, _, _)
Stmt::IfElse(_, _, _)
| Stmt::While(_, _)
| Stmt::Loop(_)
| Stmt::For(_, _, _)
| Stmt::Block(_, _) => true,
// A No-op requires a semicolon in order to know it is an empty statement!
Stmt::Noop(_) => false,
Stmt::Let(_, _, _)
| Stmt::Const(_, _, _)
| Stmt::Expr(_)
@ -1717,7 +1719,7 @@ fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pars
/// Parse an assignment.
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> {
match expr {
// var
@ -1798,17 +1800,13 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseEr
}
/// Parse an operator-assignment expression.
fn parse_op_assignment(
function: &str,
lhs: Expr,
rhs: Expr,
pos: Position,
) -> Result<Expr, ParseError> {
fn parse_op_assignment(op: &str, lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseError> {
let lhs_copy = lhs.clone();
// lhs op= rhs -> lhs = op(lhs, rhs)
parse_assignment(
lhs,
Expr::FunctionCall(function.into(), vec![lhs_copy, rhs], None, pos),
Expr::FunctionCall(op.into(), vec![lhs_copy, rhs], None, pos),
pos,
)
}
@ -1978,17 +1976,22 @@ fn parse_if<'a>(
input: &mut Peekable<TokenIterator<'a>>,
breakable: bool,
) -> Result<Stmt, ParseError> {
// if ...
input.next();
// if guard { body }
let guard = parse_expr(input)?;
let if_body = parse_block(input, breakable)?;
// if guard { body } else ...
let else_body = if matches!(input.peek(), Some((Token::Else, _))) {
input.next();
Some(Box::new(if matches!(input.peek(), Some((Token::If, _))) {
// if guard { body } else if ...
parse_if(input, breakable)?
} else {
// if guard { body } else { else-body }
parse_block(input, breakable)?
}))
} else {
@ -2000,8 +2003,10 @@ fn parse_if<'a>(
/// Parse a while loop.
fn parse_while<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
// while ...
input.next();
// while guard { body }
let guard = parse_expr(input)?;
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.
fn parse_loop<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
// loop ...
input.next();
// loop { body }
let body = parse_block(input, true)?;
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.
fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
// for ...
input.next();
// for name ...
let name = match input
.next()
.ok_or_else(|| ParseError::new(PERR::VariableExpected, Position::eof()))?
{
// Variable name
(Token::Identifier(s), _) => s,
// Bad identifier
(Token::LexError(err), pos) => {
return Err(ParseError::new(PERR::BadInput(err.to_string()), pos))
}
// Not a variable name
(_, pos) => return Err(ParseError::new(PERR::VariableExpected, pos)),
};
// for name in ...
match input
.next()
.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)),
}
// for name in expr { body }
let expr = parse_expr(input)?;
let body = parse_block(input, true)?;
@ -2051,39 +2065,43 @@ fn parse_let<'a>(
input: &mut Peekable<TokenIterator<'a>>,
var_type: VariableType,
) -> Result<Stmt, ParseError> {
let pos = input
.next()
.ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))?
.1;
// let/const... (specified in `var_type`)
input.next();
let name = match input
// let name ...
let (name, pos) = match input
.next()
.ok_or_else(|| ParseError::new(PERR::VariableExpected, Position::eof()))?
{
(Token::Identifier(s), _) => s,
(Token::Identifier(s), pos) => (s, pos),
(Token::LexError(err), pos) => {
return Err(ParseError::new(PERR::BadInput(err.to_string()), pos))
}
(_, pos) => return Err(ParseError::new(PERR::VariableExpected, pos)),
};
// let name = ...
if matches!(input.peek(), Some((Token::Equals, _))) {
input.next();
// let name = expr
let init_value = parse_expr(input)?;
match var_type {
// let name = expr
VariableType::Normal => Ok(Stmt::Let(name, Some(Box::new(init_value)), pos)),
// const name = { expr:constant }
VariableType::Constant if init_value.is_constant() => {
Ok(Stmt::Const(name, Box::new(init_value), pos))
}
// Constants require a constant expression
// const name = expr - error
VariableType::Constant => Err(ParseError(
PERR::ForbiddenConstantExpr(name.to_string()),
init_value.position(),
)),
}
} else {
// let name
Ok(Stmt::Let(name, None, pos))
}
}
@ -2093,6 +2111,7 @@ fn parse_block<'a>(
input: &mut Peekable<TokenIterator<'a>>,
breakable: bool,
) -> Result<Stmt, ParseError> {
// Must start with {
let pos = match input
.next()
.ok_or_else(|| ParseError::new(PERR::MissingLeftBrace, Position::eof()))?
@ -2113,15 +2132,19 @@ fn parse_block<'a>(
statements.push(stmt);
match input.peek() {
// EOF
None => break,
// { ... stmt }
Some((Token::RightBrace, _)) => break,
Some((Token::SemiColon, _)) => {
// { ... stmt;
Some((Token::SemiColon, _)) if need_semicolon => {
input.next();
}
// { ... { stmt } ;
Some((Token::SemiColon, _)) if !need_semicolon => (),
// { ... { stmt } ???
Some((_, _)) if !need_semicolon => (),
// { ... stmt ??? - error
Some((_, pos)) => {
// Semicolons are not optional between statements
return Err(ParseError::new(
@ -2163,6 +2186,10 @@ fn parse_stmt<'a>(
.peek()
.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"))]
(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() {
#[cfg(not(feature = "no_function"))]
{
// Collect all the function definitions
if matches!(input.peek().expect("should not be None"), (Token::Fn, _)) {
let f = parse_fn(input)?;
@ -2336,6 +2364,7 @@ fn parse_global_level<'a, 'e>(
}
}
// Actual statement
let stmt = parse_stmt(input, false)?;
let need_semicolon = !stmt.is_self_terminated();
@ -2343,12 +2372,17 @@ fn parse_global_level<'a, 'e>(
statements.push(stmt);
match input.peek() {
// EOF
None => break,
Some((Token::SemiColon, _)) => {
// stmt ;
Some((Token::SemiColon, _)) if need_semicolon => {
input.next();
}
// stmt ;
Some((Token::SemiColon, _)) if !need_semicolon => (),
// { stmt } ???
Some((_, _)) if !need_semicolon => (),
// stmt ??? - error
Some((_, pos)) => {
// Semicolons are not optional between statements
return Err(ParseError::new(
@ -2371,8 +2405,11 @@ pub fn parse<'a, 'e>(
let (statements, functions) = parse_global_level(input)?;
Ok(
// Optimize AST
#[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")]
AST(statements, functions.into_iter().map(Arc::new).collect()),
)