mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-06-04 15:51:15 -03:00
ast: Replace can_parse with static dispatch
This commit is contained in:
committed by
Johannes Altmanninger
parent
08220c2189
commit
b8d1dc93d6
310
src/ast.rs
310
src/ast.rs
@@ -446,6 +446,12 @@ fn get(&self, index: usize) -> Option<&Self::ContentsNode> {
|
||||
}
|
||||
}
|
||||
|
||||
/// This is for optional values and for lists.
|
||||
trait CheckParse {
|
||||
/// A true return means we should descend into the production, false means stop.
|
||||
fn can_be_parsed(pop: &mut Populator<'_>) -> bool;
|
||||
}
|
||||
|
||||
/// Implement the node trait.
|
||||
macro_rules! implement_node {
|
||||
(
|
||||
@@ -520,7 +526,7 @@ fn set_parents(&mut self) {}
|
||||
|
||||
/// Define a node that implements the keyword trait.
|
||||
macro_rules! define_keyword_node {
|
||||
( $name:ident, $($allowed:expr),* $(,)? ) => {
|
||||
( $name:ident, $($allowed:ident),* $(,)? ) => {
|
||||
#[derive(Default, Debug)]
|
||||
pub struct $name {
|
||||
parent: Option<*const dyn Node>,
|
||||
@@ -553,7 +559,7 @@ fn keyword_mut(&mut self) -> &mut ParseKeyword {
|
||||
&mut self.keyword
|
||||
}
|
||||
fn allowed_keywords(&self) -> &'static [ParseKeyword] {
|
||||
&[$($allowed),*]
|
||||
&[$(ParseKeyword::$allowed),*]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -561,7 +567,7 @@ fn allowed_keywords(&self) -> &'static [ParseKeyword] {
|
||||
|
||||
/// Define a node that implements the token trait.
|
||||
macro_rules! define_token_node {
|
||||
( $name:ident, $($allowed:expr),* $(,)? ) => {
|
||||
( $name:ident, $($allowed:ident),* $(,)? ) => {
|
||||
#[derive(Default, Debug)]
|
||||
pub struct $name {
|
||||
parent: Option<*const dyn Node>,
|
||||
@@ -594,9 +600,18 @@ fn token_type_mut(&mut self) -> &mut ParseTokenType {
|
||||
&mut self.parse_token_type
|
||||
}
|
||||
fn allowed_tokens(&self) -> &'static [ParseTokenType] {
|
||||
&[$($allowed),*]
|
||||
Self::ALLOWED_TOKENS
|
||||
}
|
||||
}
|
||||
impl CheckParse for $name {
|
||||
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
|
||||
let typ = pop.peek_type(0);
|
||||
Self::ALLOWED_TOKENS.contains(&typ)
|
||||
}
|
||||
}
|
||||
impl $name {
|
||||
const ALLOWED_TOKENS: &'static [ParseTokenType] = &[$(ParseTokenType::$allowed),*];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1081,6 +1096,11 @@ fn as_mut_redirection(&mut self) -> Option<&mut Redirection> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
impl CheckParse for Redirection {
|
||||
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
|
||||
pop.peek_type(0) == ParseTokenType::redirection
|
||||
}
|
||||
}
|
||||
|
||||
define_list_node!(
|
||||
VariableAssignmentList,
|
||||
@@ -1119,6 +1139,12 @@ fn as_mut_argument_or_redirection(&mut self) -> Option<&mut ArgumentOrRedirectio
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
impl CheckParse for ArgumentOrRedirection {
|
||||
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
|
||||
let typ = pop.peek_type(0);
|
||||
matches!(typ, ParseTokenType::string | ParseTokenType::redirection)
|
||||
}
|
||||
}
|
||||
|
||||
define_list_node!(
|
||||
ArgumentOrRedirectionList,
|
||||
@@ -1224,6 +1250,17 @@ fn as_mut_job_conjunction(&mut self) -> Option<&mut JobConjunction> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
impl CheckParse for JobConjunction {
|
||||
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
|
||||
let token = pop.peek_token(0);
|
||||
// These keywords end a job list.
|
||||
token.typ == ParseTokenType::string
|
||||
&& !matches!(
|
||||
token.keyword,
|
||||
ParseKeyword::kw_end | ParseKeyword::kw_else | ParseKeyword::kw_case
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ForHeader {
|
||||
@@ -1424,6 +1461,12 @@ fn as_mut_elseif_clause(&mut self) -> Option<&mut ElseifClause> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
impl CheckParse for ElseifClause {
|
||||
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
|
||||
pop.peek_token(0).keyword == ParseKeyword::kw_else
|
||||
&& pop.peek_token(1).keyword == ParseKeyword::kw_if
|
||||
}
|
||||
}
|
||||
|
||||
define_list_node!(ElseifClauseList, elseif_clause_list, ElseifClause);
|
||||
impl ConcreteNode for ElseifClauseList {
|
||||
@@ -1462,6 +1505,11 @@ fn as_mut_else_clause(&mut self) -> Option<&mut ElseClause> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
impl CheckParse for ElseClause {
|
||||
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
|
||||
pop.peek_token(0).keyword == ParseKeyword::kw_else
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct IfStatement {
|
||||
@@ -1524,6 +1572,11 @@ fn as_mut_case_item(&mut self) -> Option<&mut CaseItem> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
impl CheckParse for CaseItem {
|
||||
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
|
||||
pop.peek_token(0).keyword == ParseKeyword::kw_case
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct SwitchStatement {
|
||||
@@ -1642,6 +1695,11 @@ fn as_mut_job_continuation(&mut self) -> Option<&mut JobContinuation> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
impl CheckParse for JobContinuation {
|
||||
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
|
||||
pop.peek_type(0) == ParseTokenType::pipe
|
||||
}
|
||||
}
|
||||
|
||||
define_list_node!(JobContinuationList, job_continuation_list, JobContinuation);
|
||||
impl ConcreteNode for JobContinuationList {
|
||||
@@ -1685,6 +1743,12 @@ fn as_mut_job_conjunction_continuation(&mut self) -> Option<&mut JobConjunctionC
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
impl CheckParse for JobConjunctionContinuation {
|
||||
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
|
||||
let typ = pop.peek_type(0);
|
||||
matches!(typ, ParseTokenType::andand | ParseTokenType::oror)
|
||||
}
|
||||
}
|
||||
|
||||
/// An andor_job just wraps a job, but requires that the job have an 'and' or 'or' job_decorator.
|
||||
/// Note this is only used for andor_job_list; jobs that are not part of an andor_job_list are not
|
||||
@@ -1706,6 +1770,18 @@ fn as_mut_andor_job(&mut self) -> Option<&mut AndorJob> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
impl CheckParse for AndorJob {
|
||||
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
|
||||
let keyword = pop.peek_token(0).keyword;
|
||||
if !matches!(keyword, ParseKeyword::kw_and | ParseKeyword::kw_or) {
|
||||
return false;
|
||||
}
|
||||
// Check that the argument to and/or is a string that's not help. Otherwise
|
||||
// it's either 'and --help' or a naked 'and', and not part of this list.
|
||||
let next_token = pop.peek_token(1);
|
||||
next_token.typ == ParseTokenType::string && !next_token.is_help_argument
|
||||
}
|
||||
}
|
||||
|
||||
define_list_node!(AndorJobList, andor_job_list, AndorJob);
|
||||
impl ConcreteNode for AndorJobList {
|
||||
@@ -1816,6 +1892,25 @@ fn as_mut_variable_assignment(&mut self) -> Option<&mut VariableAssignment> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
impl CheckParse for VariableAssignment {
|
||||
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
|
||||
// Do we have a variable assignment at all?
|
||||
if !pop.peek_token(0).may_be_variable_assignment {
|
||||
return false;
|
||||
}
|
||||
// What is the token after it?
|
||||
match pop.peek_type(1) {
|
||||
// We have `a= cmd` and should treat it as a variable assignment.
|
||||
ParseTokenType::string => true,
|
||||
// We have `a=` which is OK if we are allowing incomplete, an error otherwise.
|
||||
ParseTokenType::terminate => pop.allow_incomplete(),
|
||||
// We have e.g. `a= >` which is an error.
|
||||
// Note that we do not produce an error here. Instead we return false
|
||||
// so this the token will be seen by allocate_populate_statement_contents.
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Zero or more newlines.
|
||||
#[derive(Default, Debug)]
|
||||
@@ -1867,33 +1962,74 @@ fn as_mut_argument(&mut self) -> Option<&mut Argument> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
impl CheckParse for Argument {
|
||||
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
|
||||
pop.peek_type(0) == ParseTokenType::string
|
||||
}
|
||||
}
|
||||
|
||||
define_token_node!(SemiNl, ParseTokenType::end);
|
||||
define_token_node!(String_, ParseTokenType::string);
|
||||
define_token_node!(TokenBackground, ParseTokenType::background);
|
||||
#[rustfmt::skip]
|
||||
define_token_node!(TokenConjunction, ParseTokenType::andand, ParseTokenType::oror);
|
||||
define_token_node!(TokenPipe, ParseTokenType::pipe);
|
||||
define_token_node!(TokenRedirection, ParseTokenType::redirection);
|
||||
define_token_node!(SemiNl, end);
|
||||
define_token_node!(String_, string);
|
||||
define_token_node!(TokenBackground, background);
|
||||
define_token_node!(TokenConjunction, andand, oror);
|
||||
define_token_node!(TokenPipe, pipe);
|
||||
define_token_node!(TokenRedirection, redirection);
|
||||
|
||||
#[rustfmt::skip]
|
||||
define_keyword_node!(DecoratedStatementDecorator, ParseKeyword::kw_command, ParseKeyword::kw_builtin, ParseKeyword::kw_exec);
|
||||
#[rustfmt::skip]
|
||||
define_keyword_node!(JobConjunctionDecorator, ParseKeyword::kw_and, ParseKeyword::kw_or);
|
||||
#[rustfmt::skip]
|
||||
define_keyword_node!(KeywordBegin, ParseKeyword::kw_begin);
|
||||
define_keyword_node!(KeywordCase, ParseKeyword::kw_case);
|
||||
define_keyword_node!(KeywordElse, ParseKeyword::kw_else);
|
||||
define_keyword_node!(KeywordEnd, ParseKeyword::kw_end);
|
||||
define_keyword_node!(KeywordFor, ParseKeyword::kw_for);
|
||||
define_keyword_node!(KeywordFunction, ParseKeyword::kw_function);
|
||||
define_keyword_node!(KeywordIf, ParseKeyword::kw_if);
|
||||
define_keyword_node!(KeywordIn, ParseKeyword::kw_in);
|
||||
#[rustfmt::skip]
|
||||
define_keyword_node!(KeywordNot, ParseKeyword::kw_not, ParseKeyword::kw_builtin, ParseKeyword::kw_exclam);
|
||||
define_keyword_node!(KeywordSwitch, ParseKeyword::kw_switch);
|
||||
define_keyword_node!(KeywordTime, ParseKeyword::kw_time);
|
||||
define_keyword_node!(KeywordWhile, ParseKeyword::kw_while);
|
||||
define_keyword_node!(DecoratedStatementDecorator, kw_command, kw_builtin, kw_exec);
|
||||
define_keyword_node!(JobConjunctionDecorator, kw_and, kw_or);
|
||||
define_keyword_node!(KeywordBegin, kw_begin);
|
||||
define_keyword_node!(KeywordCase, kw_case);
|
||||
define_keyword_node!(KeywordElse, kw_else);
|
||||
define_keyword_node!(KeywordEnd, kw_end);
|
||||
define_keyword_node!(KeywordFor, kw_for);
|
||||
define_keyword_node!(KeywordFunction, kw_function);
|
||||
define_keyword_node!(KeywordIf, kw_if);
|
||||
define_keyword_node!(KeywordIn, kw_in);
|
||||
define_keyword_node!(KeywordNot, kw_not, kw_builtin, kw_exclam);
|
||||
define_keyword_node!(KeywordSwitch, kw_switch);
|
||||
define_keyword_node!(KeywordTime, kw_time);
|
||||
define_keyword_node!(KeywordWhile, kw_while);
|
||||
|
||||
impl CheckParse for JobConjunctionDecorator {
|
||||
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
|
||||
// This is for a job conjunction like `and stuff`
|
||||
// But if it's `and --help` then we treat it as an ordinary command.
|
||||
let keyword = pop.peek_token(0).keyword;
|
||||
if !matches!(keyword, ParseKeyword::kw_and | ParseKeyword::kw_or) {
|
||||
return false;
|
||||
}
|
||||
!pop.peek_token(1).is_help_argument
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckParse for DecoratedStatementDecorator {
|
||||
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
|
||||
// Here the keyword is 'command' or 'builtin' or 'exec'.
|
||||
// `command stuff` executes a command called stuff.
|
||||
// `command -n` passes the -n argument to the 'command' builtin.
|
||||
// `command` by itself is a command.
|
||||
let keyword = pop.peek_token(0).keyword;
|
||||
if !matches!(
|
||||
keyword,
|
||||
ParseKeyword::kw_command | ParseKeyword::kw_builtin | ParseKeyword::kw_exec
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
let next_token = pop.peek_token(1);
|
||||
next_token.typ == ParseTokenType::string && !next_token.is_dash_prefix_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckParse for KeywordTime {
|
||||
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
|
||||
// Time keyword is only the time builtin if the next argument doesn't have a dash.
|
||||
let keyword = pop.peek_token(0).keyword;
|
||||
if !matches!(keyword, ParseKeyword::kw_time) {
|
||||
return false;
|
||||
}
|
||||
!pop.peek_token(1).is_dash_prefix_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl DecoratedStatement {
|
||||
/// \return the decoration for this statement.
|
||||
@@ -3293,120 +3429,12 @@ fn consume_excess_token_generating_error(&mut self) {
|
||||
}
|
||||
}
|
||||
|
||||
/// This is for optional values and for lists.
|
||||
/// A true return means we should descend into the production, false means stop.
|
||||
/// Note that the argument is always nullptr and should be ignored. It is provided strictly
|
||||
/// for overloading purposes.
|
||||
fn can_parse(&mut self, node: &dyn Node) -> bool {
|
||||
match node.typ() {
|
||||
Type::job_conjunction => {
|
||||
let token = self.peek_token(0);
|
||||
if token.typ != ParseTokenType::string {
|
||||
return false;
|
||||
}
|
||||
!matches!(
|
||||
token.keyword,
|
||||
// These end a job list.
|
||||
ParseKeyword::kw_end | ParseKeyword::kw_else | ParseKeyword::kw_case
|
||||
)
|
||||
}
|
||||
Type::argument => self.peek_type(0) == ParseTokenType::string,
|
||||
Type::redirection => self.peek_type(0) == ParseTokenType::redirection,
|
||||
Type::argument_or_redirection => {
|
||||
[ParseTokenType::string, ParseTokenType::redirection].contains(&self.peek_type(0))
|
||||
}
|
||||
Type::variable_assignment => {
|
||||
// Do we have a variable assignment at all?
|
||||
if !self.peek_token(0).may_be_variable_assignment {
|
||||
return false;
|
||||
}
|
||||
// What is the token after it?
|
||||
match self.peek_type(1) {
|
||||
ParseTokenType::string => {
|
||||
// We have `a= cmd` and should treat it as a variable assignment.
|
||||
true
|
||||
}
|
||||
ParseTokenType::terminate => {
|
||||
// We have `a=` which is OK if we are allowing incomplete, an error
|
||||
// otherwise.
|
||||
self.allow_incomplete()
|
||||
}
|
||||
_ => {
|
||||
// We have e.g. `a= >` which is an error.
|
||||
// Note that we do not produce an error here. Instead we return false
|
||||
// so this the token will be seen by allocate_populate_statement_contents.
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
Type::token_base => node
|
||||
.as_token()
|
||||
.unwrap()
|
||||
.allows_token(self.peek_token(0).typ),
|
||||
|
||||
// Note we have specific overloads for our keyword nodes, as they need custom logic.
|
||||
Type::keyword_base => {
|
||||
let keyword = node.as_keyword().unwrap();
|
||||
match keyword.allowed_keywords() {
|
||||
// job conjunction decorator
|
||||
[ParseKeyword::kw_and, ParseKeyword::kw_or] => {
|
||||
// This is for a job conjunction like `and stuff`
|
||||
// But if it's `and --help` then we treat it as an ordinary command.
|
||||
keyword.allows_keyword(self.peek_token(0).keyword)
|
||||
&& !self.peek_token(1).is_help_argument
|
||||
}
|
||||
// decorated statement decorator
|
||||
[ParseKeyword::kw_command, ParseKeyword::kw_builtin, ParseKeyword::kw_exec] => {
|
||||
// Here the keyword is 'command' or 'builtin' or 'exec'.
|
||||
// `command stuff` executes a command called stuff.
|
||||
// `command -n` passes the -n argument to the 'command' builtin.
|
||||
// `command` by itself is a command.
|
||||
if !keyword.allows_keyword(self.peek_token(0).keyword) {
|
||||
return false;
|
||||
}
|
||||
let tok1 = self.peek_token(1);
|
||||
tok1.typ == ParseTokenType::string && !tok1.is_dash_prefix_string()
|
||||
}
|
||||
[ParseKeyword::kw_time] => {
|
||||
// Time keyword is only the time builtin if the next argument doesn't
|
||||
// have a dash.
|
||||
keyword.allows_keyword(self.peek_token(0).keyword)
|
||||
&& !self.peek_token(1).is_dash_prefix_string()
|
||||
}
|
||||
_ => panic!("Unexpected keyword in can_parse()"),
|
||||
}
|
||||
}
|
||||
Type::job_continuation => self.peek_type(0) == ParseTokenType::pipe,
|
||||
Type::job_conjunction_continuation => {
|
||||
[ParseTokenType::andand, ParseTokenType::oror].contains(&self.peek_type(0))
|
||||
}
|
||||
Type::andor_job => {
|
||||
match self.peek_token(0).keyword {
|
||||
ParseKeyword::kw_and | ParseKeyword::kw_or => {
|
||||
// Check that the argument to and/or is a string that's not help. Otherwise
|
||||
// it's either 'and --help' or a naked 'and', and not part of this list.
|
||||
let nexttok = self.peek_token(1);
|
||||
nexttok.typ == ParseTokenType::string && !nexttok.is_help_argument
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
Type::elseif_clause => {
|
||||
self.peek_token(0).keyword == ParseKeyword::kw_else
|
||||
&& self.peek_token(1).keyword == ParseKeyword::kw_if
|
||||
}
|
||||
Type::else_clause => self.peek_token(0).keyword == ParseKeyword::kw_else,
|
||||
Type::case_item => self.peek_token(0).keyword == ParseKeyword::kw_case,
|
||||
_ => panic!("Unexpected token type in can_parse()"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Given that we are a list of type ListNodeType, whose contents type is ContentsNode,
|
||||
/// populate as many elements as we can.
|
||||
/// If exhaust_stream is set, then keep going until we get parse_token_type_t::terminate.
|
||||
fn populate_list<ListType: List>(&mut self, list: &mut ListType, exhaust_stream: bool)
|
||||
where
|
||||
<ListType as List>::ContentsNode: NodeMut,
|
||||
<ListType as List>::ContentsNode: NodeMut + CheckParse,
|
||||
{
|
||||
assert!(list.contents().is_empty(), "List is not initially empty");
|
||||
|
||||
@@ -3665,10 +3693,8 @@ fn allocate_populate_block_header(&mut self) -> Box<BlockStatementHeaderVariant>
|
||||
})
|
||||
}
|
||||
|
||||
fn try_parse<T: NodeMut + Default>(&mut self) -> Option<Box<T>> {
|
||||
// TODO Optimize this.
|
||||
let prototype = T::default();
|
||||
if !self.can_parse(&prototype) {
|
||||
fn try_parse<T: NodeMut + Default + CheckParse>(&mut self) -> Option<Box<T>> {
|
||||
if !T::can_be_parsed(self) {
|
||||
return None;
|
||||
}
|
||||
Some(self.allocate_visit())
|
||||
|
||||
Reference in New Issue
Block a user