lrama 0.5.12 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yaml +1 -1
  3. data/Gemfile +1 -1
  4. data/NEWS.md +20 -0
  5. data/README.md +14 -3
  6. data/Steepfile +3 -0
  7. data/lib/lrama/grammar/code/printer_code.rb +1 -1
  8. data/lib/lrama/grammar/code/rule_action.rb +1 -1
  9. data/lib/lrama/grammar/code.rb +19 -7
  10. data/lib/lrama/grammar/parameterizing_rule.rb +6 -0
  11. data/lib/lrama/grammar/parameterizing_rule_builder.rb +34 -0
  12. data/lib/lrama/grammar/parameterizing_rule_resolver.rb +30 -0
  13. data/lib/lrama/grammar/parameterizing_rule_rhs_builder.rb +53 -0
  14. data/lib/lrama/grammar/rule_builder.rb +26 -22
  15. data/lib/lrama/grammar.rb +15 -41
  16. data/lib/lrama/lexer/grammar_file.rb +21 -0
  17. data/lib/lrama/lexer/location.rb +77 -2
  18. data/lib/lrama/lexer/token/instantiate_rule.rb +18 -0
  19. data/lib/lrama/lexer/token/user_code.rb +10 -10
  20. data/lib/lrama/lexer/token.rb +1 -1
  21. data/lib/lrama/lexer.rb +21 -11
  22. data/lib/lrama/parser.rb +619 -454
  23. data/lib/lrama/states_reporter.rb +1 -1
  24. data/lib/lrama/version.rb +1 -1
  25. data/parser.y +95 -30
  26. data/sig/lrama/grammar/code/printer_code.rbs +1 -1
  27. data/sig/lrama/grammar/code.rbs +5 -5
  28. data/sig/lrama/grammar/parameterizing_rule.rbs +10 -0
  29. data/sig/lrama/grammar/parameterizing_rule_builder.rbs +19 -0
  30. data/sig/lrama/grammar/parameterizing_rule_resolver.rbs +16 -0
  31. data/sig/lrama/grammar/parameterizing_rule_rhs_builder.rbs +18 -0
  32. data/sig/lrama/grammar/parameterizing_rules/builder/base.rbs +2 -2
  33. data/sig/lrama/grammar/parameterizing_rules/builder.rbs +1 -1
  34. data/sig/lrama/grammar/rule_builder.rbs +2 -4
  35. data/sig/lrama/lexer/grammar_file.rbs +15 -0
  36. data/sig/lrama/lexer/location.rbs +13 -1
  37. data/sig/lrama/lexer/token/{parameterizing.rbs → instantiate_rule.rbs} +2 -7
  38. metadata +15 -5
  39. data/lib/lrama/lexer/token/parameterizing.rb +0 -34
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '008f1a5df39b115123be84cb5e1edd379d88458bc95256b0e4fe4659481ceb51'
4
- data.tar.gz: 02af30da8cf4cb2cad841f43b603433edafab09b0cf0e8993201c2e9aea12029
3
+ metadata.gz: 7b1a3e3181fc7060235e167ea97a92809b31aa5b74ef5978ab70415252bb87e3
4
+ data.tar.gz: 51a6ee0620321936fcd9d81c44ece3f9e444b977dc8fa940ddf920a5bb3b1dad
5
5
  SHA512:
6
- metadata.gz: 12cf8b98035052e89f9060dd73ec2b32ad9c5bec2fe4076e6ee0416832039962cf973c7ca895b482f2f434500648638c01a717d67357aa055eccce851995adaf
7
- data.tar.gz: a4cffc85bb5945a3d4d037c0eadee0df22e30236085e01f06bffe1646d0acafd09fdb606860752cdea9dbfdf45baa1fc8d244504b7a0ca29c37ab60f9a727367
6
+ metadata.gz: f2b8805ad79cc0c3f125eba2715d274387191e6a355910d11c88f918eae4906b9398444162210c7ce955b1ba3f67cabf3b0679569a65d63bdc52328f2d7dc2f7
7
+ data.tar.gz: 93de8cc0c7f9154f37825d59010a4850cc81b8a66f460d70f95aed44c0eb1224e583d1afb95155be2d9ae063d286a4fa719c133c993c6ec9bfba1c490dfc997b
@@ -13,7 +13,7 @@ jobs:
13
13
  strategy:
