Skip to content

Commit

Permalink
[PoC] Intoroduce parameterizing rules with conditonal
Browse files Browse the repository at this point in the history
I would like to propose a new grammar in this PR.
I believe that more parameterizing rules can handle more abstract rules if we can switch between rules and actions that are expanded by conditions in order to make rules common.

Syntax is as follows:
```
%rule defined_rule(X, condition): /* empty */
                                | X { $$ = $1; } %if(condition) /* 1 */
                                | %if(condition) X %endif X { $$ = $1; } /* 2 */
                                ;

%%

r_true        : defined_rule(number, %true)
              ;

r_false       : defined_rule(number, %false)
              ;
```

1. It's like a postfix if in Ruby. If condition is false, it is equivalent to missing this line.
2. If statementIf condition is false, it is equivalent to missing RHS between `%if` and`% endif`.

I believe it will solve the problem mentioned in the article below with the tight coupling with Lexer "to disable certain generation rules under certain conditions" and I would like to propose this feature to solve this problem.
https://yui-knk.hatenablog.com/entry/2023/04/04/190413

We can trace the RHS to [f_args](https://github.com/ruby/ruby/blob/2f916812a9b818b432ee7c299e021ec62d4727fb/parse.y#L5523-L5575) > [args_tail](https://github.com/ruby/ruby/blob/2f916812a9b818b432ee7c299e021ec62d4727fb/parse.y#L5487-L5503) > [args_forward](https://github.com/ruby/ruby/blob/2f916812a9b818b432ee7c299e021ec62d4727fb/parse.y#L5586-L5597), where f_args is the RHS of both the lambda argument (f_larglist) and the method definition argument (f_arglist).
So if we can switch between RHS and actions by passing parameters, we can break up the Lexer/Parser coupling here.
  • Loading branch information
ydah committed May 13, 2024
1 parent 95e0cc2 commit 9a48181
Show file tree
Hide file tree
Showing 14 changed files with 809 additions and 518 deletions.
7 changes: 6 additions & 1 deletion lib/lrama/grammar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class Grammar
:after_shift, :before_reduce, :after_reduce, :after_shift_error_token, :after_pop_stack,
:symbols_resolver, :types,
:rules, :rule_builders,
:sym_to_rules, :no_stdlib
:sym_to_rules, :no_stdlib, :if_count

def_delegators "@symbols_resolver", :symbols, :nterms, :terms, :add_nterm, :add_term,
:find_symbol_by_number!, :find_symbol_by_id!, :token_to_symbol,
Expand Down Expand Up @@ -58,6 +58,7 @@ def initialize(rule_counter)
@accept_symbol = nil
@aux = Auxiliary.new
@no_stdlib = false
@if_count = 0

append_special_symbols
end
Expand Down Expand Up @@ -170,6 +171,10 @@ def find_rules_by_symbol(sym)
@sym_to_rules[sym.number]
end

def initialize_if_count
@if_count = 0
end

private

def compute_nullable
Expand Down
4 changes: 4 additions & 0 deletions lib/lrama/grammar/binding.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ def resolve_symbol(symbol)
if symbol.is_a?(Lexer::Token::InstantiateRule)
resolved_args = symbol.args.map { |arg| resolve_symbol(arg) }
Lrama::Lexer::Token::InstantiateRule.new(s_value: symbol.s_value, location: symbol.location, args: resolved_args, lhs_tag: symbol.lhs_tag)
elsif symbol.is_a?(Lexer::Token::ControlSyntax)
resolved = symbol.dup
resolved.condition = @parameter_to_arg[symbol.condition_value]
resolved
else
@parameter_to_arg[symbol.s_value] || symbol
end
Expand Down
25 changes: 25 additions & 0 deletions lib/lrama/grammar/parameterizing_rule/rhs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,31 @@ def initialize
@precedence_sym = nil
end

def skip?(bindings)
return false unless @symbols.last
last_sym = bindings.resolve_symbol(@symbols.last)
last_sym.is_a?(Lexer::Token::ControlSyntax) && last_sym.if? && last_sym.false?
end

def resolve_symbols(bindings)
is_skip = []
@symbols.map do |sym|
resolved = bindings.resolve_symbol(sym)
if resolved.is_a?(Lexer::Token::ControlSyntax)
if resolved.if?
is_skip.push(resolved.false?)
elsif resolved.endif?
is_skip.pop
else
raise "Unexpected control syntax: #{resolved.condition_value}"
end
nil
else
resolved unless is_skip.last
end
end.compact
end

def resolve_user_code(bindings)
return unless user_code

Expand Down
6 changes: 5 additions & 1 deletion lib/lrama/grammar/rule_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,17 @@ def process_rhs
if (created_lhs = @parameterizing_rule_resolver.created_lhs(lhs_s_value))
@replaced_rhs << created_lhs
else
next if parameterizing_rule.rhs_list.all? { |r| r.skip?(bindings) }
lhs_token = Lrama::Lexer::Token::Ident.new(s_value: lhs_s_value, location: token.location)
@replaced_rhs << lhs_token
@parameterizing_rule_resolver.created_lhs_list << lhs_token
parameterizing_rule.rhs_list.each do |r|
rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, @parameterizing_rule_resolver, lhs_tag: token.lhs_tag || parameterizing_rule.tag)
rule_builder.lhs = lhs_token
r.symbols.each { |sym| rule_builder.add_rhs(bindings.resolve_symbol(sym)) }
next if r.skip?(bindings)
r.resolve_symbols(bindings).each do |sym|
rule_builder.add_rhs(sym)
end
rule_builder.line = line
rule_builder.precedence_sym = r.precedence_sym
rule_builder.user_code = r.resolve_user_code(bindings)
Expand Down
4 changes: 4 additions & 0 deletions lib/lrama/lexer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ class Lexer
%rule
%no-stdlib
%inline
%if
%endif
%true
%false
)

def initialize(grammar_file)
Expand Down
1 change: 1 addition & 0 deletions lib/lrama/lexer/token.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'lrama/lexer/token/char'
require 'lrama/lexer/token/control_syntax'
require 'lrama/lexer/token/ident'
require 'lrama/lexer/token/instantiate_rule'
require 'lrama/lexer/token/tag'
Expand Down
34 changes: 34 additions & 0 deletions lib/lrama/lexer/token/control_syntax.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module Lrama
class Lexer
class Token
class ControlSyntax < Token
attr_accessor :condition

def initialize(s_value:, location:, condition: nil)
@condition = condition
super(s_value: s_value, location: location)
end

def if?
s_value == '%if'
end

def endif?
s_value == '%endif'
end

def true?
!!@condition&.s_value
end

def false?
!true?
end

def condition_value
@condition&.s_value
end
end
end
end
end
Loading

0 comments on commit 9a48181

Please sign in to comment.