rprec 1.0.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 +31 -0
- data/.yardopts +5 -0
- data/CHANGELOG.md +10 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +19 -0
- data/Rakefile +17 -0
- data/Steepfile +14 -0
- data/lib/rprec/array_stream.rb +33 -0
- data/lib/rprec/dsl.rb +140 -0
- data/lib/rprec/grammar.rb +102 -0
- data/lib/rprec/op.rb +37 -0
- data/lib/rprec/parse_error.rb +19 -0
- data/lib/rprec/prec.rb +215 -0
- data/lib/rprec/regexp_lexer.rb +82 -0
- data/lib/rprec/stream.rb +42 -0
- data/lib/rprec/token.rb +24 -0
- data/lib/rprec/version.rb +6 -0
- data/lib/rprec.rb +141 -0
- data/rbs_collection.lock.yaml +12 -0
- data/rbs_collection.yaml +20 -0
- data/sig/rprec/array_stream.rbs +7 -0
- data/sig/rprec/dsl.rbs +26 -0
- data/sig/rprec/grammar.rbs +13 -0
- data/sig/rprec/op.rbs +15 -0
- data/sig/rprec/parse_error.rbs +6 -0
- data/sig/rprec/prec.rbs +21 -0
- data/sig/rprec/regexp_lexer.rbs +19 -0
- data/sig/rprec/stream.rbs +11 -0
- data/sig/rprec/token.rbs +9 -0
- data/sig/rprec/version.rbs +3 -0
- data/sig/rprec.rbs +4 -0
- metadata +82 -0
data/lib/rprec/prec.rb
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RPrec
|
4
|
+
# `Prec` is an operator precedence.
|
5
|
+
class Prec
|
6
|
+
# @param name [Symbol]
|
7
|
+
# @param succs [Array<Symbol>]
|
8
|
+
# @param ops [Array<Op>]
|
9
|
+
def initialize(name, succs, ops)
|
10
|
+
@name = name
|
11
|
+
@succs = succs
|
12
|
+
@ops = ops
|
13
|
+
end
|
14
|
+
|
15
|
+
# @dynamic name, succs, ops
|
16
|
+
|
17
|
+
# @return [Symbol]
|
18
|
+
attr_reader :name
|
19
|
+
# @return [Array<Symbol>]
|
20
|
+
attr_reader :succs
|
21
|
+
# @return [Array<RPrec::Op>]
|
22
|
+
attr_reader :ops
|
23
|
+
|
24
|
+
# @api private
|
25
|
+
# @return [Hash{String => RPrec::Op}]
|
26
|
+
attr_reader :prefix_table
|
27
|
+
# @api private
|
28
|
+
# @return [Hash{String => RPrec::Op}]
|
29
|
+
attr_reader :postfix_table
|
30
|
+
|
31
|
+
# @api private
|
32
|
+
# @param grammar [RPrec::Grammar]
|
33
|
+
# @return [void]
|
34
|
+
def setup(grammar)
|
35
|
+
return unless @prefix_table.nil? && @postfix_table.nil?
|
36
|
+
|
37
|
+
@prefix_table = {}
|
38
|
+
@postfix_table = {}
|
39
|
+
|
40
|
+
@ops.each do |op|
|
41
|
+
case op.type
|
42
|
+
when :prefix, :non_assoc_prefix, :closed
|
43
|
+
raise ArgumentError, "Conflict with the key token '#{op.key}'" if @prefix_table.include?(op.key)
|
44
|
+
|
45
|
+
@prefix_table[op.key] = op
|
46
|
+
when :postfix, :non_assoc_postfix, :left_assoc, :right_assoc, :non_assoc
|
47
|
+
raise ArgumentError, "Conflict with the key token '#{op.key}'" if @postfix_table.include?(op.key)
|
48
|
+
|
49
|
+
@postfix_table[op.key] = op
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
grammar.setup(@succs)
|
54
|
+
|
55
|
+
@ops.each do |op|
|
56
|
+
parts = op.parts.filter_map { _1.is_a?(Symbol) ? _1 : nil }
|
57
|
+
grammar.setup(parts)
|
58
|
+
end
|
59
|
+
|
60
|
+
@all_prefix_keys = @prefix_table.keys
|
61
|
+
@all_postfix_keys = @postfix_table.keys
|
62
|
+
@prefix_keys = @prefix_table.filter { |_, op| op.type == :prefix }.keys
|
63
|
+
@right_assoc_keys = @postfix_table.filter { |_, op| op.type == :right_assoc }.keys
|
64
|
+
@postfix_and_left_assoc_keys = @postfix_table.filter { |_, op| op.type == :postfix || op.type == :left_assoc }.keys
|
65
|
+
end
|
66
|
+
|
67
|
+
# @api private
|
68
|
+
# @param grammar [RPrec::Grammar]
|
69
|
+
# @param stream [RPrec::Stream]
|
70
|
+
# @return [Object, nil]
|
71
|
+
def parse(grammar, stream)
|
72
|
+
op = @prefix_table[stream.current.type]
|
73
|
+
|
74
|
+
if op
|
75
|
+
key_token = stream.current
|
76
|
+
stream.next
|
77
|
+
|
78
|
+
case op.type
|
79
|
+
when :prefix
|
80
|
+
parts = grammar.parse_parts(op.parts, stream)
|
81
|
+
return parse_prefix_and_right_assoc(grammar, stream, [[op, [key_token, *parts]]])
|
82
|
+
|
83
|
+
when :non_assoc_prefix
|
84
|
+
parts = grammar.parse_parts(op.parts, stream)
|
85
|
+
node = grammar.parse_precs(@succs, stream)
|
86
|
+
return op.build(key_token, *parts, node)
|
87
|
+
|
88
|
+
when :closed
|
89
|
+
parts = grammar.parse_parts(op.parts, stream)
|
90
|
+
return op.build(key_token, *parts)
|
91
|
+
|
92
|
+
else raise ScriptError, 'Unreachable'
|
93
|
+
end
|
94
|
+
end
|
95
|
+
stream.expected(@all_prefix_keys)
|
96
|
+
|
97
|
+
node = grammar.parse_precs(@succs, stream)
|
98
|
+
key_token = stream.current
|
99
|
+
op = @postfix_table[key_token.type]
|
100
|
+
unless op
|
101
|
+
stream.expected(@all_postfix_keys)
|
102
|
+
return node
|
103
|
+
end
|
104
|
+
stream.next
|
105
|
+
|
106
|
+
case op.type
|
107
|
+
when :postfix
|
108
|
+
parts = grammar.parse_parts(op.parts, stream)
|
109
|
+
node = op.build(node, key_token, *parts)
|
110
|
+
parse_postfix_and_left_assoc(grammar, stream, node)
|
111
|
+
|
112
|
+
when :non_assoc_postfix
|
113
|
+
parts = grammar.parse_parts(op.parts, stream)
|
114
|
+
op.build(node, key_token, *parts)
|
115
|
+
|
116
|
+
when :left_assoc
|
117
|
+
parts = grammar.parse_parts(op.parts, stream)
|
118
|
+
right = grammar.parse_precs(@succs, stream)
|
119
|
+
node = op.build(node, key_token, *parts, right)
|
120
|
+
parse_postfix_and_left_assoc(grammar, stream, node)
|
121
|
+
|
122
|
+
when :right_assoc
|
123
|
+
parts = grammar.parse_parts(op.parts, stream)
|
124
|
+
parse_prefix_and_right_assoc(grammar, stream, [[op, [node, key_token, *parts]]])
|
125
|
+
|
126
|
+
when :non_assoc
|
127
|
+
parts = grammar.parse_parts(op.parts, stream)
|
128
|
+
right = grammar.parse_precs(@succs, stream)
|
129
|
+
op.build(node, key_token, *parts, right)
|
130
|
+
|
131
|
+
else raise ScriptError, 'Unreachable'
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# @param grammar [RPrec::Grammar]
|
136
|
+
# @param stream [RPrec::Stream]
|
137
|
+
# @param stack [Array<Array<RPrec::Op, RPrec::Token, Object>>]
|
138
|
+
# @return [Object]
|
139
|
+
private def parse_prefix_and_right_assoc(grammar, stream, stack)
|
140
|
+
continue = true
|
141
|
+
# TODO: this untyped var seems ugly. How to fix it?
|
142
|
+
# @type var node: untyped
|
143
|
+
node = nil
|
144
|
+
while continue
|
145
|
+
key_token = stream.current
|
146
|
+
op = @prefix_table[key_token.type]
|
147
|
+
while op.is_a?(Op) && op.type == :prefix
|
148
|
+
stream.next
|
149
|
+
parts = grammar.parse_parts(op.parts, stream)
|
150
|
+
stack << [op, [key_token, *parts]]
|
151
|
+
key_token = stream.current
|
152
|
+
op = @prefix_table[key_token.type]
|
153
|
+
end
|
154
|
+
stream.expected(@prefix_keys)
|
155
|
+
|
156
|
+
node = grammar.parse_precs(@succs, stream)
|
157
|
+
key_token = stream.current
|
158
|
+
op = @postfix_table[key_token.type]
|
159
|
+
if op.is_a?(Op) && op.type == :right_assoc
|
160
|
+
stream.next
|
161
|
+
parts = grammar.parse_parts(op.parts, stream)
|
162
|
+
stack << [op, [node, key_token, *parts]]
|
163
|
+
continue = true
|
164
|
+
else
|
165
|
+
stream.expected(@right_assoc_keys)
|
166
|
+
continue = false
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
stack.reverse_each do |(parsed_op, args)|
|
171
|
+
node = parsed_op.build(*args, node)
|
172
|
+
end
|
173
|
+
node
|
174
|
+
end
|
175
|
+
|
176
|
+
# @param grammar [RPrec::Grammar]
|
177
|
+
# @param stream [RPrec::Stream]
|
178
|
+
# @param node [Object]
|
179
|
+
# @return [Object]
|
180
|
+
private def parse_postfix_and_left_assoc(grammar, stream, node)
|
181
|
+
key_token = stream.current
|
182
|
+
op = @postfix_table[key_token.type]
|
183
|
+
while op.is_a?(Op) && (op.type == :postfix || op.type == :left_assoc)
|
184
|
+
stream.next
|
185
|
+
case op.type
|
186
|
+
when :postfix
|
187
|
+
parts = grammar.parse_parts(op.parts, stream)
|
188
|
+
node = op.build(node, key_token, *parts)
|
189
|
+
when :left_assoc
|
190
|
+
parts = grammar.parse_parts(op.parts, stream)
|
191
|
+
right = grammar.parse_precs(@succs, stream)
|
192
|
+
node = op.build(node, key_token, *parts, right)
|
193
|
+
end
|
194
|
+
key_token = stream.current
|
195
|
+
op = @postfix_table[key_token.type]
|
196
|
+
end
|
197
|
+
stream.expected(@postfix_and_left_assoc_keys)
|
198
|
+
node
|
199
|
+
end
|
200
|
+
|
201
|
+
# @return [String]
|
202
|
+
def inspect
|
203
|
+
result = "prec #{name.inspect}"
|
204
|
+
result << " => #{succs.inspect}" unless succs.empty?
|
205
|
+
unless ops.empty?
|
206
|
+
result << " do\n"
|
207
|
+
ops.each do |op|
|
208
|
+
result << " #{op.inspect}\n"
|
209
|
+
end
|
210
|
+
result << 'end'
|
211
|
+
end
|
212
|
+
result
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'stream'
|
4
|
+
|
5
|
+
module RPrec
|
6
|
+
# `RegexpLexer` is a lexer using regexp patterns.
|
7
|
+
class RegexpLexer
|
8
|
+
# @param skip [Regexp]
|
9
|
+
# @param pattern [Regexp]
|
10
|
+
# @param block [Proc]
|
11
|
+
def initialize(skip:, pattern:, &block)
|
12
|
+
@skip = /\G#{skip}/
|
13
|
+
@pattern = /\G#{pattern}/
|
14
|
+
@block = block
|
15
|
+
end
|
16
|
+
|
17
|
+
# @dynamic skip, pattern, block
|
18
|
+
|
19
|
+
# @return [Regexp]
|
20
|
+
attr_reader :skip
|
21
|
+
# @return [Regexp]
|
22
|
+
attr_reader :pattern
|
23
|
+
# @return [Proc]
|
24
|
+
attr_reader :block
|
25
|
+
|
26
|
+
# @param source [String]
|
27
|
+
# @return [RPrec::RegexpStream]
|
28
|
+
def lex(source)
|
29
|
+
RegexpStream.new(self, source)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# `RegexpStream` is a stream generated by a `RegexpLexer`.
|
34
|
+
#
|
35
|
+
# @api private
|
36
|
+
class RegexpStream < Stream
|
37
|
+
# @param lexer [RPrec::Lexer]
|
38
|
+
# @param source [String]
|
39
|
+
def initialize(lexer, source)
|
40
|
+
super()
|
41
|
+
@lexer = lexer
|
42
|
+
@source = source
|
43
|
+
@offset = 0
|
44
|
+
self.next
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [RPrec::Token]
|
48
|
+
attr_reader :current
|
49
|
+
|
50
|
+
# @return [void]
|
51
|
+
def next
|
52
|
+
super
|
53
|
+
|
54
|
+
skip = @source.match(@lexer.skip, @offset)
|
55
|
+
@offset += skip.to_s.size if skip
|
56
|
+
|
57
|
+
if eof?
|
58
|
+
@current = Token.new('EOF', loc: @offset...@offset)
|
59
|
+
return
|
60
|
+
end
|
61
|
+
|
62
|
+
match = @source.match(@lexer.pattern, @offset)
|
63
|
+
raise ParseError.new("Unexpected character: #{@source[@offset].inspect}", loc: @offset...@offset + 1) unless match
|
64
|
+
|
65
|
+
type_and_value = @lexer.block.call(match)
|
66
|
+
type, value =
|
67
|
+
if type_and_value.is_a?(Array)
|
68
|
+
[type_and_value[0], type_and_value[1]]
|
69
|
+
else
|
70
|
+
[type_and_value, nil]
|
71
|
+
end
|
72
|
+
loc = @offset...@offset + match.to_s.size
|
73
|
+
@offset += match.to_s.size
|
74
|
+
@current = Token.new(type, value, loc:)
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return [Boolean]
|
78
|
+
def eof?
|
79
|
+
@source.size <= @offset
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/rprec/stream.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RPrec
|
4
|
+
# `Stream` is a token stream.
|
5
|
+
#
|
6
|
+
# @abstract `current`, `next` and `eof?` must be implemented.
|
7
|
+
class Stream
|
8
|
+
def initialize
|
9
|
+
@expected = []
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [RPrec::Token]
|
13
|
+
def current
|
14
|
+
raise ScriptError, 'Not implemented'
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [void]
|
18
|
+
def next
|
19
|
+
@expected = []
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Boolean]
|
23
|
+
def eof?
|
24
|
+
raise ScriptError, 'Not implemented'
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param tokens [Array<String>]
|
28
|
+
# @return [void]
|
29
|
+
def expected(tokens)
|
30
|
+
@expected += tokens
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [void]
|
34
|
+
def unexpected
|
35
|
+
token = current
|
36
|
+
raise ParseError.new("Unexpected token '#{token.type}'", loc: token.loc) if @expected.empty?
|
37
|
+
|
38
|
+
expected = @expected.sort.map { |tok| "'#{tok}'" }.join(', ')
|
39
|
+
raise ParseError.new("Expected token(s) #{expected}, but the unexpected token '#{token.type}' comes", loc: token.loc)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/rprec/token.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RPrec
|
4
|
+
# `Token` is a token.
|
5
|
+
class Token
|
6
|
+
# @param type [String]
|
7
|
+
# @param value [Object]
|
8
|
+
# @param loc [Range, nil]
|
9
|
+
def initialize(type, value = nil, loc: nil)
|
10
|
+
@type = type
|
11
|
+
@value = value
|
12
|
+
@loc = loc
|
13
|
+
end
|
14
|
+
|
15
|
+
# @dynamic type, value, loc
|
16
|
+
|
17
|
+
# @return [String]
|
18
|
+
attr_reader :type
|
19
|
+
# @return [Object]
|
20
|
+
attr_reader :value
|
21
|
+
# @return [Range, nil]
|
22
|
+
attr_reader :loc
|
23
|
+
end
|
24
|
+
end
|
data/lib/rprec.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# `RPrec` is an implementation of operator-precedence parsing.
|
4
|
+
# The operator-precedence parsing is also known as Pratt parsing.
|
5
|
+
# This implementation is extended for mixfix operators which are operators
|
6
|
+
# consists of multi parts (e.g. `... ? ... : ...` operator).
|
7
|
+
#
|
8
|
+
# ## Usage
|
9
|
+
#
|
10
|
+
# `RPrec::Grammar` is a grammar of the target language and also behaves
|
11
|
+
# its parser.
|
12
|
+
#
|
13
|
+
# We have a `RPrec::DSL` to easily build a `RPrec::Grammar` instance.
|
14
|
+
# The below is an example grammar for simple arithmetic expressions.
|
15
|
+
#
|
16
|
+
# ```
|
17
|
+
# grammar = RPrec::DSL.build do
|
18
|
+
# prec :main => :add_sub
|
19
|
+
#
|
20
|
+
# prec :add_sub => :mul_div do
|
21
|
+
# left_assoc '+' do |left, op_tok, right|
|
22
|
+
# [:add, left, right]
|
23
|
+
# end
|
24
|
+
# left_assoc '-' do |left, op_tok, right|
|
25
|
+
# [:sub, left, right]
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# prec :mul_div => :unary do
|
30
|
+
# left_assoc '*' do |left, op_tok, right|
|
31
|
+
# [:mul, left, right]
|
32
|
+
# end
|
33
|
+
# left_assoc '/' do |left, op_tok, right|
|
34
|
+
# [:div, left, right]
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# prec :unary => :atom do
|
39
|
+
# prefix '+' do |op_tok, expr|
|
40
|
+
# [:plus, expr]
|
41
|
+
# end
|
42
|
+
# prefix '-' do |op_tok, expr|
|
43
|
+
# [:minus, expr]
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# prec :atom do
|
48
|
+
# closed 'nat' do |nat_tok|
|
49
|
+
# [:nat, nat_tok.value]
|
50
|
+
# end
|
51
|
+
# closed '(', :add_sub, ')' do |lpar_tok, expr, rpar_tok|
|
52
|
+
# [:paren, expr]
|
53
|
+
# end
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
# ```
|
57
|
+
#
|
58
|
+
# Here, `prec` is the only available method in the `RPrec::DSL` context.
|
59
|
+
# This method is for defining a precedence and takes the argument of one of the
|
60
|
+
# following forms: `name => succs`, `name => succs`, and `name`.
|
61
|
+
# `prec name => succ` is an alias for `prec name => [succ]` and `prec name` is
|
62
|
+
# an alias for `prec name => []`. `succs` is an array of precedence names which
|
63
|
+
# have higher binding powers than this precedence's. Precedence names must be
|
64
|
+
# symbols.
|
65
|
+
#
|
66
|
+
# `prec` also can take a block to define operators belonging to this
|
67
|
+
# precedence. Operators can be defined by the following eight methods:
|
68
|
+
#
|
69
|
+
# - `prefix key, *parts, &block`
|
70
|
+
# - `postfix key, *parts, &block`
|
71
|
+
# - `non_assoc_prefix key, *parts, &block`
|
72
|
+
# - `non_assoc_postfix key, *parts, &block`
|
73
|
+
# - `closed key, *parts, &block`
|
74
|
+
# - `left_assoc key, *parts, &block`
|
75
|
+
# - `right_assoc key, *parts, &block`
|
76
|
+
# - `non_assoc key, *parts, &block`
|
77
|
+
#
|
78
|
+
# They define operators with their own name associativity. The first argument
|
79
|
+
# `key` specifies the key token that is used to determine which the operator to
|
80
|
+
# be parsed. The rest arguments are `parts`: they consist of precedence names
|
81
|
+
# or tokens, meaning that they are required after the key token. They can take
|
82
|
+
# a block, which is an action called on parsed.
|
83
|
+
#
|
84
|
+
# To parse a string using this grammar, we can use the `RPrec::Grammar#parse`
|
85
|
+
# method. This method takes a `RPrec::Stream` object. `RPrec::Stream`
|
86
|
+
# is a token stream (an array) with convenient methods for parsing. It can be
|
87
|
+
# created easily by using `RPrec::ArrayStream` class.
|
88
|
+
#
|
89
|
+
# ```
|
90
|
+
# # A stream for the source `"1 + 2 * 3"`.
|
91
|
+
# stream = RPrec::ArrayStream.new([
|
92
|
+
# RPrec::Token.new('nat', 1),
|
93
|
+
# RPrec::Token.new('+'),
|
94
|
+
# RPrec::Token.new('nat', 2),
|
95
|
+
# RPrec::Token.new('*'),
|
96
|
+
# RPrec::Token.new('nat', 3),
|
97
|
+
# ])
|
98
|
+
#
|
99
|
+
# grammar.parse(stream)
|
100
|
+
# # => [:add, [:nat, 1], [:mul, [:nat, 2], [:nat, 3]]]
|
101
|
+
# ```
|
102
|
+
#
|
103
|
+
# ## Limitations
|
104
|
+
#
|
105
|
+
# This implementation assumes that an operator can be determined by the current
|
106
|
+
# token on parsing. If a grammar is not so, this reports the "Conflict with the
|
107
|
+
# key token ..." error.
|
108
|
+
#
|
109
|
+
# For example, the following grammar is rejected because the key token `'['`
|
110
|
+
# is conflicted.
|
111
|
+
#
|
112
|
+
# ```
|
113
|
+
# grammar = RPrec::DSL.build do
|
114
|
+
# prec :range do
|
115
|
+
# closed '[', :int, ',', :int, ']'
|
116
|
+
# closed '[', :int, ',', :int, ')'
|
117
|
+
# end
|
118
|
+
# end
|
119
|
+
# # raises "Conflict with the key token '['"
|
120
|
+
# ```
|
121
|
+
#
|
122
|
+
# ## References
|
123
|
+
#
|
124
|
+
# - [Operator-precedence parser - Wikipedia](https://en.wikipedia.org/wiki/Operator-precedence_parser)
|
125
|
+
# - [Parsing Mixfix Operators | Springer Link](https://link.springer.com/chapter/10.1007/978-3-642-24452-0_5)
|
126
|
+
module RPrec
|
127
|
+
# `Error` is an error for `RPrec`.
|
128
|
+
class Error < StandardError
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
require_relative 'rprec/array_stream'
|
133
|
+
require_relative 'rprec/dsl'
|
134
|
+
require_relative 'rprec/grammar'
|
135
|
+
require_relative 'rprec/op'
|
136
|
+
require_relative 'rprec/parse_error'
|
137
|
+
require_relative 'rprec/prec'
|
138
|
+
require_relative 'rprec/regexp_lexer'
|
139
|
+
require_relative 'rprec/stream'
|
140
|
+
require_relative 'rprec/token'
|
141
|
+
require_relative 'rprec/version'
|
data/rbs_collection.yaml
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
sources:
|
2
|
+
- type: git
|
3
|
+
name: ruby/gem_rbs_collection
|
4
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
5
|
+
revision: main
|
6
|
+
repo_dir: gems
|
7
|
+
|
8
|
+
path: .gem_rbs_collection
|
9
|
+
|
10
|
+
gems:
|
11
|
+
- name: rake
|
12
|
+
ignore: true
|
13
|
+
- name: rbs
|
14
|
+
ignore: true
|
15
|
+
- name: redcarpet
|
16
|
+
ignore: true
|
17
|
+
- name: steep
|
18
|
+
ignore: true
|
19
|
+
- name: yard
|
20
|
+
ignore: true
|
data/sig/rprec/dsl.rbs
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module RPrec
|
2
|
+
class DSL[T, R < Object]
|
3
|
+
def self.build: [T, R < Object] (?Symbol) { (DSL[T, R]) [self: DSL[T, R]] -> void } -> Grammar[T, R]
|
4
|
+
def initialize: () -> void
|
5
|
+
attr_reader precs: Hash[Symbol, Prec[T, R]]
|
6
|
+
def prec:
|
7
|
+
(Symbol | Hash[Symbol, Symbol | Array[Symbol]]) ?{ (PrecDSL[T, R]) [self: PrecDSL[T, R]] -> void }
|
8
|
+
-> void
|
9
|
+
|
10
|
+
class PrecDSL[T, R < Object]
|
11
|
+
def self.build:
|
12
|
+
[T, R < Object] (Symbol, Array[Symbol]) ?{ (PrecDSL[T, R]) [self: PrecDSL[T, R]] -> void }
|
13
|
+
-> Prec[T, R]
|
14
|
+
def initialize: () -> void
|
15
|
+
attr_reader ops: Array[Op[T, R]]
|
16
|
+
def prefix: (String, *(String | Symbol)) { (*(Token[T] | R)) -> R } -> void
|
17
|
+
def postfix: (String, *(String | Symbol)) { (*(Token[T] | R)) -> R } -> void
|
18
|
+
def non_assoc_prefix: (String, *(String | Symbol)) { (*(Token[T] | R)) -> R } -> void
|
19
|
+
def non_assoc_postfix: (String, *(String | Symbol)) { (*(Token[T] | R)) -> R } -> void
|
20
|
+
def closed: (String, *(String | Symbol)) { (*(Token[T] | R)) -> R } -> void
|
21
|
+
def left_assoc: (String, *(String | Symbol)) { (*(Token[T] | R)) -> R } -> void
|
22
|
+
def right_assoc: (String, *(String | Symbol)) { (*(Token[T] | R)) -> R } -> void
|
23
|
+
def non_assoc: (String, *(String | Symbol)) { (*(Token[T] | R)) -> R } -> void
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module RPrec
|
2
|
+
class Grammar[T, R < Object]
|
3
|
+
def initialize: (Symbol, Hash[Symbol, Prec[T, R]]) -> void
|
4
|
+
def []: (Symbol) -> Prec[T, R]
|
5
|
+
def setup: (?Array[Symbol]) -> void
|
6
|
+
def parse: (Stream[T], ?check_eof: bool) -> R
|
7
|
+
def parse_prec: (Symbol, Stream[T]) -> R?
|
8
|
+
def parse_precs: (Array[Symbol], Stream[T]) -> R
|
9
|
+
def parse_parts: (Array[String | Symbol], Stream[T]) -> Array[Token[T] | R]
|
10
|
+
@main: Symbol
|
11
|
+
@precs: Hash[Symbol, Prec[T, R]]
|
12
|
+
end
|
13
|
+
end
|
data/sig/rprec/op.rbs
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
module RPrec
|
2
|
+
type op_type =
|
3
|
+
:prefix | :postfix | :non_assoc_prefix | :non_assoc_postfix | :closed |
|
4
|
+
:left_assoc | :right_assoc | :non_assoc
|
5
|
+
|
6
|
+
class Op[T, R < Object]
|
7
|
+
def initialize:
|
8
|
+
(op_type, String, Array[String | Symbol], ^(*(Token[T] | R)) -> R) -> void
|
9
|
+
attr_reader type: op_type
|
10
|
+
attr_reader key: String
|
11
|
+
attr_reader parts: Array[String | Symbol]
|
12
|
+
def build: (*(Token[T] | R)) -> R
|
13
|
+
@build: ^(*(Token[T] | R)) -> R
|
14
|
+
end
|
15
|
+
end
|
data/sig/rprec/prec.rbs
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module RPrec
|
2
|
+
class Prec[T, R < Object]
|
3
|
+
def initialize: (Symbol, Array[Symbol], Array[Op[T, R]]) -> void
|
4
|
+
attr_reader name: Symbol
|
5
|
+
attr_reader succs: Array[Symbol]
|
6
|
+
attr_reader ops: Array[Op[T, R]]
|
7
|
+
def setup: (Grammar[T, R]) -> void
|
8
|
+
def parse: (Grammar[T, R], Stream[T]) -> R?
|
9
|
+
private def parse_prefix_and_right_assoc:
|
10
|
+
(Grammar[T, R], Stream[T], Array[[Op[T, R], Array[Token[T] | R]]]) -> R
|
11
|
+
private def parse_postfix_and_left_assoc:
|
12
|
+
(Grammar[T, R], Stream[T], R) -> R
|
13
|
+
@prefix_table: Hash[String, Op[T, R]]
|
14
|
+
@postfix_table: Hash[String, Op[T, R]]
|
15
|
+
@all_prefix_keys: Array[String]
|
16
|
+
@all_postfix_keys: Array[String]
|
17
|
+
@prefix_keys: Array[String]
|
18
|
+
@right_assoc_keys: Array[String]
|
19
|
+
@postfix_and_left_assoc_keys: Array[String]
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module RPrec
|
2
|
+
class RegexpLexer[T]
|
3
|
+
def initialize:
|
4
|
+
(skip: Regexp, pattern: Regexp) { (MatchData) -> (String | [String, T]) }
|
5
|
+
-> void
|
6
|
+
def lex: (String) -> Stream[T]
|
7
|
+
attr_reader skip: Regexp
|
8
|
+
attr_reader pattern: Regexp
|
9
|
+
attr_reader block: ^(MatchData) -> (String | [String, T])
|
10
|
+
end
|
11
|
+
|
12
|
+
class RegexpStream[T] < Stream[T]
|
13
|
+
def initialize: (RegexpLexer[T], String) -> void
|
14
|
+
@lexer: RegexpLexer[T]
|
15
|
+
@source: String
|
16
|
+
@offset: Integer
|
17
|
+
@current: Token[T]
|
18
|
+
end
|
19
|
+
end
|
data/sig/rprec/token.rbs
ADDED