14
14
  fail-fast: false
15
15
  matrix:
16
- ruby: ['head', '3.2', '3.1', '3.0', '2.7', '2.6', '2.5']
16
+ ruby: ['head', '3.3', '3.2', '3.1', '3.0', '2.7', '2.6', '2.5']
17
17
  steps:
18
18
  - uses: actions/checkout@v4
19
19
  - uses: ruby/setup-ruby@v1
data/Gemfile CHANGED
@@ -12,6 +12,6 @@ gem "stackprof", platforms: [:ruby] # stackprof doesn't support Windows
12
12
  # Recent steep requires Ruby >= 3.0.0.
13
13
  # Then skip install on some CI jobs.
14
14
  if !ENV['GITHUB_ACTION'] || ENV['INSTALL_STEEP'] == 'true'
15
- gem "rbs", "3.3.0", require: false
15
+ gem "rbs", "3.4.0", require: false
16
16
  gem "steep", "1.6.0", require: false
17
17
  end
data/NEWS.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # NEWS for Lrama
2
2
 
3
+ ## Lrama 0.6.0 (2023-12-25)
4
+
5
+ ### User defined parameterizing rules
6
+
7
+ Allow to define parameterizing rule by `%rule` directive.
8
+
9
+ ```
10
+ %rule pair(X, Y): X Y { $$ = $1 + $2; }
11
+ ;
12
+
13
+ %%
14
+
15
+ program: stmt
16
+ ;
17
+
18
+ stmt: pair(ODD, EVEN) <num>
19
+ | pair(EVEN, ODD) <num>
20
+ ;
21
+ ```
22
+
3
23
  ## Lrama 0.5.11 (2023-12-02)
4
24
 
5
25
  ### Type specification of parameterizing rules
data/README.md CHANGED
@@ -49,9 +49,13 @@ Enter the formula:
49
49
 
50
50
  ## Versions and Branches
51
51
 
52
- ### v0_5 (`master` branch)
52
+ ### v0_6 (`master` branch)
53
53
 
54
- This branch is for Ruby 3.3. `lrama_0_5` branch is created from this branch, once Ruby 3.3 is released.
54
+ This branch is for Ruby 3.4. `lrama_0_6` branch is created from this branch, once Ruby 3.4 is released.
55
+
56
+ ### v0_5 (`lrama_0_5` branch)
57
+
58
+ This branch is for Ruby 3.3.
55
59
 
56
60
  ### v0_4 (`lrama_0_4` branch)
57
61
 
@@ -91,6 +95,13 @@ $ bundle exec rbs collection install
91
95
  $ bundle exec steep check
