Streamline parsing.

This commit is contained in:
Stephen Chung 2020-12-27 16:50:48 +08:00
parent 48af8719e7
commit 6b8d78d64c
4 changed files with 222 additions and 165 deletions

View File

@ -7,6 +7,11 @@ Version 0.19.9
This version removes the confusing differences between _packages_ and _modules_ This version removes the confusing differences between _packages_ and _modules_
by unifying the terminology and API under the global umbrella of _modules_. by unifying the terminology and API under the global umbrella of _modules_.
Bug fixes
---------
* Property access in
Breaking changes Breaking changes
---------------- ----------------

View File

@ -1176,6 +1176,12 @@ impl Expr {
} }
/// Is a particular [token][Token] allowed as a postfix operator to this expression? /// Is a particular [token][Token] allowed as a postfix operator to this expression?
pub fn is_valid_postfix(&self, token: &Token) -> bool { pub fn is_valid_postfix(&self, token: &Token) -> bool {
match token {
#[cfg(not(feature = "no_object"))]
Token::Period => return true,
_ => (),
}
match self { match self {
Self::Expr(x) => x.is_valid_postfix(token), Self::Expr(x) => x.is_valid_postfix(token),
@ -1193,24 +1199,20 @@ impl Expr {
| Self::Unit(_) => false, | Self::Unit(_) => false,
Self::StringConstant(_, _) Self::StringConstant(_, _)
| Self::Stmt(_, _)
| Self::FnCall(_, _) | Self::FnCall(_, _)
| Self::Stmt(_, _)
| Self::Dot(_, _) | Self::Dot(_, _)
| Self::Index(_, _) | Self::Index(_, _)
| Self::Array(_, _) | Self::Array(_, _)
| Self::Map(_, _) => match token { | Self::Map(_, _) => match token {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Token::LeftBracket => true, Token::LeftBracket => true,
#[cfg(not(feature = "no_object"))]
Token::Period => true,
_ => false, _ => false,
}, },
Self::Variable(_) => match token { Self::Variable(_) => match token {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Token::LeftBracket => true, Token::LeftBracket => true,
#[cfg(not(feature = "no_object"))]
Token::Period => true,
Token::LeftParen => true, Token::LeftParen => true,
Token::Bang => true, Token::Bang => true,
Token::DoubleColon => true, Token::DoubleColon => true,
@ -1220,8 +1222,6 @@ impl Expr {
Self::Property(_) => match token { Self::Property(_) => match token {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Token::LeftBracket => true, Token::LeftBracket => true,
#[cfg(not(feature = "no_object"))]
Token::Period => true,
Token::LeftParen => true, Token::LeftParen => true,
_ => false, _ => false,
}, },

View File

@ -278,8 +278,11 @@ fn parse_paren_expr(
input: &mut TokenStream, input: &mut TokenStream,
state: &mut ParseState, state: &mut ParseState,
lib: &mut FunctionsLib, lib: &mut FunctionsLib,
settings: ParseSettings, mut settings: ParseSettings,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
// ( ...
settings.pos = eat_token(input, Token::LeftParen);
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
@ -637,8 +640,11 @@ fn parse_array_literal(
input: &mut TokenStream, input: &mut TokenStream,
state: &mut ParseState, state: &mut ParseState,
lib: &mut FunctionsLib, lib: &mut FunctionsLib,
settings: ParseSettings, mut settings: ParseSettings,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
// [ ...
settings.pos = eat_token(input, Token::LeftBracket);
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
@ -704,8 +710,11 @@ fn parse_map_literal(
input: &mut TokenStream, input: &mut TokenStream,
state: &mut ParseState, state: &mut ParseState,
lib: &mut FunctionsLib, lib: &mut FunctionsLib,
settings: ParseSettings, mut settings: ParseSettings,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
// #{ ...
settings.pos = eat_token(input, Token::MapStart);
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
@ -947,31 +956,107 @@ fn parse_primary(
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
let (token, _) = match token {
// { - block statement as expression
Token::LeftBrace if settings.allow_stmt_expr => {
return parse_block(input, state, lib, settings.level_up()).map(|block| match block {
Stmt::Block(statements, pos) => Expr::Stmt(Box::new(statements.into()), pos),
_ => unreachable!(),
})
}
Token::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)),
_ => input.next().unwrap(),
};
let (next_token, _) = input.peek().unwrap();
let mut root_expr = match token { let mut root_expr = match token {
Token::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)),
Token::IntegerConstant(_)
| Token::CharConstant(_)
| Token::StringConstant(_)
| Token::True
| Token::False => match input.next().unwrap().0 {
Token::IntegerConstant(x) => Expr::IntegerConstant(x, settings.pos), Token::IntegerConstant(x) => Expr::IntegerConstant(x, settings.pos),
#[cfg(not(feature = "no_float"))]
Token::FloatConstant(x) => Expr::FloatConstant(x, settings.pos),
Token::CharConstant(c) => Expr::CharConstant(c, settings.pos), Token::CharConstant(c) => Expr::CharConstant(c, settings.pos),
Token::StringConstant(s) => { Token::StringConstant(s) => {
Expr::StringConstant(state.get_interned_string(s), settings.pos) Expr::StringConstant(state.get_interned_string(s), settings.pos)
} }
Token::True => Expr::BoolConstant(true, settings.pos),
Token::False => Expr::BoolConstant(false, settings.pos),
_ => unreachable!(),
},
#[cfg(not(feature = "no_float"))]
Token::FloatConstant(x) => {
let x = *x;
input.next().unwrap();
Expr::FloatConstant(x, settings.pos)
}
// { - block statement as expression
Token::LeftBrace if settings.allow_stmt_expr => {
match parse_block(input, state, lib, settings.level_up())? {
Stmt::Block(statements, pos) => Expr::Stmt(Box::new(statements.into()), pos),
_ => unreachable!(),
}
}
// ( - grouped expression
Token::LeftParen => parse_paren_expr(input, state, lib, settings.level_up())?,
// If statement is allowed to act as expressions
Token::If if settings.allow_if_expr => Expr::Stmt(
Box::new(vec![parse_if(input, state, lib, settings.level_up())?].into()),
settings.pos,
),
// Switch statement is allowed to act as expressions
Token::Switch if settings.allow_switch_expr => Expr::Stmt(
Box::new(vec![parse_switch(input, state, lib, settings.level_up())?].into()),
settings.pos,
),
// | ...
#[cfg(not(feature = "no_function"))]
Token::Pipe | Token::Or if settings.allow_anonymous_fn => {
let mut new_state = ParseState::new(
state.engine,
state.script_hash,
#[cfg(not(feature = "unchecked"))]
state.max_function_expr_depth,
#[cfg(not(feature = "unchecked"))]
state.max_function_expr_depth,
);
let settings = ParseSettings {
allow_if_expr: true,
allow_switch_expr: true,
allow_stmt_expr: true,
allow_anonymous_fn: true,
is_global: false,
is_function_scope: true,
is_breakable: false,
level: 0,
pos: settings.pos,
};
let (expr, func) = parse_anon_fn(input, &mut new_state, lib, settings)?;
#[cfg(not(feature = "no_closure"))]
new_state.externals.iter().for_each(|(closure, pos)| {
state.access_var(closure, *pos);
});
// Qualifiers (none) + function name + number of arguments.
let hash = calc_script_fn_hash(empty(), &func.name, func.params.len()).unwrap();
lib.insert(hash, func);
expr
}
// Array literal
#[cfg(not(feature = "no_index"))]
Token::LeftBracket => parse_array_literal(input, state, lib, settings.level_up())?,
// Map literal
#[cfg(not(feature = "no_object"))]
Token::MapStart => parse_map_literal(input, state, lib, settings.level_up())?,
// Identifier
Token::Identifier(_) => {
let s = match input.next().unwrap().0 {
Token::Identifier(s) => s,
_ => unreachable!(),
};
match input.peek().unwrap().0 {
// Function call // Function call
Token::Identifier(s) if *next_token == Token::LeftParen || *next_token == Token::Bang => { Token::LeftParen | Token::Bang => {
// Once the identifier consumed we must enable next variables capturing // Once the identifier consumed we must enable next variables capturing
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
{ {
@ -985,7 +1070,7 @@ fn parse_primary(
} }
// Namespace qualification // Namespace qualification
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Token::Identifier(s) if *next_token == Token::DoubleColon => { Token::DoubleColon => {
// Once the identifier consumed we must enable next variables capturing // Once the identifier consumed we must enable next variables capturing
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
{ {
@ -998,7 +1083,7 @@ fn parse_primary(
Expr::Variable(Box::new((None, None, var_name_def))) Expr::Variable(Box::new((None, None, var_name_def)))
} }
// Normal variable access // Normal variable access
Token::Identifier(s) => { _ => {
let index = state.access_var(&s, settings.pos); let index = state.access_var(&s, settings.pos);
let var_name_def = Ident { let var_name_def = Ident {
name: state.get_interned_string(s), name: state.get_interned_string(s),
@ -1006,10 +1091,22 @@ fn parse_primary(
}; };
Expr::Variable(Box::new((index, None, var_name_def))) Expr::Variable(Box::new((index, None, var_name_def)))
} }
}
}
// Reserved keyword or symbol
Token::Reserved(_) => {
let s = match input.next().unwrap().0 {
Token::Reserved(s) => s,
_ => unreachable!(),
};
match input.peek().unwrap().0 {
// Function call is allowed to have reserved keyword // Function call is allowed to have reserved keyword
Token::Reserved(s) if *next_token == Token::LeftParen || *next_token == Token::Bang => { Token::LeftParen | Token::Bang => {
if is_keyword_function(&s) { if s == KEYWORD_THIS {
return Err(PERR::Reserved(s).into_err(settings.pos));
} else if is_keyword_function(&s) {
let var_name_def = Ident { let var_name_def = Ident {
name: state.get_interned_string(s), name: state.get_interned_string(s),
pos: settings.pos, pos: settings.pos,
@ -1019,9 +1116,8 @@ fn parse_primary(
return Err(PERR::Reserved(s).into_err(settings.pos)); return Err(PERR::Reserved(s).into_err(settings.pos));
} }
} }
// Access to `this` as a variable is OK // Access to `this` as a variable is OK
Token::Reserved(s) if s == KEYWORD_THIS && *next_token != Token::LeftParen => { _ if s == KEYWORD_THIS => {
if !settings.is_function_scope { if !settings.is_function_scope {
let msg = format!("'{}' can only be used in functions", s); let msg = format!("'{}' can only be used in functions", s);
return Err(LexError::ImproperSymbol(s, msg).into_err(settings.pos)); return Err(LexError::ImproperSymbol(s, msg).into_err(settings.pos));
@ -1033,19 +1129,23 @@ fn parse_primary(
Expr::Variable(Box::new((None, None, var_name_def))) Expr::Variable(Box::new((None, None, var_name_def)))
} }
} }
_ if is_valid_identifier(s.chars()) => {
Token::Reserved(s) if is_valid_identifier(s.chars()) => {
return Err(PERR::Reserved(s).into_err(settings.pos)); return Err(PERR::Reserved(s).into_err(settings.pos));
} }
_ => {
return Err(LexError::UnexpectedInput(s).into_err(settings.pos));
}
}
}
Token::LeftParen => parse_paren_expr(input, state, lib, settings.level_up())?, Token::LexError(_) => {
#[cfg(not(feature = "no_index"))] let err = match input.next().unwrap().0 {
Token::LeftBracket => parse_array_literal(input, state, lib, settings.level_up())?, Token::LexError(err) => err,
#[cfg(not(feature = "no_object"))] _ => unreachable!(),
Token::MapStart => parse_map_literal(input, state, lib, settings.level_up())?, };
Token::True => Expr::BoolConstant(true, settings.pos),
Token::False => Expr::BoolConstant(false, settings.pos), return Err(err.into_err(settings.pos));
Token::LexError(err) => return Err(err.into_err(settings.pos)), }
_ => { _ => {
return Err( return Err(
@ -1056,26 +1156,26 @@ fn parse_primary(
// Tail processing all possible postfix operators // Tail processing all possible postfix operators
loop { loop {
let (token, _) = input.peek().unwrap(); let (tail_token, _) = input.peek().unwrap();
if !root_expr.is_valid_postfix(token) { if !root_expr.is_valid_postfix(tail_token) {
break; break;
} }
let (token, token_pos) = input.next().unwrap(); let (tail_token, tail_pos) = input.next().unwrap();
settings.pos = token_pos; settings.pos = tail_pos;
root_expr = match (root_expr, token) { root_expr = match (root_expr, tail_token) {
// Qualified function call with ! // Qualified function call with !
(Expr::Variable(x), Token::Bang) if x.1.is_some() => { (Expr::Variable(x), Token::Bang) if x.1.is_some() => {
return Err(if !match_token(input, Token::LeftParen).0 { return Err(if !match_token(input, Token::LeftParen).0 {
LexError::UnexpectedInput(Token::Bang.syntax().to_string()).into_err(token_pos) LexError::UnexpectedInput(Token::Bang.syntax().to_string()).into_err(tail_pos)
} else { } else {
LexError::ImproperSymbol( LexError::ImproperSymbol(
"!".to_string(), "!".to_string(),
"'!' cannot be used to call module functions".to_string(), "'!' cannot be used to call module functions".to_string(),
) )
.into_err(token_pos) .into_err(tail_pos)
}); });
} }
// Function call with ! // Function call with !
@ -1132,7 +1232,7 @@ fn parse_primary(
(expr, Token::LeftBracket) => { (expr, Token::LeftBracket) => {
parse_index_chain(input, state, lib, expr, settings.level_up())? parse_index_chain(input, state, lib, expr, settings.level_up())?
} }
// Method access // Property access
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
(expr, Token::Period) => { (expr, Token::Period) => {
// prevents capturing of the object properties as vars: xxx.<var> // prevents capturing of the object properties as vars: xxx.<var>
@ -1142,7 +1242,7 @@ fn parse_primary(
} }
let rhs = parse_unary(input, state, lib, settings.level_up())?; let rhs = parse_unary(input, state, lib, settings.level_up())?;
make_dot_expr(state, expr, rhs, token_pos)? make_dot_expr(state, expr, rhs, tail_pos)?
} }
// Unknown postfix operator // Unknown postfix operator
(expr, token) => unreachable!( (expr, token) => unreachable!(
@ -1193,16 +1293,6 @@ fn parse_unary(
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
match token { match token {
// If statement is allowed to act as expressions
Token::If if settings.allow_if_expr => Ok(Expr::Stmt(
Box::new(vec![parse_if(input, state, lib, settings.level_up())?].into()),
settings.pos,
)),
// Switch statement is allowed to act as expressions
Token::Switch if settings.allow_switch_expr => Ok(Expr::Stmt(
Box::new(vec![parse_switch(input, state, lib, settings.level_up())?].into()),
settings.pos,
)),
// -expr // -expr
Token::UnaryMinus => { Token::UnaryMinus => {
let pos = eat_token(input, Token::UnaryMinus); let pos = eat_token(input, Token::UnaryMinus);
@ -1286,44 +1376,6 @@ fn parse_unary(
pos, pos,
)) ))
} }
// | ...
#[cfg(not(feature = "no_function"))]
Token::Pipe | Token::Or if settings.allow_anonymous_fn => {
let mut new_state = ParseState::new(
state.engine,
state.script_hash,
#[cfg(not(feature = "unchecked"))]
state.max_function_expr_depth,
#[cfg(not(feature = "unchecked"))]
state.max_function_expr_depth,
);
let settings = ParseSettings {
allow_if_expr: true,
allow_switch_expr: true,
allow_stmt_expr: true,
allow_anonymous_fn: true,
is_global: false,
is_function_scope: true,
is_breakable: false,
level: 0,
pos: *token_pos,
};
let (expr, func) = parse_anon_fn(input, &mut new_state, lib, settings)?;
#[cfg(not(feature = "no_closure"))]
new_state.externals.iter().for_each(|(closure, pos)| {
state.access_var(closure, *pos);
});
// Qualifiers (none) + function name + number of arguments.
let hash = calc_script_fn_hash(empty(), &func.name, func.params.len()).unwrap();
lib.insert(hash, func);
Ok(expr)
}
// <EOF> // <EOF>
Token::EOF => Err(PERR::UnexpectedEOF.into_err(settings.pos)), Token::EOF => Err(PERR::UnexpectedEOF.into_err(settings.pos)),
// All other tokens // All other tokens
@ -1816,13 +1868,12 @@ fn parse_binary_op(
make_in_expr(current_lhs, rhs, pos)? make_in_expr(current_lhs, rhs, pos)?
} }
#[cfg(not(feature = "no_object"))] // #[cfg(not(feature = "no_object"))]
Token::Period => { // Token::Period => {
let rhs = args.pop().unwrap(); // let rhs = args.pop().unwrap();
let current_lhs = args.pop().unwrap(); // let current_lhs = args.pop().unwrap();
make_dot_expr(state, current_lhs, rhs, pos)? // make_dot_expr(state, current_lhs, rhs, pos)?
} // }
Token::Custom(s) Token::Custom(s)
if state if state
.engine .engine
@ -2583,6 +2634,7 @@ fn parse_stmt(
} }
Token::If => parse_if(input, state, lib, settings.level_up()).map(Some), Token::If => parse_if(input, state, lib, settings.level_up()).map(Some),
Token::Switch => parse_switch(input, state, lib, settings.level_up()).map(Some),
Token::While | Token::Loop => { Token::While | Token::Loop => {
parse_while_loop(input, state, lib, settings.level_up()).map(Some) parse_while_loop(input, state, lib, settings.level_up()).map(Some)
} }