mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
tools/lrama: update to lrama 0.7.0; close #6384
This commit is contained in:
+346
-31
@@ -1,12 +1,327 @@
|
||||
# NEWS for Lrama
|
||||
|
||||
## Lrama 0.7.0 (2025-01-21)
|
||||
|
||||
## [EXPERIMENTAL] Support the generation of the IELR(1) parser described in this paper
|
||||
|
||||
Support the generation of the IELR(1) parser described in this paper.
|
||||
https://www.sciencedirect.com/science/article/pii/S0167642309001191
|
||||
|
||||
If you use IELR(1) parser, you can write the following directive in your grammar file.
|
||||
|
||||
```yacc
|
||||
%define lr.type ielr
|
||||
```
|
||||
|
||||
But, currently IELR(1) parser is experimental feature. If you find any bugs, please report it to us. Thank you.
|
||||
|
||||
## Support `-t` option as same as `--debug` option
|
||||
|
||||
Support to `-t` option as same as `--debug` option.
|
||||
These options align with Bison behavior. So same as `--debug` option.
|
||||
|
||||
## Trace only explicit rules
|
||||
|
||||
Support to trace only explicit rules.
|
||||
If you use `--trace=rules` option, it shows include mid-rule actions. If you want to show only explicit rules, you can use `--trace=only-explicit-rules` option.
|
||||
|
||||
Example:
|
||||
|
||||
```yacc
|
||||
%{
|
||||
%}
|
||||
%union {
|
||||
int i;
|
||||
}
|
||||
%token <i> number
|
||||
%type <i> program
|
||||
%%
|
||||
program : number { printf("%d", $1); } number { $$ = $1 + $3; }
|
||||
;
|
||||
%%
|
||||
```
|
||||
|
||||
Result of `--trace=rules`:
|
||||
|
||||
```console
|
||||
$ exe/lrama --trace=rules sample.y
|
||||
Grammar rules:
|
||||
$accept -> program YYEOF
|
||||
$@1 -> ε
|
||||
program -> number $@1 number
|
||||
```
|
||||
|
||||
Result of `--trace=only-explicit-rules`:
|
||||
|
||||
```console
|
||||
$ exe/lrama --trace=explicit-rules sample.y
|
||||
Grammar rules:
|
||||
$accept -> program YYEOF
|
||||
program -> number number
|
||||
```
|
||||
|
||||
## Lrama 0.6.11 (2024-12-23)
|
||||
|
||||
### Add support for %type declarations using %nterm in Nonterminal Symbols
|
||||
|
||||
Allow to use `%nterm` in Nonterminal Symbols for `%type` declarations.
|
||||
|
||||
```yacc
|
||||
%nterm <type> nonterminal…
|
||||
```
|
||||
|
||||
This directive is also supported for compatibility with Bison, and only non-terminal symbols are allowed. In other words, definitions like the following will result in an error:
|
||||
|
||||
```yacc
|
||||
%{
|
||||
// Prologue
|
||||
%}
|
||||
|
||||
%token EOI 0 "EOI"
|
||||
%nterm EOI
|
||||
|
||||
%%
|
||||
|
||||
program: /* empty */
|
||||
;
|
||||
```
|
||||
|
||||
It show an error message like the following:
|
||||
|
||||
```command
|
||||
❯ exe/lrama nterm.y
|
||||
nterm.y:6:7: symbol EOI redeclared as a nonterminal
|
||||
%nterm EOI
|
||||
^^^
|
||||
```
|
||||
|
||||
## Lrama 0.6.10 (2024-09-11)
|
||||
|
||||
### Aliased Named References for actions of RHS in parameterizing rules
|
||||
|
||||
Allow to use aliased named references for actions of RHS in parameterizing rules.
|
||||
|
||||
```yacc
|
||||
%rule sum(X, Y): X[summand] '+' Y[addend] { $$ = $summand + $addend }
|
||||
;
|
||||
```
|
||||
|
||||
https://github.com/ruby/lrama/pull/410
|
||||
|
||||
### Named References for actions of RHS in parameterizing rules caller side
|
||||
|
||||
Allow to use named references for actions of RHS in parameterizing rules caller side.
|
||||
|
||||
```yacc
|
||||
opt_nl: '\n'?[nl] <str> { $$ = $nl; }
|
||||
;
|
||||
```
|
||||
|
||||
https://github.com/ruby/lrama/pull/414
|
||||
|
||||
### Widen the definable position of parameterizing rules
|
||||
|
||||
Allow to define parameterizing rules in the middle of the grammar.
|
||||
|
||||
```yacc
|
||||
%rule defined_option(X): /* empty */
|
||||
| X
|
||||
;
|
||||
|
||||
%%
|
||||
|
||||
program : defined_option(number) <i>
|
||||
| defined_list(number) <i>
|
||||
;
|
||||
|
||||
%rule defined_list(X): /* empty */ /* <--- here */
|
||||
| defined_list(X) number
|
||||
;
|
||||
```
|
||||
|
||||
https://github.com/ruby/lrama/pull/420
|
||||
|
||||
### Report unused terminal symbols
|
||||
|
||||
Support to report unused terminal symbols.
|
||||
Run `exe/lrama --report=terms` to show unused terminal symbols.
|
||||
|
||||
```console
|
||||
$ exe/lrama --report=terms sample/calc.y
|
||||
11 Unused Terms
|
||||
0 YYerror
|
||||
1 YYUNDEF
|
||||
2 '\\\\'
|
||||
3 '\\13'
|
||||
4 keyword_class2
|
||||
5 tNUMBER
|
||||
6 tPLUS
|
||||
7 tMINUS
|
||||
8 tEQ
|
||||
9 tEQEQ
|
||||
10 '>'
|
||||
```
|
||||
|
||||
https://github.com/ruby/lrama/pull/439
|
||||
|
||||
### Report unused rules
|
||||
|
||||
Support to report unused rules.
|
||||
Run `exe/lrama --report=rules` to show unused rules.
|
||||
|
||||
```console
|
||||
$ exe/lrama --report=rules sample/calc.y
|
||||
3 Unused Rules
|
||||
0 unused_option
|
||||
1 unused_list
|
||||
2 unused_nonempty_list
|
||||
```
|
||||
|
||||
https://github.com/ruby/lrama/pull/441
|
||||
|
||||
### Ensure compatibility with Bison for `%locations` directive
|
||||
|
||||
Support `%locations` directive to ensure compatibility with Bison.
|
||||
Change to `%locations` directive not set by default.
|
||||
|
||||
https://github.com/ruby/lrama/pull/446
|
||||
|
||||
### Diagnostics report for parameterizing rules redefine
|
||||
|
||||
Support to warning redefined parameterizing rules.
|
||||
Run `exe/lrama -W` or `exe/lrama --warnings` to show redefined parameterizing rules.
|
||||
|
||||
```console
|
||||
$ exe/lrama -W sample/calc.y
|
||||
parameterizing rule redefined: redefined_method(X)
|
||||
parameterizing rule redefined: redefined_method(X)
|
||||
```
|
||||
|
||||
https://github.com/ruby/lrama/pull/448
|
||||
|
||||
### Support `-v` and `--verbose` option
|
||||
|
||||
Support to `-v` and `--verbose` option.
|
||||
These options align with Bison behavior. So same as '--report=state' option.
|
||||
|
||||
https://github.com/ruby/lrama/pull/457
|
||||
|
||||
## Lrama 0.6.9 (2024-05-02)
|
||||
|
||||
### Callee side tag specification of parameterizing rules
|
||||
|
||||
Allow to specify tag on callee side of parameterizing rules.
|
||||
|
||||
```yacc
|
||||
%union {
|
||||
int i;
|
||||
}
|
||||
|
||||
%rule with_tag(X) <i>: X { $$ = $1; }
|
||||
;
|
||||
```
|
||||
|
||||
### Named References for actions of RHS in parameterizing rules
|
||||
|
||||
Allow to use named references for actions of RHS in parameterizing rules.
|
||||
|
||||
```yacc
|
||||
%rule option(number): /* empty */
|
||||
| number { $$ = $number; }
|
||||
;
|
||||
```
|
||||
|
||||
## Lrama 0.6.8 (2024-04-29)
|
||||
|
||||
### Nested parameterizing rules with tag
|
||||
|
||||
Allow to nested parameterizing rules with tag.
|
||||
|
||||
```yacc
|
||||
%union {
|
||||
int i;
|
||||
}
|
||||
|
||||
%rule nested_nested_option(X): /* empty */
|
||||
| X
|
||||
;
|
||||
|
||||
%rule nested_option(X): /* empty */
|
||||
| nested_nested_option(X) <i>
|
||||
;
|
||||
|
||||
%rule option(Y): /* empty */
|
||||
| nested_option(Y) <i>
|
||||
;
|
||||
```
|
||||
|
||||
## Lrama 0.6.7 (2024-04-28)
|
||||
|
||||
### RHS of user defined parameterizing rules contains `'symbol'?`, `'symbol'+` and `'symbol'*`.
|
||||
|
||||
User can use `'symbol'?`, `'symbol'+` and `'symbol'*` in RHS of user defined parameterizing rules.
|
||||
|
||||
```
|
||||
%rule with_word_seps(X): /* empty */
|
||||
| X ' '+
|
||||
;
|
||||
```
|
||||
|
||||
## Lrama 0.6.6 (2024-04-27)
|
||||
|
||||
### Trace actions
|
||||
|
||||
Support trace actions for debugging.
|
||||
Run `exe/lrama --trace=actions` to show grammar rules with actions.
|
||||
|
||||
```console
|
||||
$ exe/lrama --trace=actions sample/calc.y
|
||||
Grammar rules with actions:
|
||||
$accept -> list, YYEOF {}
|
||||
list -> ε {}
|
||||
list -> list, LF {}
|
||||
list -> list, expr, LF { printf("=> %d\n", $2); }
|
||||
expr -> NUM {}
|
||||
expr -> expr, '+', expr { $$ = $1 + $3; }
|
||||
expr -> expr, '-', expr { $$ = $1 - $3; }
|
||||
expr -> expr, '*', expr { $$ = $1 * $3; }
|
||||
expr -> expr, '/', expr { $$ = $1 / $3; }
|
||||
expr -> '(', expr, ')' { $$ = $2; }
|
||||
```
|
||||
|
||||
### Inlining
|
||||
|
||||
Support inlining for rules.
|
||||
The `%inline` directive causes all references to symbols to be replaced with its definition.
|
||||
|
||||
```yacc
|
||||
%rule %inline op: PLUS { + }
|
||||
| TIMES { * }
|
||||
;
|
||||
|
||||
%%
|
||||
|
||||
expr : number { $$ = $1; }
|
||||
| expr op expr { $$ = $1 $2 $3; }
|
||||
;
|
||||
```
|
||||
|
||||
as same as
|
||||
|
||||
```yacc
|
||||
expr : number { $$ = $1; }
|
||||
| expr '+' expr { $$ = $1 + $3; }
|
||||
| expr '*' expr { $$ = $1 * $3; }
|
||||
;
|
||||
```
|
||||
|
||||
## Lrama 0.6.5 (2024-03-25)
|
||||
|
||||
### Typed Midrule Actions
|
||||
|
||||
User can specify the type of mid rule action by tag (`<bar>`) instead of specifying it with in an action.
|
||||
|
||||
```
|
||||
```yacc
|
||||
primary: k_case expr_value terms?
|
||||
{
|
||||
$<val>$ = p->case_labels;
|
||||
@@ -21,7 +336,7 @@ primary: k_case expr_value terms?
|
||||
|
||||
can be written as
|
||||
|
||||
```
|
||||
```yacc
|
||||
primary: k_case expr_value terms?
|
||||
{
|
||||
$$ = p->case_labels;
|
||||
@@ -46,7 +361,7 @@ Bison supports this feature from 3.1.
|
||||
|
||||
Support `preceded`, `terminated` and `delimited` rules.
|
||||
|
||||
```
|
||||
```text
|
||||
program: preceded(opening, X)
|
||||
|
||||
// Expanded to
|
||||
@@ -73,7 +388,7 @@ program: delimited_opening_X_closing
|
||||
delimited_opening_X_closing: opening X closing
|
||||
```
|
||||
|
||||
<https://github.com/ruby/lrama/pull/382>
|
||||
https://github.com/ruby/lrama/pull/382
|
||||
|
||||
### Support `%destructor` declaration
|
||||
|
||||
@@ -82,7 +397,7 @@ In general, these resources are freed by actions or after parsing.
|
||||
However if syntax error happens in parsing, these codes may not be executed.
|
||||
Codes associated to `%destructor` are executed when semantic value is popped from the stack by an error.
|
||||
|
||||
```
|
||||
```yacc
|
||||
%token <val1> NUM
|
||||
%type <val2> expr2
|
||||
%type <val3> expr
|
||||
@@ -102,7 +417,7 @@ Codes associated to `%destructor` are executed when semantic value is popped fro
|
||||
|
||||
Bison supports this feature from 1.75b.
|
||||
|
||||
<https://github.com/ruby/lrama/pull/385>
|
||||
https://github.com/ruby/lrama/pull/385
|
||||
|
||||
## Lrama 0.6.3 (2024-02-15)
|
||||
|
||||
@@ -130,7 +445,7 @@ Lrama provides these five callbacks. Registered functions are called when each e
|
||||
User also needs to access semantic value of their stack in grammar action. `$:n` provides the way to access to it. `$:n` is translated to the minus index from the top of the stack.
|
||||
For example
|
||||
|
||||
```
|
||||
```yacc
|
||||
primary: k_if expr_value then compstmt if_tail k_end
|
||||
{
|
||||
/*% ripper: if!($:2, $:4, $:5) %*/
|
||||
@@ -138,7 +453,7 @@ primary: k_if expr_value then compstmt if_tail k_end
|
||||
}
|
||||
```
|
||||
|
||||
<https://github.com/ruby/lrama/pull/367>
|
||||
https://github.com/ruby/lrama/pull/367
|
||||
|
||||
## Lrama 0.6.2 (2024-01-27)
|
||||
|
||||
@@ -147,7 +462,7 @@ primary: k_if expr_value then compstmt if_tail k_end
|
||||
If `%no-stdlib` directive is set, Lrama doesn't load Lrama standard library for
|
||||
parameterizing rules, stdlib.y.
|
||||
|
||||
<https://github.com/ruby/lrama/pull/344>
|
||||
https://github.com/ruby/lrama/pull/344
|
||||
|
||||
## Lrama 0.6.1 (2024-01-13)
|
||||
|
||||
@@ -155,7 +470,7 @@ parameterizing rules, stdlib.y.
|
||||
|
||||
Allow to pass an instantiated rule to other parameterizing rules.
|
||||
|
||||
```
|
||||
```yacc
|
||||
%rule constant(X) : X
|
||||
;
|
||||
|
||||
@@ -172,7 +487,7 @@ program : option(constant(number)) // Nested rule
|
||||
|
||||
Allow to use nested parameterizing rules when define parameterizing rules.
|
||||
|
||||
```
|
||||
```yacc
|
||||
%rule option(x) : /* empty */
|
||||
| X
|
||||
;
|
||||
@@ -191,7 +506,7 @@ program : double_opt(number)
|
||||
%%
|
||||
```
|
||||
|
||||
<https://github.com/ruby/lrama/pull/337>
|
||||
https://github.com/ruby/lrama/pull/337
|
||||
|
||||
## Lrama 0.6.0 (2023-12-25)
|
||||
|
||||
@@ -199,7 +514,7 @@ program : double_opt(number)
|
||||
|
||||
Allow to define parameterizing rule by `%rule` directive.
|
||||
|
||||
```
|
||||
```yacc
|
||||
%rule pair(X, Y): X Y { $$ = $1 + $2; }
|
||||
;
|
||||
|
||||
@@ -213,7 +528,7 @@ stmt: pair(ODD, EVEN) <num>
|
||||
;
|
||||
```
|
||||
|
||||
<https://github.com/ruby/lrama/pull/285>
|
||||
https://github.com/ruby/lrama/pull/285
|
||||
|
||||
## Lrama 0.5.11 (2023-12-02)
|
||||
|
||||
@@ -222,7 +537,7 @@ stmt: pair(ODD, EVEN) <num>
|
||||
Allow to specify type of rules by specifying tag, `<i>` in below example.
|
||||
Tag is post-modification style.
|
||||
|
||||
```
|
||||
```yacc
|
||||
%union {
|
||||
int i;
|
||||
}
|
||||
@@ -234,7 +549,7 @@ program : option(number) <i>
|
||||
;
|
||||
```
|
||||
|
||||
<https://github.com/ruby/lrama/pull/272>
|
||||
https://github.com/ruby/lrama/pull/272
|
||||
|
||||
## Lrama 0.5.10 (2023-11-18)
|
||||
|
||||
@@ -242,13 +557,13 @@ program : option(number) <i>
|
||||
|
||||
Support function call style parameterizing rules for `option`, `nonempty_list` and `list`.
|
||||
|
||||
<https://github.com/ruby/lrama/pull/197>
|
||||
https://github.com/ruby/lrama/pull/197
|
||||
|
||||
### Parameterizing rules (separated_list)
|
||||
|
||||
Support `separated_list` and `separated_nonempty_list` parameterizing rules.
|
||||
|
||||
```
|
||||
```text
|
||||
program: separated_list(',', number)
|
||||
|
||||
// Expanded to
|
||||
@@ -270,7 +585,7 @@ separated_nonempty_list_number: number
|
||||
separated_nonempty_list_number: separated_nonempty_list_number ',' number
|
||||
```
|
||||
|
||||
<https://github.com/ruby/lrama/pull/204>
|
||||
https://github.com/ruby/lrama/pull/204
|
||||
|
||||
## Lrama 0.5.9 (2023-11-05)
|
||||
|
||||
@@ -279,7 +594,7 @@ separated_nonempty_list_number: separated_nonempty_list_number ',' number
|
||||
Parameterizing rules are template of rules.
|
||||
It's very common pattern to write "list" grammar rule like:
|
||||
|
||||
```
|
||||
```yacc
|
||||
opt_args: /* none */
|
||||
| args
|
||||
;
|
||||
@@ -294,18 +609,18 @@ Lrama supports these suffixes:
|
||||
- `+`: nonempty list
|
||||
- `*`: list
|
||||
|
||||
Idea of Parameterizing rules comes from Menhir LR(1) parser generator (<https://gallium.inria.fr/~fpottier/menhir/manual.html#sec32>).
|
||||
Idea of Parameterizing rules comes from Menhir LR(1) parser generator (https://gallium.inria.fr/~fpottier/menhir/manual.html#sec32).
|
||||
|
||||
<https://github.com/ruby/lrama/pull/181>
|
||||
https://github.com/ruby/lrama/pull/181
|
||||
|
||||
## Lrama 0.5.7 (2023-10-23)
|
||||
|
||||
### Racc parser
|
||||
|
||||
Replace Lrama's parser from hand written parser to LR parser generated by Racc.
|
||||
Lrama uses `--embedded` option to generate LR parser because Racc is changed from default gem to bundled gem by Ruby 3.3 (<https://github.com/ruby/lrama/pull/132>).
|
||||
Lrama uses `--embedded` option to generate LR parser because Racc is changed from default gem to bundled gem by Ruby 3.3 (https://github.com/ruby/lrama/pull/132).
|
||||
|
||||
<https://github.com/ruby/lrama/pull/62>
|
||||
https://github.com/ruby/lrama/pull/62
|
||||
|
||||
## Lrama 0.5.4 (2023-08-17)
|
||||
|
||||
@@ -316,7 +631,7 @@ Make error recovery function configurable on runtime by two new macros.
|
||||
- `YYMAXREPAIR`: Expected to return max length of repair operations. `%parse-param` is passed to this function.
|
||||
- `YYERROR_RECOVERY_ENABLED`: Expected to return bool value to determine error recovery is enabled or not. `%parse-param` is passed to this function.
|
||||
|
||||
<https://github.com/ruby/lrama/pull/74>
|
||||
https://github.com/ruby/lrama/pull/74
|
||||
|
||||
## Lrama 0.5.3 (2023-08-05)
|
||||
|
||||
@@ -325,7 +640,7 @@ Make error recovery function configurable on runtime by two new macros.
|
||||
Support token insert base Error Recovery.
|
||||
`-e` option is needed to generate parser with error recovery functions.
|
||||
|
||||
<https://github.com/ruby/lrama/pull/44>
|
||||
https://github.com/ruby/lrama/pull/44
|
||||
|
||||
## Lrama 0.5.2 (2023-06-14)
|
||||
|
||||
@@ -334,7 +649,7 @@ Support token insert base Error Recovery.
|
||||
Instead of positional references like `$1` or `$$`,
|
||||
named references allow to access to symbol by name.
|
||||
|
||||
```
|
||||
```yacc
|
||||
primary: k_class cpath superclass bodystmt k_end
|
||||
{
|
||||
$primary = new_class($cpath, $bodystmt, $superclass);
|
||||
@@ -343,7 +658,7 @@ primary: k_class cpath superclass bodystmt k_end
|
||||
|
||||
Alias name can be declared.
|
||||
|
||||
```
|
||||
```yacc
|
||||
expr[result]: expr[ex-left] '+' expr[ex.right]
|
||||
{
|
||||
$result = $[ex-left] + $[ex.right];
|
||||
@@ -363,9 +678,9 @@ Bison supports this feature from 2.5.
|
||||
- `YY_REDUCE_PRINT`
|
||||
- `yysyntax_error`
|
||||
|
||||
<https://github.com/ruby/lrama/pull/40>
|
||||
https://github.com/ruby/lrama/pull/40
|
||||
|
||||
See also: <https://github.com/ruby/ruby/pull/7807>
|
||||
See also: https://github.com/ruby/ruby/pull/7807
|
||||
|
||||
## Lrama 0.5.0 (2023-05-17)
|
||||
|
||||
@@ -373,7 +688,7 @@ See also: <https://github.com/ruby/ruby/pull/7807>
|
||||
|
||||
When `-` is given as grammar file name, reads the grammar source from STDIN, and takes the next argument as the input file name. This mode helps pre-process a grammar source.
|
||||
|
||||
<https://github.com/ruby/lrama/pull/8>
|
||||
https://github.com/ruby/lrama/pull/8
|
||||
|
||||
## Lrama 0.4.0 (2023-05-13)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
$LOAD_PATH << File.join(__dir__, "../lib")
|
||||
require "lrama"
|
||||
|
||||
+22
-17
@@ -1,17 +1,22 @@
|
||||
require "lrama/bitmap"
|
||||
require "lrama/command"
|
||||
require "lrama/context"
|
||||
require "lrama/counterexamples"
|
||||
require "lrama/digraph"
|
||||
require "lrama/grammar"
|
||||
require "lrama/lexer"
|
||||
require "lrama/option_parser"
|
||||
require "lrama/options"
|
||||
require "lrama/output"
|
||||
require "lrama/parser"
|
||||
require "lrama/report"
|
||||
require "lrama/state"
|
||||
require "lrama/states"
|
||||
require "lrama/states_reporter"
|
||||
require "lrama/version"
|
||||
require "lrama/warning"
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "lrama/bitmap"
|
||||
require_relative "lrama/command"
|
||||
require_relative "lrama/context"
|
||||
require_relative "lrama/counterexamples"
|
||||
require_relative "lrama/diagnostics"
|
||||
require_relative "lrama/digraph"
|
||||
require_relative "lrama/grammar"
|
||||
require_relative "lrama/grammar_validator"
|
||||
require_relative "lrama/lexer"
|
||||
require_relative "lrama/logger"
|
||||
require_relative "lrama/option_parser"
|
||||
require_relative "lrama/options"
|
||||
require_relative "lrama/output"
|
||||
require_relative "lrama/parser"
|
||||
require_relative "lrama/report"
|
||||
require_relative "lrama/state"
|
||||
require_relative "lrama/states"
|
||||
require_relative "lrama/states_reporter"
|
||||
require_relative "lrama/trace_reporter"
|
||||
require_relative "lrama/version"
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# rbs_inline: enabled
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
module Bitmap
|
||||
# @rbs (Array[Integer] ary) -> Integer
|
||||
def self.from_array(ary)
|
||||
bit = 0
|
||||
|
||||
@@ -10,8 +14,9 @@ module Lrama
|
||||
bit
|
||||
end
|
||||
|
||||
# @rbs (Integer int) -> Array[Integer]
|
||||
def self.to_array(int)
|
||||
a = []
|
||||
a = [] #: Array[Integer]
|
||||
i = 0
|
||||
|
||||
while int > 0 do
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Command
|
||||
LRAMA_LIB = File.realpath(File.join(File.dirname(__FILE__)))
|
||||
@@ -14,11 +16,10 @@ module Lrama
|
||||
|
||||
Report::Duration.enable if options.trace_opts[:time]
|
||||
|
||||
warning = Lrama::Warning.new
|
||||
text = options.y.read
|
||||
options.y.close if options.y != STDIN
|
||||
begin
|
||||
grammar = Lrama::Parser.new(text, options.grammar_file, options.debug).parse
|
||||
grammar = Lrama::Parser.new(text, options.grammar_file, options.debug, options.define).parse
|
||||
unless grammar.no_stdlib
|
||||
stdlib_grammar = Lrama::Parser.new(File.read(STDLIB_FILE_PATH), STDLIB_FILE_PATH, options.debug).parse
|
||||
grammar.insert_before_parameterizing_rules(stdlib_grammar.parameterizing_rules)
|
||||
@@ -31,8 +32,9 @@ module Lrama
|
||||
message = message.gsub(/.+/, "\e[1m\\&\e[m") if Exception.to_tty?
|
||||
abort message
|
||||
end
|
||||
states = Lrama::States.new(grammar, warning, trace_state: (options.trace_opts[:automaton] || options.trace_opts[:closure]))
|
||||
states = Lrama::States.new(grammar, trace_state: (options.trace_opts[:automaton] || options.trace_opts[:closure]))
|
||||
states.compute
|
||||
states.compute_ielr if grammar.ielr_defined?
|
||||
context = Lrama::Context.new(states)
|
||||
|
||||
if options.report_file
|
||||
@@ -42,15 +44,8 @@ module Lrama
|
||||
end
|
||||
end
|
||||
|
||||
if options.trace_opts && options.trace_opts[:rules]
|
||||
puts "Grammar rules:"
|
||||
puts grammar.rules
|
||||
end
|
||||
|
||||
if options.trace_opts && options.trace_opts[:actions]
|
||||
puts "Grammar rules with actions:"
|
||||
grammar.rules.each { |rule| puts rule.with_actions }
|
||||
end
|
||||
reporter = Lrama::TraceReporter.new(grammar)
|
||||
reporter.report(**options.trace_opts)
|
||||
|
||||
File.open(options.outfile, "w+") do |f|
|
||||
Lrama::Output.new(
|
||||
@@ -65,9 +60,9 @@ module Lrama
|
||||
).render
|
||||
end
|
||||
|
||||
if warning.has_error?
|
||||
exit false
|
||||
end
|
||||
logger = Lrama::Logger.new
|
||||
exit false unless Lrama::GrammarValidator.new(grammar, states, logger).valid?
|
||||
Lrama::Diagnostics.new(grammar, states, logger).run(options.diagnostic)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
require "lrama/report/duration"
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "report/duration"
|
||||
|
||||
module Lrama
|
||||
# This is passed to a template
|
||||
@@ -253,7 +255,7 @@ module Lrama
|
||||
|
||||
# If no default_reduction_rule, default behavior is an
|
||||
# error then replace ErrorActionNumber with zero.
|
||||
if !state.default_reduction_rule
|
||||
unless state.default_reduction_rule
|
||||
actions.map! do |e|
|
||||
if e == ErrorActionNumber
|
||||
0
|
||||
@@ -301,10 +303,7 @@ module Lrama
|
||||
end
|
||||
|
||||
@states.nterms.each do |nterm|
|
||||
if !(states = nterm_to_next_states[nterm])
|
||||
default_goto = 0
|
||||
not_default_gotos = []
|
||||
else
|
||||
if (states = nterm_to_next_states[nterm])
|
||||
default_state = states.map(&:last).group_by {|s| s }.max_by {|_, v| v.count }.first
|
||||
default_goto = default_state.id
|
||||
not_default_gotos = []
|
||||
@@ -312,6 +311,9 @@ module Lrama
|
||||
next if to_state.id == default_goto
|
||||
not_default_gotos << [from_state.id, to_state.id]
|
||||
end
|
||||
else
|
||||
default_goto = 0
|
||||
not_default_gotos = []
|
||||
end
|
||||
|
||||
k = nterm_number_to_sequence_number(nterm.number)
|
||||
@@ -403,7 +405,7 @@ module Lrama
|
||||
@check = []
|
||||
# Key is froms_and_tos, value is index position
|
||||
pushed = {}
|
||||
userd_res = {}
|
||||
used_res = {}
|
||||
lowzero = 0
|
||||
high = 0
|
||||
|
||||
@@ -428,7 +430,7 @@ module Lrama
|
||||
end
|
||||
end
|
||||
|
||||
if ok && userd_res[res]
|
||||
if ok && used_res[res]
|
||||
ok = false
|
||||
end
|
||||
|
||||
@@ -456,7 +458,7 @@ module Lrama
|
||||
|
||||
@base[state_id] = res
|
||||
pushed[froms_and_tos] = res
|
||||
userd_res[res] = true
|
||||
used_res[res] = true
|
||||
end
|
||||
|
||||
@yylast = high
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "set"
|
||||
|
||||
require "lrama/counterexamples/derivation"
|
||||
require "lrama/counterexamples/example"
|
||||
require "lrama/counterexamples/path"
|
||||
require "lrama/counterexamples/production_path"
|
||||
require "lrama/counterexamples/start_path"
|
||||
require "lrama/counterexamples/state_item"
|
||||
require "lrama/counterexamples/transition_path"
|
||||
require "lrama/counterexamples/triple"
|
||||
require_relative "counterexamples/derivation"
|
||||
require_relative "counterexamples/example"
|
||||
require_relative "counterexamples/path"
|
||||
require_relative "counterexamples/production_path"
|
||||
require_relative "counterexamples/start_path"
|
||||
require_relative "counterexamples/state_item"
|
||||
require_relative "counterexamples/transition_path"
|
||||
require_relative "counterexamples/triple"
|
||||
|
||||
module Lrama
|
||||
# See: https://www.cs.cornell.edu/andru/papers/cupex/cupex.pdf
|
||||
@@ -30,8 +32,10 @@ module Lrama
|
||||
conflict_state.conflicts.flat_map do |conflict|
|
||||
case conflict.type
|
||||
when :shift_reduce
|
||||
# @type var conflict: State::ShiftReduceConflict
|
||||
shift_reduce_example(conflict_state, conflict)
|
||||
when :reduce_reduce
|
||||
# @type var conflict: State::ReduceReduceConflict
|
||||
reduce_reduce_examples(conflict_state, conflict)
|
||||
end
|
||||
end.compact
|
||||
@@ -46,7 +50,7 @@ module Lrama
|
||||
@reverse_transitions = {}
|
||||
|
||||
@states.states.each do |src_state|
|
||||
trans = {}
|
||||
trans = {} #: Hash[Grammar::Symbol, State]
|
||||
|
||||
src_state.transitions.each do |shift, next_state|
|
||||
trans[shift.next_sym] = next_state
|
||||
@@ -64,6 +68,7 @@ module Lrama
|
||||
|
||||
@transitions[[src_state_item, sym]] = dest_state_item
|
||||
|
||||
# @type var key: [StateItem, Grammar::Symbol]
|
||||
key = [dest_state_item, sym]
|
||||
@reverse_transitions[key] ||= Set.new
|
||||
@reverse_transitions[key] << src_state_item
|
||||
@@ -80,7 +85,7 @@ module Lrama
|
||||
|
||||
@states.states.each do |state|
|
||||
# LHS => Set(Item)
|
||||
h = {}
|
||||
h = {} #: Hash[Grammar::Symbol, Set[States::Item]]
|
||||
|
||||
state.closure.each do |item|
|
||||
sym = item.lhs
|
||||
@@ -95,6 +100,7 @@ module Lrama
|
||||
|
||||
sym = item.next_sym
|
||||
state_item = StateItem.new(state, item)
|
||||
# @type var key: [State, Grammar::Symbol]
|
||||
key = [state, sym]
|
||||
|
||||
@productions[state_item] = h[sym]
|
||||
@@ -107,6 +113,7 @@ module Lrama
|
||||
|
||||
def shift_reduce_example(conflict_state, conflict)
|
||||
conflict_symbol = conflict.symbols.first
|
||||
# @type var shift_conflict_item: ::Lrama::States::Item
|
||||
shift_conflict_item = conflict_state.items.find { |item| item.next_sym == conflict_symbol }
|
||||
path2 = shortest_path(conflict_state, conflict.reduce.item, conflict_symbol)
|
||||
path1 = find_shift_conflict_shortest_path(path2, conflict_state, shift_conflict_item)
|
||||
@@ -151,12 +158,14 @@ module Lrama
|
||||
prev_state_item = prev_path&.to
|
||||
|
||||
if target_state_item == state_item || target_state_item.item.start_item?
|
||||
result.concat(reversed_reduce_path[_j..-1].map(&:to))
|
||||
result.concat(
|
||||
reversed_reduce_path[_j..-1] #: Array[StartPath|TransitionPath|ProductionPath]
|
||||
.map(&:to))
|
||||
break
|
||||
end
|
||||
|
||||
if target_state_item.item.beginning_of_rule?
|
||||
queue = []
|
||||
queue = [] #: Array[Array[StateItem]]
|
||||
queue << [target_state_item]
|
||||
|
||||
# Find reverse production
|
||||
@@ -171,10 +180,18 @@ module Lrama
|
||||
break
|
||||
end
|
||||
|
||||
if !si.item.beginning_of_rule?
|
||||
if si.item.beginning_of_rule?
|
||||
# @type var key: [State, Grammar::Symbol]
|
||||
key = [si.state, si.item.lhs]
|
||||
@reverse_productions[key].each do |item|
|
||||
state_item = StateItem.new(si.state, item)
|
||||
queue << (sis + [state_item])
|
||||
end
|
||||
else
|
||||
# @type var key: [StateItem, Grammar::Symbol]
|
||||
key = [si, si.item.previous_sym]
|
||||
@reverse_transitions[key].each do |prev_target_state_item|
|
||||
next if prev_target_state_item.state != prev_state_item.state
|
||||
next if prev_target_state_item.state != prev_state_item&.state
|
||||
sis.shift
|
||||
result.concat(sis)
|
||||
result << prev_target_state_item
|
||||
@@ -183,19 +200,14 @@ module Lrama
|
||||
queue.clear
|
||||
break
|
||||
end
|
||||
else
|
||||
key = [si.state, si.item.lhs]
|
||||
@reverse_productions[key].each do |item|
|
||||
state_item = StateItem.new(si.state, item)
|
||||
queue << (sis + [state_item])
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
# Find reverse transition
|
||||
# @type var key: [StateItem, Grammar::Symbol]
|
||||
key = [target_state_item, target_state_item.item.previous_sym]
|
||||
@reverse_transitions[key].each do |prev_target_state_item|
|
||||
next if prev_target_state_item.state != prev_state_item.state
|
||||
next if prev_target_state_item.state != prev_state_item&.state
|
||||
result << prev_target_state_item
|
||||
target_state_item = prev_target_state_item
|
||||
i = j
|
||||
@@ -222,9 +234,9 @@ module Lrama
|
||||
|
||||
def shortest_path(conflict_state, conflict_reduce_item, conflict_term)
|
||||
# queue: is an array of [Triple, [Path]]
|
||||
queue = []
|
||||
visited = {}
|
||||
start_state = @states.states.first
|
||||
queue = [] #: Array[[Triple, Array[StartPath|TransitionPath|ProductionPath]]]
|
||||
visited = {} #: Hash[Triple, true]
|
||||
start_state = @states.states.first #: Lrama::State
|
||||
raise "BUG: Start state should be just one kernel." if start_state.kernels.count != 1
|
||||
|
||||
start = Triple.new(start_state, start_state.kernels.first, Set.new([@states.eof_symbol]))
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Counterexamples
|
||||
class Derivation
|
||||
@@ -16,7 +18,7 @@ module Lrama
|
||||
alias :inspect :to_s
|
||||
|
||||
def render_strings_for_report
|
||||
result = []
|
||||
result = [] #: Array[String]
|
||||
_render_for_report(self, 0, result, 0)
|
||||
result.map(&:rstrip)
|
||||
end
|
||||
@@ -42,18 +44,19 @@ module Lrama
|
||||
str << "#{item.next_sym.display_name}"
|
||||
length = _render_for_report(derivation.left, len, strings, index + 1)
|
||||
# I want String#ljust!
|
||||
str << " " * (length - str.length)
|
||||
str << " " * (length - str.length) if length > str.length
|
||||
else
|
||||
str << " • #{item.symbols_after_dot.map(&:display_name).join(" ")} "
|
||||
return str.length
|
||||
end
|
||||
|
||||
if derivation.right&.left
|
||||
length = _render_for_report(derivation.right.left, str.length, strings, index + 1)
|
||||
str << "#{item.symbols_after_dot[1..-1].map(&:display_name).join(" ")} "
|
||||
left = derivation.right&.left #: Derivation
|
||||
length = _render_for_report(left, str.length, strings, index + 1)
|
||||
str << "#{item.symbols_after_dot[1..-1].map(&:display_name).join(" ")} " # steep:ignore
|
||||
str << " " * (length - str.length) if length > str.length
|
||||
elsif item.next_next_sym
|
||||
str << "#{item.symbols_after_dot[1..-1].map(&:display_name).join(" ")} "
|
||||
str << "#{item.symbols_after_dot[1..-1].map(&:display_name).join(" ")} " # steep:ignore
|
||||
end
|
||||
|
||||
return str.length
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Counterexamples
|
||||
class Example
|
||||
@@ -36,9 +38,10 @@ module Lrama
|
||||
private
|
||||
|
||||
def _derivations(paths)
|
||||
derivation = nil
|
||||
derivation = nil #: Derivation
|
||||
current = :production
|
||||
lookahead_sym = paths.last.to.item.end_of_rule? ? @conflict_symbol : nil
|
||||
last_path = paths.last #: Path
|
||||
lookahead_sym = last_path.to.item.end_of_rule? ? @conflict_symbol : nil
|
||||
|
||||
paths.reverse_each do |path|
|
||||
item = path.to.item
|
||||
@@ -55,12 +58,14 @@ module Lrama
|
||||
when ProductionPath
|
||||
derivation = Derivation.new(item, derivation)
|
||||
current = :production
|
||||
else
|
||||
raise "Unexpected. #{path}"
|
||||
end
|
||||
|
||||
if lookahead_sym && item.next_next_sym && item.next_next_sym.first_set.include?(lookahead_sym)
|
||||
state_item = @counterexamples.transitions[[path.to, item.next_sym]]
|
||||
derivation2 = find_derivation_for_symbol(state_item, lookahead_sym)
|
||||
derivation.right = derivation2
|
||||
derivation.right = derivation2 # steep:ignore
|
||||
lookahead_sym = nil
|
||||
end
|
||||
|
||||
@@ -87,7 +92,7 @@ module Lrama
|
||||
end
|
||||
|
||||
def find_derivation_for_symbol(state_item, sym)
|
||||
queue = []
|
||||
queue = [] #: Array[Array[StateItem]]
|
||||
queue << [state_item]
|
||||
|
||||
while (sis = queue.shift)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Counterexamples
|
||||
class Path
|
||||
@@ -18,6 +20,10 @@ module Lrama
|
||||
"#<Path(#{type})>"
|
||||
end
|
||||
alias :inspect :to_s
|
||||
|
||||
def type
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Counterexamples
|
||||
class ProductionPath < Path
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Counterexamples
|
||||
class StartPath < Path
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Counterexamples
|
||||
class StateItem < Struct.new(:state, :item)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Counterexamples
|
||||
class TransitionPath < Path
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Counterexamples
|
||||
# s: state
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Diagnostics
|
||||
def initialize(grammar, states, logger)
|
||||
@grammar = grammar
|
||||
@states = states
|
||||
@logger = logger
|
||||
end
|
||||
|
||||
def run(diagnostic)
|
||||
if diagnostic
|
||||
diagnose_conflict
|
||||
diagnose_parameterizing_redefined
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def diagnose_conflict
|
||||
if @states.sr_conflicts_count != 0
|
||||
@logger.warn("shift/reduce conflicts: #{@states.sr_conflicts_count} found")
|
||||
end
|
||||
|
||||
if @states.rr_conflicts_count != 0
|
||||
@logger.warn("reduce/reduce conflicts: #{@states.rr_conflicts_count} found")
|
||||
end
|
||||
end
|
||||
|
||||
def diagnose_parameterizing_redefined
|
||||
@grammar.parameterizing_rule_resolver.redefined_rules.each do |rule|
|
||||
@logger.warn("parameterizing rule redefined: #{rule}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,21 +1,52 @@
|
||||
# rbs_inline: enabled
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
# Algorithm Digraph of https://dl.acm.org/doi/pdf/10.1145/69622.357187 (P. 625)
|
||||
#
|
||||
# @rbs generic X < Object -- Type of a member of `sets`
|
||||
# @rbs generic Y < _Or -- Type of sets assigned to a member of `sets`
|
||||
class Digraph
|
||||
# TODO: rbs-inline 0.10.0 doesn't support instance variables.
|
||||
# Move these type declarations above instance variable definitions, once it's supported.
|
||||
#
|
||||
# @rbs!
|
||||
# interface _Or
|
||||
# def |: (self) -> self
|
||||
# end
|
||||
# @sets: Array[X]
|
||||
# @relation: Hash[X, Array[X]]
|
||||
# @base_function: Hash[X, Y]
|
||||
# @stack: Array[X]
|
||||
# @h: Hash[X, (Integer|Float)?]
|
||||
# @result: Hash[X, Y]
|
||||
|
||||
# @rbs sets: Array[X]
|
||||
# @rbs relation: Hash[X, Array[X]]
|
||||
# @rbs base_function: Hash[X, Y]
|
||||
# @rbs return: void
|
||||
def initialize(sets, relation, base_function)
|
||||
|
||||
# X in the paper
|
||||
@sets = sets
|
||||
|
||||
# R in the paper
|
||||
@relation = relation
|
||||
|
||||
# F' in the paper
|
||||
@base_function = base_function
|
||||
|
||||
# S in the paper
|
||||
@stack = []
|
||||
|
||||
# N in the paper
|
||||
@h = Hash.new(0)
|
||||
|
||||
# F in the paper
|
||||
@result = {}
|
||||
end
|
||||
|
||||
# @rbs () -> Hash[X, Y]
|
||||
def compute
|
||||
@sets.each do |x|
|
||||
next if @h[x] != 0
|
||||
@@ -27,6 +58,7 @@ module Lrama
|
||||
|
||||
private
|
||||
|
||||
# @rbs (X x) -> void
|
||||
def traverse(x)
|
||||
@stack.push(x)
|
||||
d = @stack.count
|
||||
|
||||
@@ -1,44 +1,41 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "forwardable"
|
||||
require "lrama/grammar/auxiliary"
|
||||
require "lrama/grammar/binding"
|
||||
require "lrama/grammar/code"
|
||||
require "lrama/grammar/counter"
|
||||
require "lrama/grammar/destructor"
|
||||
require "lrama/grammar/error_token"
|
||||
require "lrama/grammar/parameterizing_rule"
|
||||
require "lrama/grammar/percent_code"
|
||||
require "lrama/grammar/precedence"
|
||||
require "lrama/grammar/printer"
|
||||
require "lrama/grammar/reference"
|
||||
require "lrama/grammar/rule"
|
||||
require "lrama/grammar/rule_builder"
|
||||
require "lrama/grammar/symbol"
|
||||
require "lrama/grammar/symbols"
|
||||
require "lrama/grammar/type"
|
||||
require "lrama/grammar/union"
|
||||
require "lrama/lexer"
|
||||
require_relative "grammar/auxiliary"
|
||||
require_relative "grammar/binding"
|
||||
require_relative "grammar/code"
|
||||
require_relative "grammar/counter"
|
||||
require_relative "grammar/destructor"
|
||||
require_relative "grammar/error_token"
|
||||
require_relative "grammar/parameterizing_rule"
|
||||
require_relative "grammar/percent_code"
|
||||
require_relative "grammar/precedence"
|
||||
require_relative "grammar/printer"
|
||||
require_relative "grammar/reference"
|
||||
require_relative "grammar/rule"
|
||||
require_relative "grammar/rule_builder"
|
||||
require_relative "grammar/symbol"
|
||||
require_relative "grammar/symbols"
|
||||
require_relative "grammar/type"
|
||||
require_relative "grammar/union"
|
||||
require_relative "lexer"
|
||||
|
||||
module Lrama
|
||||
# Grammar is the result of parsing an input grammar file
|
||||
class Grammar
|
||||
extend Forwardable
|
||||
|
||||
attr_reader :percent_codes, :eof_symbol, :error_symbol, :undef_symbol, :accept_symbol, :aux
|
||||
attr_accessor :union, :expect,
|
||||
:printers, :error_tokens,
|
||||
:lex_param, :parse_param, :initial_action,
|
||||
attr_reader :percent_codes, :eof_symbol, :error_symbol, :undef_symbol, :accept_symbol, :aux, :parameterizing_rule_resolver
|
||||
attr_accessor :union, :expect, :printers, :error_tokens, :lex_param, :parse_param, :initial_action,
|
||||
:after_shift, :before_reduce, :after_reduce, :after_shift_error_token, :after_pop_stack,
|
||||
:symbols_resolver, :types,
|
||||
:rules, :rule_builders,
|
||||
:sym_to_rules, :no_stdlib
|
||||
:symbols_resolver, :types, :rules, :rule_builders, :sym_to_rules, :no_stdlib, :locations, :define
|
||||
|
||||
def_delegators "@symbols_resolver", :symbols, :nterms, :terms, :add_nterm, :add_term,
|
||||
def_delegators "@symbols_resolver", :symbols, :nterms, :terms, :add_nterm, :add_term, :find_term_by_s_value,
|
||||
:find_symbol_by_number!, :find_symbol_by_id!, :token_to_symbol,
|
||||
:find_symbol_by_s_value!, :fill_symbol_number, :fill_nterm_type,
|
||||
:fill_printer, :fill_destructor, :fill_error_token, :sort_by_number!
|
||||
|
||||
|
||||
def initialize(rule_counter)
|
||||
def initialize(rule_counter, define = {})
|
||||
@rule_counter = rule_counter
|
||||
|
||||
# Code defined by "%code"
|
||||
@@ -59,10 +56,16 @@ module Lrama
|
||||
@accept_symbol = nil
|
||||
@aux = Auxiliary.new
|
||||
@no_stdlib = false
|
||||
@locations = false
|
||||
@define = define.map {|d| d.split('=') }.to_h
|
||||
|
||||
append_special_symbols
|
||||
end
|
||||
|
||||
def create_rule_builder(rule_counter, midrule_action_counter)
|
||||
RuleBuilder.new(rule_counter, midrule_action_counter, @parameterizing_rule_resolver)
|
||||
end
|
||||
|
||||
def add_percent_code(id:, code:)
|
||||
@percent_codes << PercentCode.new(id.s_value, code.s_value)
|
||||
end
|
||||
@@ -141,6 +144,7 @@ module Lrama
|
||||
end
|
||||
|
||||
def prepare
|
||||
resolve_inline_rules
|
||||
normalize_rules
|
||||
collect_symbols
|
||||
set_lhs_and_rhs
|
||||
@@ -149,6 +153,7 @@ module Lrama
|
||||
fill_sym_to_rules
|
||||
compute_nullable
|
||||
compute_first_set
|
||||
set_locations
|
||||
end
|
||||
|
||||
# TODO: More validation methods
|
||||
@@ -167,6 +172,10 @@ module Lrama
|
||||
@sym_to_rules[sym.number]
|
||||
end
|
||||
|
||||
def ielr_defined?
|
||||
@define.key?('lr.type') && @define['lr.type'] == 'ielr'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def compute_nullable
|
||||
@@ -255,7 +264,7 @@ module Lrama
|
||||
|
||||
def setup_rules
|
||||
@rule_builders.each do |builder|
|
||||
builder.setup_rules(@parameterizing_rule_resolver)
|
||||
builder.setup_rules
|
||||
end
|
||||
end
|
||||
|
||||
@@ -289,10 +298,23 @@ module Lrama
|
||||
@accept_symbol = term
|
||||
end
|
||||
|
||||
def resolve_inline_rules
|
||||
while @rule_builders.any?(&:has_inline_rules?) do
|
||||
@rule_builders = @rule_builders.flat_map do |builder|
|
||||
if builder.has_inline_rules?
|
||||
builder.resolve_inline_rules
|
||||
else
|
||||
builder
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def normalize_rules
|
||||
# Add $accept rule to the top of rules
|
||||
lineno = @rule_builders.first ? @rule_builders.first.line : 0
|
||||
@rules << Rule.new(id: @rule_counter.increment, _lhs: @accept_symbol.id, _rhs: [@rule_builders.first.lhs, @eof_symbol.id], token_code: nil, lineno: lineno)
|
||||
rule_builder = @rule_builders.first # : RuleBuilder
|
||||
lineno = rule_builder ? rule_builder.line : 0
|
||||
@rules << Rule.new(id: @rule_counter.increment, _lhs: @accept_symbol.id, _rhs: [rule_builder.lhs, @eof_symbol.id], token_code: nil, lineno: lineno)
|
||||
|
||||
setup_rules
|
||||
|
||||
@@ -365,17 +387,21 @@ module Lrama
|
||||
end
|
||||
|
||||
def validate_rule_lhs_is_nterm!
|
||||
errors = []
|
||||
errors = [] #: Array[String]
|
||||
|
||||
rules.each do |rule|
|
||||
next if rule.lhs.nterm?
|
||||
|
||||
errors << "[BUG] LHS of #{rule} (line: #{rule.lineno}) is term. It should be nterm."
|
||||
errors << "[BUG] LHS of #{rule.display_name} (line: #{rule.lineno}) is term. It should be nterm."
|
||||
end
|
||||
|
||||
return if errors.empty?
|
||||
|
||||
raise errors.join("\n")
|
||||
end
|
||||
|
||||
def set_locations
|
||||
@locations = @locations || @rules.any? {|rule| rule.contains_at_reference? }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
# Grammar file information not used by States but by Output
|
||||
|
||||
@@ -1,22 +1,65 @@
|
||||
# rbs_inline: enabled
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class Binding
|
||||
attr_reader :actual_args, :count
|
||||
# @rbs @actual_args: Array[Lexer::Token]
|
||||
# @rbs @param_to_arg: Hash[String, Lexer::Token]
|
||||
|
||||
def initialize(parameterizing_rule, actual_args)
|
||||
@parameters = parameterizing_rule.parameters
|
||||
# @rbs (Array[Lexer::Token] params, Array[Lexer::Token] actual_args) -> void
|
||||
def initialize(params, actual_args)
|
||||
@actual_args = actual_args
|
||||
@parameter_to_arg = @parameters.zip(actual_args).map do |param, arg|
|
||||
@param_to_arg = map_params_to_args(params, @actual_args)
|
||||
end
|
||||
|
||||
# @rbs (Lexer::Token sym) -> Lexer::Token
|
||||
def resolve_symbol(sym)
|
||||
if sym.is_a?(Lexer::Token::InstantiateRule)
|
||||
Lrama::Lexer::Token::InstantiateRule.new(
|
||||
s_value: sym.s_value, location: sym.location, args: resolved_args(sym), lhs_tag: sym.lhs_tag
|
||||
)
|
||||
else
|
||||
param_to_arg(sym)
|
||||
end
|
||||
end
|
||||
|
||||
# @rbs (Lexer::Token::InstantiateRule token) -> String
|
||||
def concatenated_args_str(token)
|
||||
"#{token.rule_name}_#{token_to_args_s_values(token).join('_')}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @rbs (Array[Lexer::Token] params, Array[Lexer::Token] actual_args) -> Hash[String, Lexer::Token]
|
||||
def map_params_to_args(params, actual_args)
|
||||
params.zip(actual_args).map do |param, arg|
|
||||
[param.s_value, arg]
|
||||
end.to_h
|
||||
end
|
||||
|
||||
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)
|
||||
else
|
||||
@parameter_to_arg[symbol.s_value] || symbol
|
||||
# @rbs (Lexer::Token::InstantiateRule sym) -> Array[Lexer::Token]
|
||||
def resolved_args(sym)
|
||||
sym.args.map { |arg| resolve_symbol(arg) }
|
||||
end
|
||||
|
||||
# @rbs (Lexer::Token sym) -> Lexer::Token
|
||||
def param_to_arg(sym)
|
||||
if (arg = @param_to_arg[sym.s_value].dup)
|
||||
arg.alias_name = sym.alias_name
|
||||
end
|
||||
arg || sym
|
||||
end
|
||||
|
||||
# @rbs (Lexer::Token::InstantiateRule token) -> Array[String]
|
||||
def token_to_args_s_values(token)
|
||||
token.args.flat_map do |arg|
|
||||
resolved = resolve_symbol(arg)
|
||||
if resolved.is_a?(Lexer::Token::InstantiateRule)
|
||||
[resolved.s_value] + resolved.args.map(&:s_value)
|
||||
else
|
||||
[resolved.s_value]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "forwardable"
|
||||
require "lrama/grammar/code/destructor_code"
|
||||
require "lrama/grammar/code/initial_action_code"
|
||||
require "lrama/grammar/code/no_reference_code"
|
||||
require "lrama/grammar/code/printer_code"
|
||||
require "lrama/grammar/code/rule_action"
|
||||
require_relative "code/destructor_code"
|
||||
require_relative "code/initial_action_code"
|
||||
require_relative "code/no_reference_code"
|
||||
require_relative "code/printer_code"
|
||||
require_relative "code/rule_action"
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class Code
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class Code
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class Code
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class Code
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class Code
|
||||
@@ -41,6 +43,7 @@ module Lrama
|
||||
when ref.type == :dollar && ref.name == "$" # $$
|
||||
tag = ref.ex_tag || lhs.tag
|
||||
raise_tag_not_found_error(ref) unless tag
|
||||
# @type var tag: Lexer::Token::Tag
|
||||
"(yyval.#{tag.member})"
|
||||
when ref.type == :at && ref.name == "$" # @$
|
||||
"(yyloc)"
|
||||
@@ -50,6 +53,7 @@ module Lrama
|
||||
i = -position_in_rhs + ref.index
|
||||
tag = ref.ex_tag || rhs[ref.index - 1].tag
|
||||
raise_tag_not_found_error(ref) unless tag
|
||||
# @type var tag: Lexer::Token::Tag
|
||||
"(yyvsp[#{i}].#{tag.member})"
|
||||
when ref.type == :at # @n
|
||||
i = -position_in_rhs + ref.index
|
||||
@@ -69,18 +73,18 @@ module Lrama
|
||||
@rule.position_in_original_rule_rhs || @rule.rhs.count
|
||||
end
|
||||
|
||||
# If this is midrule action, RHS is a RHS of the original rule.
|
||||
# If this is midrule action, RHS is an RHS of the original rule.
|
||||
def rhs
|
||||
(@rule.original_rule || @rule).rhs
|
||||
end
|
||||
|
||||
# Unlike `rhs`, LHS is always a LHS of the rule.
|
||||
# Unlike `rhs`, LHS is always an LHS of the rule.
|
||||
def lhs
|
||||
@rule.lhs
|
||||
end
|
||||
|
||||
def raise_tag_not_found_error(ref)
|
||||
raise "Tag is not specified for '$#{ref.value}' in '#{@rule}'"
|
||||
raise "Tag is not specified for '$#{ref.value}' in '#{@rule.display_name}'"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class Counter
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class Destructor < Struct.new(:ident_or_tags, :token_code, :lineno, keyword_init: true)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class ErrorToken < Struct.new(:ident_or_tags, :token_code, :lineno, keyword_init: true)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'parameterizing_rule/resolver'
|
||||
require_relative 'parameterizing_rule/rhs'
|
||||
require_relative 'parameterizing_rule/rule'
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class ParameterizingRule
|
||||
@@ -18,13 +20,17 @@ module Lrama
|
||||
end
|
||||
|
||||
def find_inline(token)
|
||||
@rules.select { |rule| rule.name == token.s_value && rule.is_inline }.last
|
||||
@rules.reverse.find { |rule| rule.name == token.s_value && rule.is_inline }
|
||||
end
|
||||
|
||||
def created_lhs(lhs_s_value)
|
||||
@created_lhs_list.reverse.find { |created_lhs| created_lhs.s_value == lhs_s_value }
|
||||
end
|
||||
|
||||
def redefined_rules
|
||||
@rules.select { |rule| @rules.count { |r| r.name == rule.name && r.required_parameters_count == rule.required_parameters_count } > 1 }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def select_rules(rules, token)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class ParameterizingRule
|
||||
@@ -13,7 +15,8 @@ module Lrama
|
||||
def resolve_user_code(bindings)
|
||||
return unless user_code
|
||||
|
||||
var_to_arg = {}
|
||||
resolved = Lexer::Token::UserCode.new(s_value: user_code.s_value, location: user_code.location)
|
||||
var_to_arg = {} #: Hash[String, String]
|
||||
symbols.each do |sym|
|
||||
resolved_sym = bindings.resolve_symbol(sym)
|
||||
if resolved_sym != sym
|
||||
@@ -22,14 +25,14 @@ module Lrama
|
||||
end
|
||||
|
||||
var_to_arg.each do |var, arg|
|
||||
user_code.references.each do |ref|
|
||||
resolved.references.each do |ref|
|
||||
if ref.name == var
|
||||
ref.name = arg
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return user_code
|
||||
return resolved
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class ParameterizingRule
|
||||
@@ -12,6 +14,10 @@ module Lrama
|
||||
@is_inline = is_inline
|
||||
@required_parameters_count = parameters.count
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{@name}(#{@parameters.map(&:s_value).join(', ')})"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class PercentCode
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class Precedence < Struct.new(:type, :precedence, keyword_init: true)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class Printer < Struct.new(:ident_or_tags, :token_code, :lineno, keyword_init: true)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
# type: :dollar or :at
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
# _rhs holds original RHS element. Use rhs to refer to Symbol.
|
||||
@@ -16,10 +18,17 @@ module Lrama
|
||||
self.lineno == other.lineno
|
||||
end
|
||||
|
||||
# TODO: Change this to display_name
|
||||
def to_s
|
||||
def display_name
|
||||
l = lhs.id.s_value
|
||||
r = empty_rule? ? "ε" : rhs.map {|r| r.id.s_value }.join(" ")
|
||||
"#{l} -> #{r}"
|
||||
end
|
||||
|
||||
def display_name_without_action
|
||||
l = lhs.id.s_value
|
||||
r = empty_rule? ? "ε" : rhs.map do |r|
|
||||
r.id.s_value if r.first_set.any?
|
||||
end.compact.join(" ")
|
||||
|
||||
"#{l} -> #{r}"
|
||||
end
|
||||
@@ -33,7 +42,7 @@ module Lrama
|
||||
end
|
||||
|
||||
def with_actions
|
||||
"#{to_s} {#{token_code&.s_value}}"
|
||||
"#{display_name} {#{token_code&.s_value}}"
|
||||
end
|
||||
|
||||
# opt_nl: ε <-- empty_rule
|
||||
@@ -55,6 +64,12 @@ module Lrama
|
||||
|
||||
Code::RuleAction.new(type: :rule_action, token_code: token_code, rule: self).translated_code
|
||||
end
|
||||
|
||||
def contains_at_reference?
|
||||
return false unless token_code
|
||||
|
||||
token_code.references.any? {|r| r.type == :at }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class RuleBuilder
|
||||
attr_accessor :lhs, :line
|
||||
attr_reader :lhs_tag, :rhs, :user_code, :precedence_sym
|
||||
|
||||
def initialize(rule_counter, midrule_action_counter, position_in_original_rule_rhs = nil, lhs_tag: nil, skip_preprocess_references: false)
|
||||
def initialize(rule_counter, midrule_action_counter, parameterizing_rule_resolver, position_in_original_rule_rhs = nil, lhs_tag: nil, skip_preprocess_references: false)
|
||||
@rule_counter = rule_counter
|
||||
@midrule_action_counter = midrule_action_counter
|
||||
@parameterizing_rule_resolver = parameterizing_rule_resolver
|
||||
@position_in_original_rule_rhs = position_in_original_rule_rhs
|
||||
@skip_preprocess_references = skip_preprocess_references
|
||||
|
||||
@@ -19,16 +22,12 @@ module Lrama
|
||||
@rules = []
|
||||
@rule_builders_for_parameterizing_rules = []
|
||||
@rule_builders_for_derived_rules = []
|
||||
@rule_builders_for_inline_rules = []
|
||||
@parameterizing_rules = []
|
||||
@inline_rules = []
|
||||
@midrule_action_rules = []
|
||||
end
|
||||
|
||||
def add_rhs(rhs)
|
||||
if !@line
|
||||
@line = rhs.line
|
||||
end
|
||||
@line ||= rhs.line
|
||||
|
||||
flush_user_code
|
||||
|
||||
@@ -36,9 +35,7 @@ module Lrama
|
||||
end
|
||||
|
||||
def user_code=(user_code)
|
||||
if !@line
|
||||
@line = user_code&.line
|
||||
end
|
||||
@line ||= user_code&.line
|
||||
|
||||
flush_user_code
|
||||
|
||||
@@ -55,18 +52,41 @@ module Lrama
|
||||
freeze_rhs
|
||||
end
|
||||
|
||||
def setup_rules(parameterizing_rule_resolver)
|
||||
def setup_rules
|
||||
preprocess_references unless @skip_preprocess_references
|
||||
if rhs.any? { |token| parameterizing_rule_resolver.find_inline(token) }
|
||||
resolve_inline(parameterizing_rule_resolver)
|
||||
else
|
||||
process_rhs(parameterizing_rule_resolver)
|
||||
end
|
||||
process_rhs
|
||||
build_rules
|
||||
end
|
||||
|
||||
def rules
|
||||
@parameterizing_rules + @inline_rules + @midrule_action_rules + @rules
|
||||
@parameterizing_rules + @midrule_action_rules + @rules
|
||||
end
|
||||
|
||||
def has_inline_rules?
|
||||
rhs.any? { |token| @parameterizing_rule_resolver.find_inline(token) }
|
||||
end
|
||||
|
||||
def resolve_inline_rules
|
||||
resolved_builders = [] #: Array[RuleBuilder]
|
||||
rhs.each_with_index do |token, i|
|
||||
if (inline_rule = @parameterizing_rule_resolver.find_inline(token))
|
||||
inline_rule.rhs_list.each do |inline_rhs|
|
||||
rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, @parameterizing_rule_resolver, lhs_tag: lhs_tag)
|
||||
if token.is_a?(Lexer::Token::InstantiateRule)
|
||||
resolve_inline_rhs(rule_builder, inline_rhs, i, Binding.new(inline_rule.parameters, token.args))
|
||||
else
|
||||
resolve_inline_rhs(rule_builder, inline_rhs, i)
|
||||
end
|
||||
rule_builder.lhs = lhs
|
||||
rule_builder.line = line
|
||||
rule_builder.precedence_sym = precedence_sym
|
||||
rule_builder.user_code = replace_inline_user_code(inline_rhs, i)
|
||||
resolved_builders << rule_builder
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
resolved_builders
|
||||
end
|
||||
|
||||
private
|
||||
@@ -82,31 +102,25 @@ module Lrama
|
||||
def build_rules
|
||||
tokens = @replaced_rhs
|
||||
|
||||
if tokens
|
||||
rule = Rule.new(
|
||||
id: @rule_counter.increment, _lhs: lhs, _rhs: tokens, lhs_tag: lhs_tag, token_code: user_code,
|
||||
position_in_original_rule_rhs: @position_in_original_rule_rhs, precedence_sym: precedence_sym, lineno: line
|
||||
)
|
||||
@rules = [rule]
|
||||
@parameterizing_rules = @rule_builders_for_parameterizing_rules.map do |rule_builder|
|
||||
rule_builder.rules
|
||||
end.flatten
|
||||
@midrule_action_rules = @rule_builders_for_derived_rules.map do |rule_builder|
|
||||
rule_builder.rules
|
||||
end.flatten
|
||||
@midrule_action_rules.each do |r|
|
||||
r.original_rule = rule
|
||||
end
|
||||
else
|
||||
@inline_rules = @rule_builders_for_inline_rules.map do |rule_builder|
|
||||
rule_builder.rules
|
||||
end.flatten
|
||||
rule = Rule.new(
|
||||
id: @rule_counter.increment, _lhs: lhs, _rhs: tokens, lhs_tag: lhs_tag, token_code: user_code,
|
||||
position_in_original_rule_rhs: @position_in_original_rule_rhs, precedence_sym: precedence_sym, lineno: line
|
||||
)
|
||||
@rules = [rule]
|
||||
@parameterizing_rules = @rule_builders_for_parameterizing_rules.map do |rule_builder|
|
||||
rule_builder.rules
|
||||
end.flatten
|
||||
@midrule_action_rules = @rule_builders_for_derived_rules.map do |rule_builder|
|
||||
rule_builder.rules
|
||||
end.flatten
|
||||
@midrule_action_rules.each do |r|
|
||||
r.original_rule = rule
|
||||
end
|
||||
end
|
||||
|
||||
# rhs is a mixture of variety type of tokens like `Ident`, `InstantiateRule`, `UserCode` and so on.
|
||||
# `#process_rhs` replaces some kind of tokens to `Ident` so that all `@replaced_rhs` are `Ident` or `Char`.
|
||||
def process_rhs(parameterizing_rule_resolver)
|
||||
def process_rhs
|
||||
return if @replaced_rhs
|
||||
|
||||
@replaced_rhs = []
|
||||
@@ -118,26 +132,26 @@ module Lrama
|
||||
when Lrama::Lexer::Token::Ident
|
||||
@replaced_rhs << token
|
||||
when Lrama::Lexer::Token::InstantiateRule
|
||||
parameterizing_rule = parameterizing_rule_resolver.find_rule(token)
|
||||
parameterizing_rule = @parameterizing_rule_resolver.find_rule(token)
|
||||
raise "Unexpected token. #{token}" unless parameterizing_rule
|
||||
|
||||
bindings = Binding.new(parameterizing_rule, token.args)
|
||||
lhs_s_value = lhs_s_value(token, bindings)
|
||||
if (created_lhs = parameterizing_rule_resolver.created_lhs(lhs_s_value))
|
||||
bindings = Binding.new(parameterizing_rule.parameters, token.args)
|
||||
lhs_s_value = bindings.concatenated_args_str(token)
|
||||
if (created_lhs = @parameterizing_rule_resolver.created_lhs(lhs_s_value))
|
||||
@replaced_rhs << created_lhs
|
||||
else
|
||||
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_resolver.created_lhs_list << lhs_token
|
||||
parameterizing_rule.rhs_list.each do |r|
|
||||
rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, lhs_tag: token.lhs_tag || parameterizing_rule.tag)
|
||||
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)) }
|
||||
rule_builder.line = line
|
||||
rule_builder.precedence_sym = r.precedence_sym
|
||||
rule_builder.user_code = r.resolve_user_code(bindings)
|
||||
rule_builder.complete_input
|
||||
rule_builder.setup_rules(parameterizing_rule_resolver)
|
||||
rule_builder.setup_rules
|
||||
@rule_builders_for_parameterizing_rules << rule_builder
|
||||
end
|
||||
end
|
||||
@@ -147,11 +161,11 @@ module Lrama
|
||||
new_token = Lrama::Lexer::Token::Ident.new(s_value: prefix + @midrule_action_counter.increment.to_s)
|
||||
@replaced_rhs << new_token
|
||||
|
||||
rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, i, lhs_tag: tag, skip_preprocess_references: true)
|
||||
rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, @parameterizing_rule_resolver, i, lhs_tag: tag, skip_preprocess_references: true)
|
||||
rule_builder.lhs = new_token
|
||||
rule_builder.user_code = token
|
||||
rule_builder.complete_input
|
||||
rule_builder.setup_rules(parameterizing_rule_resolver)
|
||||
rule_builder.setup_rules
|
||||
|
||||
@rule_builders_for_derived_rules << rule_builder
|
||||
else
|
||||
@@ -160,39 +174,10 @@ module Lrama
|
||||
end
|
||||
end
|
||||
|
||||
def lhs_s_value(token, bindings)
|
||||
s_values = token.args.map do |arg|
|
||||
resolved = bindings.resolve_symbol(arg)
|
||||
if resolved.is_a?(Lexer::Token::InstantiateRule)
|
||||
[resolved.s_value, resolved.args.map(&:s_value)]
|
||||
else
|
||||
resolved.s_value
|
||||
end
|
||||
end
|
||||
"#{token.rule_name}_#{s_values.join('_')}"
|
||||
end
|
||||
|
||||
def resolve_inline(parameterizing_rule_resolver)
|
||||
rhs.each_with_index do |token, i|
|
||||
if inline_rule = parameterizing_rule_resolver.find_inline(token)
|
||||
inline_rule.rhs_list.each_with_index do |inline_rhs|
|
||||
rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, lhs_tag: lhs_tag, skip_preprocess_references: true)
|
||||
resolve_inline_rhs(rule_builder, inline_rhs, i)
|
||||
rule_builder.lhs = lhs
|
||||
rule_builder.line = line
|
||||
rule_builder.user_code = replace_inline_user_code(inline_rhs, i)
|
||||
rule_builder.complete_input
|
||||
rule_builder.setup_rules(parameterizing_rule_resolver)
|
||||
@rule_builders_for_inline_rules << rule_builder
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def resolve_inline_rhs(rule_builder, inline_rhs, index)
|
||||
def resolve_inline_rhs(rule_builder, inline_rhs, index, bindings = nil)
|
||||
rhs.each_with_index do |token, i|
|
||||
if index == i
|
||||
inline_rhs.symbols.each { |sym| rule_builder.add_rhs(sym) }
|
||||
inline_rhs.symbols.each { |sym| rule_builder.add_rhs(bindings.nil? ? sym : bindings.resolve_symbol(sym)) }
|
||||
else
|
||||
rule_builder.add_rhs(token)
|
||||
end
|
||||
@@ -204,6 +189,11 @@ module Lrama
|
||||
return user_code if user_code.nil?
|
||||
|
||||
code = user_code.s_value.gsub(/\$#{index + 1}/, inline_rhs.user_code.s_value)
|
||||
user_code.references.each do |ref|
|
||||
next if ref.index.nil? || ref.index <= index # nil is a case for `$$`
|
||||
code = code.gsub(/\$#{ref.index}/, "$#{ref.index + (inline_rhs.symbols.count-1)}")
|
||||
code = code.gsub(/@#{ref.index}/, "@#{ref.index + (inline_rhs.symbols.count-1)}")
|
||||
end
|
||||
Lrama::Lexer::Token::UserCode.new(s_value: code, location: user_code.location)
|
||||
end
|
||||
|
||||
@@ -238,9 +228,6 @@ module Lrama
|
||||
end
|
||||
|
||||
if ref.number
|
||||
# TODO: When Inlining is implemented, for example, if `$1` is expanded to multiple RHS tokens,
|
||||
# `$2` needs to access `$2 + n` to actually access it. So, after the Inlining implementation,
|
||||
# it needs resolves from number to index.
|
||||
ref.index = ref.number
|
||||
end
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Symbol is both of nterm and term
|
||||
# `number` is both for nterm and term
|
||||
# `token_id` is tokentype for term, internal sequence number for nterm
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "symbols/resolver"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class Symbols
|
||||
@@ -42,7 +44,9 @@ module Lrama
|
||||
end
|
||||
|
||||
def add_nterm(id:, alias_name: nil, tag: nil)
|
||||
return if find_symbol_by_id(id)
|
||||
if (sym = find_symbol_by_id(id))
|
||||
return sym
|
||||
end
|
||||
|
||||
@symbols = nil
|
||||
nterm = Symbol.new(
|
||||
@@ -53,6 +57,10 @@ module Lrama
|
||||
nterm
|
||||
end
|
||||
|
||||
def find_term_by_s_value(s_value)
|
||||
terms.find { |s| s.id.s_value == s_value }
|
||||
end
|
||||
|
||||
def find_symbol_by_s_value(s_value)
|
||||
symbols.find { |s| s.id.s_value == s_value }
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class Type
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Grammar
|
||||
class Union < Struct.new(:code, :lineno, keyword_init: true)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class GrammarValidator
|
||||
def initialize(grammar, states, logger)
|
||||
@grammar = grammar
|
||||
@states = states
|
||||
@logger = logger
|
||||
end
|
||||
|
||||
def valid?
|
||||
conflicts_within_threshold?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def conflicts_within_threshold?
|
||||
return true unless @grammar.expect
|
||||
|
||||
[sr_conflicts_within_threshold(@grammar.expect), rr_conflicts_within_threshold(0)].all?
|
||||
end
|
||||
|
||||
def sr_conflicts_within_threshold(expected)
|
||||
return true if expected == @states.sr_conflicts_count
|
||||
|
||||
@logger.error("shift/reduce conflicts: #{@states.sr_conflicts_count} found, #{expected} expected")
|
||||
false
|
||||
end
|
||||
|
||||
def rr_conflicts_within_threshold(expected)
|
||||
return true if expected == @states.rr_conflicts_count
|
||||
|
||||
@logger.error("reduce/reduce conflicts: #{@states.rr_conflicts_count} found, #{expected} expected")
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,19 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "strscan"
|
||||
|
||||
require "lrama/lexer/grammar_file"
|
||||
require "lrama/lexer/location"
|
||||
require "lrama/lexer/token"
|
||||
require_relative "lexer/grammar_file"
|
||||
require_relative "lexer/location"
|
||||
require_relative "lexer/token"
|
||||
|
||||
module Lrama
|
||||
class Lexer
|
||||
attr_reader :head_line, :head_column, :line
|
||||
attr_accessor :status, :end_symbol
|
||||
|
||||
SYMBOLS = ['%{', '%}', '%%', '{', '}', '\[', '\]', '\(', '\)', '\,', ':', '\|', ';']
|
||||
SYMBOLS = ['%{', '%}', '%%', '{', '}', '\[', '\]', '\(', '\)', '\,', ':', '\|', ';'].freeze
|
||||
PERCENT_TOKENS = %w(
|
||||
%union
|
||||
%token
|
||||
%type
|
||||
%nterm
|
||||
%left
|
||||
%right
|
||||
%nonassoc
|
||||
@@ -38,7 +41,8 @@ module Lrama
|
||||
%rule
|
||||
%no-stdlib
|
||||
%inline
|
||||
)
|
||||
%locations
|
||||
).freeze
|
||||
|
||||
def initialize(grammar_file)
|
||||
@grammar_file = grammar_file
|
||||
@@ -71,7 +75,7 @@ module Lrama
|
||||
end
|
||||
|
||||
def lex_token
|
||||
while !@scanner.eos? do
|
||||
until @scanner.eos? do
|
||||
case
|
||||
when @scanner.scan(/\n/)
|
||||
newline
|
||||
@@ -126,7 +130,7 @@ module Lrama
|
||||
code = ''
|
||||
reset_first_position
|
||||
|
||||
while !@scanner.eos? do
|
||||
until @scanner.eos? do
|
||||
case
|
||||
when @scanner.scan(/{/)
|
||||
code += @scanner.matched
|
||||
@@ -163,14 +167,13 @@ module Lrama
|
||||
private
|
||||
|
||||
def lex_comment
|
||||
while !@scanner.eos? do
|
||||
until @scanner.eos? do
|
||||
case
|
||||
when @scanner.scan(/\n/)
|
||||
newline
|
||||
when @scanner.scan(/\*\//)
|
||||
when @scanner.scan_until(/[\s\S]*?\*\//)
|
||||
@scanner.matched.count("\n").times { newline }
|
||||
return
|
||||
else
|
||||
@scanner.getch
|
||||
when @scanner.scan_until(/\n/)
|
||||
newline
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,28 +1,37 @@
|
||||
# rbs_inline: enabled
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Lexer
|
||||
class GrammarFile
|
||||
class Text < String
|
||||
# @rbs () -> String
|
||||
def inspect
|
||||
length <= 50 ? super : "#{self[0..47]}...".inspect
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :path, :text
|
||||
attr_reader :path #: String
|
||||
attr_reader :text #: String
|
||||
|
||||
# @rbs (String path, String text) -> void
|
||||
def initialize(path, text)
|
||||
@path = path
|
||||
@text = Text.new(text).freeze
|
||||
end
|
||||
|
||||
# @rbs () -> String
|
||||
def inspect
|
||||
"<#{self.class}: @path=#{path}, @text=#{text.inspect}>"
|
||||
end
|
||||
|
||||
# @rbs (GrammarFile other) -> bool
|
||||
def ==(other)
|
||||
self.class == other.class &&
|
||||
self.path == other.path
|
||||
end
|
||||
|
||||
# @rbs () -> Array[String]
|
||||
def lines
|
||||
@lines ||= text.split("\n")
|
||||
end
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
# rbs_inline: enabled
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Lexer
|
||||
class Location
|
||||
attr_reader :grammar_file, :first_line, :first_column, :last_line, :last_column
|
||||
attr_reader :grammar_file #: GrammarFile
|
||||
attr_reader :first_line #: Integer
|
||||
attr_reader :first_column #: Integer
|
||||
attr_reader :last_line #: Integer
|
||||
attr_reader :last_column #: Integer
|
||||
|
||||
# @rbs (grammar_file: GrammarFile, first_line: Integer, first_column: Integer, last_line: Integer, last_column: Integer) -> void
|
||||
def initialize(grammar_file:, first_line:, first_column:, last_line:, last_column:)
|
||||
@grammar_file = grammar_file
|
||||
@first_line = first_line
|
||||
@@ -11,6 +19,7 @@ module Lrama
|
||||
@last_column = last_column
|
||||
end
|
||||
|
||||
# @rbs (Location other) -> bool
|
||||
def ==(other)
|
||||
self.class == other.class &&
|
||||
self.grammar_file == other.grammar_file &&
|
||||
@@ -20,6 +29,7 @@ module Lrama
|
||||
self.last_column == other.last_column
|
||||
end
|
||||
|
||||
# @rbs (Integer left, Integer right) -> Location
|
||||
def partial_location(left, right)
|
||||
offset = -first_column
|
||||
new_first_line = -1
|
||||
@@ -50,10 +60,12 @@ module Lrama
|
||||
)
|
||||
end
|
||||
|
||||
# @rbs () -> String
|
||||
def to_s
|
||||
"#{path} (#{first_line},#{first_column})-(#{last_line},#{last_column})"
|
||||
end
|
||||
|
||||
# @rbs (String error_message) -> String
|
||||
def generate_error_message(error_message)
|
||||
<<~ERROR.chomp
|
||||
#{path}:#{first_line}:#{first_column}: #{error_message}
|
||||
@@ -61,6 +73,7 @@ module Lrama
|
||||
ERROR
|
||||
end
|
||||
|
||||
# @rbs () -> String
|
||||
def line_with_carets
|
||||
<<~TEXT
|
||||
#{text}
|
||||
@@ -70,22 +83,27 @@ module Lrama
|
||||
|
||||
private
|
||||
|
||||
# @rbs () -> String
|
||||
def path
|
||||
grammar_file.path
|
||||
end
|
||||
|
||||
# @rbs () -> String
|
||||
def blanks
|
||||
(text[0...first_column] or raise "#{first_column} is invalid").gsub(/[^\t]/, ' ')
|
||||
end
|
||||
|
||||
# @rbs () -> String
|
||||
def carets
|
||||
blanks + '^' * (last_column - first_column)
|
||||
end
|
||||
|
||||
# @rbs () -> String
|
||||
def text
|
||||
@text ||= _text.join("\n")
|
||||
end
|
||||
|
||||
# @rbs () -> Array[String]
|
||||
def _text
|
||||
@_text ||=begin
|
||||
range = (first_line - 1)...last_line
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
require 'lrama/lexer/token/char'
|
||||
require 'lrama/lexer/token/ident'
|
||||
require 'lrama/lexer/token/instantiate_rule'
|
||||
require 'lrama/lexer/token/tag'
|
||||
require 'lrama/lexer/token/user_code'
|
||||
# rbs_inline: enabled
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'token/char'
|
||||
require_relative 'token/ident'
|
||||
require_relative 'token/instantiate_rule'
|
||||
require_relative 'token/tag'
|
||||
require_relative 'token/user_code'
|
||||
|
||||
module Lrama
|
||||
class Lexer
|
||||
class Token
|
||||
attr_reader :s_value, :location
|
||||
attr_accessor :alias_name, :referred
|
||||
attr_reader :s_value #: String
|
||||
attr_reader :location #: Location
|
||||
attr_accessor :alias_name #: String
|
||||
attr_accessor :referred #: bool
|
||||
|
||||
# @rbs (s_value: String, ?alias_name: String, ?location: Location) -> void
|
||||
def initialize(s_value:, alias_name: nil, location: nil)
|
||||
s_value.freeze
|
||||
@s_value = s_value
|
||||
@@ -17,36 +23,44 @@ module Lrama
|
||||
@location = location
|
||||
end
|
||||
|
||||
# @rbs () -> String
|
||||
def to_s
|
||||
"value: `#{s_value}`, location: #{location}"
|
||||
end
|
||||
|
||||
# @rbs (String string) -> bool
|
||||
def referred_by?(string)
|
||||
[self.s_value, self.alias_name].compact.include?(string)
|
||||
end
|
||||
|
||||
# @rbs (Token other) -> bool
|
||||
def ==(other)
|
||||
self.class == other.class && self.s_value == other.s_value
|
||||
end
|
||||
|
||||
# @rbs () -> Integer
|
||||
def first_line
|
||||
location.first_line
|
||||
end
|
||||
alias :line :first_line
|
||||
|
||||
# @rbs () -> Integer
|
||||
def first_column
|
||||
location.first_column
|
||||
end
|
||||
alias :column :first_column
|
||||
|
||||
# @rbs () -> Integer
|
||||
def last_line
|
||||
location.last_line
|
||||
end
|
||||
|
||||
# @rbs () -> Integer
|
||||
def last_column
|
||||
location.last_column
|
||||
end
|
||||
|
||||
# @rbs (Lrama::Grammar::Reference ref, String message) -> bot
|
||||
def invalid_ref(ref, message)
|
||||
location = self.location.partial_location(ref.first_column, ref.last_column)
|
||||
raise location.generate_error_message(message)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# rbs_inline: enabled
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Lexer
|
||||
class Token
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# rbs_inline: enabled
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Lexer
|
||||
class Token
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
# rbs_inline: enabled
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Lexer
|
||||
class Token
|
||||
class InstantiateRule < Token
|
||||
attr_reader :args, :lhs_tag
|
||||
attr_reader :args #: Array[Lexer::Token]
|
||||
attr_reader :lhs_tag #: Lexer::Token::Tag?
|
||||
|
||||
# @rbs (s_value: String, ?alias_name: String, ?location: Location, ?args: Array[Lexer::Token], ?lhs_tag: Lexer::Token::Tag?) -> void
|
||||
def initialize(s_value:, alias_name: nil, location: nil, args: [], lhs_tag: nil)
|
||||
super s_value: s_value, alias_name: alias_name, location: location
|
||||
@args = args
|
||||
@lhs_tag = lhs_tag
|
||||
end
|
||||
|
||||
# @rbs () -> String
|
||||
def rule_name
|
||||
s_value
|
||||
end
|
||||
|
||||
# @rbs () -> Integer
|
||||
def args_count
|
||||
args.count
|
||||
end
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
# rbs_inline: enabled
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Lexer
|
||||
class Token
|
||||
class Tag < Token
|
||||
# Omit "<>"
|
||||
# @rbs () -> String
|
||||
def member
|
||||
# Omit "<>"
|
||||
s_value[1..-2] or raise "Unexpected Tag format (#{s_value})"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
# rbs_inline: enabled
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "strscan"
|
||||
|
||||
module Lrama
|
||||
class Lexer
|
||||
class Token
|
||||
class UserCode < Token
|
||||
attr_accessor :tag
|
||||
attr_accessor :tag #: Lexer::Token::Tag
|
||||
|
||||
# @rbs () -> Array[Lrama::Grammar::Reference]
|
||||
def references
|
||||
@references ||= _references
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @rbs () -> Array[Lrama::Grammar::Reference]
|
||||
def _references
|
||||
scanner = StringScanner.new(s_value)
|
||||
references = []
|
||||
references = [] #: Array[Grammar::Reference]
|
||||
|
||||
while !scanner.eos? do
|
||||
until scanner.eos? do
|
||||
case
|
||||
when reference = scan_reference(scanner)
|
||||
references << reference
|
||||
@@ -30,6 +35,7 @@ module Lrama
|
||||
references
|
||||
end
|
||||
|
||||
# @rbs (StringScanner scanner) -> Lrama::Grammar::Reference?
|
||||
def scan_reference(scanner)
|
||||
start = scanner.pos
|
||||
case
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
module Lrama
|
||||
class Warning
|
||||
attr_reader :errors, :warns
|
||||
# rbs_inline: enabled
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Logger
|
||||
# @rbs (IO out) -> void
|
||||
def initialize(out = STDERR)
|
||||
@out = out
|
||||
@errors = []
|
||||
@warns = []
|
||||
end
|
||||
|
||||
def error(message)
|
||||
@out << message << "\n"
|
||||
@errors << message
|
||||
end
|
||||
|
||||
# @rbs (String message) -> void
|
||||
def warn(message)
|
||||
@out << message << "\n"
|
||||
@warns << message
|
||||
end
|
||||
|
||||
def has_error?
|
||||
!@errors.empty?
|
||||
# @rbs (String message) -> void
|
||||
def error(message)
|
||||
@out << message << "\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'optparse'
|
||||
|
||||
module Lrama
|
||||
@@ -16,7 +18,7 @@ module Lrama
|
||||
@options.report_opts = validate_report(@report)
|
||||
@options.grammar_file = argv.shift
|
||||
|
||||
if !@options.grammar_file
|
||||
unless @options.grammar_file
|
||||
abort "File should be specified\n"
|
||||
end
|
||||
|
||||
@@ -57,26 +59,42 @@ module Lrama
|
||||
o.separator ''
|
||||
o.separator 'Tuning the Parser:'
|
||||
o.on('-S', '--skeleton=FILE', 'specify the skeleton to use') {|v| @options.skeleton = v }
|
||||
o.on('-t', 'reserved, do nothing') { }
|
||||
o.on('--debug', 'display debugging outputs of internal parser') {|v| @options.debug = true }
|
||||
o.on('-t', '--debug', 'display debugging outputs of internal parser') {|v| @options.debug = true }
|
||||
o.on('-D', '--define=NAME[=VALUE]', Array, "similar to '%define NAME VALUE'") {|v| @options.define = v }
|
||||
o.separator ''
|
||||
o.separator 'Output:'
|
||||
o.on('-H', '--header=[FILE]', 'also produce a header file named FILE') {|v| @options.header = true; @options.header_file = v }
|
||||
o.on('-d', 'also produce a header file') { @options.header = true }
|
||||
o.on('-r', '--report=THINGS', Array, 'also produce details on the automaton') {|v| @report = v }
|
||||
o.on('-r', '--report=REPORTS', Array, 'also produce details on the automaton') {|v| @report = v }
|
||||
o.on_tail ''
|
||||
o.on_tail 'Valid Reports:'
|
||||
o.on_tail " #{VALID_REPORTS.join(' ')}"
|
||||
|
||||
o.on_tail 'REPORTS is a list of comma-separated words that can include:'
|
||||
o.on_tail ' states describe the states'
|
||||
o.on_tail ' itemsets complete the core item sets with their closure'
|
||||
o.on_tail ' lookaheads explicitly associate lookahead tokens to items'
|
||||
o.on_tail ' solved describe shift/reduce conflicts solving'
|
||||
o.on_tail ' counterexamples, cex generate conflict counterexamples'
|
||||
o.on_tail ' rules list unused rules'
|
||||
o.on_tail ' terms list unused terminals'
|
||||
o.on_tail ' verbose report detailed internal state and analysis results'
|
||||
o.on_tail ' all include all the above reports'
|
||||
o.on_tail ' none disable all reports'
|
||||
o.on('--report-file=FILE', 'also produce details on the automaton output to a file named FILE') {|v| @options.report_file = v }
|
||||
o.on('-o', '--output=FILE', 'leave output to FILE') {|v| @options.outfile = v }
|
||||
|
||||
o.on('--trace=THINGS', Array, 'also output trace logs at runtime') {|v| @trace = v }
|
||||
o.on('--trace=TRACES', Array, 'also output trace logs at runtime') {|v| @trace = v }
|
||||
o.on_tail ''
|
||||
o.on_tail 'Valid Traces:'
|
||||
o.on_tail " #{VALID_TRACES.join(' ')}"
|
||||
|
||||
o.on('-v', 'reserved, do nothing') { }
|
||||
o.on_tail 'TRACES is a list of comma-separated words that can include:'
|
||||
o.on_tail ' automaton display states'
|
||||
o.on_tail ' closure display states'
|
||||
o.on_tail ' rules display grammar rules'
|
||||
o.on_tail ' only-explicit-rules display only explicit grammar rules'
|
||||
o.on_tail ' actions display grammar rules with actions'
|
||||
o.on_tail ' time display generation time'
|
||||
o.on_tail ' all include all the above traces'
|
||||
o.on_tail ' none disable all traces'
|
||||
o.on('-v', '--verbose', "same as '--report=state'") {|_v| @report << 'states' }
|
||||
o.separator ''
|
||||
o.separator 'Diagnostics:'
|
||||
o.on('-W', '--warnings', 'report the warnings') {|v| @options.diagnostic = true }
|
||||
o.separator ''
|
||||
o.separator 'Error Recovery:'
|
||||
o.on('-e', 'enable error recovery') {|v| @options.error_recovery = true }
|
||||
@@ -89,48 +107,57 @@ module Lrama
|
||||
end
|
||||
end
|
||||
|
||||
BISON_REPORTS = %w[states itemsets lookaheads solved counterexamples cex all none]
|
||||
OTHER_REPORTS = %w[verbose]
|
||||
NOT_SUPPORTED_REPORTS = %w[cex none]
|
||||
VALID_REPORTS = BISON_REPORTS + OTHER_REPORTS - NOT_SUPPORTED_REPORTS
|
||||
ALIASED_REPORTS = { cex: :counterexamples }.freeze
|
||||
VALID_REPORTS = %i[states itemsets lookaheads solved counterexamples rules terms verbose].freeze
|
||||
|
||||
def validate_report(report)
|
||||
list = VALID_REPORTS
|
||||
h = { grammar: true }
|
||||
return h if report.empty?
|
||||
return {} if report == ['none']
|
||||
if report == ['all']
|
||||
VALID_REPORTS.each { |r| h[r] = true }
|
||||
return h
|
||||
end
|
||||
|
||||
report.each do |r|
|
||||
if list.include?(r)
|
||||
h[r.to_sym] = true
|
||||
aliased = aliased_report_option(r)
|
||||
if VALID_REPORTS.include?(aliased)
|
||||
h[aliased] = true
|
||||
else
|
||||
raise "Invalid report option \"#{r}\"."
|
||||
end
|
||||
end
|
||||
|
||||
if h[:all]
|
||||
(BISON_REPORTS - NOT_SUPPORTED_REPORTS).each do |r|
|
||||
h[r.to_sym] = true
|
||||
end
|
||||
|
||||
h.delete(:all)
|
||||
end
|
||||
|
||||
return h
|
||||
end
|
||||
|
||||
def aliased_report_option(opt)
|
||||
(ALIASED_REPORTS[opt.to_sym] || opt).to_sym
|
||||
end
|
||||
|
||||
VALID_TRACES = %w[
|
||||
none locations scan parse automaton bitsets
|
||||
closure grammar rules actions resource
|
||||
sets muscles tools m4-early m4 skeleton time
|
||||
ielr cex all
|
||||
]
|
||||
locations scan parse automaton bitsets closure
|
||||
grammar rules only-explicit-rules actions resource
|
||||
sets muscles tools m4-early m4 skeleton time ielr cex
|
||||
].freeze
|
||||
NOT_SUPPORTED_TRACES = %w[
|
||||
locations scan parse bitsets grammar resource
|
||||
sets muscles tools m4-early m4 skeleton ielr cex
|
||||
].freeze
|
||||
SUPPORTED_TRACES = VALID_TRACES - NOT_SUPPORTED_TRACES
|
||||
|
||||
def validate_trace(trace)
|
||||
list = VALID_TRACES
|
||||
h = {}
|
||||
return h if trace.empty? || trace == ['none']
|
||||
all_traces = SUPPORTED_TRACES - %w[only-explicit-rules]
|
||||
if trace == ['all']
|
||||
all_traces.each { |t| h[t.gsub(/-/, '_').to_sym] = true }
|
||||
return h
|
||||
end
|
||||
|
||||
trace.each do |t|
|
||||
if list.include?(t)
|
||||
h[t.to_sym] = true
|
||||
if SUPPORTED_TRACES.include?(t)
|
||||
h[t.gsub(/-/, '_').to_sym] = true
|
||||
else
|
||||
raise "Invalid trace option \"#{t}\"."
|
||||
end
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
# Command line options.
|
||||
class Options
|
||||
attr_accessor :skeleton, :header, :header_file,
|
||||
:report_file, :outfile,
|
||||
:error_recovery, :grammar_file,
|
||||
:trace_opts, :report_opts, :y,
|
||||
:debug
|
||||
:trace_opts, :report_opts,
|
||||
:diagnostic, :y, :debug, :define
|
||||
|
||||
def initialize
|
||||
@skeleton = "bison/yacc.c"
|
||||
@define = {}
|
||||
@header = false
|
||||
@header_file = nil
|
||||
@report_file = nil
|
||||
@@ -17,6 +20,7 @@ module Lrama
|
||||
@grammar_file = nil
|
||||
@trace_opts = nil
|
||||
@report_opts = nil
|
||||
@diagnostic = false
|
||||
@y = STDIN
|
||||
@debug = false
|
||||
end
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "erb"
|
||||
require "forwardable"
|
||||
require "lrama/report/duration"
|
||||
require_relative "report/duration"
|
||||
|
||||
module Lrama
|
||||
class Output
|
||||
@@ -63,37 +65,29 @@ module Lrama
|
||||
|
||||
# A part of b4_token_enums
|
||||
def token_enums
|
||||
str = ""
|
||||
|
||||
@context.yytokentype.each do |s_value, token_id, display_name|
|
||||
@context.yytokentype.map do |s_value, token_id, display_name|
|
||||
s = sprintf("%s = %d%s", s_value, token_id, token_id == yymaxutok ? "" : ",")
|
||||
|
||||
if display_name
|
||||
str << sprintf(" %-30s /* %s */\n", s, display_name)
|
||||
sprintf(" %-30s /* %s */\n", s, display_name)
|
||||
else
|
||||
str << sprintf(" %s\n", s)
|
||||
sprintf(" %s\n", s)
|
||||
end
|
||||
end
|
||||
|
||||
str
|
||||
end.join
|
||||
end
|
||||
|
||||
# b4_symbol_enum
|
||||
def symbol_enum
|
||||
str = ""
|
||||
|
||||
last_sym_number = @context.yysymbol_kind_t.last[1]
|
||||
@context.yysymbol_kind_t.each do |s_value, sym_number, display_name|
|
||||
@context.yysymbol_kind_t.map do |s_value, sym_number, display_name|
|
||||
s = sprintf("%s = %d%s", s_value, sym_number, (sym_number == last_sym_number) ? "" : ",")
|
||||
|
||||
if display_name
|
||||
str << sprintf(" %-40s /* %s */\n", s, display_name)
|
||||
sprintf(" %-40s /* %s */\n", s, display_name)
|
||||
else
|
||||
str << sprintf(" %s\n", s)
|
||||
sprintf(" %s\n", s)
|
||||
end
|
||||
end
|
||||
|
||||
str
|
||||
end.join
|
||||
end
|
||||
|
||||
def yytranslate
|
||||
@@ -132,12 +126,10 @@ module Lrama
|
||||
end
|
||||
|
||||
def symbol_actions_for_printer
|
||||
str = ""
|
||||
|
||||
@grammar.symbols.each do |sym|
|
||||
@grammar.symbols.map do |sym|
|
||||
next unless sym.printer
|
||||
|
||||
str << <<-STR
|
||||
<<-STR
|
||||
case #{sym.enum_name}: /* #{sym.comment} */
|
||||
#line #{sym.printer.lineno} "#{@grammar_file_path}"
|
||||
{#{sym.printer.translated_code(sym.tag)}}
|
||||
@@ -145,18 +137,14 @@ module Lrama
|
||||
break;
|
||||
|
||||
STR
|
||||
end
|
||||
|
||||
str
|
||||
end.join
|
||||
end
|
||||
|
||||
def symbol_actions_for_destructor
|
||||
str = ""
|
||||
|
||||
@grammar.symbols.each do |sym|
|
||||
@grammar.symbols.map do |sym|
|
||||
next unless sym.destructor
|
||||
|
||||
str << <<-STR
|
||||
<<-STR
|
||||
case #{sym.enum_name}: /* #{sym.comment} */
|
||||
#line #{sym.destructor.lineno} "#{@grammar_file_path}"
|
||||
{#{sym.destructor.translated_code(sym.tag)}}
|
||||
@@ -164,9 +152,7 @@ module Lrama
|
||||
break;
|
||||
|
||||
STR
|
||||
end
|
||||
|
||||
str
|
||||
end.join
|
||||
end
|
||||
|
||||
# b4_user_initial_action
|
||||
@@ -236,12 +222,10 @@ module Lrama
|
||||
end
|
||||
|
||||
def symbol_actions_for_error_token
|
||||
str = ""
|
||||
|
||||
@grammar.symbols.each do |sym|
|
||||
@grammar.symbols.map do |sym|
|
||||
next unless sym.error_token
|
||||
|
||||
str << <<-STR
|
||||
<<-STR
|
||||
case #{sym.enum_name}: /* #{sym.comment} */
|
||||
#line #{sym.error_token.lineno} "#{@grammar_file_path}"
|
||||
{#{sym.error_token.translated_code(sym.tag)}}
|
||||
@@ -249,22 +233,18 @@ module Lrama
|
||||
break;
|
||||
|
||||
STR
|
||||
end
|
||||
|
||||
str
|
||||
end.join
|
||||
end
|
||||
|
||||
# b4_user_actions
|
||||
def user_actions
|
||||
str = ""
|
||||
|
||||
@context.states.rules.each do |rule|
|
||||
action = @context.states.rules.map do |rule|
|
||||
next unless rule.token_code
|
||||
|
||||
code = rule.token_code
|
||||
spaces = " " * (code.column - 1)
|
||||
|
||||
str << <<-STR
|
||||
<<-STR
|
||||
case #{rule.id + 1}: /* #{rule.as_comment} */
|
||||
#line #{code.line} "#{@grammar_file_path}"
|
||||
#{spaces}{#{rule.translated_code}}
|
||||
@@ -272,14 +252,12 @@ module Lrama
|
||||
break;
|
||||
|
||||
STR
|
||||
end
|
||||
end.join
|
||||
|
||||
str << <<-STR
|
||||
action + <<-STR
|
||||
|
||||
#line [@oline@] [@ofile@]
|
||||
STR
|
||||
|
||||
str
|
||||
end
|
||||
|
||||
def omit_blanks(param)
|
||||
@@ -343,7 +321,7 @@ module Lrama
|
||||
|
||||
# b4_parse_param_use
|
||||
def parse_param_use(val, loc)
|
||||
str = <<-STR
|
||||
str = <<-STR.dup
|
||||
YY_USE (#{val});
|
||||
YY_USE (#{loc});
|
||||
STR
|
||||
@@ -357,7 +335,8 @@ module Lrama
|
||||
|
||||
# b4_yylex_formals
|
||||
def yylex_formals
|
||||
ary = ["&yylval", "&yylloc"]
|
||||
ary = ["&yylval"]
|
||||
ary << "&yylloc" if @grammar.locations
|
||||
|
||||
if @grammar.lex_param
|
||||
ary << lex_param_name
|
||||
@@ -397,17 +376,9 @@ module Lrama
|
||||
def int_array_to_string(ary)
|
||||
last = ary.count - 1
|
||||
|
||||
s = ary.each_with_index.each_slice(10).map do |slice|
|
||||
str = " "
|
||||
|
||||
slice.each do |e, i|
|
||||
str << sprintf("%6d%s", e, (i == last) ? "" : ",")
|
||||
end
|
||||
|
||||
str
|
||||
end
|
||||
|
||||
s.join("\n")
|
||||
ary.each_with_index.each_slice(10).map do |slice|
|
||||
" " + slice.map { |e, i| sprintf("%6d%s", e, (i == last) ? "" : ",") }.join
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
def spec_mapped_header_file
|
||||
@@ -457,26 +428,24 @@ module Lrama
|
||||
end
|
||||
|
||||
def template_dir
|
||||
File.expand_path("../../../template", __FILE__)
|
||||
File.expand_path('../../template', __dir__)
|
||||
end
|
||||
|
||||
def string_array_to_string(ary)
|
||||
str = ""
|
||||
result = ""
|
||||
tmp = " "
|
||||
|
||||
ary.each do |s|
|
||||
s = s.gsub('\\', '\\\\\\\\')
|
||||
s = s.gsub('"', '\\"')
|
||||
|
||||
if (tmp + s + " \"\",").length > 75
|
||||
str << tmp << "\n"
|
||||
tmp = " \"#{s}\","
|
||||
replaced = s.gsub('\\', '\\\\\\\\').gsub('"', '\\"')
|
||||
if (tmp + replaced + " \"\",").length > 75
|
||||
result = "#{result}#{tmp}\n"
|
||||
tmp = " \"#{replaced}\","
|
||||
else
|
||||
tmp << " \"#{s}\","
|
||||
tmp = "#{tmp} \"#{replaced}\","
|
||||
end
|
||||
end
|
||||
|
||||
str << tmp
|
||||
result + tmp
|
||||
end
|
||||
|
||||
def replace_special_variables(str, ofile)
|
||||
|
||||
+750
-840
File diff suppressed because it is too large
Load Diff
@@ -1,2 +1,4 @@
|
||||
require 'lrama/report/duration'
|
||||
require 'lrama/report/profile'
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'report/duration'
|
||||
require_relative 'report/profile'
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Report
|
||||
module Duration
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class Report
|
||||
module Profile
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
require "lrama/state/reduce"
|
||||
require "lrama/state/reduce_reduce_conflict"
|
||||
require "lrama/state/resolved_conflict"
|
||||
require "lrama/state/shift"
|
||||
require "lrama/state/shift_reduce_conflict"
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "state/reduce"
|
||||
require_relative "state/reduce_reduce_conflict"
|
||||
require_relative "state/resolved_conflict"
|
||||
require_relative "state/shift"
|
||||
require_relative "state/shift_reduce_conflict"
|
||||
|
||||
module Lrama
|
||||
class State
|
||||
attr_reader :id, :accessing_symbol, :kernels, :conflicts, :resolved_conflicts,
|
||||
:default_reduction_rule, :closure, :items
|
||||
attr_accessor :shifts, :reduces
|
||||
attr_accessor :shifts, :reduces, :ielr_isocores, :lalr_isocore
|
||||
|
||||
def initialize(id, accessing_symbol, kernels)
|
||||
@id = id
|
||||
@@ -21,6 +23,12 @@ module Lrama
|
||||
@conflicts = []
|
||||
@resolved_conflicts = []
|
||||
@default_reduction_rule = nil
|
||||
@predecessors = []
|
||||
@lalr_isocore = self
|
||||
@ielr_isocores = [self]
|
||||
@internal_dependencies = {}
|
||||
@successor_dependencies = {}
|
||||
@always_follows = {}
|
||||
end
|
||||
|
||||
def closure=(closure)
|
||||
@@ -82,6 +90,18 @@ module Lrama
|
||||
@transitions ||= shifts.map {|shift| [shift, @items_to_state[shift.next_items]] }
|
||||
end
|
||||
|
||||
def update_transition(shift, next_state)
|
||||
set_items_to_state(shift.next_items, next_state)
|
||||
next_state.append_predecessor(self)
|
||||
clear_transitions_cache
|
||||
end
|
||||
|
||||
def clear_transitions_cache
|
||||
@nterm_transitions = nil
|
||||
@term_transitions = nil
|
||||
@transitions = nil
|
||||
end
|
||||
|
||||
def selected_term_transitions
|
||||
term_transitions.reject do |shift, next_state|
|
||||
shift.not_selected
|
||||
@@ -140,5 +160,274 @@ module Lrama
|
||||
conflict.type == :reduce_reduce
|
||||
end
|
||||
end
|
||||
|
||||
def propagate_lookaheads(next_state)
|
||||
next_state.kernels.map {|item|
|
||||
lookahead_sets =
|
||||
if item.position == 1
|
||||
goto_follow_set(item.lhs)
|
||||
else
|
||||
kernel = kernels.find {|k| k.predecessor_item_of?(item) }
|
||||
item_lookahead_set[kernel]
|
||||
end
|
||||
|
||||
[item, lookahead_sets & next_state.lookahead_set_filters[item]]
|
||||
}.to_h
|
||||
end
|
||||
|
||||
def lookaheads_recomputed
|
||||
!@item_lookahead_set.nil?
|
||||
end
|
||||
|
||||
def compatible_lookahead?(filtered_lookahead)
|
||||
!lookaheads_recomputed ||
|
||||
@lalr_isocore.annotation_list.all? {|token, actions|
|
||||
a = dominant_contribution(token, actions, item_lookahead_set)
|
||||
b = dominant_contribution(token, actions, filtered_lookahead)
|
||||
a.nil? || b.nil? || a == b
|
||||
}
|
||||
end
|
||||
|
||||
def lookahead_set_filters
|
||||
kernels.map {|kernel|
|
||||
[kernel,
|
||||
@lalr_isocore.annotation_list.select {|token, actions|
|
||||
token.term? && actions.any? {|action, contributions|
|
||||
!contributions.nil? && contributions.key?(kernel) && contributions[kernel]
|
||||
}
|
||||
}.map {|token, _| token }
|
||||
]
|
||||
}.to_h
|
||||
end
|
||||
|
||||
def dominant_contribution(token, actions, lookaheads)
|
||||
a = actions.select {|action, contributions|
|
||||
contributions.nil? || contributions.any? {|item, contributed| contributed && lookaheads[item].include?(token) }
|
||||
}.map {|action, _| action }
|
||||
return nil if a.empty?
|
||||
a.reject {|action|
|
||||
if action.is_a?(State::Shift)
|
||||
action.not_selected
|
||||
elsif action.is_a?(State::Reduce)
|
||||
action.not_selected_symbols.include?(token)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def inadequacy_list
|
||||
return @inadequacy_list if @inadequacy_list
|
||||
|
||||
shift_contributions = shifts.map {|shift|
|
||||
[shift.next_sym, [shift]]
|
||||
}.to_h
|
||||
reduce_contributions = reduces.map {|reduce|
|
||||
(reduce.look_ahead || []).map {|sym|
|
||||
[sym, [reduce]]
|
||||
}.to_h
|
||||
}.reduce(Hash.new([])) {|hash, cont|
|
||||
hash.merge(cont) {|_, a, b| a | b }
|
||||
}
|
||||
|
||||
list = shift_contributions.merge(reduce_contributions) {|_, a, b| a | b }
|
||||
@inadequacy_list = list.select {|token, actions| token.term? && actions.size > 1 }
|
||||
end
|
||||
|
||||
def annotation_list
|
||||
return @annotation_list if @annotation_list
|
||||
|
||||
@annotation_list = annotate_manifestation
|
||||
@annotation_list = @items_to_state.values.map {|next_state| next_state.annotate_predecessor(self) }
|
||||
.reduce(@annotation_list) {|result, annotations|
|
||||
result.merge(annotations) {|_, actions_a, actions_b|
|
||||
if actions_a.nil? || actions_b.nil?
|
||||
actions_a || actions_b
|
||||
else
|
||||
actions_a.merge(actions_b) {|_, contributions_a, contributions_b|
|
||||
if contributions_a.nil? || contributions_b.nil?
|
||||
next contributions_a || contributions_b
|
||||
end
|
||||
|
||||
contributions_a.merge(contributions_b) {|_, contributed_a, contributed_b|
|
||||
contributed_a || contributed_b
|
||||
}
|
||||
}
|
||||
end
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def annotate_manifestation
|
||||
inadequacy_list.transform_values {|actions|
|
||||
actions.map {|action|
|
||||
if action.is_a?(Shift)
|
||||
[action, nil]
|
||||
elsif action.is_a?(Reduce)
|
||||
if action.rule.empty_rule?
|
||||
[action, lhs_contributions(action.rule.lhs, inadequacy_list.key(actions))]
|
||||
else
|
||||
contributions = kernels.map {|kernel| [kernel, kernel.rule == action.rule && kernel.end_of_rule?] }.to_h
|
||||
[action, contributions]
|
||||
end
|
||||
end
|
||||
}.to_h
|
||||
}
|
||||
end
|
||||
|
||||
def annotate_predecessor(predecessor)
|
||||
annotation_list.transform_values {|actions|
|
||||
token = annotation_list.key(actions)
|
||||
actions.transform_values {|inadequacy|
|
||||
next nil if inadequacy.nil?
|
||||
lhs_adequacy = kernels.any? {|kernel|
|
||||
inadequacy[kernel] && kernel.position == 1 && predecessor.lhs_contributions(kernel.lhs, token).nil?
|
||||
}
|
||||
if lhs_adequacy
|
||||
next nil
|
||||
else
|
||||
predecessor.kernels.map {|pred_k|
|
||||
[pred_k, kernels.any? {|k|
|
||||
inadequacy[k] && (
|
||||
pred_k.predecessor_item_of?(k) && predecessor.item_lookahead_set[pred_k].include?(token) ||
|
||||
k.position == 1 && predecessor.lhs_contributions(k.lhs, token)[pred_k]
|
||||
)
|
||||
}]
|
||||
}.to_h
|
||||
end
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def lhs_contributions(sym, token)
|
||||
shift, next_state = nterm_transitions.find {|sh, _| sh.next_sym == sym }
|
||||
if always_follows(shift, next_state).include?(token)
|
||||
nil
|
||||
else
|
||||
kernels.map {|kernel| [kernel, follow_kernel_items(shift, next_state, kernel) && item_lookahead_set[kernel].include?(token)] }.to_h
|
||||
end
|
||||
end
|
||||
|
||||
def follow_kernel_items(shift, next_state, kernel)
|
||||
queue = [[self, shift, next_state]]
|
||||
until queue.empty?
|
||||
st, sh, next_st = queue.pop
|
||||
return true if kernel.next_sym == sh.next_sym && kernel.symbols_after_transition.all?(&:nullable)
|
||||
st.internal_dependencies(sh, next_st).each {|v| queue << v }
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
def item_lookahead_set
|
||||
return @item_lookahead_set if @item_lookahead_set
|
||||
|
||||
kernels.map {|item|
|
||||
value =
|
||||
if item.lhs.accept_symbol?
|
||||
[]
|
||||
elsif item.position > 1
|
||||
prev_items = predecessors_with_item(item)
|
||||
prev_items.map {|st, i| st.item_lookahead_set[i] }.reduce([]) {|acc, syms| acc |= syms }
|
||||
elsif item.position == 1
|
||||
prev_state = @predecessors.find {|p| p.shifts.any? {|shift| shift.next_sym == item.lhs } }
|
||||
shift, next_state = prev_state.nterm_transitions.find {|shift, _| shift.next_sym == item.lhs }
|
||||
prev_state.goto_follows(shift, next_state)
|
||||
end
|
||||
[item, value]
|
||||
}.to_h
|
||||
end
|
||||
|
||||
def item_lookahead_set=(k)
|
||||
@item_lookahead_set = k
|
||||
end
|
||||
|
||||
def predecessors_with_item(item)
|
||||
result = []
|
||||
@predecessors.each do |pre|
|
||||
pre.items.each do |i|
|
||||
result << [pre, i] if i.predecessor_item_of?(item)
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def append_predecessor(prev_state)
|
||||
@predecessors << prev_state
|
||||
@predecessors.uniq!
|
||||
end
|
||||
|
||||
def goto_follow_set(nterm_token)
|
||||
return [] if nterm_token.accept_symbol?
|
||||
shift, next_state = @lalr_isocore.nterm_transitions.find {|sh, _| sh.next_sym == nterm_token }
|
||||
|
||||
@kernels
|
||||
.select {|kernel| follow_kernel_items(shift, next_state, kernel) }
|
||||
.map {|kernel| item_lookahead_set[kernel] }
|
||||
.reduce(always_follows(shift, next_state)) {|result, terms| result |= terms }
|
||||
end
|
||||
|
||||
def goto_follows(shift, next_state)
|
||||
queue = internal_dependencies(shift, next_state) + predecessor_dependencies(shift, next_state)
|
||||
terms = always_follows(shift, next_state)
|
||||
until queue.empty?
|
||||
st, sh, next_st = queue.pop
|
||||
terms |= st.always_follows(sh, next_st)
|
||||
st.internal_dependencies(sh, next_st).each {|v| queue << v }
|
||||
st.predecessor_dependencies(sh, next_st).each {|v| queue << v }
|
||||
end
|
||||
terms
|
||||
end
|
||||
|
||||
def always_follows(shift, next_state)
|
||||
return @always_follows[[shift, next_state]] if @always_follows[[shift, next_state]]
|
||||
|
||||
queue = internal_dependencies(shift, next_state) + successor_dependencies(shift, next_state)
|
||||
terms = []
|
||||
until queue.empty?
|
||||
st, sh, next_st = queue.pop
|
||||
terms |= next_st.term_transitions.map {|sh, _| sh.next_sym }
|
||||
st.internal_dependencies(sh, next_st).each {|v| queue << v }
|
||||
st.successor_dependencies(sh, next_st).each {|v| queue << v }
|
||||
end
|
||||
@always_follows[[shift, next_state]] = terms
|
||||
end
|
||||
|
||||
def internal_dependencies(shift, next_state)
|
||||
return @internal_dependencies[[shift, next_state]] if @internal_dependencies[[shift, next_state]]
|
||||
|
||||
syms = @items.select {|i|
|
||||
i.next_sym == shift.next_sym && i.symbols_after_transition.all?(&:nullable) && i.position == 0
|
||||
}.map(&:lhs).uniq
|
||||
@internal_dependencies[[shift, next_state]] = nterm_transitions.select {|sh, _| syms.include?(sh.next_sym) }.map {|goto| [self, *goto] }
|
||||
end
|
||||
|
||||
def successor_dependencies(shift, next_state)
|
||||
return @successor_dependencies[[shift, next_state]] if @successor_dependencies[[shift, next_state]]
|
||||
|
||||
@successor_dependencies[[shift, next_state]] =
|
||||
next_state.nterm_transitions
|
||||
.select {|next_shift, _| next_shift.next_sym.nullable }
|
||||
.map {|transition| [next_state, *transition] }
|
||||
end
|
||||
|
||||
def predecessor_dependencies(shift, next_state)
|
||||
state_items = []
|
||||
@kernels.select {|kernel|
|
||||
kernel.next_sym == shift.next_sym && kernel.symbols_after_transition.all?(&:nullable)
|
||||
}.each do |item|
|
||||
queue = predecessors_with_item(item)
|
||||
until queue.empty?
|
||||
st, i = queue.pop
|
||||
if i.position == 0
|
||||
state_items << [st, i]
|
||||
else
|
||||
st.predecessors_with_item(i).each {|v| queue << v }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
state_items.map {|state, item|
|
||||
sh, next_st = state.nterm_transitions.find {|shi, _| shi.next_sym == item.lhs }
|
||||
[state, sh, next_st]
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class State
|
||||
class Reduce
|
||||
@@ -24,8 +26,8 @@ module Lrama
|
||||
end
|
||||
|
||||
def selected_look_ahead
|
||||
if @look_ahead
|
||||
@look_ahead - @not_selected_symbols
|
||||
if look_ahead
|
||||
look_ahead - @not_selected_symbols
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class State
|
||||
class ReduceReduceConflict < Struct.new(:symbols, :reduce1, :reduce2, keyword_init: true)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class State
|
||||
# * symbol: A symbol under discussion
|
||||
@@ -6,7 +8,7 @@ module Lrama
|
||||
class ResolvedConflict < Struct.new(:symbol, :reduce, :which, :same_prec, keyword_init: true)
|
||||
def report_message
|
||||
s = symbol.display_name
|
||||
r = reduce.rule.precedence_sym.display_name
|
||||
r = reduce.rule.precedence_sym&.display_name
|
||||
case
|
||||
when which == :shift && same_prec
|
||||
msg = "resolved as #{which} (%right #{s})"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class State
|
||||
class Shift
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class State
|
||||
class ShiftReduceConflict < Struct.new(:symbols, :shift, :reduce, keyword_init: true)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "forwardable"
|
||||
require "lrama/report/duration"
|
||||
require "lrama/states/item"
|
||||
require_relative "report/duration"
|
||||
require_relative "states/item"
|
||||
|
||||
module Lrama
|
||||
# States is passed to a template file
|
||||
@@ -16,9 +18,8 @@ module Lrama
|
||||
|
||||
attr_reader :states, :reads_relation, :includes_relation, :lookback_relation
|
||||
|
||||
def initialize(grammar, warning, trace_state: false)
|
||||
def initialize(grammar, trace_state: false)
|
||||
@grammar = grammar
|
||||
@warning = warning
|
||||
@trace_state = trace_state
|
||||
|
||||
@states = []
|
||||
@@ -40,6 +41,8 @@ module Lrama
|
||||
# value is array of [state.id, nterm.token_id].
|
||||
@reads_relation = {}
|
||||
|
||||
# `Read(p, A) =s DR(p, A) ∪ ∪{Read(r, C) | (p, A) reads (r, C)}`
|
||||
#
|
||||
# `@read_sets` is a hash whose
|
||||
# key is [state.id, nterm.token_id],
|
||||
# value is bitmap of term.
|
||||
@@ -61,6 +64,8 @@ module Lrama
|
||||
# value is array of [state.id, nterm.token_id].
|
||||
@lookback_relation = {}
|
||||
|
||||
# `Follow(p, A) =s Read(p, A) ∪ ∪{Follow(p', B) | (p, A) includes (p', B)}`
|
||||
#
|
||||
# `@follow_sets` is a hash whose
|
||||
# key is [state.id, rule.id],
|
||||
# value is bitmap of term.
|
||||
@@ -89,8 +94,20 @@ module Lrama
|
||||
report_duration(:compute_conflicts) { compute_conflicts }
|
||||
|
||||
report_duration(:compute_default_reduction) { compute_default_reduction }
|
||||
end
|
||||
|
||||
check_conflicts
|
||||
def compute_ielr
|
||||
report_duration(:split_states) { split_states }
|
||||
report_duration(:compute_direct_read_sets) { compute_direct_read_sets }
|
||||
report_duration(:compute_reads_relation) { compute_reads_relation }
|
||||
report_duration(:compute_read_sets) { compute_read_sets }
|
||||
report_duration(:compute_includes_relation) { compute_includes_relation }
|
||||
report_duration(:compute_lookback_relation) { compute_lookback_relation }
|
||||
report_duration(:compute_follow_sets) { compute_follow_sets }
|
||||
report_duration(:compute_look_ahead_sets) { compute_look_ahead_sets }
|
||||
report_duration(:compute_conflicts) { compute_conflicts }
|
||||
|
||||
report_duration(:compute_default_reduction) { compute_default_reduction }
|
||||
end
|
||||
|
||||
def reporter
|
||||
@@ -125,16 +142,16 @@ module Lrama
|
||||
end
|
||||
end
|
||||
|
||||
def sr_conflicts_count
|
||||
@sr_conflicts_count ||= @states.flat_map(&:sr_conflicts).count
|
||||
end
|
||||
|
||||
def rr_conflicts_count
|
||||
@rr_conflicts_count ||= @states.flat_map(&:rr_conflicts).count
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sr_conflicts
|
||||
@states.flat_map(&:sr_conflicts)
|
||||
end
|
||||
|
||||
def rr_conflicts
|
||||
@states.flat_map(&:rr_conflicts)
|
||||
end
|
||||
|
||||
def trace_state
|
||||
if @trace_state
|
||||
yield STDERR
|
||||
@@ -236,7 +253,7 @@ module Lrama
|
||||
# Trace
|
||||
previous = state.kernels.first.previous_sym
|
||||
trace_state do |out|
|
||||
out << sprintf("state_list_append (state = %d, symbol = %d (%s))",
|
||||
out << sprintf("state_list_append (state = %d, symbol = %d (%s))\n",
|
||||
@states.count, previous.number, previous.display_name)
|
||||
end
|
||||
|
||||
@@ -266,7 +283,10 @@ module Lrama
|
||||
state.shifts.each do |shift|
|
||||
new_state, created = create_state(shift.next_sym, shift.next_items, states_created)
|
||||
state.set_items_to_state(shift.next_items, new_state)
|
||||
enqueue_state(states, new_state) if created
|
||||
if created
|
||||
enqueue_state(states, new_state)
|
||||
new_state.append_predecessor(state)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -350,7 +370,7 @@ module Lrama
|
||||
# TODO: need to omit if state == state2 ?
|
||||
@includes_relation[key] ||= []
|
||||
@includes_relation[key] << [state.id, nterm.token_id]
|
||||
break if !sym.nullable
|
||||
break unless sym.nullable
|
||||
i -= 1
|
||||
end
|
||||
end
|
||||
@@ -385,7 +405,7 @@ module Lrama
|
||||
@states.each do |state|
|
||||
rules.each do |rule|
|
||||
ary = @lookback_relation[[state.id, rule.id]]
|
||||
next if !ary
|
||||
next unless ary
|
||||
|
||||
ary.each do |state2_id, nterm_token_id|
|
||||
# q = state, A -> ω = rule, p = state2, A = nterm
|
||||
@@ -428,7 +448,7 @@ module Lrama
|
||||
sym = shift.next_sym
|
||||
|
||||
next unless reduce.look_ahead
|
||||
next if !reduce.look_ahead.include?(sym)
|
||||
next unless reduce.look_ahead.include?(sym)
|
||||
|
||||
# Shift/Reduce conflict
|
||||
shift_prec = sym.precedence
|
||||
@@ -492,17 +512,17 @@ module Lrama
|
||||
states.each do |state|
|
||||
count = state.reduces.count
|
||||
|
||||
for i in 0...count do
|
||||
(0...count).each do |i|
|
||||
reduce1 = state.reduces[i]
|
||||
next if reduce1.look_ahead.nil?
|
||||
|
||||
for j in (i+1)...count do
|
||||
((i+1)...count).each do |j|
|
||||
reduce2 = state.reduces[j]
|
||||
next if reduce2.look_ahead.nil?
|
||||
|
||||
intersection = reduce1.look_ahead & reduce2.look_ahead
|
||||
|
||||
if !intersection.empty?
|
||||
unless intersection.empty?
|
||||
state.conflicts << State::ReduceReduceConflict.new(symbols: intersection, reduce1: reduce1, reduce2: reduce2)
|
||||
end
|
||||
end
|
||||
@@ -514,7 +534,7 @@ module Lrama
|
||||
states.each do |state|
|
||||
next if state.reduces.empty?
|
||||
# Do not set, if conflict exist
|
||||
next if !state.conflicts.empty?
|
||||
next unless state.conflicts.empty?
|
||||
# Do not set, if shift with `error` exists.
|
||||
next if state.shifts.map(&:next_sym).include?(@grammar.error_symbol)
|
||||
|
||||
@@ -526,30 +546,49 @@ module Lrama
|
||||
end
|
||||
end
|
||||
|
||||
def check_conflicts
|
||||
sr_count = sr_conflicts.count
|
||||
rr_count = rr_conflicts.count
|
||||
|
||||
if @grammar.expect
|
||||
|
||||
expected_sr_conflicts = @grammar.expect
|
||||
expected_rr_conflicts = 0
|
||||
|
||||
if expected_sr_conflicts != sr_count
|
||||
@warning.error("shift/reduce conflicts: #{sr_count} found, #{expected_sr_conflicts} expected")
|
||||
def split_states
|
||||
@states.each do |state|
|
||||
state.transitions.each do |shift, next_state|
|
||||
compute_state(state, shift, next_state)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if expected_rr_conflicts != rr_count
|
||||
@warning.error("reduce/reduce conflicts: #{rr_count} found, #{expected_rr_conflicts} expected")
|
||||
def merge_lookaheads(state, filtered_lookaheads)
|
||||
return if state.kernels.all? {|item| (filtered_lookaheads[item] - state.item_lookahead_set[item]).empty? }
|
||||
|
||||
state.item_lookahead_set = state.item_lookahead_set.merge {|_, v1, v2| v1 | v2 }
|
||||
state.transitions.each do |shift, next_state|
|
||||
next if next_state.lookaheads_recomputed
|
||||
compute_state(state, shift, next_state)
|
||||
end
|
||||
end
|
||||
|
||||
def compute_state(state, shift, next_state)
|
||||
filtered_lookaheads = state.propagate_lookaheads(next_state)
|
||||
s = next_state.ielr_isocores.find {|st| st.compatible_lookahead?(filtered_lookaheads) }
|
||||
|
||||
if s.nil?
|
||||
s = next_state.ielr_isocores.last
|
||||
new_state = State.new(@states.count, s.accessing_symbol, s.kernels)
|
||||
new_state.closure = s.closure
|
||||
new_state.compute_shifts_reduces
|
||||
s.transitions.each do |sh, next_state|
|
||||
new_state.set_items_to_state(sh.next_items, next_state)
|
||||
end
|
||||
@states << new_state
|
||||
new_state.lalr_isocore = s
|
||||
s.ielr_isocores << new_state
|
||||
s.ielr_isocores.each do |st|
|
||||
st.ielr_isocores = s.ielr_isocores
|
||||
end
|
||||
new_state.item_lookahead_set = filtered_lookaheads
|
||||
state.update_transition(shift, new_state)
|
||||
elsif(!s.lookaheads_recomputed)
|
||||
s.item_lookahead_set = filtered_lookaheads
|
||||
else
|
||||
if sr_count != 0
|
||||
@warning.warn("shift/reduce conflicts: #{sr_count} found")
|
||||
end
|
||||
|
||||
if rr_count != 0
|
||||
@warning.warn("reduce/reduce conflicts: #{rr_count} found")
|
||||
end
|
||||
state.update_transition(shift, s)
|
||||
merge_lookaheads(s, filtered_lookaheads)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# TODO: Validate position is not over rule rhs
|
||||
|
||||
require "forwardable"
|
||||
@@ -54,14 +56,18 @@ module Lrama
|
||||
Item.new(rule: rule, position: position + 1)
|
||||
end
|
||||
|
||||
def symbols_before_dot
|
||||
def symbols_before_dot # steep:ignore
|
||||
rhs[0...position]
|
||||
end
|
||||
|
||||
def symbols_after_dot
|
||||
def symbols_after_dot # steep:ignore
|
||||
rhs[position..-1]
|
||||
end
|
||||
|
||||
def symbols_after_transition
|
||||
rhs[position+1..-1]
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{lhs.id.s_value}: #{display_name}"
|
||||
end
|
||||
@@ -73,9 +79,13 @@ module Lrama
|
||||
|
||||
# Right after position
|
||||
def display_rest
|
||||
r = rhs[position..-1].map(&:display_name).join(" ")
|
||||
r = symbols_after_dot.map(&:display_name).join(" ")
|
||||
". #{r} (rule #{rule_id})"
|
||||
end
|
||||
|
||||
def predecessor_item_of?(other_item)
|
||||
rule == other_item.rule && position == other_item.position - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class StatesReporter
|
||||
include Lrama::Report::Duration
|
||||
@@ -14,15 +16,54 @@ module Lrama
|
||||
|
||||
private
|
||||
|
||||
def _report(io, grammar: false, states: false, itemsets: false, lookaheads: false, solved: false, counterexamples: false, verbose: false)
|
||||
# TODO: Unused terms
|
||||
# TODO: Unused rules
|
||||
|
||||
def _report(io, grammar: false, rules: false, terms: false, states: false, itemsets: false, lookaheads: false, solved: false, counterexamples: false, verbose: false)
|
||||
report_unused_rules(io) if rules
|
||||
report_unused_terms(io) if terms
|
||||
report_conflicts(io)
|
||||
report_grammar(io) if grammar
|
||||
report_states(io, itemsets, lookaheads, solved, counterexamples, verbose)
|
||||
end
|
||||
|
||||
def report_unused_terms(io)
|
||||
look_aheads = @states.states.each do |state|
|
||||
state.reduces.flat_map do |reduce|
|
||||
reduce.look_ahead unless reduce.look_ahead.nil?
|
||||
end
|
||||
end
|
||||
|
||||
next_terms = @states.states.flat_map do |state|
|
||||
state.shifts.map(&:next_sym).select(&:term?)
|
||||
end
|
||||
|
||||
unused_symbols = @states.terms.select do |term|
|
||||
!(look_aheads + next_terms).include?(term)
|
||||
end
|
||||
|
||||
unless unused_symbols.empty?
|
||||
io << "#{unused_symbols.count} Unused Terms\n\n"
|
||||
unused_symbols.each_with_index do |term, index|
|
||||
io << sprintf("%5d %s\n", index, term.id.s_value)
|
||||
end
|
||||
io << "\n\n"
|
||||
end
|
||||
end
|
||||
|
||||
def report_unused_rules(io)
|
||||
used_rules = @states.rules.flat_map(&:rhs)
|
||||
|
||||
unused_rules = @states.rules.map(&:lhs).select do |rule|
|
||||
!used_rules.include?(rule) && rule.token_id != 0
|
||||
end
|
||||
|
||||
unless unused_rules.empty?
|
||||
io << "#{unused_rules.count} Unused Rules\n\n"
|
||||
unused_rules.each_with_index do |rule, index|
|
||||
io << sprintf("%5d %s\n", index, rule.display_name)
|
||||
end
|
||||
io << "\n\n"
|
||||
end
|
||||
end
|
||||
|
||||
def report_conflicts(io)
|
||||
has_conflict = false
|
||||
|
||||
@@ -37,7 +78,7 @@ module Lrama
|
||||
messages << "#{cs[:reduce_reduce].count} reduce/reduce"
|
||||
end
|
||||
|
||||
if !messages.empty?
|
||||
unless messages.empty?
|
||||
has_conflict = true
|
||||
io << "State #{state.id} conflicts: #{messages.join(', ')}\n"
|
||||
end
|
||||
@@ -98,7 +139,7 @@ module Lrama
|
||||
if lookaheads && item.end_of_rule?
|
||||
reduce = state.find_reduce_by_item!(item)
|
||||
look_ahead = reduce.selected_look_ahead
|
||||
if !look_ahead.empty?
|
||||
unless look_ahead.empty?
|
||||
la = " [#{look_ahead.map(&:display_name).join(", ")}]"
|
||||
end
|
||||
end
|
||||
@@ -118,7 +159,7 @@ module Lrama
|
||||
tmp.each do |term, state_id|
|
||||
io << " #{term.display_name.ljust(max_len)} shift, and go to state #{state_id}\n"
|
||||
end
|
||||
io << "\n" if !tmp.empty?
|
||||
io << "\n" unless tmp.empty?
|
||||
|
||||
# Report error caused by %nonassoc
|
||||
nl = false
|
||||
@@ -132,7 +173,7 @@ module Lrama
|
||||
nl = true
|
||||
io << " #{name.ljust(max_len)} error (nonassociative)\n"
|
||||
end
|
||||
io << "\n" if !tmp.empty?
|
||||
io << "\n" unless tmp.empty?
|
||||
|
||||
# Report reduces
|
||||
nl = false
|
||||
@@ -181,14 +222,14 @@ module Lrama
|
||||
tmp.each do |nterm, state_id|
|
||||
io << " #{nterm.id.s_value.ljust(max_len)} go to state #{state_id}\n"
|
||||
end
|
||||
io << "\n" if !tmp.empty?
|
||||
io << "\n" unless tmp.empty?
|
||||
|
||||
if solved
|
||||
# Report conflict resolutions
|
||||
state.resolved_conflicts.each do |resolved|
|
||||
io << " #{resolved.report_message}\n"
|
||||
end
|
||||
io << "\n" if !state.resolved_conflicts.empty?
|
||||
io << "\n" unless state.resolved_conflicts.empty?
|
||||
end
|
||||
|
||||
if counterexamples && state.has_conflicts?
|
||||
@@ -219,7 +260,7 @@ module Lrama
|
||||
direct_read_sets = @states.direct_read_sets
|
||||
@states.nterms.each do |nterm|
|
||||
terms = direct_read_sets[[state.id, nterm.token_id]]
|
||||
next if !terms
|
||||
next unless terms
|
||||
next if terms.empty?
|
||||
|
||||
str = terms.map {|sym| sym.id.s_value }.join(", ")
|
||||
@@ -231,7 +272,7 @@ module Lrama
|
||||
io << " [Reads Relation]\n"
|
||||
@states.nterms.each do |nterm|
|
||||
a = @states.reads_relation[[state.id, nterm.token_id]]
|
||||
next if !a
|
||||
next unless a
|
||||
|
||||
a.each do |state_id2, nterm_id2|
|
||||
n = @states.nterms.find {|n| n.token_id == nterm_id2 }
|
||||
@@ -245,7 +286,7 @@ module Lrama
|
||||
read_sets = @states.read_sets
|
||||
@states.nterms.each do |nterm|
|
||||
terms = read_sets[[state.id, nterm.token_id]]
|
||||
next if !terms
|
||||
next unless terms
|
||||
next if terms.empty?
|
||||
|
||||
terms.each do |sym|
|
||||
@@ -258,7 +299,7 @@ module Lrama
|
||||
io << " [Includes Relation]\n"
|
||||
@states.nterms.each do |nterm|
|
||||
a = @states.includes_relation[[state.id, nterm.token_id]]
|
||||
next if !a
|
||||
next unless a
|
||||
|
||||
a.each do |state_id2, nterm_id2|
|
||||
n = @states.nterms.find {|n| n.token_id == nterm_id2 }
|
||||
@@ -271,11 +312,11 @@ module Lrama
|
||||
io << " [Lookback Relation]\n"
|
||||
@states.rules.each do |rule|
|
||||
a = @states.lookback_relation[[state.id, rule.id]]
|
||||
next if !a
|
||||
next unless a
|
||||
|
||||
a.each do |state_id2, nterm_id2|
|
||||
n = @states.nterms.find {|n| n.token_id == nterm_id2 }
|
||||
io << " (Rule: #{rule}) -> (State #{state_id2}, #{n.id.s_value})\n"
|
||||
io << " (Rule: #{rule.display_name}) -> (State #{state_id2}, #{n.id.s_value})\n"
|
||||
end
|
||||
end
|
||||
io << "\n"
|
||||
@@ -286,7 +327,7 @@ module Lrama
|
||||
@states.nterms.each do |nterm|
|
||||
terms = follow_sets[[state.id, nterm.token_id]]
|
||||
|
||||
next if !terms
|
||||
next unless terms
|
||||
|
||||
terms.each do |sym|
|
||||
io << " #{nterm.id.s_value} -> #{sym.id.s_value}\n"
|
||||
@@ -300,7 +341,7 @@ module Lrama
|
||||
max_len = 0
|
||||
@states.rules.each do |rule|
|
||||
syms = @states.la[[state.id, rule.id]]
|
||||
next if !syms
|
||||
next unless syms
|
||||
|
||||
tmp << [rule, syms]
|
||||
max_len = ([max_len] + syms.map {|s| s.id.s_value.length }).max
|
||||
@@ -310,7 +351,7 @@ module Lrama
|
||||
io << " #{sym.id.s_value.ljust(max_len)} reduce using rule #{rule.id} (#{rule.lhs.id.s_value})\n"
|
||||
end
|
||||
end
|
||||
io << "\n" if !tmp.empty?
|
||||
io << "\n" unless tmp.empty?
|
||||
end
|
||||
|
||||
# End of Report State
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# rbs_inline: enabled
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
class TraceReporter
|
||||
# @rbs (Lrama::Grammar grammar) -> void
|
||||
def initialize(grammar)
|
||||
@grammar = grammar
|
||||
end
|
||||
|
||||
# @rbs (**Hash[Symbol, bool] options) -> void
|
||||
def report(**options)
|
||||
_report(**options)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @rbs rules: (bool rules, bool actions, bool only_explicit_rules, **untyped _) -> void
|
||||
def _report(rules: false, actions: false, only_explicit_rules: false, **_)
|
||||
report_rules if rules && !only_explicit_rules
|
||||
report_only_explicit_rules if only_explicit_rules
|
||||
report_actions if actions
|
||||
end
|
||||
|
||||
# @rbs () -> void
|
||||
def report_rules
|
||||
puts "Grammar rules:"
|
||||
@grammar.rules.each { |rule| puts rule.display_name }
|
||||
end
|
||||
|
||||
# @rbs () -> void
|
||||
def report_only_explicit_rules
|
||||
puts "Grammar rules:"
|
||||
@grammar.rules.each do |rule|
|
||||
puts rule.display_name_without_action if rule.lhs.first_set.any?
|
||||
end
|
||||
end
|
||||
|
||||
# @rbs () -> void
|
||||
def report_actions
|
||||
puts "Grammar rules with actions:"
|
||||
@grammar.rules.each { |rule| puts rule.with_actions }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Lrama
|
||||
VERSION = "0.6.9".freeze
|
||||
VERSION = "0.7.0".freeze
|
||||
end
|
||||
|
||||
@@ -1166,9 +1166,9 @@ yydestruct (const char *yymsg,
|
||||
#endif
|
||||
|
||||
enum yy_repair_type {
|
||||
insert,
|
||||
delete,
|
||||
shift,
|
||||
inserting,
|
||||
deleting,
|
||||
shifting,
|
||||
};
|
||||
|
||||
struct yy_repair {
|
||||
@@ -1401,27 +1401,27 @@ yyrecover(yy_state_t *yyss, yy_state_t *yyssp, int yychar<%= output.user_formals
|
||||
if (current->repair_length + 1 > YYMAXREPAIR(<%= output.parse_param_name %>))
|
||||
continue;
|
||||
|
||||
yy_repairs *new = (yy_repairs *) YYMALLOC (sizeof (yy_repairs));
|
||||
new->id = count;
|
||||
new->next = 0;
|
||||
new->stack_length = stack_length;
|
||||
new->states = (yy_state_t *) YYMALLOC (sizeof (yy_state_t) * (stack_length));
|
||||
new->state = new->states + (current->state - current->states);
|
||||
YYCOPY (new->states, current->states, current->state - current->states + 1);
|
||||
new->repair_length = current->repair_length + 1;
|
||||
new->prev_repair = current;
|
||||
new->repair.type = insert;
|
||||
new->repair.term = (yysymbol_kind_t) yyx;
|
||||
yy_repairs *reps = (yy_repairs *) YYMALLOC (sizeof (yy_repairs));
|
||||
reps->id = count;
|
||||
reps->next = 0;
|
||||
reps->stack_length = stack_length;
|
||||
reps->states = (yy_state_t *) YYMALLOC (sizeof (yy_state_t) * (stack_length));
|
||||
reps->state = reps->states + (current->state - current->states);
|
||||
YYCOPY (reps->states, current->states, current->state - current->states + 1);
|
||||
reps->repair_length = current->repair_length + 1;
|
||||
reps->prev_repair = current;
|
||||
reps->repair.type = inserting;
|
||||
reps->repair.term = (yysymbol_kind_t) yyx;
|
||||
|
||||
/* Process PDA assuming next token is yyx */
|
||||
if (! yy_process_repairs (new, yyx))
|
||||
if (! yy_process_repairs (reps, (yysymbol_kind_t)yyx))
|
||||
{
|
||||
YYFREE (new);
|
||||
YYFREE (reps);
|
||||
continue;
|
||||
}
|
||||
|
||||
tail->next = new;
|
||||
tail = new;
|
||||
tail->next = reps;
|
||||
tail = reps;
|
||||
count++;
|
||||
|
||||
if (yyx == yytoken)
|
||||
@@ -1437,7 +1437,7 @@ yyrecover(yy_state_t *yyss, yy_state_t *yyssp, int yychar<%= output.user_formals
|
||||
YYDPRINTF ((stderr,
|
||||
"New repairs is enqueued. count: %d, yystate: %d, yyx: %d\n",
|
||||
count, yystate, yyx));
|
||||
yy_print_repairs (new<%= output.user_args %>);
|
||||
yy_print_repairs (reps<%= output.user_args %>);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1475,7 +1475,12 @@ int yychar;
|
||||
/* The semantic value of the lookahead symbol. */
|
||||
/* Default value used for initialization, for pacifying older GCCs
|
||||
or non-GCC compilers. */
|
||||
YY_INITIAL_VALUE (static YYSTYPE yyval_default;)
|
||||
#ifdef __cplusplus
|
||||
static const YYSTYPE yyval_default = {};
|
||||
(void) yyval_default;
|
||||
#else
|
||||
YY_INITIAL_VALUE (static const YYSTYPE yyval_default;)
|
||||
#endif
|
||||
YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default);
|
||||
|
||||
/* Location data for the lookahead symbol. */
|
||||
|
||||
Reference in New Issue
Block a user