92
96
  ```
93
97
 
98
+ Running both of them:
99
+
100
+ ```shell
101
+ $ bundle install
102
+ $ bundle exec rake
103
+ ```
104
+
94
105
  ### Profiling Lrama
95
106
 
96
107
  #### 1. Create parse.tmp.y in ruby/ruby
@@ -136,7 +147,7 @@ $ stackprof --d3-flamegraph tmp/stackprof-cpu-myapp.dump > tmp/flamegraph.html
136
147
 
137
148
  ## Release flow
138
149
 
139
- 1. Update `Lrama::VERSION`
150
+ 1. Update `Lrama::VERSION` and NEWS.md
140
151
  2. Release as a gem by `rake release`
141
152
  3. Update Lrama in ruby/ruby by `cp -r LEGAL.md NEWS.md MIT exe lib template ruby/tool/lrama`
142
153
  4. Create new release on [GitHub](https://github.com/ruby/lrama/releases)
data/Steepfile CHANGED
@@ -8,6 +8,9 @@ target :lib do
8
8
  check "lib/lrama/grammar/code.rb"
9
9
  check "lib/lrama/grammar/counter.rb"
10
10
  check "lib/lrama/grammar/error_token.rb"
11
+ check "lib/lrama/grammar/parameterizing_rule_builder.rb"
12
+ check "lib/lrama/grammar/parameterizing_rule_resolver.rb"
13
+ check "lib/lrama/grammar/parameterizing_rule_rhs_builder.rb"
11
14
  check "lib/lrama/grammar/parameterizing_rules"
12
15
  check "lib/lrama/grammar/percent_code.rb"
13
16
  check "lib/lrama/grammar/precedence.rb"
@@ -2,7 +2,7 @@ module Lrama
2
2
  class Grammar
3
3
  class Code
4
4
  class PrinterCode < Code
5
- def initialize(type: nil, token_code: nil, tag: nil)
5
+ def initialize(type:, token_code:, tag:)
6
6
  super(type: type, token_code: token_code)
7
7
  @tag = tag
8
8
  end
@@ -2,7 +2,7 @@ module Lrama
2
2
  class Grammar
3
3
  class Code
4
4
  class RuleAction < Code
5
- def initialize(type: nil, token_code: nil, rule: nil)
5
+ def initialize(type:, token_code:, rule:)
6
6
  super(type: type, token_code: token_code)
7
7
  @rule = rule
8
8
  end
@@ -1,12 +1,29 @@
1
1
  require "forwardable"
2
+ require "lrama/grammar/code/initial_action_code"
3
+ require "lrama/grammar/code/no_reference_code"
4
+ require "lrama/grammar/code/printer_code"
5
+ require "lrama/grammar/code/rule_action"
2
6
 
3
7
  module Lrama
4
8
  class Grammar
5
- class Code < Struct.new(:type, :token_code, keyword_init: true)
9
+ class Code
6
10
  extend Forwardable
7
11
 
8
12
  def_delegators "token_code", :s_value, :line, :column, :references
9
13
 
14
+ attr_reader :type, :token_code
15
+
16
+ def initialize(type:, token_code:)
17
+ @type = type
18
+ @token_code = token_code
19
+ end
20
+
21
+ def ==(other)
22
+ self.class == other.class &&
23
+ self.type == other.type &&
24
+ self.token_code == other.token_code
25
+ end
26
+
10
27
  # $$, $n, @$, @n are translated to C code
11
28
  def translated_code
12
29
  t_code = s_value.dup
@@ -17,7 +34,7 @@ module Lrama
17
34
 
18
35
  str = reference_to_c(ref)
19
36
 
20
- t_code[first_column..last_column] = str
37
+ t_code[first_column...last_column] = str
21
38
  end
22
39
 
23
40
  return t_code
@@ -31,8 +48,3 @@ module Lrama
31
48
  end
32
49
  end
33
50
  end
34
-
35
- require "lrama/grammar/code/initial_action_code"
36
- require "lrama/grammar/code/no_reference_code"
37
- require "lrama/grammar/code/printer_code"
38
- require "lrama/grammar/code/rule_action"
@@ -0,0 +1,6 @@
1
+ module Lrama
2
+ class Grammar
3
+ class ParameterizingRule < Struct.new(:rules, :token, keyword_init: true)
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,34 @@
1
+ module Lrama
2
+ class Grammar
3
+ class ParameterizingRuleBuilder
4
+ attr_reader :name, :parameters, :rhs
5
+
6
+ def initialize(name, parameters, rhs)
7
+ @name = name
8
+ @parameters = parameters
9
+ @rhs = rhs
10
+ @required_parameters_count = parameters.count
11
+ end
12
+
13
+ def build_rules(token, actual_args, rule_counter, lhs_tag, line, rule_builders)
14
+ validate_argument_number!(token)
15
+ lhs = lhs(actual_args)
16
+ @rhs.map do |rhs|
17
+ rhs.build_rules(token, actual_args, parameters, rule_counter, lhs, lhs_tag, line, rule_builders)
18
+ end.flatten
19
+ end
20
+
21
+ private
22
+
23
+ def validate_argument_number!(token)
24
+ unless @required_parameters_count == token.args.count
25
+ raise "Invalid number of arguments. expect: #{@required_parameters_count} actual: #{token.args.count}"
26
+ end
27
+ end
28
+
29
+ def lhs(actual_args)
30
+ Lrama::Lexer::Token::Ident.new(s_value: "#{name}_#{actual_args.map(&:s_value).join('_')}")
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,30 @@
1
+ module Lrama
2
+ class Grammar
3
+ class ParameterizingRuleResolver
4
+ def initialize
5
+ @parameterizing_rule_builders = []
6
+ end
7
+
8
+ def add_parameterizing_rule_builder(builder)
9
+ @parameterizing_rule_builders << builder
10
+ end
11
+
12
+ def defined?(name)
13
+ !rule_builders(name).empty?
14
+ end
15
+
16
+ def build_rules(token, rule_counter, lhs_tag, line)
17
+ builder = rule_builders(token.s_value).last
18
+ raise "Unknown parameterizing rule #{token.s_value} at line #{token.line}" unless builder
19
+
20
+ builder.build_rules(token, token.args, rule_counter, lhs_tag, line, @parameterizing_rule_builders)
21
+ end
22
+
23
+ private
24
+
25
+ def rule_builders(name)
26
+ @parameterizing_rule_builders.select { |builder| builder.name == name }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,53 @@
1
+ module Lrama
2
+ class Grammar
3
+ class ParameterizingRuleRhsBuilder
4
+ attr_accessor :symbols, :user_code, :precedence_sym
5
+
6
+ def initialize
7
+ @symbols = []
8
+ @user_code = nil
9
+ @precedence_sym = nil
10
+ end
11
+
12
+ def build_rules(token, actual_args, parameters, rule_counter, lhs, lhs_tag, line, rule_builders)
13
+ nested_rules = build_nested_rules(token, actual_args, parameters, rule_counter, lhs_tag, line, rule_builders)
14
+ rule = Rule.new(id: rule_counter.increment, _lhs: lhs, _rhs: rhs(token, actual_args, parameters, nested_rules.last), lhs_tag: lhs_tag, token_code: user_code, precedence_sym: precedence_sym, lineno: line)
15
+ ParameterizingRule.new(rules: nested_rules.map(&:rules) + [rule], token: lhs)
16
+ end
17
+
18
+ private
19
+
20
+ def build_nested_rules(token, actual_args, parameters, rule_counter, lhs_tag, line, rule_builders)
21
+ symbols.each_with_index.map do |sym, i|
22
+ next unless sym.is_a?(Lexer::Token::InstantiateRule)
23
+
24
+ builder = rule_builders.select { |builder| builder.name == sym.s_value }.last
25
+ raise "Unknown parameterizing rule #{token.s_value} at line #{token.line}" unless builder
26
+
27
+ builder.build_rules(sym, nested_actual_args(actual_args, parameters, i), rule_counter, lhs_tag, line, rule_builders)
28
+ end.flatten.compact
29
+ end
30
+
31
+ def nested_actual_args(actual_args, parameters, idx)
32
+ symbols[idx].args.map do |arg|
33
+ i = parameters.index { |parameter| parameter.s_value == arg.s_value }
34
+ i.nil? ? arg : actual_args[i]
35
+ end
36
+ end
37
+
38
+ def rhs(token, actual_args, parameters, nested_rule)
39
+ symbols.map do |sym|
40
+ if sym.is_a?(Lexer::Token::InstantiateRule)
41
+ sym.args.map do |arg|
42
+ idx = parameters.index { |parameter| parameter.s_value == arg.s_value }
43
+ idx.nil? ? sym : nested_rule&.token
44
+ end
45
+ else
46
+ idx = parameters.index { |parameter| parameter.s_value == sym.s_value }
47
+ idx.nil? ? sym : actual_args[idx]
48
+ end
49
+ end.flatten
50
+ end
51
+ end
52
+ end
53
+ end
@@ -51,22 +51,14 @@ module Lrama
51
51
  freeze_rhs
52
52
  end
53
53
 
54
- def setup_rules
54
+ def setup_rules(parameterizing_resolver)
55
55
  preprocess_references unless @skip_preprocess_references
56
- process_rhs
56
+ process_rhs(parameterizing_resolver)
57
57
  build_rules
58
58
  end
59
59
 
60
- def parameterizing_rules
61
- @parameterizing_rules
62
- end
63
-
64
- def midrule_action_rules
65
- @midrule_action_rules
66
- end
67
-
68
60
  def rules
69
- @rules
61
+ @parameterizing_rules + @midrule_action_rules + @rules
70
62
  end
71
63
 
72
64
  private
@@ -95,9 +87,9 @@ module Lrama
95
87
  end
96
88
  end
97
89
 
98
- # rhs is a mixture of variety type of tokens like `Ident`, `Parameterizing`, `UserCode` and so on.
90
+ # rhs is a mixture of variety type of tokens like `Ident`, `InstantiateRule`, `UserCode` and so on.
99
91
  # `#process_rhs` replaces some kind of tokens to `Ident` so that all `@replaced_rhs` are `Ident` or `Char`.
