kdl 0.1.1 → 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.
data/lib/kdl/kdl.yy CHANGED
@@ -4,6 +4,7 @@ class KDL::Parser
4
4
  STRING RAWSTRING
5
5
  INTEGER FLOAT TRUE FALSE NULL
6
6
  WS NEWLINE
7
+ LBRACE RBRACE
7
8
  LPAREN RPAREN
8
9
  EQUALS
9
10
  SEMICOLON
@@ -13,37 +14,46 @@ rule
13
14
  document : nodes { KDL::Document.new(val[0]) }
14
15
  | linespaces { KDL::Document.new([])}
15
16
 
16
- nodes : none { [] }
17
- | linespace_star node { [val[1]] }
18
- | linespace_star empty_node { [] }
19
- | nodes node { [*val[0], val[1]] }
20
- | nodes empty_node { val[0] }
21
- node : node_decl node_term { val[0] }
22
- | node_decl node_children node_term { val[0].tap { |x| x.children = val[1] } }
23
- | node_decl empty_children node_term { val[0] }
24
- node_decl : identifier { KDL::Node.new(val[0]) }
25
- | node_decl WS value { val[0].tap { |x| x.arguments << val[2] } }
26
- | node_decl WS SLASHDASH ws_star value { val[0] }
27
- | node_decl WS property { val[0].tap { |x| x.properties[val[2][0]] = val[2][1] } }
28
- | node_decl WS SLASHDASH ws_star property { val[0] }
29
- empty_node: SLASHDASH ws_star node
30
- node_children: ws_star LPAREN nodes RPAREN { val[2] }
17
+ nodes : none { [] }
18
+ | linespace_star node { [val[1]] }
19
+ | linespace_star empty_node { [] }
20
+ | nodes node { [*val[0], val[1]] }
21
+ | nodes empty_node { val[0] }
22
+ node : untyped_node { val[0] }
23
+ | type untyped_node { val[1].as_type(val[0], @type_parsers.fetch(val[0], nil)) }
24
+ untyped_node : node_decl node_term { val[0].tap { |x| x.children = nil } }
25
+ | node_decl node_children node_term { val[0].tap { |x| x.children = val[1] } }
26
+ | node_decl empty_children node_term { val[0].tap { |x| x.children = nil } }
27
+ node_decl : identifier { KDL::Node.new(val[0]) }
28
+ | node_decl WS value { val[0].tap { |x| x.arguments << val[2] } }
29
+ | node_decl WS SLASHDASH ws_star value { val[0] }
30
+ | node_decl WS property { val[0].tap { |x| x.properties[val[2][0]] = val[2][1] } }
31
+ | node_decl WS SLASHDASH ws_star property { val[0] }
32
+ empty_node : SLASHDASH ws_star node
33
+ node_children : ws_star LBRACE nodes RBRACE { val[2] }
34
+ | ws_star LBRACE linespace_star RBRACE { [] }
31
35
  empty_children: SLASHDASH node_children
32
36
  | WS empty_children
33
37
  node_term: linespaces | semicolon_term
34
38
  semicolon_term: SEMICOLON | SEMICOLON linespaces
35
39
 
36
- identifier: IDENT { val[0] }
37
- | STRING { val[0] }
40
+ type : LPAREN identifier RPAREN { val[1] }
41
+
42
+ identifier: IDENT { val[0].value }
43
+ | STRING { val[0].value }
44
+ | RAWSTRING { val[0].value }
38
45
 
39
46
  property: identifier EQUALS value { [val[0], val[2]] }
40
47
 
41
- value : STRING { KDL::Value::String.new(val[0]) }
42
- | RAWSTRING { KDL::Value::String.new(val[0]) }
43
- | INTEGER { KDL::Value::Int.new(val[0]) }
44
- | FLOAT { KDL::Value::Float.new(val[0]) }
45
- | boolean { KDL::Value::Boolean.new(val[0]) }
46
- | NULL { KDL::Value::Null }
48
+ value : untyped_value
49
+ | type untyped_value { val[1].as_type(val[0], @type_parsers.fetch(val[0], nil)) }
50
+
51
+ untyped_value : STRING { KDL::Value::String.new(val[0].value) }
52
+ | RAWSTRING { KDL::Value::String.new(val[0].value) }
53
+ | INTEGER { KDL::Value::Int.new(val[0].value) }
54
+ | FLOAT { KDL::Value::Float.new(val[0].value, format: val[0].meta[:format]) }
55
+ | boolean { KDL::Value::Boolean.new(val[0]) }
56
+ | NULL { KDL::Value::Null }
47
57
 
