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,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module Frontend
|
|
5
|
+
# Grammar frontend node types.
|
|
6
|
+
module AST
|
|
7
|
+
# @rbs!
|
|
8
|
+
# type declaration = Tokens | Precedence | Options | Expect | Start | Convert
|
|
9
|
+
# type item = SymbolReference | InlineAction | Optional | Star | Plus | Group | SeparatedList
|
|
10
|
+
# type user_code = Hash[String, Array[UserCode]]
|
|
11
|
+
|
|
12
|
+
# Adds deterministic, recursively serializable hashes to Struct nodes.
|
|
13
|
+
# @rbs module-self Struct[untyped]
|
|
14
|
+
module Node
|
|
15
|
+
def to_h
|
|
16
|
+
fields = each_pair.to_h { |name, value| [name, serialize(value)] }
|
|
17
|
+
{ node: self.class.name.split("::").last }.merge(fields)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def serialize(value)
|
|
23
|
+
return value if value.nil?
|
|
24
|
+
|
|
25
|
+
case value
|
|
26
|
+
when Array then value.map { |item| serialize(item) }
|
|
27
|
+
when Hash then value.to_h { |key, item| [key, serialize(item)] }
|
|
28
|
+
else value.respond_to?(:to_h) ? value.to_h : value
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
Root = Struct.new(
|
|
34
|
+
:class_name, #: String
|
|
35
|
+
:superclass, #: String?
|
|
36
|
+
:declarations, #: Array[declaration]
|
|
37
|
+
:rules, #: Array[Rule]
|
|
38
|
+
:user_code, #: user_code
|
|
39
|
+
:loc, #: Location
|
|
40
|
+
keyword_init: true
|
|
41
|
+
) do
|
|
42
|
+
include Node
|
|
43
|
+
end
|
|
44
|
+
Tokens = Struct.new(
|
|
45
|
+
:names, #: Array[String]
|
|
46
|
+
:loc, #: Location
|
|
47
|
+
keyword_init: true
|
|
48
|
+
) { include Node }
|
|
49
|
+
Precedence = Struct.new(
|
|
50
|
+
:direction, #: Symbol
|
|
51
|
+
:levels, #: Array[PrecedenceLevel]
|
|
52
|
+
:loc, #: Location
|
|
53
|
+
keyword_init: true
|
|
54
|
+
) { include Node }
|
|
55
|
+
PrecedenceLevel = Struct.new(
|
|
56
|
+
:associativity, #: Symbol
|
|
57
|
+
:symbols, #: Array[String]
|
|
58
|
+
:loc, #: Location
|
|
59
|
+
keyword_init: true
|
|
60
|
+
) { include Node }
|
|
61
|
+
Options = Struct.new(
|
|
62
|
+
:names, #: Array[String]
|
|
63
|
+
:loc, #: Location
|
|
64
|
+
keyword_init: true
|
|
65
|
+
) { include Node }
|
|
66
|
+
Expect = Struct.new(
|
|
67
|
+
:conflicts, #: Integer
|
|
68
|
+
:loc, #: Location
|
|
69
|
+
keyword_init: true
|
|
70
|
+
) { include Node }
|
|
71
|
+
Start = Struct.new(
|
|
72
|
+
:name, #: String
|
|
73
|
+
:loc, #: Location
|
|
74
|
+
keyword_init: true
|
|
75
|
+
) { include Node }
|
|
76
|
+
Convert = Struct.new(
|
|
77
|
+
:pairs, #: Array[Conversion]
|
|
78
|
+
:loc, #: Location
|
|
79
|
+
keyword_init: true
|
|
80
|
+
) { include Node }
|
|
81
|
+
Conversion = Struct.new(
|
|
82
|
+
:name, #: String
|
|
83
|
+
:expression, #: String
|
|
84
|
+
:loc, #: Location
|
|
85
|
+
keyword_init: true
|
|
86
|
+
) { include Node }
|
|
87
|
+
Rule = Struct.new(
|
|
88
|
+
:lhs, #: String
|
|
89
|
+
:alternatives, #: Array[Alternative]
|
|
90
|
+
:loc, #: Location
|
|
91
|
+
keyword_init: true
|
|
92
|
+
) { include Node }
|
|
93
|
+
Alternative = Struct.new(
|
|
94
|
+
:items, #: Array[item]
|
|
95
|
+
:action, #: InlineAction?
|
|
96
|
+
:precedence, #: String?
|
|
97
|
+
:loc, #: Location
|
|
98
|
+
keyword_init: true
|
|
99
|
+
) { include Node }
|
|
100
|
+
SymbolReference = Struct.new(
|
|
101
|
+
:name, #: String
|
|
102
|
+
:named_reference, #: String?
|
|
103
|
+
:loc, #: Location
|
|
104
|
+
keyword_init: true
|
|
105
|
+
) { include Node }
|
|
106
|
+
InlineAction = Struct.new(
|
|
107
|
+
:code, #: String
|
|
108
|
+
:loc, #: Location
|
|
109
|
+
keyword_init: true
|
|
110
|
+
) { include Node }
|
|
111
|
+
Optional = Struct.new(
|
|
112
|
+
:item, #: item
|
|
113
|
+
:loc, #: Location
|
|
114
|
+
keyword_init: true
|
|
115
|
+
) { include Node }
|
|
116
|
+
Star = Struct.new(
|
|
117
|
+
:item, #: item
|
|
118
|
+
:loc, #: Location
|
|
119
|
+
keyword_init: true
|
|
120
|
+
) { include Node }
|
|
121
|
+
Plus = Struct.new(
|
|
122
|
+
:item, #: item
|
|
123
|
+
:loc, #: Location
|
|
124
|
+
keyword_init: true
|
|
125
|
+
) { include Node }
|
|
126
|
+
Group = Struct.new(
|
|
127
|
+
:alternatives, #: Array[Array[item]]
|
|
128
|
+
:loc, #: Location
|
|
129
|
+
keyword_init: true
|
|
130
|
+
) { include Node }
|
|
131
|
+
SeparatedList = Struct.new(
|
|
132
|
+
:item, #: item
|
|
133
|
+
:separator, #: item
|
|
134
|
+
:nonempty, #: bool
|
|
135
|
+
:loc, #: Location
|
|
136
|
+
keyword_init: true
|
|
137
|
+
) { include Node }
|
|
138
|
+
UserCode = Struct.new(
|
|
139
|
+
:name, #: String
|
|
140
|
+
:code, #: String
|
|
141
|
+
:loc, #: Location
|
|
142
|
+
keyword_init: true
|
|
143
|
+
) { include Node }
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module Frontend
|
|
5
|
+
# Handwritten parser used only to regenerate the self-hosted frontend.
|
|
6
|
+
class BootstrapParser
|
|
7
|
+
include BootstrapParserDeclarations
|
|
8
|
+
include BootstrapParserRules
|
|
9
|
+
|
|
10
|
+
# @rbs @tokens: Array[Token]
|
|
11
|
+
# @rbs @index: Integer
|
|
12
|
+
# @rbs @mode: Symbol
|
|
13
|
+
|
|
14
|
+
# @rbs (String | Array[Token] source, ?file: String, ?mode: Symbol) -> void
|
|
15
|
+
def initialize(source, file: "(grammar)", mode: :racc)
|
|
16
|
+
raise ArgumentError, "mode must be :racc or :extended" unless %i[racc extended].include?(mode)
|
|
17
|
+
|
|
18
|
+
@tokens = source.is_a?(Array) ? source : Lexer.new(source, file: file).tokenize
|
|
19
|
+
@index = 0
|
|
20
|
+
@mode = mode
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @rbs () -> AST::Root
|
|
24
|
+
def parse
|
|
25
|
+
location = expect_keyword("class").location
|
|
26
|
+
class_name = parse_constant_path
|
|
27
|
+
superclass = accept(:<) ? parse_constant_path : nil
|
|
28
|
+
parse_pragmas
|
|
29
|
+
declarations = parse_declarations
|
|
30
|
+
expect_keyword("rule")
|
|
31
|
+
rules = parse_rules
|
|
32
|
+
expect_keyword("end")
|
|
33
|
+
user_code = parse_user_code
|
|
34
|
+
expect(:eof)
|
|
35
|
+
AST::Root.new(class_name: class_name, superclass: superclass, declarations: declarations,
|
|
36
|
+
rules: rules, user_code: user_code, loc: location)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
# @rbs () -> String
|
|
42
|
+
def parse_constant_path
|
|
43
|
+
parts = [token_string(expect(:identifier))]
|
|
44
|
+
parts << token_string(expect(:identifier)) while accept(:scope)
|
|
45
|
+
parts.join("::")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @rbs () -> String
|
|
49
|
+
def parse_symbol_name
|
|
50
|
+
token_string(expect_symbol)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @rbs () -> Token
|
|
54
|
+
def expect_symbol
|
|
55
|
+
return advance if %i[identifier literal].include?(current.type)
|
|
56
|
+
|
|
57
|
+
fail_expected("a grammar symbol")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @rbs (Array[String] values) -> Token
|
|
61
|
+
def expect_one_of(values)
|
|
62
|
+
return advance if current.type == :identifier && values.include?(current.value)
|
|
63
|
+
|
|
64
|
+
fail_expected(values.join(" or "))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @rbs (String value) -> bool
|
|
68
|
+
def keyword?(value)
|
|
69
|
+
current.type == :identifier && current.value == value
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @rbs (String value) -> Token
|
|
73
|
+
def expect_keyword(value)
|
|
74
|
+
return advance if keyword?(value)
|
|
75
|
+
|
|
76
|
+
fail_expected(value)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @rbs (Symbol type) -> (Token | false)
|
|
80
|
+
def accept(type)
|
|
81
|
+
return false unless current.type == type
|
|
82
|
+
|
|
83
|
+
advance
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @rbs (Symbol type) -> Token
|
|
87
|
+
def expect(type)
|
|
88
|
+
return advance if current.type == type
|
|
89
|
+
|
|
90
|
+
fail_expected(type)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @rbs () -> Token
|
|
94
|
+
def current
|
|
95
|
+
@tokens.fetch(@index)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @rbs () -> Token
|
|
99
|
+
def lookahead
|
|
100
|
+
@tokens.fetch(@index + 1) { @tokens.fetch(-1) }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @rbs () -> Token
|
|
104
|
+
def advance
|
|
105
|
+
token = current
|
|
106
|
+
@index += 1
|
|
107
|
+
token
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# @rbs () -> AST::user_code
|
|
111
|
+
def parse_user_code
|
|
112
|
+
blocks = Hash.new { |hash, key| hash[key] = Array.new(0) } #: AST::user_code
|
|
113
|
+
while current.type == :user_code
|
|
114
|
+
token = advance
|
|
115
|
+
value = token_user_code(token)
|
|
116
|
+
blocks[value[:name]] << AST::UserCode.new(name: value[:name], code: value[:code], loc: token.location)
|
|
117
|
+
end
|
|
118
|
+
blocks
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @rbs (Location location, String feature) -> void
|
|
122
|
+
def extended_only!(location, feature)
|
|
123
|
+
return if @mode == :extended
|
|
124
|
+
|
|
125
|
+
fail_at(location, "#{feature} require extended mode")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# @rbs (String | Symbol expected) -> bot
|
|
129
|
+
def fail_expected(expected)
|
|
130
|
+
fail_at(current.location, "expected #{expected}, got #{current.value || current.type}")
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# @rbs (Location location, String message) -> bot
|
|
134
|
+
def fail_at(location, message)
|
|
135
|
+
raise Ibex::Error, "#{location}: #{message}"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# @rbs (Token token) -> String
|
|
139
|
+
def token_string(token)
|
|
140
|
+
value = token.value
|
|
141
|
+
return value if value.is_a?(String)
|
|
142
|
+
|
|
143
|
+
fail_at(token.location, "expected text token")
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# @rbs (Token token) -> Integer
|
|
147
|
+
def token_integer(token)
|
|
148
|
+
value = token.value
|
|
149
|
+
return value if value.is_a?(Integer)
|
|
150
|
+
|
|
151
|
+
fail_at(token.location, "expected integer token")
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# @rbs (Token token) -> user_code_token
|
|
155
|
+
def token_user_code(token)
|
|
156
|
+
value = token.value
|
|
157
|
+
return value if value.is_a?(Hash)
|
|
158
|
+
|
|
159
|
+
fail_at(token.location, "expected user-code token")
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module Frontend
|
|
5
|
+
# Builds the same Grammar AST as the text frontend through a Ruby API.
|
|
6
|
+
module DSL
|
|
7
|
+
# @rbs (class_name: Object, ?superclass: Object?, ?file: String) { (Builder) -> void } -> AST::Root
|
|
8
|
+
def grammar(class_name:, superclass: nil, file: "(dsl)")
|
|
9
|
+
builder = Builder.new(class_name: class_name, superclass: superclass, file: file)
|
|
10
|
+
yield builder
|
|
11
|
+
builder.to_ast
|
|
12
|
+
end
|
|
13
|
+
module_function :grammar
|
|
14
|
+
|
|
15
|
+
# Mutable definition context whose output is an immutable-shape AST.
|
|
16
|
+
class Builder
|
|
17
|
+
# @rbs @class_name: String
|
|
18
|
+
# @rbs @superclass: String?
|
|
19
|
+
# @rbs @file: String
|
|
20
|
+
# @rbs @line: Integer
|
|
21
|
+
# @rbs @declarations: Array[AST::declaration]
|
|
22
|
+
# @rbs @rules: Array[AST::Rule]
|
|
23
|
+
# @rbs @user_code: AST::user_code
|
|
24
|
+
|
|
25
|
+
# @rbs (class_name: Object, superclass: Object?, file: String) -> void
|
|
26
|
+
def initialize(class_name:, superclass:, file:)
|
|
27
|
+
@class_name = class_name.to_s
|
|
28
|
+
@superclass = superclass&.to_s
|
|
29
|
+
@file = file
|
|
30
|
+
@line = 1
|
|
31
|
+
@declarations = [] #: Array[AST::declaration]
|
|
32
|
+
@rules = [] #: Array[AST::Rule]
|
|
33
|
+
@user_code = Hash.new { |hash, key| hash[key] = Array.new(0) } #: AST::user_code
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @rbs (*Object names) -> void
|
|
37
|
+
def token(*names)
|
|
38
|
+
@declarations << AST::Tokens.new(names: names.map(&:to_s), loc: next_location)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @rbs (*Object names) -> void
|
|
42
|
+
def options(*names)
|
|
43
|
+
@declarations << AST::Options.new(names: names.map(&:to_s), loc: next_location)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @rbs (Integer conflicts) -> void
|
|
47
|
+
def expect(conflicts)
|
|
48
|
+
@declarations << AST::Expect.new(conflicts: conflicts, loc: next_location)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @rbs (Object name) -> void
|
|
52
|
+
def start(name)
|
|
53
|
+
@declarations << AST::Start.new(name: name.to_s, loc: next_location)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @rbs (Object name, Object expression) -> void
|
|
57
|
+
def convert(name, expression)
|
|
58
|
+
location = next_location
|
|
59
|
+
pair = AST::Conversion.new(name: name.to_s, expression: expression.to_s, loc: location)
|
|
60
|
+
@declarations << AST::Convert.new(pairs: [pair], loc: location)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @rbs (?direction: Symbol) { (PrecedenceBuilder) -> void } -> void
|
|
64
|
+
def precedence(direction: :low_to_high)
|
|
65
|
+
location = next_location
|
|
66
|
+
builder = PrecedenceBuilder.new(self)
|
|
67
|
+
yield builder
|
|
68
|
+
@declarations << AST::Precedence.new(direction: direction, levels: builder.levels, loc: location)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @rbs (Object lhs) { (RuleBuilder) -> void } -> void
|
|
72
|
+
def rule(lhs)
|
|
73
|
+
location = next_location
|
|
74
|
+
builder = RuleBuilder.new(self, location)
|
|
75
|
+
yield builder
|
|
76
|
+
@rules << AST::Rule.new(lhs: lhs.to_s, alternatives: builder.alternatives, loc: location)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @rbs (Object name, Object code) -> void
|
|
80
|
+
def user_code(name, code)
|
|
81
|
+
key = name.to_s
|
|
82
|
+
@user_code[key] << AST::UserCode.new(name: key, code: code.to_s, loc: next_location)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @rbs (Object name, ?as: Object?) -> AST::SymbolReference
|
|
86
|
+
def ref(name, as: nil)
|
|
87
|
+
AST::SymbolReference.new(name: name.to_s, named_reference: as&.to_s, loc: next_location)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @rbs (Object item) -> AST::Optional
|
|
91
|
+
def optional(item) = AST::Optional.new(item: normalize_item(item), loc: next_location)
|
|
92
|
+
# @rbs (Object item) -> AST::Star
|
|
93
|
+
def star(item) = AST::Star.new(item: normalize_item(item), loc: next_location)
|
|
94
|
+
# @rbs (Object item) -> AST::Plus
|
|
95
|
+
def plus(item) = AST::Plus.new(item: normalize_item(item), loc: next_location)
|
|
96
|
+
|
|
97
|
+
# @rbs (*Object alternatives) -> AST::Group
|
|
98
|
+
def group(*alternatives)
|
|
99
|
+
normalized = alternatives.map { |alternative| Array(alternative).map { |item| normalize_item(item) } }
|
|
100
|
+
AST::Group.new(alternatives: normalized, loc: next_location)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @rbs (Object item, Object separator, ?nonempty: bool) -> AST::SeparatedList
|
|
104
|
+
def separated_list(item, separator, nonempty: false)
|
|
105
|
+
AST::SeparatedList.new(item: normalize_item(item), separator: normalize_item(separator),
|
|
106
|
+
nonempty: nonempty, loc: next_location)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @rbs (Object code) -> AST::InlineAction
|
|
110
|
+
def inline(code)
|
|
111
|
+
AST::InlineAction.new(code: code.to_s, loc: next_location)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# @rbs () -> AST::Root
|
|
115
|
+
def to_ast
|
|
116
|
+
location = Location.new(file: @file, line: 1, column: 1)
|
|
117
|
+
AST::Root.new(class_name: @class_name, superclass: @superclass, declarations: @declarations,
|
|
118
|
+
rules: @rules, user_code: @user_code, loc: location)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @rbs (Object item) -> AST::item
|
|
122
|
+
def normalize_item(item)
|
|
123
|
+
if item.respond_to?(:loc)
|
|
124
|
+
located_item = item #: AST::item
|
|
125
|
+
return located_item
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
ref(item)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# @rbs () -> Location
|
|
132
|
+
def next_location
|
|
133
|
+
location = Location.new(file: @file, line: @line, column: 1)
|
|
134
|
+
@line += 1
|
|
135
|
+
location
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Collects ordered associativity levels.
|
|
140
|
+
class PrecedenceBuilder
|
|
141
|
+
attr_reader :levels #: Array[AST::PrecedenceLevel]
|
|
142
|
+
|
|
143
|
+
# @rbs @grammar: Builder
|
|
144
|
+
|
|
145
|
+
# @rbs (Builder grammar) -> void
|
|
146
|
+
def initialize(grammar)
|
|
147
|
+
@grammar = grammar
|
|
148
|
+
@levels = [] #: Array[AST::PrecedenceLevel]
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# @rbs (*Object symbols) -> void
|
|
152
|
+
def left(*symbols) = add_level(:left, symbols)
|
|
153
|
+
|
|
154
|
+
# @rbs (*Object symbols) -> void
|
|
155
|
+
def right(*symbols) = add_level(:right, symbols)
|
|
156
|
+
|
|
157
|
+
# @rbs (*Object symbols) -> void
|
|
158
|
+
def nonassoc(*symbols) = add_level(:nonassoc, symbols)
|
|
159
|
+
|
|
160
|
+
private
|
|
161
|
+
|
|
162
|
+
# @rbs (Symbol associativity, Array[Object] symbols) -> void
|
|
163
|
+
def add_level(associativity, symbols)
|
|
164
|
+
@levels << AST::PrecedenceLevel.new(associativity: associativity, symbols: symbols.map(&:to_s),
|
|
165
|
+
loc: @grammar.next_location)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Collects alternatives for one nonterminal.
|
|
170
|
+
class RuleBuilder
|
|
171
|
+
attr_reader :alternatives #: Array[AST::Alternative]
|
|
172
|
+
|
|
173
|
+
# @rbs @grammar: Builder
|
|
174
|
+
# @rbs @default_location: Location
|
|
175
|
+
|
|
176
|
+
# @rbs (Builder grammar, Location default_location) -> void
|
|
177
|
+
def initialize(grammar, default_location)
|
|
178
|
+
@grammar = grammar
|
|
179
|
+
@default_location = default_location
|
|
180
|
+
@alternatives = [] #: Array[AST::Alternative]
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# @rbs (*Object items, ?action: Object?, ?precedence: Object?) -> void
|
|
184
|
+
def alt(*items, action: nil, precedence: nil)
|
|
185
|
+
location = @grammar.next_location || @default_location
|
|
186
|
+
normalized = items.map { |item| @grammar.normalize_item(item) }
|
|
187
|
+
action_node = action && AST::InlineAction.new(code: action.to_s, loc: location)
|
|
188
|
+
alternative = AST::Alternative.new(
|
|
189
|
+
items: normalized, action: action_node, precedence: precedence&.to_s, loc: location
|
|
190
|
+
)
|
|
191
|
+
@alternatives << alternative
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|