rprec 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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