Mandatory semiclolons separating statements.

This commit is contained in:
Stephen Chung 2020-03-17 14:29:22 +08:00
parent ad2601972a
commit 2f7ca3935b
3 changed files with 84 additions and 21 deletions

View File

@ -60,6 +60,8 @@ pub enum ParseErrorType {
MissingRightBracket(String), MissingRightBracket(String),
/// A list of expressions is missing the separating ','. /// A list of expressions is missing the separating ','.
MissingComma(String), MissingComma(String),
/// A statement is missing the ending ';'.
MissingSemicolon(String),
/// An expression in function call arguments `()` has syntax error. /// An expression in function call arguments `()` has syntax error.
MalformedCallExpr(String), MalformedCallExpr(String),
/// An expression in indexing brackets `[]` has syntax error. /// An expression in indexing brackets `[]` has syntax error.
@ -119,6 +121,7 @@ impl ParseError {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
ParseErrorType::MissingRightBracket(_) => "Expecting ']'", ParseErrorType::MissingRightBracket(_) => "Expecting ']'",
ParseErrorType::MissingComma(_) => "Expecting ','", ParseErrorType::MissingComma(_) => "Expecting ','",
ParseErrorType::MissingSemicolon(_) => "Expecting ';'",
ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments", ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments",
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression", ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression",
@ -130,7 +133,7 @@ impl ParseError {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration", ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration",
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
ParseErrorType::WrongFnDefinition => "Function definitions must be at top level and cannot be inside a block or another function", ParseErrorType::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function",
ParseErrorType::AssignmentToInvalidLHS => "Cannot assign to this expression", ParseErrorType::AssignmentToInvalidLHS => "Cannot assign to this expression",
ParseErrorType::AssignmentToCopy => "Cannot assign to this expression because it will only be changing a copy of the value", ParseErrorType::AssignmentToCopy => "Cannot assign to this expression because it will only be changing a copy of the value",
ParseErrorType::AssignmentToConstant(_) => "Cannot assign to a constant variable." ParseErrorType::AssignmentToConstant(_) => "Cannot assign to a constant variable."
@ -168,7 +171,9 @@ impl fmt::Display for ParseError {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
ParseErrorType::MissingRightBracket(ref s) => write!(f, "{} for {}", self.desc(), s)?, ParseErrorType::MissingRightBracket(ref s) => write!(f, "{} for {}", self.desc(), s)?,
ParseErrorType::MissingComma(ref s) => write!(f, "{} for {}", self.desc(), s)?, ParseErrorType::MissingSemicolon(ref s) | ParseErrorType::MissingComma(ref s) => {
write!(f, "{} for {}", self.desc(), s)?
}
ParseErrorType::AssignmentToConstant(ref s) if s.is_empty() => { ParseErrorType::AssignmentToConstant(ref s) if s.is_empty() => {
write!(f, "{}", self.desc())? write!(f, "{}", self.desc())?

View File

@ -451,7 +451,7 @@ pub(crate) fn optimize<'a>(
if let Stmt::Const(name, value, _) = &stmt { if let Stmt::Const(name, value, _) = &stmt {
// Load constants // Load constants
state.push_constant(name, value.as_ref().clone()); state.push_constant(name, value.as_ref().clone());
stmt // Keep it in the top scope stmt // Keep it in the global scope
} else { } else {
// Keep all variable declarations at this level // Keep all variable declarations at this level
// and always keep the last return value // and always keep the last return value
@ -470,7 +470,7 @@ pub(crate) fn optimize<'a>(
// Eliminate code that is pure but always keep the last statement // Eliminate code that is pure but always keep the last statement
let last_stmt = result.pop(); let last_stmt = result.pop();
// Remove all pure statements at top level // Remove all pure statements at global level
result.retain(|stmt| !matches!(stmt, Stmt::Expr(expr) if expr.is_pure())); result.retain(|stmt| !matches!(stmt, Stmt::Expr(expr) if expr.is_pure()));
if let Some(stmt) = last_stmt { if let Some(stmt) = last_stmt {

View File

@ -203,6 +203,23 @@ impl Stmt {
matches!(self, Stmt::Let(_, _, _)) matches!(self, Stmt::Let(_, _, _))
} }
pub fn is_self_terminated(&self) -> bool {
match self {
Stmt::Noop(_)
| Stmt::IfElse(_, _, _)
| Stmt::While(_, _)
| Stmt::Loop(_)
| Stmt::For(_, _, _)
| Stmt::Block(_, _) => true,
Stmt::Let(_, _, _)
| Stmt::Const(_, _, _)
| Stmt::Expr(_)
| Stmt::Break(_)
| Stmt::ReturnWithVal(_, _, _) => false,
}
}
pub fn position(&self) -> Position { pub fn position(&self) -> Position {
match self { match self {
Stmt::Noop(pos) Stmt::Noop(pos)
@ -1913,22 +1930,42 @@ fn parse_block<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Pars
})? { })? {
(Token::RightBrace, _) => (), // empty block (Token::RightBrace, _) => (), // empty block
#[cfg(not(feature = "no_function"))]
(Token::Fn, pos) => return Err(ParseError::new(PERR::WrongFnDefinition, *pos)),
_ => { _ => {
while input.peek().is_some() { while input.peek().is_some() {
// Parse statements inside the block // Parse statements inside the block
statements.push(parse_stmt(input)?); let stmt = parse_stmt(input)?;
// Notice semicolons are optional // See if it needs a terminating semicolon
if let Some((Token::SemiColon, _)) = input.peek() { let need_semicolon = !stmt.is_self_terminated();
input.next();
}
statements.push(stmt);
// End block with right brace
if let Some((Token::RightBrace, _)) = input.peek() { if let Some((Token::RightBrace, _)) = input.peek() {
break; break;
} }
match input.peek() {
Some((Token::SemiColon, _)) => {
input.next();
}
Some((_, _)) if !need_semicolon => (),
Some((_, pos)) => {
// Semicolons are not optional between statements
return Err(ParseError::new(
PERR::MissingSemicolon("terminating a statement".into()),
*pos,
));
}
None => {
return Err(ParseError::new(
PERR::MissingRightBrace("end of block".into()),
Position::eof(),
))
}
}
} }
} }
} }
@ -1959,6 +1996,9 @@ fn parse_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
.peek() .peek()
.ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))? .ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))?
{ {
#[cfg(not(feature = "no_function"))]
(Token::Fn, pos) => return Err(ParseError::new(PERR::WrongFnDefinition, *pos)),
(Token::If, _) => parse_if(input), (Token::If, _) => parse_if(input),
(Token::While, _) => parse_while(input), (Token::While, _) => parse_while(input),
(Token::Loop, _) => parse_loop(input), (Token::Loop, _) => parse_loop(input),
@ -2097,16 +2137,16 @@ fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseE
}) })
} }
fn parse_top_level<'a, 'e>( fn parse_global_level<'a, 'e>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
) -> Result<(Vec<Stmt>, Vec<FnDef>), ParseError> { ) -> Result<(Vec<Stmt>, Vec<FnDef>), ParseError> {
let mut statements = Vec::<Stmt>::new(); let mut statements = Vec::<Stmt>::new();
let mut functions = Vec::<FnDef>::new(); let mut functions = Vec::<FnDef>::new();
while input.peek().is_some() { while input.peek().is_some() {
match input.peek().expect("should not be None") { #[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_function"))] {
(Token::Fn, _) => { if matches!(input.peek().expect("should not be None"), (Token::Fn, _)) {
let f = parse_fn(input)?; let f = parse_fn(input)?;
// Ensure list is sorted // Ensure list is sorted
@ -2114,13 +2154,31 @@ fn parse_top_level<'a, 'e>(
Ok(n) => functions[n] = f, // Override previous definition Ok(n) => functions[n] = f, // Override previous definition
Err(n) => functions.insert(n, f), // New function definition Err(n) => functions.insert(n, f), // New function definition
} }
continue;
} }
_ => statements.push(parse_stmt(input)?),
} }
// Notice semicolons are optional let stmt = parse_stmt(input)?;
if let Some((Token::SemiColon, _)) = input.peek() {
input.next(); let need_semicolon = !stmt.is_self_terminated();
statements.push(stmt);
match input.peek() {
None => break,
Some((Token::SemiColon, _)) => {
input.next();
}
Some((_, _)) if !need_semicolon => (),
Some((_, pos)) => {
// Semicolons are not optional between statements
return Err(ParseError::new(
PERR::MissingSemicolon("terminating a statement".into()),
*pos,
));
}
} }
} }
@ -2132,7 +2190,7 @@ pub fn parse<'a, 'e>(
engine: &Engine<'e>, engine: &Engine<'e>,
scope: &Scope, scope: &Scope,
) -> Result<AST, ParseError> { ) -> Result<AST, ParseError> {
let (statements, functions) = parse_top_level(input)?; let (statements, functions) = parse_global_level(input)?;
Ok( Ok(
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]