ibex 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rubocop.yml +43 -0
- data/CHANGELOG.md +30 -0
- data/LICENSE.txt +21 -0
- data/README.md +194 -0
- data/Rakefile +25 -0
- data/Steepfile +10 -0
- data/docs/architecture.md +99 -0
- data/docs/compat-notes.md +37 -0
- data/docs/grammar-reference.md +127 -0
- data/docs/lexer-coverage.md +14 -0
- data/docs/phase10-extensions.md +27 -0
- data/docs/racc-migration.md +47 -0
- data/exe/ibex +6 -0
- data/gemfiles/Gemfile +7 -0
- data/gemfiles/Gemfile.lock +98 -0
- data/lib/ibex/analysis/sets.rb +194 -0
- data/lib/ibex/analysis.rb +8 -0
- data/lib/ibex/cli/counterexample_options.rb +38 -0
- data/lib/ibex/cli/outputs.rb +88 -0
- data/lib/ibex/cli.rb +333 -0
- data/lib/ibex/codegen/dot.rb +52 -0
- data/lib/ibex/codegen/html.rb +110 -0
- data/lib/ibex/codegen/rbs.rb +54 -0
- data/lib/ibex/codegen/report.rb +149 -0
- data/lib/ibex/codegen/ruby.rb +235 -0
- data/lib/ibex/codegen/symbol_labels.rb +18 -0
- data/lib/ibex/error.rb +7 -0
- data/lib/ibex/frontend/action_scanner.rb +266 -0
- data/lib/ibex/frontend/ast.rb +146 -0
- data/lib/ibex/frontend/bootstrap_parser.rb +163 -0
- data/lib/ibex/frontend/dsl.rb +196 -0
- data/lib/ibex/frontend/generated_parser.rb +434 -0
- data/lib/ibex/frontend/generated_parser_base.rb +273 -0
- data/lib/ibex/frontend/grammar.y +156 -0
- data/lib/ibex/frontend/lexer.rb +165 -0
- data/lib/ibex/frontend/parser/declarations.rb +163 -0
- data/lib/ibex/frontend/parser/rules.rb +154 -0
- data/lib/ibex/frontend/parser.rb +23 -0
- data/lib/ibex/frontend/regenerator.rb +41 -0
- data/lib/ibex/frontend/source_cursor.rb +94 -0
- data/lib/ibex/frontend/token_adapter/declaration_state.rb +248 -0
- data/lib/ibex/frontend/token_adapter/delimiter_tracker.rb +37 -0
- data/lib/ibex/frontend/token_adapter/rule_state.rb +162 -0
- data/lib/ibex/frontend/token_adapter.rb +108 -0
- data/lib/ibex/frontend.rb +28 -0
- data/lib/ibex/ir/automaton_ir.rb +109 -0
- data/lib/ibex/ir/grammar_ir.rb +192 -0
- data/lib/ibex/ir/serialize.rb +154 -0
- data/lib/ibex/ir.rb +53 -0
- data/lib/ibex/lalr/builder.rb +270 -0
- data/lib/ibex/lalr/conflict.rb +120 -0
- data/lib/ibex/lalr/conflict_search.rb +332 -0
- data/lib/ibex/lalr/conflict_search_limits.rb +39 -0
- data/lib/ibex/lalr/counterexample.rb +138 -0
- data/lib/ibex/lalr/default_reductions.rb +67 -0
- data/lib/ibex/lalr.rb +27 -0
- data/lib/ibex/normalize/declarations.rb +60 -0
- data/lib/ibex/normalize/diagnostics.rb +99 -0
- data/lib/ibex/normalize/expander.rb +218 -0
- data/lib/ibex/normalize/expression.rb +54 -0
- data/lib/ibex/normalize.rb +166 -0
- data/lib/ibex/runtime/parser.rb +360 -0
- data/lib/ibex/runtime.rb +8 -0
- data/lib/ibex/tables.rb +125 -0
- data/lib/ibex/version.rb +6 -0
- data/lib/ibex.rb +23 -0
- data/sig/ibex/analysis/sets.rbs +72 -0
- data/sig/ibex/analysis.rbs +6 -0
- data/sig/ibex/cli/counterexample_options.rbs +16 -0
- data/sig/ibex/cli/outputs.rbs +31 -0
- data/sig/ibex/cli.rbs +108 -0
- data/sig/ibex/codegen/dot.rbs +19 -0
- data/sig/ibex/codegen/html.rbs +35 -0
- data/sig/ibex/codegen/rbs.rbs +31 -0
- data/sig/ibex/codegen/report.rbs +39 -0
- data/sig/ibex/codegen/ruby.rbs +92 -0
- data/sig/ibex/codegen/symbol_labels.rbs +11 -0
- data/sig/ibex/error.rbs +7 -0
- data/sig/ibex/frontend/action_scanner.rbs +74 -0
- data/sig/ibex/frontend/ast.rbs +223 -0
- data/sig/ibex/frontend/bootstrap_parser.rbs +80 -0
- data/sig/ibex/frontend/dsl.rbs +124 -0
- data/sig/ibex/frontend/generated_parser.rbs +163 -0
- data/sig/ibex/frontend/generated_parser_base.rbs +114 -0
- data/sig/ibex/frontend/lexer.rbs +61 -0
- data/sig/ibex/frontend/parser/declarations.rbs +56 -0
- data/sig/ibex/frontend/parser/rules.rbs +48 -0
- data/sig/ibex/frontend/parser.rbs +16 -0
- data/sig/ibex/frontend/regenerator.rbs +16 -0
- data/sig/ibex/frontend/source_cursor.rbs +73 -0
- data/sig/ibex/frontend/token_adapter/declaration_state.rbs +97 -0
- data/sig/ibex/frontend/token_adapter/delimiter_tracker.rbs +25 -0
- data/sig/ibex/frontend/token_adapter/rule_state.rbs +77 -0
- data/sig/ibex/frontend/token_adapter.rbs +67 -0
- data/sig/ibex/frontend.rbs +15 -0
- data/sig/ibex/ir/automaton_ir.rbs +77 -0
- data/sig/ibex/ir/grammar_ir.rbs +143 -0
- data/sig/ibex/ir/serialize.rbs +50 -0
- data/sig/ibex/ir.rbs +43 -0
- data/sig/ibex/lalr/builder.rbs +85 -0
- data/sig/ibex/lalr/conflict.rbs +47 -0
- data/sig/ibex/lalr/conflict_search.rbs +131 -0
- data/sig/ibex/lalr/conflict_search_limits.rbs +29 -0
- data/sig/ibex/lalr/counterexample.rbs +53 -0
- data/sig/ibex/lalr/default_reductions.rbs +20 -0
- data/sig/ibex/lalr.rbs +23 -0
- data/sig/ibex/normalize/declarations.rbs +20 -0
- data/sig/ibex/normalize/diagnostics.rbs +32 -0
- data/sig/ibex/normalize/expander.rbs +66 -0
- data/sig/ibex/normalize/expression.rbs +21 -0
- data/sig/ibex/normalize.rbs +96 -0
- data/sig/ibex/runtime/parser.rbs +167 -0
- data/sig/ibex/runtime.rbs +6 -0
- data/sig/ibex/tables.rbs +50 -0
- data/sig/ibex/version.rbs +6 -0
- data/sig/ibex.rbs +6 -0
- metadata +161 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module Frontend
|
|
5
|
+
class TokenAdapter
|
|
6
|
+
# Classifies tokens through the class header and declaration section.
|
|
7
|
+
class DeclarationState
|
|
8
|
+
DECLARATIONS = {
|
|
9
|
+
"token" => %i[TOKEN token_symbols], "options" => %i[OPTIONS options_identifiers],
|
|
10
|
+
"expect" => %i[EXPECT expect_integer], "start" => %i[START start_symbol],
|
|
11
|
+
"convert" => %i[CONVERT convert_name], "pragma" => %i[PRAGMA pragma_value],
|
|
12
|
+
"rule" => %i[RULE rules]
|
|
13
|
+
}.freeze #: Hash[String, [external_token, Symbol]]
|
|
14
|
+
ASSOCIATIONS = {
|
|
15
|
+
"left" => :LEFT, "right" => :RIGHT, "nonassoc" => :NONASSOC
|
|
16
|
+
}.freeze #: Hash[String, external_token]
|
|
17
|
+
SCALAR_TYPES = {
|
|
18
|
+
literal: :LITERAL, integer: :INTEGER, action: :ACTION, user_code: :USER_CODE
|
|
19
|
+
}.freeze #: Hash[Symbol, external_token]
|
|
20
|
+
EXPECTATIONS = {
|
|
21
|
+
class_keyword: "class", class_name: "identifier", superclass_name: "identifier",
|
|
22
|
+
expect_integer: "integer", start_symbol: "a grammar symbol"
|
|
23
|
+
}.freeze #: Hash[Symbol, String]
|
|
24
|
+
|
|
25
|
+
attr_reader :conversion_name #: Token?
|
|
26
|
+
attr_reader :declaration #: Symbol?
|
|
27
|
+
attr_reader :precedence_closer #: String?
|
|
28
|
+
attr_reader :state #: Symbol
|
|
29
|
+
|
|
30
|
+
# @rbs () -> void
|
|
31
|
+
def initialize
|
|
32
|
+
@state = :class_keyword
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @rbs (Token token, Array[Token] remaining) -> external_token
|
|
36
|
+
def classify(token, remaining)
|
|
37
|
+
return classify_identifier(token, remaining) if token.type == :identifier
|
|
38
|
+
return classify_scalar(token, remaining) if SCALAR_TYPES.key?(token.type)
|
|
39
|
+
|
|
40
|
+
classify_punctuation(token)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @rbs () -> bool
|
|
44
|
+
def rules?
|
|
45
|
+
@state == :rules
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @rbs () -> bool
|
|
49
|
+
def extended_pragma?
|
|
50
|
+
@extended_pragma == true
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @rbs (Token? token) -> String?
|
|
54
|
+
def expectation(token)
|
|
55
|
+
return "identifier" if @state == :pragma_value && token&.type != :identifier
|
|
56
|
+
|
|
57
|
+
expected = EXPECTATIONS[@state]
|
|
58
|
+
return expected if expected
|
|
59
|
+
|
|
60
|
+
if @declaration == :precedence
|
|
61
|
+
precedence_expectation(token)
|
|
62
|
+
elsif @declaration == :convert
|
|
63
|
+
"end"
|
|
64
|
+
elsif @state == :declaration
|
|
65
|
+
token&.type == :eof ? "rule" : "a declaration or rule"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
# @rbs (Token token, Array[Token] remaining) -> external_token
|
|
72
|
+
def classify_identifier(token, remaining)
|
|
73
|
+
case @state
|
|
74
|
+
when :class_keyword then class_keyword(token)
|
|
75
|
+
when :class_name, :superclass_name then constant_name(remaining)
|
|
76
|
+
when :declaration then begin_declaration(token)
|
|
77
|
+
when :token_symbols, :options_identifiers then declaration_symbol(token)
|
|
78
|
+
when :precedence_association, :precedence_symbols then precedence_identifier(token)
|
|
79
|
+
when :start_symbol then finish_single_symbol(:IDENTIFIER)
|
|
80
|
+
when :pragma_value then finish_pragma(token)
|
|
81
|
+
when :convert_name then begin_conversion(token, :IDENTIFIER, remaining)
|
|
82
|
+
else :IDENTIFIER
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @rbs (Token token) -> external_token
|
|
87
|
+
def class_keyword(token)
|
|
88
|
+
return :IDENTIFIER unless string_value(token) == "class"
|
|
89
|
+
|
|
90
|
+
@state = :class_name
|
|
91
|
+
:CLASS
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @rbs (Array[Token] remaining) -> external_token
|
|
95
|
+
def constant_name(remaining)
|
|
96
|
+
following = remaining.first
|
|
97
|
+
raise Ibex::Error, "unexpected end of token stream" unless following
|
|
98
|
+
|
|
99
|
+
@state = if following.type == :scope
|
|
100
|
+
@state
|
|
101
|
+
elsif following.type == :<
|
|
102
|
+
:superclass_marker
|
|
103
|
+
else
|
|
104
|
+
:declaration
|
|
105
|
+
end
|
|
106
|
+
:IDENTIFIER
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @rbs (Token token) -> external_token
|
|
110
|
+
def begin_declaration(token)
|
|
111
|
+
value = string_value(token)
|
|
112
|
+
return begin_precedence(token) if %w[prechigh preclow].include?(value)
|
|
113
|
+
|
|
114
|
+
raise Ibex::Error, "#{token.location}: duplicate pragma extended" if value == "pragma" && @extended_pragma
|
|
115
|
+
|
|
116
|
+
terminal, next_state = DECLARATIONS[value]
|
|
117
|
+
return :IDENTIFIER unless terminal
|
|
118
|
+
|
|
119
|
+
@state = next_state
|
|
120
|
+
@declaration = value.to_sym unless terminal == :RULE
|
|
121
|
+
@declaration = nil if terminal == :RULE
|
|
122
|
+
terminal
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# @rbs (Token token) -> external_token
|
|
126
|
+
def finish_pragma(token)
|
|
127
|
+
value = string_value(token)
|
|
128
|
+
raise Ibex::Error, "#{token.location}: unknown pragma #{value}" unless value == "extended"
|
|
129
|
+
|
|
130
|
+
@extended_pragma = true
|
|
131
|
+
@state = :declaration
|
|
132
|
+
@declaration = nil
|
|
133
|
+
:IDENTIFIER
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# @rbs (Token token) -> external_token
|
|
137
|
+
def begin_precedence(token)
|
|
138
|
+
high_to_low = string_value(token) == "prechigh"
|
|
139
|
+
@precedence_closer = high_to_low ? "preclow" : "prechigh"
|
|
140
|
+
@declaration = :precedence
|
|
141
|
+
@state = :precedence_association
|
|
142
|
+
high_to_low ? :PRECHIGH : :PRECLOW
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# @rbs (Token token) -> external_token
|
|
146
|
+
def declaration_symbol(token)
|
|
147
|
+
return begin_declaration(token) if declaration_boundary?(string_value(token))
|
|
148
|
+
|
|
149
|
+
:IDENTIFIER
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# @rbs (String value) -> bool
|
|
153
|
+
def declaration_boundary?(value)
|
|
154
|
+
DECLARATIONS.key?(value) || %w[prechigh preclow].include?(value)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# @rbs (Token token) -> external_token
|
|
158
|
+
def precedence_identifier(token)
|
|
159
|
+
value = string_value(token)
|
|
160
|
+
return finish_precedence(token) if value == @precedence_closer
|
|
161
|
+
|
|
162
|
+
association = ASSOCIATIONS[value]
|
|
163
|
+
@state = :precedence_symbols if association
|
|
164
|
+
association || :IDENTIFIER
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# @rbs (Token token) -> external_token
|
|
168
|
+
def finish_precedence(token)
|
|
169
|
+
@state = :declaration
|
|
170
|
+
@declaration = nil
|
|
171
|
+
@precedence_closer = nil
|
|
172
|
+
string_value(token) == "prechigh" ? :PRECHIGH : :PRECLOW
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# @rbs (Token token, external_token type, Array[Token] remaining) -> external_token
|
|
176
|
+
def begin_conversion(token, type, remaining)
|
|
177
|
+
if token.type == :identifier && token.value == "end"
|
|
178
|
+
@state = :declaration
|
|
179
|
+
@declaration = nil
|
|
180
|
+
return :END
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
validate_conversion_line(token, remaining)
|
|
184
|
+
@conversion_name = token
|
|
185
|
+
@state = :convert_expression
|
|
186
|
+
type
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# @rbs (Token token, Array[Token] remaining) -> external_token
|
|
190
|
+
def classify_scalar(token, remaining)
|
|
191
|
+
type = SCALAR_TYPES.fetch(token.type)
|
|
192
|
+
return finish_single_symbol(type) if @state == :expect_integer && type == :INTEGER
|
|
193
|
+
return finish_single_symbol(type) if @state == :start_symbol && type == :LITERAL
|
|
194
|
+
return begin_conversion(token, type, remaining) if @state == :convert_name && type == :LITERAL
|
|
195
|
+
return finish_conversion(type) if @state == :convert_expression && type == :LITERAL
|
|
196
|
+
|
|
197
|
+
type
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# @rbs (external_token type) -> external_token
|
|
201
|
+
def finish_single_symbol(type)
|
|
202
|
+
@state = :declaration
|
|
203
|
+
@declaration = nil
|
|
204
|
+
type
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# @rbs (external_token type) -> external_token
|
|
208
|
+
def finish_conversion(type)
|
|
209
|
+
@state = :convert_name
|
|
210
|
+
@conversion_name = nil
|
|
211
|
+
type
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# @rbs (Token token) -> external_token
|
|
215
|
+
def classify_punctuation(token)
|
|
216
|
+
@state = :superclass_name if @state == :superclass_marker && token.type == :<
|
|
217
|
+
string_value(token)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# @rbs (Token name, Array[Token] remaining) -> void
|
|
221
|
+
def validate_conversion_line(name, remaining)
|
|
222
|
+
line = name.location.line
|
|
223
|
+
rest = remaining.take_while do |token|
|
|
224
|
+
token.type != :eof && token.location.line == line && !(token.type == :identifier && token.value == "end")
|
|
225
|
+
end
|
|
226
|
+
return if rest.length == 1 && rest.first.type == :literal
|
|
227
|
+
|
|
228
|
+
raise Ibex::Error, "#{name.location}: expected a quoted Ruby conversion expression"
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# @rbs (Token? token) -> String?
|
|
232
|
+
def precedence_expectation(token)
|
|
233
|
+
return @precedence_closer if token&.type == :eof
|
|
234
|
+
|
|
235
|
+
"left or right or nonassoc" if @state == :precedence_association
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# @rbs (Token token) -> String
|
|
239
|
+
def string_value(token)
|
|
240
|
+
value = token.value
|
|
241
|
+
return value if value.is_a?(String)
|
|
242
|
+
|
|
243
|
+
raise Ibex::Error, "#{token.location}: expected text token"
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module Frontend
|
|
5
|
+
class TokenAdapter
|
|
6
|
+
# Tracks ordinary EBNF groups separately from separated-list calls.
|
|
7
|
+
class DelimiterTracker
|
|
8
|
+
attr_reader :group_opening #: Token?
|
|
9
|
+
attr_reader :open_kind #: delimiter_kind?
|
|
10
|
+
|
|
11
|
+
# @rbs @stack: Array[[delimiter_kind, Token]]
|
|
12
|
+
|
|
13
|
+
# @rbs () -> void
|
|
14
|
+
def initialize
|
|
15
|
+
@stack = [] #: Array[[delimiter_kind, Token]]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @rbs () -> bool
|
|
19
|
+
def empty?
|
|
20
|
+
@stack.empty?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @rbs (Token token, external_token external, external_token? previous_external) -> void
|
|
24
|
+
def observe(token, external, previous_external)
|
|
25
|
+
if external == "("
|
|
26
|
+
separated = %i[SEPARATED_LIST SEPARATED_NONEMPTY_LIST].include?(previous_external)
|
|
27
|
+
@stack << [separated ? :separated : :group, token] #: [delimiter_kind, Token]
|
|
28
|
+
elsif external == ")"
|
|
29
|
+
@stack.pop
|
|
30
|
+
end
|
|
31
|
+
@open_kind = @stack.last&.first
|
|
32
|
+
@group_opening = @stack.reverse.find { |kind, _opening| kind == :group }&.last
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module Frontend
|
|
5
|
+
class TokenAdapter
|
|
6
|
+
# Classifies context-dependent tokens in the grammar rule section.
|
|
7
|
+
class RuleState
|
|
8
|
+
SCALAR_TYPES = {
|
|
9
|
+
literal: :LITERAL, integer: :INTEGER, action: :ACTION, user_code: :USER_CODE
|
|
10
|
+
}.freeze #: Hash[Symbol, external_token]
|
|
11
|
+
|
|
12
|
+
attr_reader :section #: parser_section
|
|
13
|
+
attr_reader :state #: Symbol
|
|
14
|
+
|
|
15
|
+
# @rbs @delimiters: DelimiterTracker
|
|
16
|
+
# @rbs @lhs_column: Integer?
|
|
17
|
+
# @rbs @rule_seen: bool?
|
|
18
|
+
|
|
19
|
+
# @rbs () -> void
|
|
20
|
+
def initialize
|
|
21
|
+
@state = :rules_lhs
|
|
22
|
+
@section = :rules
|
|
23
|
+
@delimiters = DelimiterTracker.new
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @rbs (Token token, Array[Token] remaining, last_external: external_token?,
|
|
27
|
+
# previous_external: external_token?) -> external_token
|
|
28
|
+
def classify(token, remaining, last_external:, previous_external:)
|
|
29
|
+
external = if token.type == :identifier
|
|
30
|
+
classify_identifier(token, remaining, last_external, previous_external)
|
|
31
|
+
elsif SCALAR_TYPES.key?(token.type)
|
|
32
|
+
SCALAR_TYPES.fetch(token.type)
|
|
33
|
+
else
|
|
34
|
+
classify_punctuation(token)
|
|
35
|
+
end
|
|
36
|
+
@delimiters.observe(token, external, last_external)
|
|
37
|
+
external
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @rbs () -> Token?
|
|
41
|
+
def group_opening
|
|
42
|
+
@delimiters.group_opening
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @rbs () -> delimiter_kind?
|
|
46
|
+
def open_delimiter_kind
|
|
47
|
+
@delimiters.open_kind
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @rbs (Token? token) -> String?
|
|
51
|
+
def expectation(token)
|
|
52
|
+
if @state == :rules_lhs && @section == :rules
|
|
53
|
+
rules_lhs_expectation(token)
|
|
54
|
+
elsif @state == :rule_colon
|
|
55
|
+
":"
|
|
56
|
+
elsif @state == :rule_rhs && %w[) ,].include?(token&.value)
|
|
57
|
+
"a grammar symbol"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
# @rbs (Token token, Array[Token] remaining, external_token? last_external,
|
|
64
|
+
# external_token? previous_external) -> external_token
|
|
65
|
+
def classify_identifier(token, remaining, last_external, previous_external)
|
|
66
|
+
return rule_lhs(token) if @state == :rules_lhs
|
|
67
|
+
return :IDENTIFIER unless @state == :rule_rhs
|
|
68
|
+
return :IDENTIFIER if named_reference_name?(last_external, previous_external)
|
|
69
|
+
|
|
70
|
+
value = string_value(token)
|
|
71
|
+
return :IDENTIFIER if value == "end" && @delimiters.open_kind == :separated
|
|
72
|
+
return finish_rules if value == "end"
|
|
73
|
+
|
|
74
|
+
following = remaining.first
|
|
75
|
+
return :IDENTIFIER unless following
|
|
76
|
+
return start_rule(token) if rule_boundary?(token, following)
|
|
77
|
+
return separated_terminal(token) if separated_list_call?(token, following)
|
|
78
|
+
|
|
79
|
+
:IDENTIFIER
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @rbs (Token token) -> external_token
|
|
83
|
+
def rule_lhs(token)
|
|
84
|
+
if string_value(token) == "end"
|
|
85
|
+
@section = :user_code
|
|
86
|
+
return :END
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
@rule_seen = true
|
|
90
|
+
@lhs_column = token.location.column
|
|
91
|
+
@state = :rule_colon
|
|
92
|
+
:LHS
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @rbs (external_token? last_external, external_token? previous_external) -> bool
|
|
96
|
+
def named_reference_name?(last_external, previous_external)
|
|
97
|
+
last_external == ":" && %i[IDENTIFIER LITERAL].include?(previous_external)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# @rbs (Token token, Token following) -> bool
|
|
101
|
+
def rule_boundary?(token, following)
|
|
102
|
+
lhs_column = @lhs_column
|
|
103
|
+
return false unless lhs_column
|
|
104
|
+
|
|
105
|
+
@delimiters.empty? && following.type == :":" && token.location.column <= lhs_column
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# @rbs (Token token) -> external_token
|
|
109
|
+
def start_rule(token)
|
|
110
|
+
@rule_seen = true
|
|
111
|
+
@lhs_column = token.location.column
|
|
112
|
+
@state = :rule_colon
|
|
113
|
+
:LHS
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# @rbs () -> external_token
|
|
117
|
+
def finish_rules
|
|
118
|
+
@section = :user_code
|
|
119
|
+
:END
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# @rbs (Token token, Token following) -> bool
|
|
123
|
+
def separated_list_call?(token, following)
|
|
124
|
+
%w[separated_list separated_nonempty_list].include?(string_value(token)) && following.type == :"("
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @rbs (Token token) -> external_token
|
|
128
|
+
def separated_terminal(token)
|
|
129
|
+
string_value(token) == "separated_list" ? :SEPARATED_LIST : :SEPARATED_NONEMPTY_LIST
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# @rbs (Token token) -> external_token
|
|
133
|
+
def classify_punctuation(token)
|
|
134
|
+
@state = :rule_rhs if @state == :rule_colon && token.type == :":"
|
|
135
|
+
reset_rule_boundary if @state == :rule_rhs && token.type == :";" && @delimiters.empty?
|
|
136
|
+
string_value(token)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# @rbs () -> void
|
|
140
|
+
def reset_rule_boundary
|
|
141
|
+
@lhs_column = nil
|
|
142
|
+
@state = :rules_lhs
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# @rbs (Token? token) -> String
|
|
146
|
+
def rules_lhs_expectation(token)
|
|
147
|
+
return "identifier" unless token&.type == :eof
|
|
148
|
+
|
|
149
|
+
@rule_seen ? "end" : "at least one rule"
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# @rbs (Token token) -> String
|
|
153
|
+
def string_value(token)
|
|
154
|
+
value = token.value
|
|
155
|
+
return value if value.is_a?(String)
|
|
156
|
+
|
|
157
|
+
raise Ibex::Error, "#{token.location}: expected text token"
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module Frontend
|
|
5
|
+
# Delivers lexer tokens through declaration and rule context classifiers.
|
|
6
|
+
class TokenAdapter
|
|
7
|
+
attr_reader :eof_token #: Token?
|
|
8
|
+
attr_reader :last_token #: Token?
|
|
9
|
+
attr_reader :previous_external #: external_token?
|
|
10
|
+
|
|
11
|
+
# @rbs @tokens: Array[Token]
|
|
12
|
+
# @rbs @index: Integer
|
|
13
|
+
# @rbs @declarations: DeclarationState
|
|
14
|
+
# @rbs @classifier: DeclarationState | RuleState
|
|
15
|
+
# @rbs @rules: RuleState?
|
|
16
|
+
# @rbs @last_external: external_token?
|
|
17
|
+
|
|
18
|
+
# @rbs (Array[Token] tokens) -> void
|
|
19
|
+
def initialize(tokens)
|
|
20
|
+
@tokens = tokens
|
|
21
|
+
@index = 0
|
|
22
|
+
@declarations = DeclarationState.new
|
|
23
|
+
@classifier = @declarations
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @rbs () -> ([external_token, Token] | false)
|
|
27
|
+
def next_token
|
|
28
|
+
token = @tokens.fetch(@index)
|
|
29
|
+
@index += 1
|
|
30
|
+
@last_token = token
|
|
31
|
+
if token.type == :eof
|
|
32
|
+
@eof_token = token
|
|
33
|
+
return false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
external = classify(token)
|
|
37
|
+
enter_rules if @classifier == @declarations && @declarations.rules?
|
|
38
|
+
@previous_external = @last_external
|
|
39
|
+
@last_external = external
|
|
40
|
+
[external, token]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @rbs () -> Token?
|
|
44
|
+
def conversion_name
|
|
45
|
+
@declarations.conversion_name
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @rbs () -> Symbol?
|
|
49
|
+
def declaration
|
|
50
|
+
@declarations.declaration
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @rbs () -> String?
|
|
54
|
+
def precedence_closer
|
|
55
|
+
@declarations.precedence_closer
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @rbs () -> bool
|
|
59
|
+
def extended_pragma?
|
|
60
|
+
@declarations.extended_pragma?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @rbs () -> parser_section
|
|
64
|
+
def section
|
|
65
|
+
rules = @rules
|
|
66
|
+
rules ? rules.section : :declarations
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @rbs () -> Symbol
|
|
70
|
+
def state
|
|
71
|
+
@classifier.state
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @rbs () -> Token?
|
|
75
|
+
def group_opening
|
|
76
|
+
@rules&.group_opening
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @rbs () -> delimiter_kind?
|
|
80
|
+
def open_delimiter_kind
|
|
81
|
+
@rules&.open_delimiter_kind
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @rbs (Token? token) -> String?
|
|
85
|
+
def expectation(token)
|
|
86
|
+
@classifier.expectation(token)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
# @rbs (Token token) -> external_token
|
|
92
|
+
def classify(token)
|
|
93
|
+
remaining = @tokens.drop(@index)
|
|
94
|
+
rules = @rules
|
|
95
|
+
return @declarations.classify(token, remaining) unless rules
|
|
96
|
+
|
|
97
|
+
rules.classify(token, remaining, last_external: @last_external,
|
|
98
|
+
previous_external: @previous_external)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# @rbs () -> void
|
|
102
|
+
def enter_rules
|
|
103
|
+
@rules = RuleState.new
|
|
104
|
+
@classifier = @rules
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "error"
|
|
4
|
+
require_relative "tables"
|
|
5
|
+
require_relative "runtime"
|
|
6
|
+
require_relative "frontend/source_cursor"
|
|
7
|
+
require_relative "frontend/action_scanner"
|
|
8
|
+
require_relative "frontend/lexer"
|
|
9
|
+
require_relative "frontend/ast"
|
|
10
|
+
require_relative "frontend/token_adapter/declaration_state"
|
|
11
|
+
require_relative "frontend/token_adapter/delimiter_tracker"
|
|
12
|
+
require_relative "frontend/token_adapter/rule_state"
|
|
13
|
+
require_relative "frontend/token_adapter"
|
|
14
|
+
require_relative "frontend/generated_parser_base"
|
|
15
|
+
require_relative "frontend/generated_parser"
|
|
16
|
+
require_relative "frontend/parser"
|
|
17
|
+
require_relative "frontend/dsl"
|
|
18
|
+
|
|
19
|
+
module Ibex
|
|
20
|
+
module Frontend
|
|
21
|
+
# @rbs!
|
|
22
|
+
# type user_code_token = { name: String, code: String }
|
|
23
|
+
# type token_value = String | Integer | user_code_token | nil
|
|
24
|
+
# type external_token = Symbol | String
|
|
25
|
+
# type parser_section = :declarations | :rules | :user_code
|
|
26
|
+
# type delimiter_kind = :group | :separated
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module IR
|
|
5
|
+
# A merged LALR item with its lookahead token ids.
|
|
6
|
+
class AutomatonItem
|
|
7
|
+
attr_reader :production #: Integer
|
|
8
|
+
attr_reader :dot #: Integer
|
|
9
|
+
attr_reader :lookaheads #: Array[Integer]
|
|
10
|
+
|
|
11
|
+
# @rbs (production: Integer, dot: Integer, lookaheads: Array[Integer]) -> void
|
|
12
|
+
def initialize(production:, dot:, lookaheads:)
|
|
13
|
+
@production = production
|
|
14
|
+
@dot = dot
|
|
15
|
+
@lookaheads = lookaheads.sort.freeze
|
|
16
|
+
freeze
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @rbs (Grammar grammar) -> Hash[Symbol, untyped]
|
|
20
|
+
def to_h(grammar)
|
|
21
|
+
{ production: @production, dot: @dot,
|
|
22
|
+
lookaheads: @lookaheads.map do |id|
|
|
23
|
+
symbol = grammar.symbol_by_id(id) || raise(Ibex::Error, "missing grammar symbol id #{id}")
|
|
24
|
+
symbol.name
|
|
25
|
+
end }
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# A deterministic LALR automaton state.
|
|
30
|
+
class AutomatonState
|
|
31
|
+
attr_reader :id #: Integer
|
|
32
|
+
attr_reader :items #: Array[AutomatonItem]
|
|
33
|
+
attr_reader :transitions #: Hash[Integer, Integer]
|
|
34
|
+
attr_reader :actions #: Hash[Integer, parser_action]
|
|
35
|
+
attr_reader :gotos #: Hash[Integer, Integer]
|
|
36
|
+
attr_reader :default_action #: parser_action?
|
|
37
|
+
attr_reader :conflicts #: Array[conflict]
|
|
38
|
+
|
|
39
|
+
# @rbs (id: Integer, items: Array[AutomatonItem], transitions: Hash[Integer, Integer],
|
|
40
|
+
# actions: Hash[Integer, parser_action], gotos: Hash[Integer, Integer], ?default_action: parser_action?,
|
|
41
|
+
# ?conflicts: Array[conflict]) -> void
|
|
42
|
+
def initialize(id:, items:, transitions:, actions:, gotos:, default_action: nil, conflicts: [])
|
|
43
|
+
@id = id
|
|
44
|
+
@items = items.freeze
|
|
45
|
+
@transitions = IR.deep_freeze(transitions)
|
|
46
|
+
@actions = IR.deep_freeze(actions)
|
|
47
|
+
@gotos = IR.deep_freeze(gotos)
|
|
48
|
+
@default_action = IR.deep_freeze(default_action)
|
|
49
|
+
@conflicts = IR.deep_freeze(conflicts)
|
|
50
|
+
freeze
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @rbs (Grammar grammar) -> Hash[Symbol, untyped]
|
|
54
|
+
def to_h(grammar)
|
|
55
|
+
{ id: @id, items: @items.map { |item| item.to_h(grammar) },
|
|
56
|
+
transitions: named_keys(@transitions, grammar), actions: named_keys(@actions, grammar),
|
|
57
|
+
gotos: named_keys(@gotos, grammar), default_action: @default_action, conflicts: @conflicts }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
# @rbs (Hash[Integer, untyped] values, Grammar grammar) -> Hash[String, untyped]
|
|
63
|
+
def named_keys(values, grammar)
|
|
64
|
+
values.to_h do |symbol_id, value|
|
|
65
|
+
symbol = grammar.symbol_by_id(symbol_id) || raise(Ibex::Error, "missing grammar symbol id #{symbol_id}")
|
|
66
|
+
[symbol.name, value]
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Immutable LALR automaton and its source grammar.
|
|
72
|
+
class Automaton
|
|
73
|
+
attr_reader :algorithm #: String
|
|
74
|
+
attr_reader :grammar_digest #: String
|
|
75
|
+
attr_reader :grammar #: Grammar
|
|
76
|
+
attr_reader :states #: Array[AutomatonState]
|
|
77
|
+
attr_reader :conflict_summary #: conflict_summary
|
|
78
|
+
attr_reader :schema_version #: Integer
|
|
79
|
+
|
|
80
|
+
# @rbs (grammar: Grammar, states: Array[AutomatonState], conflict_summary: conflict_summary,
|
|
81
|
+
# ?algorithm: String, ?grammar_digest: String?, ?schema_version: Integer) -> void
|
|
82
|
+
def initialize(grammar:, states:, conflict_summary:, algorithm: "lalr1", grammar_digest: nil,
|
|
83
|
+
schema_version: SCHEMA_VERSION)
|
|
84
|
+
@algorithm = algorithm.freeze
|
|
85
|
+
@grammar = grammar
|
|
86
|
+
@grammar_digest = (grammar_digest || digest_for(grammar)).freeze
|
|
87
|
+
@states = states.freeze
|
|
88
|
+
@conflict_summary = IR.deep_freeze(conflict_summary)
|
|
89
|
+
@schema_version = schema_version
|
|
90
|
+
freeze
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @rbs () -> Hash[Symbol, untyped]
|
|
94
|
+
def to_h
|
|
95
|
+
{ ibex_ir: "automaton", schema_version: @schema_version, algorithm: @algorithm,
|
|
96
|
+
grammar_digest: @grammar_digest, grammar: @grammar.to_h,
|
|
97
|
+
states: @states.map { |state| state.to_h(@grammar) }, conflict_summary: @conflict_summary }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
# @rbs (Grammar grammar) -> String
|
|
103
|
+
def digest_for(grammar)
|
|
104
|
+
require "digest"
|
|
105
|
+
"sha256:#{Digest::SHA256.hexdigest(IR::Serialize.dump(grammar))}"
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|