100
- def process_rhs
92
+ def process_rhs(parameterizing_resolver)
101
93
  return if @replaced_rhs
102
94
 
103
95
  @replaced_rhs = []
@@ -109,12 +101,17 @@ module Lrama
109
101
  @replaced_rhs << token
110
102
  when Lrama::Lexer::Token::Ident
111
103
  @replaced_rhs << token
112
- when Lrama::Lexer::Token::Parameterizing
113
- parameterizing = ParameterizingRules::Builder.new(token, @rule_counter, @lhs_tag, user_code, precedence_sym, line)
114
- parameterizing.build.each do |r|
115
- @parameterizing_rules << r
104
+ when Lrama::Lexer::Token::InstantiateRule
105
+ if parameterizing_resolver.defined?(token.rule_name)
106
+ parameterizing = parameterizing_resolver.build_rules(token, @rule_counter, @lhs_tag, line)
107
+ @parameterizing_rules = @parameterizing_rules + parameterizing.map(&:rules).flatten
108
+ @replaced_rhs = @replaced_rhs + parameterizing.map(&:token).flatten.uniq
109
+ else
110
+ # TODO: Delete when the standard library will defined as a grammar file.
111
+ parameterizing = ParameterizingRules::Builder.new(token, @rule_counter, @lhs_tag, user_code, precedence_sym, line)
112
+ @parameterizing_rules = @parameterizing_rules + parameterizing.build
113
+ @replaced_rhs << parameterizing.build_token
116
114
  end
