rupkl 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.
@@ -0,0 +1,309 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuPkl
4
+ class Parser
5
+ #
6
+ # Boolean literal
7
+ #
8
+ define_parser do
9
+ rule(:boolean_literal) do
10
+ kw_true.as(:true_value) | kw_false.as(:false_value)
11
+ end
12
+ end
13
+
14
+ define_transform do
15
+ rule(true_value: simple(:v)) do
16
+ Node::Boolean.new(true, node_position(v))
17
+ end
18
+
19
+ rule(false_value: simple(:v)) do
20
+ Node::Boolean.new(false, node_position(v))
21
+ end
22
+ end
23
+
24
+ #
25
+ # Integer literal
26
+ #
27
+ define_parser do
28
+ rule(:bin_literal) do
29
+ str('0b') >> match('[01]') >> match('[_01]').repeat
30
+ end
31
+
32
+ rule(:oct_literal) do
33
+ str('0o') >> match('[0-7]') >> match('[_0-7]').repeat
34
+ end
35
+
36
+ rule(:dec_literal) do
37
+ match('[\d]') >> match('[_\d]').repeat
38
+ end
39
+
40
+ rule(:hex_literal) do
41
+ str('0x') >> match('[\h]') >> match('[_\h]').repeat
42
+ end
43
+
44
+ rule(:integer_literal) do
45
+ (
46
+ bin_literal | oct_literal | hex_literal | dec_literal
47
+ ).as(:integer_literal)
48
+ end
49
+ end
50
+
51
+ define_transform do
52
+ rule(integer_literal: simple(:v)) do
53
+ base = { 'b' => 2, 'o' => 8, 'x' => 16 }.fetch(v.to_s[1], 10)
54
+ value = v.to_s.tr('_', '').to_i(base)
55
+ Node::Integer.new(value, node_position(v))
56
+ end
57
+ end
58
+
59
+ #
60
+ # Float literal
61
+ #
62
+ define_parser do
63
+ rule(:float_literal) do
64
+ (
65
+ (dec_literal.maybe >> str('.') >> dec_literal >> exponent.maybe) |
66
+ (dec_literal >> exponent)
67
+ ).as(:float_literal)
68
+ end
69
+
70
+ rule(:exponent) do
71
+ match('[eE]') >> (match('[+-]') >> str('_').maybe).maybe >> dec_literal
72
+ end
73
+ end
74
+
75
+ define_transform do
76
+ rule(float_literal: simple(:f)) do
77
+ v = f.to_s.tr('_', '').to_f
78
+ Node::Float.new(v, node_position(f))
79
+ end
80
+ end
81
+
82
+ #
83
+ # String literal
84
+ #
85
+ ESCAPED_CHARS =
86
+ {
87
+ 't' => "\t", 'n' => "\n", 'r' => "\r",
88
+ '"' => '"', '\\' => '\\'
89
+ }.freeze
90
+
91
+ define_parser do
92
+ rule(:ss_empty_literal) do
93
+ ss_bq(false).as(:ss_bq) >> ss_eq('').as(:ss_eq)
94
+ end
95
+
96
+ rule(:ss_literal) do
97
+ ss_bq(false).as(:ss_bq) >>
98
+ ss_portions('').as(:ss_portions) >> ss_eq('').as(:ss_eq)
99
+ end
100
+
101
+ rule(:ss_empty_literal_custom_delimiters) do
102
+ ss_bq(true).capture(:bq).as(:ss_bq) >>
103
+ dynamic do |_, c|
104
+ ss_eq(c.captures[:bq].to_s[0..-2]).as(:ss_eq)
105
+ end
106
+ end
107
+
108
+ rule(:ss_literal_custom_delimiters) do
109
+ ss_bq(true).capture(:bq).as(:ss_bq) >>
110
+ dynamic do |_, c|
111
+ pounds = c.captures[:bq].to_s[0..-2]
112
+ ss_portions(pounds).as(:ss_portions) >> ss_eq(pounds).as(:ss_eq)
113
+ end
114
+ end
115
+
116
+ rule(:ms_empty_literal) do
117
+ ms_bq(false).as(:ms_bq) >> ms_eq('', false).as(:ms_eq)
118
+ end
119
+
120
+ rule(:ms_literal) do
121
+ ms_bq(false).as(:ms_bq) >>
122
+ ms_portions('').as(:ms_portions) >> ms_eq('', false).as(:ms_eq)
123
+ end
124
+
125
+ rule(:ms_empty_literal_custom_delimiters) do
126
+ ms_bq(true).capture(:bq).as(:ms_bq) >>
127
+ dynamic do |_, c|
128
+ ms_eq(c.captures[:bq].to_s[0..-4], false).as(:ms_eq)
129
+ end
130
+ end
131
+
132
+ rule(:ms_literal_custom_delimiters) do
133
+ ms_bq(true).capture(:bq).as(:ms_bq) >>
134
+ dynamic do |_, c|
135
+ pounds = c.captures[:bq].to_s[0..-4]
136
+ ms_portions(pounds).as(:ms_portions) >> ms_eq(pounds, false).as(:ms_eq)
137
+ end
138
+ end
139
+
140
+ rule(:string_literal) do
141
+ [
142
+ ms_empty_literal_custom_delimiters, ms_literal_custom_delimiters,
143
+ ms_empty_literal, ms_literal,
144
+ ss_empty_literal_custom_delimiters, ss_literal_custom_delimiters,
145
+ ss_empty_literal, ss_literal
146
+ ].inject(:|)
147
+ end
148
+
149
+ private
150
+
151
+ def interplation(pounds)
152
+ str("\\#{pounds}") >> bracketed(expression, '(', ')')
153
+ end
154
+
155
+ def unicode_char(pounds)
156
+ str("\\#{pounds}u") >> str('{') >> match('[\h]').repeat(1) >> str('}')
157
+ end
158
+
159
+ def escaped_char(pounds)
160
+ str("\\#{pounds}") >> match('[^\(u]')
161
+ end
162
+
163
+ def ss_char(pounds)
164
+ (nl | str("\\#{pounds}") | ss_eq(pounds)).absent? >> any
165
+ end
166
+
167
+ def ss_string(pounds)
168
+ (unicode_char(pounds) | escaped_char(pounds) | ss_char(pounds)).repeat(1)
169
+ end
170
+
171
+ def ss_portions(pounds)
172
+ (
173
+ interplation(pounds).as(:interplation) |
174
+ ss_string(pounds).as(:ss_string)
175
+ ).repeat(1)
176
+ end
177
+
178
+ def ss_bq(custom)
179
+ if custom
180
+ str('#').repeat(1) >> str('"')
181
+ else
182
+ str('"')
183
+ end
184
+ end
185
+
186
+ def ss_eq(pounds)
187
+ str("\"#{pounds}")
188
+ end
189
+
190
+ def ms_char(pounds)
191
+ (nl | str("\\#{pounds}") | ms_eq(pounds, true)).absent? >> any
192
+ end
193
+
194
+ def ms_string(pounds)
195
+ (unicode_char(pounds) | escaped_char(pounds) | ms_char(pounds)).repeat(1)
196
+ end
197
+
198
+ def ms_portion(pounds)
199
+ (
200
+ interplation(pounds).as(:interplation) |
201
+ ms_string(pounds).as(:ms_string)
202
+ ).repeat(1)
203
+ end
204
+
205
+ def ms_portions(pounds)
206
+ (ms_portion(pounds).maybe >> nl.as(:ms_nl)).repeat(1)
207
+ end
208
+
209
+ def ms_bq(custom)
210
+ if custom
211
+ str('#').repeat(1) >> str('"""') >> nl.ignore
212
+ else
213
+ str('"""') >> nl.ignore
214
+ end
215
+ end
216
+
217
+ def ms_eq(pounds, pattern_only)
218
+ if pattern_only
219
+ str('"""'"#{pounds}")
220
+ else
221
+ match('[ \t]').repeat >> str('"""'"#{pounds}")
222
+ end
223
+ end
224
+ end
225
+
226
+ define_transform do
227
+ rule(ss_bq: simple(:bq), ss_eq: simple(:eq)) do
228
+ Node::String.new(nil, nil, node_position(bq))
229
+ end
230
+
231
+ rule(ss_bq: simple(:bq), ss_portions: subtree(:portions), ss_eq: simple(:eq)) do
232
+ Node::String.new(nil, process_ss_portions(portions, bq), node_position(bq))
233
+ end
234
+
235
+ rule(ms_bq: simple(:bq), ms_eq: simple(:eq)) do
236
+ Node::String.new(nil, nil, node_position(bq))
237
+ end
238
+
239
+ rule(ms_bq: simple(:bq), ms_portions: subtree(:portions), ms_eq: simple(:eq)) do
240
+ Node::String.new(nil, process_ms_portions(portions, bq, eq), node_position(bq))
241
+ end
242
+
243
+ private
244
+
245
+ def process_ss_portions(portions, ss_bq)
246
+ pounds = ss_bq.to_s[0..-2]
247
+ portions.map { process_sring_portion(_1.first, pounds) }
248
+ end
249
+
250
+ def process_ms_portions(portions, ms_bq, ms_eq)
251
+ pounds = ms_bq.to_s[0..-4]
252
+ portions
253
+ .flat_map(&:to_a)
254
+ .then { _1[0..-2] }
255
+ .then { trim_leading_shapes(_1, ms_eq) }
256
+ .map { process_sring_portion(_1, pounds) }
257
+ end
258
+
259
+ def trim_leading_shapes(portins, ms_eq)
260
+ prefix = ms_eq.to_s[/^[ \t]+/]
261
+ return portins unless prefix
262
+
263
+ portins.map do |t, s|
264
+ if include_bol?(s)
265
+ [t, s.to_s.delete_prefix(prefix)]
266
+ else
267
+ [t, s]
268
+ end
269
+ end
270
+ end
271
+
272
+ def include_bol?(string)
273
+ _, column = string.line_and_column
274
+ column == 1
275
+ end
276
+
277
+ def process_sring_portion(portion, pounds)
278
+ type, string = portion
279
+ case type
280
+ when :ss_string, :ms_string then unescape_string(string, pounds)
281
+ when :ms_nl then "\n"
282
+ when :interplation then string
283
+ end
284
+ end
285
+
286
+ def unescape_string(string, pounds)
287
+ string
288
+ .to_s
289
+ .then { unescape_unicode(_1, pounds) }
290
+ .then { unescape_char(_1, pounds, string) }
291
+ end
292
+
293
+ def unescape_unicode(string, pounds)
294
+ re = /\\#{pounds}u\{([\h]+)\}/
295
+ string.gsub(re) { Regexp.last_match(1).to_i(16).chr(Encoding::UTF_8) }
296
+ end
297
+
298
+ def unescape_char(string, pounds, node)
299
+ string.gsub(/(\\#{pounds}.)/) do |m|
300
+ ESCAPED_CHARS[m[-1]] ||
301
+ begin
302
+ message = "invalid escape sequence is given: #{m}"
303
+ parse_error(message, node_position(node))
304
+ end
305
+ end
306
+ end
307
+ end
308
+ end
309
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuPkl
4
+ class Parser
5
+ WS_PATTERN = '[ \t\f\r\n;]'
6
+
7
+ define_parser do
8
+ rule(:line_comment) do
9
+ str('//') >> match('[^\n]').repeat >> (nl | eof)
10
+ end
11
+
12
+ rule(:block_comment) do
13
+ str('/*') >> (block_comment | (str('*/').absent? >> any)).repeat >> str('*/')
14
+ end
15
+
16
+ rule(:comment) do
17
+ line_comment | block_comment
18
+ end
19
+
20
+ rule(:nl) do
21
+ match('\n')
22
+ end
23
+
24
+ rule(:eof) do
25
+ any.absent?
26
+ end
27
+
28
+ rule(:ws) do
29
+ (match(WS_PATTERN) | comment).repeat(1).ignore
30
+ end
31
+
32
+ rule(:ws?) do
33
+ (match(WS_PATTERN) | comment).repeat.ignore
34
+ end
35
+
36
+ rule(:pure_ws?) do
37
+ (match('[ \t\f]') | comment).repeat.ignore
38
+ end
39
+
40
+ private
41
+
42
+ def bracketed(atom, bra = '(', cket = ')')
43
+ bra_matcher, cket_matcher =
44
+ [bra, cket]
45
+ .map { _1.is_a?(String) && str(_1).ignore || _1 }
46
+ bra_matcher >> ws? >> atom >> ws? >> cket_matcher
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuPkl
4
+ class Parser
5
+ define_parser do
6
+ rule(:object) do
7
+ bracketed(
8
+ object_members.as(:members).maybe,
9
+ str('{').as(:start), '}'
10
+ ).as(:object)
11
+ end
12
+
13
+ rule(:object_members) do
14
+ object_member >> (ws >> object_member).repeat
15
+ end
16
+
17
+ rule(:object_member) do
18
+ object_property | object_entry | object_element
19
+ end
20
+
21
+ rule(:object_property) do
22
+ (
23
+ id.as(:name) >> ws? >>
24
+ (
25
+ (str('=').ignore >> ws? >> expression.as(:value)) |
26
+ (object >> (ws? >> object).repeat).as(:objects)
27
+ )
28
+ ).as(:object_property)
29
+ end
30
+
31
+ rule(:object_entry) do
32
+ (
33
+ bracketed(expression.as(:key), '[', ']') >> ws? >>
34
+ (
35
+ (str('=').ignore >> ws? >> expression.as(:value)) |
36
+ (object >> (ws? >> object).repeat).as(:objects)
37
+ )
38
+ ).as(:object_entry)
39
+ end
40
+
41
+ rule(:object_element) do
42
+ expression
43
+ end
44
+ end
45
+
46
+ define_transform do
47
+ rule(object: { start: simple(:s) }) do
48
+ Node::UnresolvedObject.new(nil, node_position(s))
49
+ end
50
+
51
+ rule(object: { start: simple(:s), members: subtree(:m) }) do
52
+ Node::UnresolvedObject.new(Array(m), node_position(s))
53
+ end
54
+
55
+ rule(object_property: { name: simple(:n), value: simple(:v) }) do
56
+ Node::ObjectProperty.new(n, v, nil, n.position)
57
+ end
58
+
59
+ rule(object_property: { name: simple(:n), objects: subtree(:o) }) do
60
+ Node::ObjectProperty.new(n, nil, Array(o), n.position)
61
+ end
62
+
63
+ rule(object_entry: { key: simple(:k), value: simple(:v) }) do
64
+ Node::ObjectEntry.new(k, v, nil, k.position)
65
+ end
66
+
67
+ rule(object_entry: { key: simple(:k), objects: subtree(:o) }) do
68
+ Node::ObjectEntry.new(k, nil, Array(o), k.position)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuPkl
4
+ class Parser
5
+ define_parser do
6
+ rule(:pkl_class_property) do
7
+ (
8
+ id.as(:name) >> ws? >>
9
+ (
10
+ (str('=').ignore >> ws? >> expression.as(:value)) |
11
+ (object >> (ws? >> object).repeat).as(:objects)
12
+ )
13
+ ).as(:class_property)
14
+ end
15
+ end
16
+
17
+ define_transform do
18
+ rule(class_property: { name: simple(:n), value: simple(:v) }) do
19
+ Node::PklClassProperty.new(n, v, nil, n.position)
20
+ end
21
+ end
22
+
23
+ define_transform do
24
+ rule(class_property: { name: simple(:n), objects: subtree(:o) }) do
25
+ Node::PklClassProperty.new(n, nil, Array(o), n.position)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuPkl
4
+ class Parser
5
+ define_parser do
6
+ rule(:pkl_module) do
7
+ (
8
+ ws? >> pkl_module_items.as(:items).maybe >> ws?
9
+ ).as(:pkl_module)
10
+ end
11
+
12
+ rule(:pkl_module_items) do
13
+ pkl_module_item >> (ws >> pkl_module_item).repeat
14
+ end
15
+
16
+ rule(:pkl_module_item) do
17
+ pkl_class_property
18
+ end
19
+ end
20
+
21
+ define_transform do
22
+ rule(pkl_module: simple(:_)) do
23
+ Node::PklModule.new(nil, sof_position)
24
+ end
25
+
26
+ rule(pkl_module: { items: subtree(:items) }) do
27
+ Array(items)
28
+ .then { Node::PklModule.new(_1, _1.first.position) }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuPkl
4
+ class Parser
5
+ Position = Struct.new(:filename, :line, :column)
6
+
7
+ class Parser < Parslet::Parser
8
+ def parse(io, filename: nil, root: nil)
9
+ root_parser(root).parse(io)
10
+ rescue Parslet::ParseFailed => e
11
+ raise_parse_error(e, filename)
12
+ end
13
+
14
+ def root_parser(root)
15
+ root && __send__(root) || __send__(:root)
16
+ end
17
+
18
+ private
19
+
20
+ def raise_parse_error(error, filename)
21
+ cause = error.parse_failure_cause
22
+ pos = create_error_pos(cause, filename)
23
+ message = compose_error_message(cause)
24
+ raise ParseError.new(message, pos, cause)
25
+ end
26
+
27
+ def create_error_pos(cause, filename)
28
+ Position.new(filename, *cause.source.line_and_column(cause.pos))
29
+ end
30
+
31
+ def compose_error_message(cause)
32
+ Array(cause.message)
33
+ .map { |m| m.respond_to?(:to_slice) ? m.str.inspect : m.to_s }
34
+ .join
35
+ end
36
+ end
37
+
38
+ class Context < Parslet::Context
39
+ def initialize(bindings, transform)
40
+ super(bindings)
41
+ @__transform = transform
42
+ end
43
+
44
+ def method_missing(method, ...)
45
+ if @__transform.respond_to?(method, true)
46
+ __define_delegator__(method)
47
+ __send__(method, ...)
48
+ else
49
+ # :nocov:
50
+ super
51
+ # :nocov:
52
+ end
53
+ end
54
+
55
+ def respond_to_missing?(method, include_private)
56
+ # :nocov:
57
+ super || @__transform.respond_to?(method, include_private)
58
+ # :nocov:
59
+ end
60
+
61
+ private
62
+
63
+ def __define_delegator__(method)
64
+ self.class.class_eval(<<~M, __FILE__, __LINE__ + 1)
65
+ # def foo(...)
66
+ # @__transform.__send__(:foo, ...)
67
+ # end
68
+ def #{method}(...)
69
+ @__transform.__send__(:#{method}, ...)
70
+ end
71
+ M
72
+ end
73
+ end
74
+
75
+ class Transform < Parslet::Transform
76
+ def apply(obj, context = nil, filename: nil)
77
+ @filename = filename if filename
78
+ super(obj, context)
79
+ end
80
+
81
+ def call_on_match(bindings, block)
82
+ return unless block
83
+
84
+ context = Context.new(bindings, self)
85
+ context.instance_exec(&block)
86
+ end
87
+
88
+ private
89
+
90
+ def node_position(node)
91
+ Position.new(@filename, *node.line_and_column)
92
+ end
93
+
94
+ def sof_position
95
+ Position.new(@filename, 1, 1)
96
+ end
97
+
98
+ def parse_error(message, position)
99
+ raise ParseError.new(message, position, nil)
100
+ end
101
+ end
102
+
103
+ class << self
104
+ private
105
+
106
+ def define_parser(&body)
107
+ Parser.class_eval(&body)
108
+ end
109
+
110
+ def define_transform(&body)
111
+ Transform.class_eval(&body)
112
+ end
113
+ end
114
+
115
+ def parse(string, filename: nil, root: nil)
116
+ tree = parse_string(string, filename, root)
117
+ transform_tree(tree, filename)
118
+ end
119
+
120
+ private
121
+
122
+ def parse_string(string, filename, root)
123
+ parser.parse(string, filename: filename, root: root)
124
+ end
125
+
126
+ def parser
127
+ @parser ||= Parser.new
128
+ end
129
+
130
+ def transform_tree(tree, filename)
131
+ transform.apply(tree, filename: filename)
132
+ end
133
+
134
+ def transform
135
+ @transform ||= Transform.new
136
+ end
137
+ end
138
+ end