Mandatory semiclolons separating statements.
This commit is contained in:
parent
ad2601972a
commit
2f7ca3935b
@ -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())?
|
||||||
|
@ -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 {
|
||||||
|
@ -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"))]
|
||||||
|
Loading…
Reference in New Issue
Block a user