48
58
  boolean : TRUE { true }
49
59
  | FALSE { false }
@@ -56,7 +66,13 @@ rule
56
66
  none: { nil }
57
67
 
58
68
  ---- inner
59
- def parse(str)
69
+
70
+ def parse(str, options = {})
71
+ if options.fetch(:parse_types, true)
72
+ @type_parsers = ::KDL::Types::MAPPING.merge(options.fetch(:type_parsers, {}))
73
+ else
74
+ @type_parsers = {}
75
+ end
60
76
  @tokenizer = ::KDL::Tokenizer.new(str)
61
77
  do_parse
62
78
  end
data/lib/kdl/node.rb CHANGED
@@ -1,31 +1,33 @@
1
1
  module KDL
2
2
  class Node
3
- attr_accessor :name, :arguments, :properties, :children
3
+ attr_accessor :name, :arguments, :properties, :children, :type
4
4
 
5
- def initialize(name, arguments = [], properties = {}, children = [])
5
+ def initialize(name, arguments = [], properties = {}, children = nil, type: nil)
6
6
  @name = name
7
7
  @arguments = arguments
8
8
  @properties = properties
9
9
  @children = children
10
+ @type = type
10
11
  end
11
12
 
12
13
  def to_s(level = 0)
13
14
  indent = ' ' * level