117
- @replaced_rhs << parameterizing.build_token
118
115
  when Lrama::Lexer::Token::UserCode
119
116
  prefix = token.referred ? "@" : "$@"
120
117
  new_token = Lrama::Lexer::Token::Ident.new(s_value: prefix + @midrule_action_counter.increment.to_s)
@@ -124,7 +121,7 @@ module Lrama
124
121
  rule_builder.lhs = new_token
125
122
  rule_builder.user_code = token
126
123
  rule_builder.complete_input
127
- rule_builder.setup_rules
124
+ rule_builder.setup_rules(parameterizing_resolver)
128
125
 
129
126
  @rule_builders_for_derived_rules << rule_builder
130
127
  else
@@ -146,8 +143,15 @@ module Lrama
146
143
  else
147
144
  candidates = rhs.each_with_index.select {|token, i| token.referred_by?(ref_name) }
148
145
 
149
- raise "Referring symbol `#{ref_name}` is duplicated. #{token}" if candidates.size >= 2
150
- raise "Referring symbol `#{ref_name}` is not found. #{token}" unless referring_symbol = candidates.first
146
+ if candidates.size >= 2
147
+ location = token.location.partial_location(ref.first_column, ref.last_column)
148
+ raise location.generate_error_message("Referring symbol `#{ref_name}` is duplicated.")
149
+ end
150
+
151
+ unless (referring_symbol = candidates.first)
152
+ location = token.location.partial_location(ref.first_column, ref.last_column)
153
+ raise location.generate_error_message("Referring symbol `#{ref_name}` is not found.")
154
+ end
151
155
 
152
156
  ref.index = referring_symbol[1] + 1
153
157
  end
@@ -167,7 +171,7 @@ module Lrama
167
171
  end
168
172
 
169
173
  def flush_user_code
170
- if c = @user_code
174
+ if (c = @user_code)
171
175
  @rhs << c
172
176
  @user_code = nil
173
177
  end
data/lib/lrama/grammar.rb CHANGED
@@ -8,6 +8,10 @@ require "lrama/grammar/printer"
8
8
  require "lrama/grammar/reference"
