Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion example/calculator/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,13 @@ A(i32) : A plus a2=A {
| M
;

M(i32) : M star m2=M { M * m2 }
M(i32) : M_optim star m2=M_optim { M_optim * m2 }
| P
;

// check for single-rule optimization
M_optim(i32): M { M * 1 };

P(i32) : num {
if let Token::Num(n) = num { n }
else { return Err(format!("{:?}", num)); }
Expand Down
2 changes: 1 addition & 1 deletion rusty_lr_buildscript/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -761,7 +761,7 @@ impl Builder {
// diagnostics for optimization
if grammar.optimize {
use rusty_lr_parser::grammar::OptimizeRemove;
let optimized = grammar.optimize(10);
let optimized = grammar.optimize(25);

if self.note_optimization {
// terminals merged into terminal class
Expand Down
2 changes: 1 addition & 1 deletion rusty_lr_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub fn lr1(input: TokenStream) -> TokenStream {
Err(e) => return e.to_compile_error().into(),
};
if grammar.optimize {
grammar.optimize(5);
grammar.optimize(15);
}
grammar.builder = grammar.create_builder();
let diags = grammar.build_grammar();
Expand Down
204 changes: 174 additions & 30 deletions rusty_lr_parser/src/emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1102,6 +1102,45 @@ impl Grammar {
// TokenStream to define reduce function for each production rule
let mut fn_reduce_for_each_rule_stream = TokenStream::new();

for (i, action) in self.custom_reduce_actions.iter().enumerate() {
let fn_name = format_ident!("custom_reduce_action_{}", i);

let data_arg = action
.input_type
.as_ref()
.map(|(name, ty)| {
quote! { mut #name: #ty, }
})
.unwrap_or_default();

let location_arg = action
.input_location
.as_ref()
.map(|name| {
quote! { mut #name: #location_typename, }
})
.unwrap_or_default();

let body = &action.body.body;

let output_type = if let Some(ty) = action.output_type.as_ref() {
ty.clone()
} else {
quote! { () }
};

fn_reduce_for_each_rule_stream.extend(quote! {
fn #fn_name(
#data_arg
#location_arg
#user_data_parameter_name: &mut #user_data_typename,
__rustylr_location0: &mut #location_typename,
) -> Result<#output_type, #reduce_error_typename> {
Ok(#body)
}
});
}

let mut rule_index: usize = 0;
for (nonterm_idx, nonterm) in self.nonterminals.iter().enumerate() {
for (rule_local_id, rule) in nonterm.rules.iter().enumerate() {
Expand Down Expand Up @@ -1129,9 +1168,8 @@ impl Grammar {
if rule.is_used {
let mut debug_tag_check_stream = TokenStream::new();
let mut stack_mapto_map = std::collections::BTreeMap::new();
for (token_index_from_end, token) in
rule.tokens.iter().rev().enumerate()
{
for (token_idx, token) in rule.tokens.iter().enumerate().rev() {
let token_index_from_end = rule.tokens.len() - 1 - token_idx;
let stack_name = token_to_stack_name(token.token);
let tag_name = stack_name.unwrap_or(&empty_tag_name);

Expand All @@ -1152,59 +1190,82 @@ impl Grammar {
}

if let Some(stack_name) = stack_name {
// if variable was not used at this reduce action,
// we can use `truncate` instead of `pop` for optimization
// so check it here
let mapto = if let Some(mapto) = &token.mapto {
if reduce_action.contains_ident(mapto) {
Some(mapto.clone())
let mapto = if let Some(first_chain) =
token.reduce_action_chains.first()
{
let first_chain = &self.custom_reduce_actions[*first_chain];
if first_chain.input_type.is_some() {
Some(format_ident!("__rustylr_data_{}", token_idx))
} else {
None
}
} else {
None
// if variable was not used at this reduce action,
// we can use `truncate` instead of `pop` for optimization
// so check it here
if let Some(mapto) = &token.mapto {
if reduce_action.contains_ident(mapto) {
Some(mapto.clone())
} else {
None
}
} else {
None
}
};
stack_mapto_map
.entry(StackName::DataStack(stack_name.clone()))
.or_insert_with(Vec::new)
.push(mapto);
}
let location_mapto = if let Some(mapto) = &token.mapto {
let location_varname =
format_ident!("__rustylr_location_{}", mapto);

// if variable was not used at this reduce action,
// we can use `truncate` instead of `pop` for optimization
// so check it here
if reduce_action.contains_ident(&location_varname) {
Some(location_varname)
let location_mapto = if token.reduce_action_chains.is_empty() {
if let Some(mapto) = &token.mapto {
let location_varname =
format_ident!("__rustylr_location_{}", mapto);

// if variable was not used at this reduce action,
// we can use `truncate` instead of `pop` for optimization
// so check it here
if reduce_action.contains_ident(&location_varname) {
Some(location_varname)
} else {
None
}
} else {
None
}
} else {
None
if token.reduce_action_chains.iter().any(|&idx| {
let action = &self.custom_reduce_actions[idx];
action.input_location.is_some()
}) {
Some(format_ident!("__rustylr_location_{}", token_idx))
} else {
None
}
};
stack_mapto_map
.entry(StackName::LocationStack)
.or_insert_with(Vec::new)
.push(location_mapto);

debug_tag_check_stream.extend(quote! {
debug_assert!(
__data_stack.#tag_stack_name.get(
__data_stack.#tag_stack_name.len()-1-#token_index_from_end
) == Some( &#tag_enum_name::#tag_name )
);
});
debug_assert!(
__data_stack.#tag_stack_name.get(
__data_stack.#tag_stack_name.len()-1-#token_index_from_end
) == Some( &#tag_enum_name::#tag_name )
);
});
}

// if there are variable with same name, the last one will be used (by shadowing)
// so set the front one to `None`
// this will also help optimizing performance by using `truncate` instead of `pop`
for maptos in &mut stack_mapto_map.values_mut() {
for var_right in (0..maptos.len()).rev() {
// maptos is in reverse order of the rule tokens
for var_right in 0..maptos.len() {
if let Some(var_name) = maptos[var_right].as_ref().cloned() {
for var_left in 0..var_right {
for var_left in var_right + 1..maptos.len() {
if maptos[var_left].as_ref() == Some(&var_name) {
maptos[var_left] = None;
Comment on lines +1266 to 1270

Copilot AI Sep 13, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The variable names var_right and var_left are misleading since the loop direction has changed. With the comment indicating reverse order and the loops now going forward, these names create confusion about the actual iteration direction.

Copilot uses AI. Check for mistakes.
}
Expand Down Expand Up @@ -1273,8 +1334,89 @@ impl Grammar {
}
if last_none_count > 0 {
extract_data_stream.extend(quote! {
#stack_stream.truncate(#stack_stream.len() - #last_none_count);
});
#stack_stream.truncate(#stack_stream.len() - #last_none_count);
});
}
}

let mut custom_reduce_action_stream = TokenStream::new();
for (token_idx, token) in rule.tokens.iter().enumerate() {
if token.reduce_action_chains.is_empty() {
continue;
}
let data_varname = format_ident!("__rustylr_data_{}", token_idx);
let location_varname =
format_ident!("__rustylr_location_{}", token_idx);
let location_used_in_this_action = if let Some(mapto) = &token.mapto
{
let location_mapto_varname =
format_ident!("__rustylr_location_{}", mapto);
reduce_action.contains_ident(&location_mapto_varname)
} else {
false
};

for (idx, &chain_idx) in
token.reduce_action_chains.iter().enumerate()
{
let action = &self.custom_reduce_actions[chain_idx];
let fn_name =
format_ident!("custom_reduce_action_{}", chain_idx);

let data_arg = if action.input_type.is_some() {
quote! { #data_varname, }
} else {
quote! {}
};

// if location is needed for later reduce action, pass by clone
// else, pass by move
let mut location_used_later = location_used_in_this_action;
if !location_used_later {
for &chain_idx in
token.reduce_action_chains[idx + 1..].iter()
{
let action = &self.custom_reduce_actions[chain_idx];
if action.input_location.is_some() {
location_used_later = true;
break;
}
}
}
let location_arg = if action.input_location.is_some() {
if location_used_later {
quote! { #location_varname.clone(), }
} else {
quote! { #location_varname, }
}
} else {
quote! {}
};

custom_reduce_action_stream.extend(quote! {
let #data_varname = Self::#fn_name(
#data_arg
#location_arg
#user_data_parameter_name,
__rustylr_location0,
)?;
});
}

if let Some(mapto) = &token.mapto {
if reduce_action.contains_ident(mapto) {
custom_reduce_action_stream.extend(quote! {
let mut #mapto = #data_varname;
});
}
let location_mapto_varname =
format_ident!("__rustylr_location_{}", mapto);

if reduce_action.contains_ident(&location_mapto_varname) {
custom_reduce_action_stream.extend(quote! {
let mut #location_mapto_varname = #location_varname;
});
}
}
}

Expand Down Expand Up @@ -1305,6 +1447,7 @@ impl Grammar {
#modify_tag_stream

#extract_data_stream
#custom_reduce_action_stream

let __res = #body;
__data_stack.#stack_name.push(__res);
Expand Down Expand Up @@ -1332,6 +1475,7 @@ impl Grammar {
#modify_tag_stream

#extract_data_stream
#custom_reduce_action_stream

#body

Expand Down
Loading