14
- s = "#{indent}#{name}"
15
+ s = "#{indent}#{type ? "(#{id_to_s type})" : ''}#{id_to_s name}"
15
16
  unless arguments.empty?
16
17
  s += " #{arguments.map(&:to_s).join(' ')}"
17
18
  end
18
19
  unless properties.empty?
19
- s += " #{properties.map { |k, v| "#{k}=#{v}" }.join(' ')}"
20
+ s += " #{properties.map { |k, v| "#{id_to_s k}=#{v}" }.join(' ')}"
20
21
  end
21
- unless children.empty?
22
+ unless children.nil?
22
23
  s += " {\n"
23
- s += children.map { |c| c.to_s(level + 1) }.join("\n")
24
- s += "\n#{indent}}"
24
+ unless children.empty?
25
+ s += children.map { |c| "#{c.to_s(level + 1)}\n" }.join("\n")
26
+ end
27
+ s += "#{indent}}"
25
28
  end
26
29
  s
27
30
  end
28
- alias inspect to_s
29
31
 
30
32
  def ==(other)
31
33
  return false unless other.is_a?(Node)
@@ -35,5 +37,28 @@ module KDL
35
37
  properties == other.properties &&
36
38
  children == other.children
37
39
  end
40
+
41
+ def as_type(type, parser = nil)
42
+ if parser.nil?
43
+ @type = type
44
+ self
45
+ else
46
+ result = parser.call(self, type)
47
+
48
+ return self.as_type(type) if result.nil?
49
+
50
+ unless result.is_a?(::KDL::Node)
51
+ raise ArgumentError, "expected parser to return an instance of ::KDL::Node, got `#{result.class}'"
52
+ end
53
+
54
+ result
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def id_to_s(id)
61
+ StringDumper.stringify_identifier(id)
62
+ end
38
63
  end
39
64
  end
@@ -0,0 +1,45 @@
1
+ module KDL
2
+ module StringDumper
3
+ class << self
4
+ def call(string)
5
+ %("#{string.each_char.map { |char| escape(char) }.join}")
6
+ end
7
+
8
+ def stringify_identifier(ident)
9
+ if bare_identifier?(ident)
10
+ ident
11
+ else
12
+ call(ident)
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def print?(char)
19
+ ' ' <= char && char <= '\x7e'
20
+ end
21
+
22
+ def escape(char)
23
+ case char
24
+ when "\n" then '\n'
25
+ when "\r" then '\r'
26
+ when "\t" then '\t'
27
+ when '\\' then '\\\\'
28
+ when '"' then '\"'
29
+ when "\b" then '\b'
30
+ when "\f" then '\f'
31
+ else char
32
+ end
33
+ end
34
+
35
+ def unicode_escape(char)
36
+ "\\u{#{char.codepoints.first.to_s(16)}}"
37
+ end
38
+
39
+ def bare_identifier?(name)
40
+ escape_chars = '\\\/(){}<>;\[\]=,"'
41
+ name =~ /^([^0-9\-+\s#{escape_chars}][^\s#{escape_chars}]*|[\-+](?!true|false|null)[^0-9\s#{escape_chars}][^\s#{escape_chars}]*)$/
42
+ end
43
+ end
44
+ end
45
+ end
data/lib/kdl/tokenizer.rb CHANGED
@@ -1,12 +1,41 @@
1
+ require 'bigdecimal'
2
+
1
3
  module KDL
2
4
  class Tokenizer
3
- class Error < StandardError; end
5
+ class Error < StandardError
6
+ def initialize(message, line, column)
7
+ super("#{message} (#{line}:#{column})")
8
+ end
9
+ end
10
+
11
+ class Token
12
+ attr_reader :type, :value, :line, :column, :meta
13
+
14
+ def initialize(type, value, line, column, meta = {})
15
+ @type = type
16
+ @value = value
17
+ @line = line
18
+ @column = column
19
+ @meta = meta
20
+ end
21
+
22
+ def ==(other)
23
+ return false unless other.is_a?(Token)
24
+
25
+ type == other.type && value == other.value && line == other.line && column == other.column
26
+ end
27
+
28
+ def to_s
29
+ "#{value.inspect} (#{line}:#{column})"
30
+ end
31
+ alias inspect to_s
32
+ end
4
33
 
5
34
  attr_reader :index
6
35
 
7
36
  SYMBOLS = {
8
- '{' => :LPAREN,
9
- '}' => :RPAREN,
37
+ '{' => :LBRACE,
38
+ '}' => :RBRACE,
10
39
  '=' => :EQUALS,
11
40
  '=' => :EQUALS,
12
41
  ';' => :SEMICOLON
@@ -20,10 +49,13 @@ module KDL
20
49
 
21
50
  NEWLINES = ["\u000A", "\u0085", "\u000C", "\u2028", "\u2029"]
22
51
 
23
- NON_IDENTIFIER_CHARS = Regexp.escape "#{SYMBOLS.keys.join('')}\\<>[]\","
52
+ NON_IDENTIFIER_CHARS = Regexp.escape "#{SYMBOLS.keys.join('')}()/\\<>[]\","
24
53
  IDENTIFIER_CHARS = /[^#{NON_IDENTIFIER_CHARS}\x0-\x20]/
25
54
  INITIAL_IDENTIFIER_CHARS = /[^#{NON_IDENTIFIER_CHARS}0-9\x0-\x20]/
26
55
 
56
+ ALLOWED_IN_TYPE = [:ident, :string, :rawstring]
57
+ NOT_ALLOWED_AFTER_TYPE = [:single_line_comment, :multi_line_comment]
58
+
27
59
  def initialize(str, start = 0)
28
60
  @str = str
29
61
  @context = nil
@@ -32,11 +64,17 @@ module KDL
32
64
  @buffer = ""
33
65
  @done = false
34
66
  @previous_context = nil
67
+ @line = 1
68
+ @column = 1
69
+ @type_context = false
70
+ @last_token = nil
35
71
  end
36
72
 
37
73
  def next_token
38
74
  @context = nil
39
75
  @previous_context = nil
76
+ @line_at_start = @line
77
+ @column_at_start = @column
40
78
  loop do
41
79
  c = @str[@index]
42
80
  case @context
@@ -45,11 +83,11 @@ module KDL
45
83
  when '"'
46
84
  self.context = :string
47
85
  @buffer = ''
48
- @index += 1
86
+ traverse(1)
49
87
  when 'r'
50
88
  if @str[@index + 1] == '"'
51
89
  self.context = :rawstring
52
- @index += 2
90
+ traverse(2)
53
91
  @rawstring_hashes = 0
54
92
  @buffer = ''
55
93
  next
@@ -69,20 +107,20 @@ module KDL
69
107
  end
70
108
  self.context = :ident
71
109
  @buffer = c
72
- @index += 1
110
+ traverse(1)
73
111
  when /[0-9\-+]/
74
112
  n = @str[@index + 1]
75
113
  if c == '0' && n =~ /[box]/
76
- @index += 2
114
+ traverse(2)
77
115
  @buffer = ''
78
116
  self.context = case n
79
- when 'b' then :binary
80
- when 'o' then :octal
81
- when 'x' then :hexadecimal
82
- end
117
+ when 'b' then :binary
118
+ when 'o' then :octal
119
+ when 'x' then :hexadecimal
120
+ end
83
121
  else
84
122
  self.context = :decimal
85
- @index += 1
123
+ traverse(1)
86
124
  @buffer = c
87
125
  end
88
126
  when '\\'
@@ -90,68 +128,80 @@ module KDL
90
128
  la = t.next_token[0]
91
129
  if la == :NEWLINE
92
130
  @index = t.index
131
+ new_line
93
132
  elsif la == :WS && (lan = t.next_token[0]) == :NEWLINE
94
133
  @index = t.index
134
+ new_line
95
135
  else
96
- raise Error, "Unexpected '\\'"
136
+ raise_error "Unexpected '\\'"
97
137
  end
98
138
  when *SYMBOLS.keys
99
- @index += 1
100
- return [SYMBOLS[c], c]
139
+ return token(SYMBOLS[c], c).tap { traverse(1) }
101
140
  when "\r"
102
141
  n = @str[@index + 1]
103
142
  if n == "\n"
104
- @index += 2
105
- return [:NEWLINE, "#{c}#{n}"]
143
+ return token(:NEWLINE, "#{c}#{n}").tap do
144
+ traverse(2)
145
+ new_line
146
+ end
106
147
  else
107
- @index += 1
108
- return [:NEWLINE, c]
148
+ return token(:NEWLINE, c).tap do
149
+ traverse(1)
150
+ new_line
151
+ end
109
152
  end
110
153
  when *NEWLINES
111
- @index += 1
112
- return [:NEWLINE, c]
154
+ return token(:NEWLINE, c).tap do
155
+ traverse(1)
156
+ new_line
157
+ end
113
158
  when "/"
114
159
  if @str[@index + 1] == '/'
115
160
  self.context = :single_line_comment
116
- @index += 2
161
+ traverse(2)
117
162
  elsif @str[@index + 1] == '*'
118
163
  self.context = :multi_line_comment
119
164
  @comment_nesting = 1
120
- @index += 2
165
+ traverse(2)
121
166
  elsif @str[@index + 1] == '-'
122
- @index += 2
123
- return [:SLASHDASH, '/-']
167
+ return token(:SLASHDASH, '/-').tap { traverse(2) }
124
168
  else
125
169
  self.context = :ident
126
170
  @buffer = c
127
- @index += 1
171
+ traverse(1)
128
172
  end
129
173
  when *WHITEPACE
130
174
  self.context = :whitespace
131
175
  @buffer = c
132
- @index += 1
176
+ traverse(1)
133
177
  when nil
134
- return [false, false] if @done
178
+ return [false, token(:EOF, :EOF)[1]] if @done
135
179
  @done = true
136
- return [:EOF, '']
180
+ return token(:EOF, :EOF)
137
181
  when INITIAL_IDENTIFIER_CHARS
138
182
  self.context = :ident
139
183
  @buffer = c
140
- @index += 1
184
+ traverse(1)
185
+ when '('
186
+ @type_context = true
187
+ return token(:LPAREN, c).tap { traverse(1) }
188
+ when ')'
189
+ @type_context = false
190
+ return token(:RPAREN, c).tap { traverse(1) }
141
191
  else
142
- raise Error, "Unexpected character #{c.inspect}"
192
+ raise_error "Unexpected character #{c.inspect}"
143
193
  end
144
194
  when :ident
145
195
  case c
146
196
  when IDENTIFIER_CHARS
147
- @index += 1
197
+ traverse(1)
148
198
  @buffer += c
149
199
  else
150
200
  case @buffer
151
- when 'true' then return [:TRUE, true]
152
- when 'false' then return [:FALSE, false]
153
- when 'null' then return [:NULL, nil]
154
- else return [:IDENT, @buffer]
201
+ when 'true' then return token(:TRUE, true)
202
+ when 'false' then return token(:FALSE, false)
203
+ when 'null' then return token(:NULL, nil)
204
+ else return token(:IDENT, @buffer)
155
205
  end
156
206
  end
157
207
  when :string
@@ -159,18 +209,17 @@ module KDL
159
209
  when '\\'
160
210
  @buffer += c
161
211
  @buffer += @str[@index + 1]
162
- @index += 2
212
+ traverse(2)
163
213
  when '"'
164
- @index += 1
165
- return [:STRING, convert_escapes(@buffer)]
214
+ return token(:STRING, convert_escapes(@buffer)).tap { traverse(1) }
166
215
  when nil
167
- raise Error, "Unterminated string literal"
216
+ raise_error "Unterminated string literal"
168
217
  else
169
218
  @buffer += c
170
- @index += 1
219
+ traverse(1)
171
220
  end
172
221
  when :rawstring
173
- raise Error, "Unterminated rawstring literal" if c.nil?
222
+ raise_error "Unterminated rawstring literal" if c.nil?
174
223
 
175
224
  if c == '"'
176
225
  h = 0
@@ -178,17 +227,16 @@ module KDL
178
227
  h += 1
179
228
  end
180
229
  if h == @rawstring_hashes
181
- @index += 1 + h
182
- return [:RAWSTRING, @buffer]
230
+ return token(:RAWSTRING, @buffer).tap { traverse(1 + h) }
183
231
  end
184
232
  end
185
233
 
186
234
  @buffer += c
187
- @index += 1
235
+ traverse(1)
188
236
  when :decimal
189
237
  case c
190
238
  when /[0-9.\-+_eE]/
191
- @index += 1
239
+ traverse(1)
192
240
  @buffer += c
193
241
  else
194
242
  return parse_decimal(@buffer)
@@ -196,7 +244,7 @@ module KDL
196
244
  when :hexadecimal
197
245
  case c
198
246
  when /[0-9a-fA-F_]/
199
- @index += 1
247
+ traverse(1)
200
248
  @buffer += c
201
249
  else
202
250
  return parse_hexadecimal(@buffer)
@@ -204,7 +252,7 @@ module KDL
204
252
  when :octal
205
253
  case c
206
254
  when /[0-7_]/
207
- @index += 1
255
+ traverse(1)
208
256
  @buffer += c
209
257
  else
210
258
  return parse_octal(@buffer)
@@ -212,7 +260,7 @@ module KDL
212
260
  when :binary
213
261
  case c
214
262
  when /[01_]/
215
- @index += 1
263
+ traverse(1)
216
264
  @buffer += c
217
265
  else
218
266
  return parse_binary(@buffer)
@@ -220,52 +268,80 @@ module KDL
220
268
  when :single_line_comment
221
269
  if NEWLINES.include?(c) || c == "\r"
222
270
  self.context = nil
271
+ @column_at_start = @column
223
272
  next
224
273
  elsif c.nil?
225
274
  @done = true
226
- return [:EOF, '']
275
+ return token(:EOF, :EOF)
227
276
  else
228
- @index += 1
277
+ traverse(1)
229
278
  end
230
279
  when :multi_line_comment
231
280
  if c == '/' && @str[@index + 1] == '*'
232
281
  @comment_nesting += 1
233
- @index += 2
282
+ traverse(2)
234
283
  elsif c == '*' && @str[@index + 1] == '/'
235
284
  @comment_nesting -= 1
236
- @index += 2
285
+ traverse(2)
237
286
  if @comment_nesting == 0
238
287
  revert_context
239
288
  end
240
289
  else
241
- @index += 1
290
+ traverse(1)
242
291
  end
243
292
  when :whitespace
244
293
  if WHITEPACE.include?(c)
245
- @index += 1
294
+ traverse(1)
246
295
  @buffer += c
247
296
  elsif c == "\\"
248
297
  t = Tokenizer.new(@str, @index + 1)
249
298
  la = t.next_token[0]
250
299
  if la == :NEWLINE
251
300
  @index = t.index
301
+ new_line
252
302
  elsif (la == :WS && (lan = t.next_token[0]) == :NEWLINE)
253
303
  @index = t.index
304
+ new_line
254
305
  else
255
- raise Error, "Unexpected '\\'"
306
+ raise_error "Unexpected '\\'"
256
307
  end
257
308
  elsif c == "/" && @str[@index + 1] == '*'
258
309
  self.context = :multi_line_comment
259
310
  @comment_nesting = 1
260
- @index += 2
311
+ traverse(2)
261
312
  else
262
- return [:WS, @buffer]
313
+ return token(:WS, @buffer)
263
314
  end
264
315
  end
265
316
  end
266
317
  end
267
318
 
319
+ private
320
+
321
+ def token(type, value, **meta)
322
+ @last_token = [type, Token.new(type, value, @line_at_start, @column_at_start, meta)]
323
+ end
324
+
325
+ def traverse(n = 1)
326
+ @column += n
327
+ @index += n
328
+ end
329
+
330
+ def raise_error(message)
331
+ raise Error.new(message, @line, @column)
332
+ end
333
+
334
+ def new_line
335
+ @column = 1
336
+ @line += 1
337
+ end
338
+
268
339
  def context=(val)
340
+ if @type_context && !ALLOWED_IN_TYPE.include?(val)
341
+ raise_error "#{val} context not allowed in type declaration"
342
+ elsif @last_token && @last_token[0] == :RPAREN && NOT_ALLOWED_AFTER_TYPE.include?(val)
343
+ raise_error 'Comments are not allowed after a type declaration'
344
+ end
269
345
  @previous_context = @context
270
346
  @context = val
271
347
  end
@@ -275,23 +351,38 @@ module KDL
275
351
  @previous_context = nil
276
352
  end
277
353
 
278
- private
279
-
280
354
  def parse_decimal(s)
281
- return [:FLOAT, Float(munch_underscores(s))] if s =~ /[.eE]/
282
- [:INTEGER, Integer(munch_underscores(s), 10)]
355
+ return parse_float(s) if s =~ /[.E]/i
356
+
357
+ token(:INTEGER, Integer(munch_underscores(s), 10), format: '%d')
283
358
  end
284
-
359
+
360
+ def parse_float(s)
361
+ match, _, fraction, exponent = *s.match(/^([-+]?[\d_]+)(?:\.(\d+))?(?:[eE]([-+]?[\d_]+))?$/)
362
+ raise_error "Invalid floating point value #{s}" if match.nil?
363
+
364
+ s = munch_underscores(s)
365
+
366
+ decimals = fraction.nil? ? 0 : fraction.size
367
+ value = Float(s)
368
+ scientific = value.abs >= 100 || (exponent && exponent.to_i.abs >= 2)
369
+ if value.infinite? || (value.zero? && exponent.to_i < 0)
370
+ token(:FLOAT, BigDecimal(s))
371
+ else
372
+ token(:FLOAT, value, format: scientific ? "%.#{decimals}E" : nil)
373
+ end
374
+ end
375
+
285
376
  def parse_hexadecimal(s)
286
- [:INTEGER, Integer(munch_underscores(s), 16)]
377
+ token(:INTEGER, Integer(munch_underscores(s), 16))
287
378
  end
288
-
379
+
289
380
  def parse_octal(s)
290
- [:INTEGER, Integer(munch_underscores(s), 8)]
381
+ token(:INTEGER, Integer(munch_underscores(s), 8))
291
382
  end
292
-
383
+
293
384
  def parse_binary(s)
294
- [:INTEGER, Integer(munch_underscores(s), 2)]
385
+ token(:INTEGER, Integer(munch_underscores(s), 2))
295
386
  end
296
387
 
297
388
  def munch_underscores(s)
@@ -308,12 +399,13 @@ module KDL
308
399
  when '\"' then "\""
309
400
  when '\b' then "\b"
310
401
  when '\f' then "\f"
311
- else raise Error, "Unexpected escape #{m.inspect}"
402
+ when '\/' then "/"
403
+ else raise_error "Unexpected escape #{m.inspect}"
312
404
  end
313
405
  end.gsub(/\\u\{[0-9a-fA-F]{0,6}\}/) do |m|
314
406
  i = Integer(m[3..-2], 16)
315
407
  if i < 0 || i > 0x10FFFF
316
- raise Error, "Invalid code point #{u}"
408
+ raise_error "Invalid code point #{u}"
317
409
  end
318
410
  i.chr(Encoding::UTF_8)
319
411
  end
@@ -0,0 +1,15 @@
1
+ require 'base64'
2
+
3
+ module KDL
4
+ module Types
5
+ class Base64 < Value
6
+ def self.call(value, type = 'base64')
7
+ return nil unless value.is_a? ::KDL::Value::String
8
+
9
+ data = ::Base64.decode64(value.value)
10
+ new(data, type: type)
11
+ end
12
+ end
13
+ MAPPING['base64'] = Base64
14
+ end
15
+ end