9
9
  require "lrama/grammar/rule"
10
10
  require "lrama/grammar/rule_builder"
11
+ require "lrama/grammar/parameterizing_rule_builder"
12
+ require "lrama/grammar/parameterizing_rule_resolver"
13
+ require "lrama/grammar/parameterizing_rule_rhs_builder"
14
+ require "lrama/grammar/parameterizing_rule"
11
15
  require "lrama/grammar/symbol"
12
16
  require "lrama/grammar/type"
13
17
  require "lrama/grammar/union"
@@ -36,6 +40,7 @@ module Lrama
36
40
  @rule_builders = []
37
41
  @rules = []
38
42
  @sym_to_rules = {}
43
+ @parameterizing_resolver = ParameterizingRuleResolver.new
39
44
  @empty_symbol = nil
40
45
  @eof_symbol = nil
41
46
  @error_symbol = nil
@@ -69,7 +74,7 @@ module Lrama
69
74
  return sym
70
75
  end
71
76
 
72
- if sym = @symbols.find {|s| s.id == id }
77
+ if (sym = @symbols.find {|s| s.id == id })
73
78
  return sym
74
79
  end
75
80
 
@@ -129,6 +134,10 @@ module Lrama
129
134
  @rule_builders << builder
130
135
  end
131
136
 
137
+ def add_parameterizing_rule_builder(builder)
138
+ @parameterizing_resolver.add_parameterizing_rule_builder(builder)
139
+ end
140
+
132
141
  def prologue_first_lineno=(prologue_first_lineno)
133
142
  @aux.prologue_first_lineno = prologue_first_lineno
134
143
  end
@@ -310,7 +319,7 @@ module Lrama
310
319
 
311
320
  def setup_rules
312
321
  @rule_builders.each do |builder|
313
- builder.setup_rules
322
+ builder.setup_rules(@parameterizing_resolver)
314
323
  end
315
324
  end
316
325
 
@@ -350,56 +359,21 @@ module Lrama
350
359
  @accept_symbol = term
351
360
  end
352
361
 
353
- # 1. Add $accept rule to the top of rules
354
- # 2. Extract action in the middle of RHS into new Empty rule
355
- # 3. Append id and extract action then create Rule
356
- #
357
- # Bison 3.8.2 uses different orders for symbol number and rule number
358
- # when a rule has actions in the middle of a rule.
359
- #
360
- # For example,
361
- #
362
- # `program: $@1 top_compstmt`
363
- #
364
- # Rules are ordered like below,
365
- #
366
- # 1 $@1: ε
367
- # 2 program: $@1 top_compstmt
368
- #
369
- # Symbols are ordered like below,
370
- #
371
- # 164 program
372
- # 165 $@1
373
- #
374
362
  def normalize_rules
375
- # 1. Add $accept rule to the top of rules
376
- accept = @accept_symbol
377
- eof = @eof_symbol
363
+ # Add $accept rule to the top of rules
378
364
  lineno = @rule_builders.first ? @rule_builders.first.line : 0
379
- @rules << Rule.new(id: @rule_counter.increment, _lhs: accept.id, _rhs: [@rule_builders.first.lhs, eof.id], token_code: nil, lineno: lineno)
365
+ @rules << Rule.new(id: @rule_counter.increment, _lhs: @accept_symbol.id, _rhs: [@rule_builders.first.lhs, @eof_symbol.id], token_code: nil, lineno: lineno)
380
366
 
381
367
  setup_rules
382
368
 
383
369
  @rule_builders.each do |builder|
384
- # Extract actions in the middle of RHS into new rules.
385
- builder.midrule_action_rules.each do |rule|
386
- @rules << rule
387
- end
388
-
389
370
  builder.rules.each do |rule|
390
- add_nterm(id: rule._lhs)
391
- @rules << rule
392
- end
393
-
394
- builder.parameterizing_rules.each do |rule|
395
371
  add_nterm(id: rule._lhs, tag: rule.lhs_tag)
396
372
  @rules << rule
397
373
  end
398
-
399
- builder.midrule_action_rules.each do |rule|
400
- add_nterm(id: rule._lhs)
401
- end
402
374
  end
