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; 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!`)

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::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>,

View File

@ -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()),
) )