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,163 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module Frontend
|
|
5
|
+
# Parses the declaration section of a grammar.
|
|
6
|
+
module BootstrapParserDeclarations
|
|
7
|
+
DECLARATIONS = %w[token prechigh preclow options expect start convert pragma rule].freeze #: Array[String]
|
|
8
|
+
ASSOCIATIVITIES = %w[left right nonassoc].freeze #: Array[String]
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# @rbs () -> Array[AST::declaration]
|
|
13
|
+
def parse_declarations
|
|
14
|
+
# @type self: BootstrapParser
|
|
15
|
+
declarations = [] #: Array[AST::declaration]
|
|
16
|
+
declarations << parse_declaration until keyword?("rule") || current.type == :eof
|
|
17
|
+
declarations
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @rbs () -> void
|
|
21
|
+
def parse_pragmas
|
|
22
|
+
# @type self: BootstrapParser
|
|
23
|
+
seen = false
|
|
24
|
+
while keyword?("pragma")
|
|
25
|
+
keyword = current
|
|
26
|
+
fail_at(keyword.location, "duplicate pragma extended") if seen
|
|
27
|
+
|
|
28
|
+
advance
|
|
29
|
+
value = expect(:identifier)
|
|
30
|
+
name = token_string(value)
|
|
31
|
+
fail_at(value.location, "unknown pragma #{name}") unless name == "extended"
|
|
32
|
+
|
|
33
|
+
@mode = :extended
|
|
34
|
+
seen = true
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @rbs () -> AST::declaration
|
|
39
|
+
def parse_declaration
|
|
40
|
+
# @type self: BootstrapParser
|
|
41
|
+
case current.value
|
|
42
|
+
when "token" then parse_tokens
|
|
43
|
+
when "prechigh", "preclow" then parse_precedence
|
|
44
|
+
when "options" then parse_options
|
|
45
|
+
when "expect" then parse_expect
|
|
46
|
+
when "start" then parse_start
|
|
47
|
+
when "convert" then parse_convert
|
|
48
|
+
when "pragma" then fail_at(current.location, "expected rule, got pragma")
|
|
49
|
+
else fail_expected("a declaration or rule")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @rbs () -> AST::Tokens
|
|
54
|
+
def parse_tokens
|
|
55
|
+
# @type self: BootstrapParser
|
|
56
|
+
location = advance.location
|
|
57
|
+
names = [] #: Array[String]
|
|
58
|
+
names << parse_symbol_name until declaration_start?
|
|
59
|
+
AST::Tokens.new(names: names, loc: location)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @rbs () -> AST::Precedence
|
|
63
|
+
def parse_precedence
|
|
64
|
+
# @type self: BootstrapParser
|
|
65
|
+
opening = advance
|
|
66
|
+
opening_name = token_string(opening)
|
|
67
|
+
closing = opening_name == "prechigh" ? "preclow" : "prechigh"
|
|
68
|
+
levels = precedence_levels(closing)
|
|
69
|
+
expect_keyword(closing)
|
|
70
|
+
AST::Precedence.new(direction: opening_name == "prechigh" ? :high_to_low : :low_to_high,
|
|
71
|
+
levels: levels, loc: opening.location)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @rbs (String closing) -> Array[AST::PrecedenceLevel]
|
|
75
|
+
def precedence_levels(closing)
|
|
76
|
+
# @type self: BootstrapParser
|
|
77
|
+
levels = [] #: Array[AST::PrecedenceLevel]
|
|
78
|
+
until keyword?(closing) || current.type == :eof
|
|
79
|
+
association = expect_one_of(ASSOCIATIVITIES)
|
|
80
|
+
symbols = [] #: Array[String]
|
|
81
|
+
symbols << parse_symbol_name until association_start? || keyword?(closing) || current.type == :eof
|
|
82
|
+
fail_at(association.location, "expected at least one precedence symbol") if symbols.empty?
|
|
83
|
+
levels << AST::PrecedenceLevel.new(associativity: token_string(association).to_sym, symbols: symbols,
|
|
84
|
+
loc: association.location)
|
|
85
|
+
end
|
|
86
|
+
levels
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @rbs () -> AST::Options
|
|
90
|
+
def parse_options
|
|
91
|
+
# @type self: BootstrapParser
|
|
92
|
+
location = advance.location
|
|
93
|
+
names = [] #: Array[String]
|
|
94
|
+
names << token_string(expect(:identifier)) until declaration_start?
|
|
95
|
+
AST::Options.new(names: names, loc: location)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @rbs () -> AST::Expect
|
|
99
|
+
def parse_expect
|
|
100
|
+
# @type self: BootstrapParser
|
|
101
|
+
location = advance.location
|
|
102
|
+
AST::Expect.new(conflicts: token_integer(expect(:integer)), loc: location)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @rbs () -> AST::Start
|
|
106
|
+
def parse_start
|
|
107
|
+
# @type self: BootstrapParser
|
|
108
|
+
location = advance.location
|
|
109
|
+
AST::Start.new(name: parse_symbol_name, loc: location)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @rbs () -> AST::Convert
|
|
113
|
+
def parse_convert
|
|
114
|
+
# @type self: BootstrapParser
|
|
115
|
+
location = advance.location
|
|
116
|
+
pairs = [] #: Array[AST::Conversion]
|
|
117
|
+
until keyword?("end") || current.type == :eof
|
|
118
|
+
name_token = current
|
|
119
|
+
name = parse_symbol_name
|
|
120
|
+
expression = decode_conversion(tokens_on_line(name_token.location.line), name_token.location)
|
|
121
|
+
pairs << AST::Conversion.new(name: name, expression: expression, loc: name_token.location)
|
|
122
|
+
end
|
|
123
|
+
expect_keyword("end")
|
|
124
|
+
AST::Convert.new(pairs: pairs, loc: location)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @rbs (Integer line) -> Array[Token]
|
|
128
|
+
def tokens_on_line(line)
|
|
129
|
+
# @type self: BootstrapParser
|
|
130
|
+
tokens = [] #: Array[Token]
|
|
131
|
+
tokens << advance while current.type != :eof && current.location.line == line && !keyword?("end")
|
|
132
|
+
tokens
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# @rbs (Array[Token] tokens, Location location) -> String
|
|
136
|
+
def decode_conversion(tokens, location)
|
|
137
|
+
# @type self: BootstrapParser
|
|
138
|
+
unless tokens.length == 1 && tokens.first.type == :literal
|
|
139
|
+
fail_at(location, "expected a quoted Ruby conversion expression")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
literal = token_string(tokens.first)
|
|
143
|
+
return literal.undump if literal.start_with?('"')
|
|
144
|
+
|
|
145
|
+
(literal[1...-1] || "").gsub("\\'", "'").gsub("\\\\", "\\")
|
|
146
|
+
rescue RuntimeError => e
|
|
147
|
+
fail_at(location, "invalid conversion expression: #{e.message}")
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# @rbs () -> bool
|
|
151
|
+
def declaration_start?
|
|
152
|
+
# @type self: BootstrapParser
|
|
153
|
+
current.type == :eof || (current.type == :identifier && DECLARATIONS.include?(current.value))
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# @rbs () -> bool
|
|
157
|
+
def association_start?
|
|
158
|
+
# @type self: BootstrapParser
|
|
159
|
+
current.type == :identifier && ASSOCIATIVITIES.include?(current.value)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module Frontend
|
|
5
|
+
# Parses productions and extended RHS items.
|
|
6
|
+
module BootstrapParserRules
|
|
7
|
+
EXTENDED_SUFFIXES = {
|
|
8
|
+
"?": AST::Optional, "*": AST::Star, "+": AST::Plus
|
|
9
|
+
}.freeze #: Hash[Symbol, singleton(AST::Optional) | singleton(AST::Star) | singleton(AST::Plus)]
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
# @rbs () -> Array[AST::Rule]
|
|
14
|
+
def parse_rules
|
|
15
|
+
# @type self: BootstrapParser
|
|
16
|
+
rules = [] #: Array[AST::Rule]
|
|
17
|
+
rules << parse_rule until keyword?("end") || current.type == :eof
|
|
18
|
+
fail_expected("at least one rule") if rules.empty?
|
|
19
|
+
rules
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @rbs () -> AST::Rule
|
|
23
|
+
def parse_rule
|
|
24
|
+
# @type self: BootstrapParser
|
|
25
|
+
lhs = expect(:identifier)
|
|
26
|
+
expect(:":")
|
|
27
|
+
alternatives = [] #: Array[AST::Alternative]
|
|
28
|
+
loop do
|
|
29
|
+
alternatives << parse_alternative(lhs)
|
|
30
|
+
next if accept(:|)
|
|
31
|
+
|
|
32
|
+
accept(:";")
|
|
33
|
+
break
|
|
34
|
+
end
|
|
35
|
+
AST::Rule.new(lhs: token_string(lhs), alternatives: alternatives, loc: lhs.location)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @rbs (Token lhs) -> AST::Alternative
|
|
39
|
+
def parse_alternative(lhs)
|
|
40
|
+
# @type self: BootstrapParser
|
|
41
|
+
location = current.location
|
|
42
|
+
items = [] #: Array[AST::item]
|
|
43
|
+
precedence = nil #: String?
|
|
44
|
+
until alternative_end?(lhs)
|
|
45
|
+
if accept(:"=")
|
|
46
|
+
precedence = parse_symbol_name
|
|
47
|
+
break
|
|
48
|
+
end
|
|
49
|
+
items << parse_item
|
|
50
|
+
end
|
|
51
|
+
action = nil #: AST::InlineAction?
|
|
52
|
+
last_item = items.last
|
|
53
|
+
if last_item.is_a?(AST::InlineAction)
|
|
54
|
+
items.pop
|
|
55
|
+
action = last_item
|
|
56
|
+
end
|
|
57
|
+
AST::Alternative.new(items: items, action: action, precedence: precedence, loc: location)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @rbs () -> AST::item
|
|
61
|
+
def parse_item
|
|
62
|
+
# @type self: BootstrapParser
|
|
63
|
+
return parse_action if current.type == :action
|
|
64
|
+
return parse_separated_list if separated_list?
|
|
65
|
+
return parse_group if current.type == :"("
|
|
66
|
+
|
|
67
|
+
token = expect_symbol
|
|
68
|
+
named_reference = parse_named_reference
|
|
69
|
+
item = AST::SymbolReference.new(name: token_string(token), named_reference: named_reference,
|
|
70
|
+
loc: token.location)
|
|
71
|
+
parse_suffix(item)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @rbs () -> AST::InlineAction
|
|
75
|
+
def parse_action
|
|
76
|
+
# @type self: BootstrapParser
|
|
77
|
+
token = advance
|
|
78
|
+
AST::InlineAction.new(code: token_string(token), loc: token.location)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @rbs () -> String?
|
|
82
|
+
def parse_named_reference
|
|
83
|
+
# @type self: BootstrapParser
|
|
84
|
+
return nil unless current.type == :":"
|
|
85
|
+
|
|
86
|
+
extended_only!(current.location, "named references")
|
|
87
|
+
advance
|
|
88
|
+
token_string(expect(:identifier))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @rbs (AST::item item) -> AST::item
|
|
92
|
+
def parse_suffix(item)
|
|
93
|
+
# @type self: BootstrapParser
|
|
94
|
+
while (wrapper = EXTENDED_SUFFIXES[current.type])
|
|
95
|
+
extended_only!(current.location, "EBNF suffixes")
|
|
96
|
+
item = wrapper.new(item: item, loc: advance.location)
|
|
97
|
+
end
|
|
98
|
+
item
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# @rbs () -> AST::item
|
|
102
|
+
def parse_group
|
|
103
|
+
# @type self: BootstrapParser
|
|
104
|
+
opening = advance
|
|
105
|
+
extended_only!(opening.location, "EBNF groups")
|
|
106
|
+
alternatives = [[]] #: Array[Array[AST::item]]
|
|
107
|
+
until current.type == :")"
|
|
108
|
+
fail_at(opening.location, "unterminated EBNF group") if current.type == :eof || keyword?("end")
|
|
109
|
+
if accept(:|)
|
|
110
|
+
alternatives << []
|
|
111
|
+
next
|
|
112
|
+
end
|
|
113
|
+
fail_at(current.location, "actions inside EBNF groups are not supported") if current.type == :action
|
|
114
|
+
|
|
115
|
+
alternatives.last << parse_item
|
|
116
|
+
end
|
|
117
|
+
expect(:")")
|
|
118
|
+
parse_suffix(AST::Group.new(alternatives: alternatives, loc: opening.location))
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @rbs () -> AST::SeparatedList
|
|
122
|
+
def parse_separated_list
|
|
123
|
+
# @type self: BootstrapParser
|
|
124
|
+
function = advance
|
|
125
|
+
extended_only!(function.location, "separated lists")
|
|
126
|
+
expect(:"(")
|
|
127
|
+
item = parse_item
|
|
128
|
+
expect(:",")
|
|
129
|
+
separator = parse_item
|
|
130
|
+
expect(:")")
|
|
131
|
+
AST::SeparatedList.new(item: item, separator: separator,
|
|
132
|
+
nonempty: function.value == "separated_nonempty_list", loc: function.location)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# @rbs (Token lhs) -> bool
|
|
136
|
+
def alternative_end?(lhs)
|
|
137
|
+
# @type self: BootstrapParser
|
|
138
|
+
%i[| ; eof].include?(current.type) || keyword?("end") || rule_start?(lhs)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# @rbs (Token lhs) -> bool
|
|
142
|
+
def rule_start?(lhs)
|
|
143
|
+
# @type self: BootstrapParser
|
|
144
|
+
current.type == :identifier && lookahead.type == :":" && current.location.column <= lhs.location.column
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# @rbs () -> bool
|
|
148
|
+
def separated_list?
|
|
149
|
+
# @type self: BootstrapParser
|
|
150
|
+
%w[separated_list separated_nonempty_list].include?(current.value) && lookahead.type == :"("
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module Frontend
|
|
5
|
+
# Public grammar parser backed by Ibex's generated LR frontend.
|
|
6
|
+
class Parser
|
|
7
|
+
attr_reader :implementation #: GeneratedParser
|
|
8
|
+
|
|
9
|
+
# @rbs (String | Array[Token] source, ?file: String, ?mode: Symbol) -> void
|
|
10
|
+
def initialize(source, file: "(grammar)", mode: :racc)
|
|
11
|
+
raise ArgumentError, "mode must be :racc or :extended" unless %i[racc extended].include?(mode)
|
|
12
|
+
|
|
13
|
+
tokens = source.is_a?(Array) ? source : Lexer.new(source, file: file).tokenize
|
|
14
|
+
@implementation = GeneratedParser.new(tokens, mode: mode)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @rbs () -> AST::Root
|
|
18
|
+
def parse
|
|
19
|
+
@implementation.parse
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../error"
|
|
4
|
+
require_relative "../tables"
|
|
5
|
+
require_relative "source_cursor"
|
|
6
|
+
require_relative "action_scanner"
|
|
7
|
+
require_relative "lexer"
|
|
8
|
+
require_relative "ast"
|
|
9
|
+
require_relative "parser/declarations"
|
|
10
|
+
require_relative "parser/rules"
|
|
11
|
+
require_relative "bootstrap_parser"
|
|
12
|
+
require_relative "../ir"
|
|
13
|
+
require_relative "../normalize"
|
|
14
|
+
require_relative "../analysis"
|
|
15
|
+
require_relative "../lalr"
|
|
16
|
+
require_relative "../codegen/ruby"
|
|
17
|
+
|
|
18
|
+
module Ibex
|
|
19
|
+
module Frontend
|
|
20
|
+
# Builds the committed frontend parser from its canonical Ibex grammar.
|
|
21
|
+
module Regenerator
|
|
22
|
+
GRAMMAR_PATH = File.expand_path("grammar.y", File.dirname(__FILE__)) #: String
|
|
23
|
+
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
# @rbs () -> String
|
|
27
|
+
def generate
|
|
28
|
+
source = File.read(GRAMMAR_PATH)
|
|
29
|
+
ast = BootstrapParser.new(source, file: relative_grammar_path).parse
|
|
30
|
+
grammar = Normalizer.new(ast).normalize
|
|
31
|
+
automaton = LALR::Builder.new(grammar).build
|
|
32
|
+
Codegen::Ruby.new(automaton, table: :compact, line_convert: false).generate
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @rbs () -> String
|
|
36
|
+
def relative_grammar_path
|
|
37
|
+
"lib/ibex/frontend/grammar.y"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module Frontend
|
|
5
|
+
# A source coordinate retained by frontend and IR objects.
|
|
6
|
+
Location = Struct.new(
|
|
7
|
+
:file, #: String
|
|
8
|
+
:line, #: Integer
|
|
9
|
+
:column, #: Integer
|
|
10
|
+
keyword_init: true
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
class Location
|
|
14
|
+
# @rbs () -> IR::location
|
|
15
|
+
def to_h
|
|
16
|
+
{ file: file, line: line, column: column }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @rbs () -> String
|
|
20
|
+
def to_s
|
|
21
|
+
"#{file}:#{line}:#{column}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# A grammar token and its source coordinate.
|
|
26
|
+
Token = Struct.new(
|
|
27
|
+
:type, #: Symbol
|
|
28
|
+
:value, #: token_value
|
|
29
|
+
:location, #: Location
|
|
30
|
+
keyword_init: true
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
class Token
|
|
34
|
+
# @rbs () -> Hash[Symbol, untyped]
|
|
35
|
+
def to_h
|
|
36
|
+
{ type: type, value: value, location: location.to_h }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Advances through source text while maintaining one-based coordinates.
|
|
41
|
+
class SourceCursor
|
|
42
|
+
attr_reader :source #: String
|
|
43
|
+
attr_reader :file #: String
|
|
44
|
+
attr_reader :index #: Integer
|
|
45
|
+
attr_reader :line #: Integer
|
|
46
|
+
attr_reader :column #: Integer
|
|
47
|
+
|
|
48
|
+
# @rbs (String source, String file) -> void
|
|
49
|
+
def initialize(source, file)
|
|
50
|
+
@source = source
|
|
51
|
+
@file = file
|
|
52
|
+
@index = 0
|
|
53
|
+
@line = 1
|
|
54
|
+
@column = 1
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @rbs () -> bool
|
|
58
|
+
def eof?
|
|
59
|
+
@index >= @source.length
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @rbs (?Integer offset) -> String?
|
|
63
|
+
def peek(offset = 0)
|
|
64
|
+
@source[@index + offset]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @rbs () -> String
|
|
68
|
+
def rest
|
|
69
|
+
@source[@index..] || ""
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @rbs () -> Location
|
|
73
|
+
def location
|
|
74
|
+
Location.new(file: @file, line: @line, column: @column)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @rbs (?Integer count) -> void
|
|
78
|
+
def advance(count = 1)
|
|
79
|
+
count.times do
|
|
80
|
+
character = @source[@index]
|
|
81
|
+
break unless character
|
|
82
|
+
|
|
83
|
+
@index += 1
|
|
84
|
+
if character == "\n"
|
|
85
|
+
@line += 1
|
|
86
|
+
@column = 1
|
|
87
|
+
else
|
|
88
|
+
@column += 1
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|