kdl 0.1.2 → 1.0.1
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 +4 -4
- data/.github/workflows/ruby.yml +4 -3
- data/.gitignore +1 -0
- data/.gitmodules +3 -0
- data/README.md +16 -2
- data/Rakefile +11 -6
- data/bin/console +2 -0
- data/bin/kdl +8 -0
- data/bin/racc +29 -0
- data/bin/rake +29 -0
- data/bin/setup +1 -0
- data/kdl.gemspec +5 -5
- data/lib/kdl/document.rb +1 -2
- data/lib/kdl/kdl.tab.rb +254 -167
- data/lib/kdl/kdl.yy +44 -27
- data/lib/kdl/node.rb +30 -7
- data/lib/kdl/string_dumper.rb +45 -0
- data/lib/kdl/tokenizer.rb +52 -30
- data/lib/kdl/types/base64.rb +15 -0
- data/lib/kdl/types/country/iso3166_countries.rb +512 -0
- data/lib/kdl/types/country/iso3166_subdivisions.rb +3927 -0
- data/lib/kdl/types/country.rb +69 -0
- data/lib/kdl/types/currency/iso4217_currencies.rb +189 -0
- data/lib/kdl/types/currency.rb +26 -0
- data/lib/kdl/types/date_time.rb +41 -0
- data/lib/kdl/types/decimal.rb +13 -0
- data/lib/kdl/types/duration/iso8601_parser.rb +147 -0
- data/lib/kdl/types/duration.rb +28 -0
- data/lib/kdl/types/email/parser.rb +151 -0
- data/lib/kdl/types/email.rb +43 -0
- data/lib/kdl/types/hostname/validator.rb +51 -0
- data/lib/kdl/types/hostname.rb +32 -0
- data/lib/kdl/types/ip.rb +32 -0
- data/lib/kdl/types/irl/parser.rb +123 -0
- data/lib/kdl/types/irl.rb +46 -0
- data/lib/kdl/types/regex.rb +13 -0
- data/lib/kdl/types/url.rb +30 -0
- data/lib/kdl/types/url_template.rb +328 -0
- data/lib/kdl/types/uuid.rb +17 -0
- data/lib/kdl/types.rb +22 -0
- data/lib/kdl/value.rb +42 -10
- data/lib/kdl/version.rb +1 -1
- data/lib/kdl.rb +4 -2
- metadata +48 -7
data/lib/kdl/kdl.yy
CHANGED
@@ -4,59 +4,76 @@ 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
|
10
11
|
EOF
|
11
12
|
SLASHDASH
|
13
|
+
ESCLINE
|
12
14
|
rule
|
13
15
|
document : nodes { KDL::Document.new(val[0]) }
|
14
16
|
| linespaces { KDL::Document.new([])}
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
node
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
18
|
+
nodes : none { [] }
|
19
|
+
| linespaces node { [val[1]] }
|
20
|
+
| linespaces empty_node { [] }
|
21
|
+
| nodes node { [*val[0], val[1]] }
|
22
|
+
| nodes empty_node { val[0] }
|
23
|
+
node : untyped_node { val[0] }
|
24
|
+
| type untyped_node { val[1].as_type(val[0], @type_parsers.fetch(val[0], nil)) }
|
25
|
+
untyped_node : node_decl node_term { val[0].tap { |x| x.children = [] } }
|
26
|
+
| node_decl node_children node_term { val[0].tap { |x| x.children = val[1] } }
|
27
|
+
| node_decl empty_children node_term { val[0].tap { |x| x.children = [] } }
|
28
|
+
node_decl : identifier { KDL::Node.new(val[0]) }
|
29
|
+
| node_decl ws value { val[0].tap { |x| x.arguments << val[2] } }
|
30
|
+
| node_decl ws SLASHDASH ws_star value { val[0] }
|
31
|
+
| node_decl ws property { val[0].tap { |x| x.properties[val[2][0]] = val[2][1] } }
|
32
|
+
| node_decl ws SLASHDASH ws_star property { val[0] }
|
33
|
+
empty_node : SLASHDASH ws_star node
|
34
|
+
node_children : ws_star LBRACE nodes RBRACE { val[2] }
|
35
|
+
| ws_star LBRACE linespaces RBRACE { [] }
|
31
36
|
empty_children: SLASHDASH node_children
|
32
|
-
|
|
37
|
+
| ws empty_children
|
33
38
|
node_term: linespaces | semicolon_term
|
34
39
|
semicolon_term: SEMICOLON | SEMICOLON linespaces
|
35
40
|
|
36
|
-
|
37
|
-
|
41
|
+
type : LPAREN identifier RPAREN { val[1] }
|
42
|
+
|
43
|
+
identifier: IDENT { val[0].value }
|
44
|
+
| STRING { val[0].value }
|
45
|
+
| RAWSTRING { val[0].value }
|
38
46
|
|
39
47
|
property: identifier EQUALS value { [val[0], val[2]] }
|
40
48
|
|
41
|
-
value :
|
42
|
-
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
49
|
+
value : untyped_value
|
50
|
+
| type untyped_value { val[1].as_type(val[0], @type_parsers.fetch(val[0], nil)) }
|
51
|
+
|
52
|
+
untyped_value : STRING { KDL::Value::String.new(val[0].value) }
|
53
|
+
| RAWSTRING { KDL::Value::String.new(val[0].value) }
|
54
|
+
| INTEGER { KDL::Value::Int.new(val[0].value) }
|
55
|
+
| FLOAT { KDL::Value::Float.new(val[0].value, format: val[0].meta[:format]) }
|
56
|
+
| boolean { KDL::Value::Boolean.new(val[0]) }
|
57
|
+
| NULL { KDL::Value::Null }
|
47
58
|
|
48
59
|
boolean : TRUE { true }
|
49
60
|
| FALSE { false }
|
50
61
|
|
51
|
-
|
62
|
+
ws: WS | ESCLINE | WS ESCLINE | ESCLINE WS | WS ESCLINE WS
|
63
|
+
ws_star: none | ws
|
52
64
|
linespace: WS | NEWLINE | EOF
|
53
65
|
linespaces: linespace | linespaces linespace
|
54
|
-
linespace_star: none | linespaces
|
55
66
|
|
56
67
|
none: { nil }
|
57
68
|
|
58
69
|
---- inner
|
59
|
-
|
70
|
+
|
71
|
+
def parse(str, options = {})
|
72
|
+
if options.fetch(:parse_types, true)
|
73
|
+
@type_parsers = ::KDL::Types::MAPPING.merge(options.fetch(:type_parsers, {}))
|
74
|
+
else
|
75
|
+
@type_parsers = {}
|
76
|
+
end
|
60
77
|
@tokenizer = ::KDL::Tokenizer.new(str)
|
61
78
|
do_parse
|
62
79
|
end
|
data/lib/kdl/node.rb
CHANGED
@@ -1,31 +1,31 @@
|
|
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 = [], 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
22
|
unless children.empty?
|
22
23
|
s += " {\n"
|
23
|
-
s += children.map { |c| c.to_s(level + 1) }.join("\n")
|
24
|
-
s += "
|
24
|
+
s += children.map { |c| "#{c.to_s(level + 1)}\n" }.join("\n")
|
25
|
+
s += "#{indent}}"
|
25
26
|
end
|
26
27
|
s
|
27
28
|
end
|
28
|
-
alias inspect to_s
|
29
29
|
|
30
30
|
def ==(other)
|
31
31
|
return false unless other.is_a?(Node)
|
@@ -35,5 +35,28 @@ module KDL
|
|
35
35
|
properties == other.properties &&
|
36
36
|
children == other.children
|
37
37
|
end
|
38
|
+
|
39
|
+
def as_type(type, parser = nil)
|
40
|
+
if parser.nil?
|
41
|
+
@type = type
|
42
|
+
self
|
43
|
+
else
|
44
|
+
result = parser.call(self, type)
|
45
|
+
|
46
|
+
return self.as_type(type) if result.nil?
|
47
|
+
|
48
|
+
unless result.is_a?(::KDL::Node)
|
49
|
+
raise ArgumentError, "expected parser to return an instance of ::KDL::Node, got `#{result.class}'"
|
50
|
+
end
|
51
|
+
|
52
|
+
result
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def id_to_s(id)
|
59
|
+
StringDumper.stringify_identifier(id)
|
60
|
+
end
|
38
61
|
end
|
39
62
|
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,3 +1,5 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
|
1
3
|
module KDL
|
2
4
|
class Tokenizer
|
3
5
|
class Error < StandardError
|
@@ -7,13 +9,14 @@ module KDL
|
|
7
9
|
end
|
8
10
|
|
9
11
|
class Token
|
10
|
-
attr_reader :type, :value, :line, :column
|
12
|
+
attr_reader :type, :value, :line, :column, :meta
|
11
13
|
|
12
|
-
def initialize(type, value, line, column)
|
14
|
+
def initialize(type, value, line, column, meta = {})
|
13
15
|
@type = type
|
14
16
|
@value = value
|
15
17
|
@line = line
|
16
18
|
@column = column
|
19
|
+
@meta = meta
|
17
20
|
end
|
18
21
|
|
19
22
|
def ==(other)
|
@@ -31,8 +34,8 @@ module KDL
|
|
31
34
|
attr_reader :index
|
32
35
|
|
33
36
|
SYMBOLS = {
|
34
|
-
'{' => :
|
35
|
-
'}' => :
|
37
|
+
'{' => :LBRACE,
|
38
|
+
'}' => :RBRACE,
|
36
39
|
'=' => :EQUALS,
|
37
40
|
'=' => :EQUALS,
|
38
41
|
';' => :SEMICOLON
|
@@ -46,10 +49,13 @@ module KDL
|
|
46
49
|
|
47
50
|
NEWLINES = ["\u000A", "\u0085", "\u000C", "\u2028", "\u2029"]
|
48
51
|
|
49
|
-
NON_IDENTIFIER_CHARS = Regexp.escape "#{SYMBOLS.keys.join('')}
|
52
|
+
NON_IDENTIFIER_CHARS = Regexp.escape "#{SYMBOLS.keys.join('')}()/\\<>[]\","
|
50
53
|
IDENTIFIER_CHARS = /[^#{NON_IDENTIFIER_CHARS}\x0-\x20]/
|
51
54
|
INITIAL_IDENTIFIER_CHARS = /[^#{NON_IDENTIFIER_CHARS}0-9\x0-\x20]/
|
52
55
|
|
56
|
+
ALLOWED_IN_TYPE = [:ident, :string, :rawstring]
|
57
|
+
NOT_ALLOWED_AFTER_TYPE = [:single_line_comment, :multi_line_comment]
|
58
|
+
|
53
59
|
def initialize(str, start = 0)
|
54
60
|
@str = str
|
55
61
|
@context = nil
|
@@ -60,6 +66,8 @@ module KDL
|
|
60
66
|
@previous_context = nil
|
61
67
|
@line = 1
|
62
68
|
@column = 1
|
69
|
+
@type_context = false
|
70
|
+
@last_token = nil
|
63
71
|
end
|
64
72
|
|
65
73
|
def next_token
|
@@ -117,15 +125,13 @@ module KDL
|
|
117
125
|
end
|
118
126
|
when '\\'
|
119
127
|
t = Tokenizer.new(@str, @index + 1)
|
120
|
-
la = t.next_token
|
121
|
-
if la == :NEWLINE
|
122
|
-
@index = t.index
|
123
|
-
new_line
|
124
|
-
elsif la == :WS && (lan = t.next_token[0]) == :NEWLINE
|
128
|
+
la = t.next_token
|
129
|
+
if la[0] == :NEWLINE || la[0] == :EOF || (la[0] == :WS && (lan = t.next_token[0]) == :NEWLINE || lan == :EOF)
|
125
130
|
@index = t.index
|
126
131
|
new_line
|
132
|
+
return token(:ESCLINE, "\\#{la[1].value}")
|
127
133
|
else
|
128
|
-
raise_error "Unexpected '\\'"
|
134
|
+
raise_error "Unexpected '\\' (#{la[0]})"
|
129
135
|
end
|
130
136
|
when *SYMBOLS.keys
|
131
137
|
return token(SYMBOLS[c], c).tap { traverse(1) }
|
@@ -174,6 +180,12 @@ module KDL
|
|
174
180
|
self.context = :ident
|
175
181
|
@buffer = c
|
176
182
|
traverse(1)
|
183
|
+
when '('
|
184
|
+
@type_context = true
|
185
|
+
return token(:LPAREN, c).tap { traverse(1) }
|
186
|
+
when ')'
|
187
|
+
@type_context = false
|
188
|
+
return token(:RPAREN, c).tap { traverse(1) }
|
177
189
|
else
|
178
190
|
raise_error "Unexpected character #{c.inspect}"
|
179
191
|
end
|
@@ -279,18 +291,6 @@ module KDL
|
|
279
291
|
if WHITEPACE.include?(c)
|
280
292
|
traverse(1)
|
281
293
|
@buffer += c
|
282
|
-
elsif c == "\\"
|
283
|
-
t = Tokenizer.new(@str, @index + 1)
|
284
|
-
la = t.next_token[0]
|
285
|
-
if la == :NEWLINE
|
286
|
-
@index = t.index
|
287
|
-
new_line
|
288
|
-
elsif (la == :WS && (lan = t.next_token[0]) == :NEWLINE)
|
289
|
-
@index = t.index
|
290
|
-
new_line
|
291
|
-
else
|
292
|
-
raise_error "Unexpected '\\'"
|
293
|
-
end
|
294
294
|
elsif c == "/" && @str[@index + 1] == '*'
|
295
295
|
self.context = :multi_line_comment
|
296
296
|
@comment_nesting = 1
|
@@ -304,8 +304,8 @@ module KDL
|
|
304
304
|
|
305
305
|
private
|
306
306
|
|
307
|
-
def token(type, value)
|
308
|
-
[type, Token.new(type, value, @line_at_start, @column_at_start)]
|
307
|
+
def token(type, value, **meta)
|
308
|
+
@last_token = [type, Token.new(type, value, @line_at_start, @column_at_start, meta)]
|
309
309
|
end
|
310
310
|
|
311
311
|
def traverse(n = 1)
|
@@ -323,6 +323,11 @@ module KDL
|
|
323
323
|
end
|
324
324
|
|
325
325
|
def context=(val)
|
326
|
+
if @type_context && !ALLOWED_IN_TYPE.include?(val)
|
327
|
+
raise_error "#{val} context not allowed in type declaration"
|
328
|
+
elsif @last_token && @last_token[0] == :RPAREN && NOT_ALLOWED_AFTER_TYPE.include?(val)
|
329
|
+
raise_error 'Comments are not allowed after a type declaration'
|
330
|
+
end
|
326
331
|
@previous_context = @context
|
327
332
|
@context = val
|
328
333
|
end
|
@@ -333,18 +338,35 @@ module KDL
|
|
333
338
|
end
|
334
339
|
|
335
340
|
def parse_decimal(s)
|
336
|
-
return
|
337
|
-
|
341
|
+
return parse_float(s) if s =~ /[.E]/i
|
342
|
+
|
343
|
+
token(:INTEGER, Integer(munch_underscores(s), 10), format: '%d')
|
344
|
+
end
|
345
|
+
|
346
|
+
def parse_float(s)
|
347
|
+
match, _, fraction, exponent = *s.match(/^([-+]?[\d_]+)(?:\.([\d_]+))?(?:[eE]([-+]?[\d_]+))?$/)
|
348
|
+
raise_error "Invalid floating point value #{s}" if match.nil?
|
349
|
+
|
350
|
+
s = munch_underscores(s)
|
351
|
+
|
352
|
+
decimals = fraction.nil? ? 0 : fraction.size
|
353
|
+
value = Float(s)
|
354
|
+
scientific = value.abs >= 100 || (exponent && exponent.to_i.abs >= 2)
|
355
|
+
if value.infinite? || (value.zero? && exponent.to_i < 0)
|
356
|
+
token(:FLOAT, BigDecimal(s))
|
357
|
+
else
|
358
|
+
token(:FLOAT, value, format: scientific ? "%.#{decimals}E" : nil)
|
359
|
+
end
|
338
360
|
end
|
339
|
-
|
361
|
+
|
340
362
|
def parse_hexadecimal(s)
|
341
363
|
token(:INTEGER, Integer(munch_underscores(s), 16))
|
342
364
|
end
|
343
|
-
|
365
|
+
|
344
366
|
def parse_octal(s)
|
345
367
|
token(:INTEGER, Integer(munch_underscores(s), 8))
|
346
368
|
end
|
347
|
-
|
369
|
+
|
348
370
|
def parse_binary(s)
|
349
371
|
token(:INTEGER, Integer(munch_underscores(s), 2))
|
350
372
|
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
|