375
+
376
+ @rules.sort_by!(&:id)
403
377
  end
404
378
 
405
379
  # Collect symbols from rules
@@ -0,0 +1,21 @@
1
+ module Lrama
2
+ class Lexer
3
+ class GrammarFile
4
+ attr_reader :path, :text
5
+
6
+ def initialize(path, text)
7
+ @path = path
8
+ @text = text
9
+ end
10
+
11
+ def ==(other)
12
+ self.class == other.class &&
13
+ self.path == other.path
14
+ end
15
+
16
+ def lines
17
+ @lines ||= text.split("\n")
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,9 +1,10 @@
1
1
  module Lrama
2
2
  class Lexer
3
3
  class Location
4
- attr_reader :first_line, :first_column, :last_line, :last_column
4
+ attr_reader :grammar_file, :first_line, :first_column, :last_line, :last_column
5
5
 
6
- def initialize(first_line:, first_column:, last_line:, last_column:)
6
+ def initialize(grammar_file:, first_line:, first_column:, last_line:, last_column:)
7
+ @grammar_file = grammar_file
7
8
  @first_line = first_line
8
9
  @first_column = first_column
9
10
  @last_line = last_line
@@ -12,11 +13,85 @@ module Lrama
12
13
 
13
14
  def ==(other)
14
15
  self.class == other.class &&
16
+ self.grammar_file == other.grammar_file &&
15
17
  self.first_line == other.first_line &&
16
18
  self.first_column == other.first_column &&
17
19
  self.last_line == other.last_line &&
18
20
  self.last_column == other.last_column
19
21
  end
22
+
23
+ def partial_location(left, right)
24
+ offset = -first_column
25
+ new_first_line = -1
26
+ new_first_column = -1
27
+ new_last_line = -1
28
+ new_last_column = -1
29
+
30
+ _text.each.with_index do |line, index|
31
+ new_offset = offset + line.length + 1
32
+
33
+ if offset <= left && left <= new_offset
34
+ new_first_line = first_line + index
35
+ new_first_column = left - offset
36
+ end
37
+
38
+ if offset <= right && right <= new_offset
39
+ new_last_line = first_line + index
40
+ new_last_column = right - offset
41
+ end
42
+
43
+ offset = new_offset
44
+ end
45
+
46
+ Location.new(
47
+ grammar_file: grammar_file,
48
+ first_line: new_first_line, first_column: new_first_column,
49
+ last_line: new_last_line, last_column: new_last_column
50
+ )
51
+ end
52
+
53
+ def to_s
54
+ "#{path} (#{first_line},#{first_column})-(#{last_line},#{last_column})"
55
+ end
56
+
57
+ def generate_error_message(error_message)
58
+ <<~ERROR.chomp
59
+ #{path}:#{first_line}:#{first_column}: #{error_message}
60
+ #{line_with_carets}
61
+ ERROR
62
+ end
63
+
64
+ def line_with_carets
65
+ <<~TEXT
66
+ #{text}
67
+ #{carets}
68
+ TEXT
69
+ end
70
+
71
+ private
72
+
73
+ def path
74
+ grammar_file.path
75
+ end
76
+
77
+ def blanks
78
+ (text[0...first_column] or raise "#{first_column} is invalid").gsub(/[^\t]/, ' ')
79
+ end
80
+
81
+ def carets
82
+ blanks + '^' * (last_column - first_column)
83
+ end
84
+
85
+ def text
86
+ @text ||= _text.join("\n")
87
+ end
88
+
89
+ def _text
90
+ @_text ||=begin
91
+ range = (first_line - 1)...last_line
92
+ grammar_file.lines[range] or raise "#{range} is invalid"
93
+ end
94
+ end
20
95
  end
21
96
  end
22
97
  end
@@ -0,0 +1,18 @@
1
+ module Lrama
2
+ class Lexer
3
+ class Token
4
+ class InstantiateRule < Token
5
+ attr_accessor :args
6
+
7
+ def initialize(s_value:, alias_name: nil, location: nil, args: [])
8
+ super s_value: s_value, alias_name: alias_name, location: location
9
+ @args = args
10
+ end
11
+
12
+ def rule_name
13
+ s_value
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end