kdl 1.0.5 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +7 -1
- data/.gitignore +1 -0
- data/.gitmodules +4 -0
- data/Gemfile +6 -1
- data/README.md +51 -7
- data/Rakefile +6 -1
- data/bin/kdl +1 -1
- data/kdl.gemspec +2 -2
- data/lib/kdl/document.rb +58 -2
- data/lib/kdl/kdl.tab.rb +303 -228
- data/lib/kdl/kdl.yy +57 -49
- data/lib/kdl/node.rb +113 -12
- data/lib/kdl/parser_common.rb +26 -0
- data/lib/kdl/string_dumper.rb +30 -33
- data/lib/kdl/tokenizer.rb +350 -113
- data/lib/kdl/types/base64.rb +1 -1
- data/lib/kdl/types/country/iso3166_countries.rb +1 -1
- data/lib/kdl/types/country/iso3166_subdivisions.rb +1 -1
- data/lib/kdl/types/country.rb +2 -2
- data/lib/kdl/types/currency/iso4217_currencies.rb +1 -1
- data/lib/kdl/types/currency.rb +1 -1
- data/lib/kdl/types/date_time.rb +3 -3
- data/lib/kdl/types/decimal.rb +1 -1
- data/lib/kdl/types/duration/iso8601_parser.rb +1 -1
- data/lib/kdl/types/duration.rb +1 -1
- data/lib/kdl/types/email/parser.rb +2 -2
- data/lib/kdl/types/email.rb +1 -1
- data/lib/kdl/types/hostname/validator.rb +1 -1
- data/lib/kdl/types/hostname.rb +1 -1
- data/lib/kdl/types/ip.rb +1 -1
- data/lib/kdl/types/irl/parser.rb +1 -1
- data/lib/kdl/types/irl.rb +1 -1
- data/lib/kdl/types/regex.rb +1 -1
- data/lib/kdl/types/url.rb +1 -1
- data/lib/kdl/types/url_template.rb +1 -1
- data/lib/kdl/types/uuid.rb +1 -1
- data/lib/kdl/v1/document.rb +17 -0
- data/lib/kdl/v1/kdl.tab.rb +594 -0
- data/lib/kdl/v1/kdl.yy +89 -0
- data/lib/kdl/v1/node.rb +30 -0
- data/lib/kdl/v1/string_dumper.rb +28 -0
- data/lib/kdl/v1/tokenizer.rb +296 -0
- data/lib/kdl/v1/value.rb +89 -0
- data/lib/kdl/v1.rb +11 -0
- data/lib/kdl/value.rb +81 -12
- data/lib/kdl/version.rb +1 -1
- data/lib/kdl.rb +40 -1
- metadata +13 -4
data/lib/kdl/kdl.yy
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
class KDL::Parser
|
2
2
|
options no_result_var
|
3
|
-
token IDENT
|
4
|
-
STRING RAWSTRING
|
3
|
+
token IDENT STRING RAWSTRING
|
5
4
|
INTEGER FLOAT TRUE FALSE NULL
|
6
5
|
WS NEWLINE
|
7
6
|
LBRACE RBRACE
|
@@ -10,57 +9,66 @@ class KDL::Parser
|
|
10
9
|
SEMICOLON
|
11
10
|
EOF
|
12
11
|
SLASHDASH
|
13
|
-
ESCLINE
|
14
12
|
rule
|
15
|
-
document : nodes {
|
16
|
-
| linespaces {
|
13
|
+
document : nodes { @output_module::Document.new(val[0]) }
|
14
|
+
| linespaces { @output_module::Document.new([]) }
|
17
15
|
|
18
|
-
nodes
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
node
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
16
|
+
nodes : none { [] }
|
17
|
+
| linespaces node { [val[1]] }
|
18
|
+
| linespaces empty_node { [] }
|
19
|
+
| nodes node { [*val[0], val[1]] }
|
20
|
+
| nodes empty_node { val[0] }
|
21
|
+
node : unterm_node node_term { val[0] }
|
22
|
+
unterm_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 { val[0].tap { |x| x.children = [] } }
|
25
|
+
| node_decl node_children { val[0].tap { |x| x.children = val[1] } }
|
26
|
+
| node_decl empty_childrens { val[0].tap { |x| x.children = [] } }
|
27
|
+
| node_decl empty_childrens node_children { val[0].tap { |x| x.children = val[2] } }
|
28
|
+
| node_decl node_children empty_childrens { val[0].tap { |x| x.children = val[1] } }
|
29
|
+
| node_decl empty_childrens node_children empty_childrens { val[0].tap { |x| x.children = val[2] } }
|
30
|
+
node_decl : identifier { @output_module::Node.new(val[0]) }
|
31
|
+
| node_decl ws_plus value { val[0].tap { |x| x.arguments << val[2] } }
|
32
|
+
| node_decl ws_plus slashdash value { val[0] }
|
33
|
+
| node_decl ws_plus property { val[0].tap { |x| x.properties[val[2][0]] = val[2][1] } }
|
34
|
+
| node_decl ws_plus slashdash property { val[0] }
|
35
|
+
| node_decl ws_plus { val[0] }
|
36
|
+
empty_node : slashdash node
|
37
|
+
node_children : ws_star LBRACE nodes RBRACE { val[2] }
|
38
|
+
| ws_star LBRACE linespaces RBRACE { [] }
|
39
|
+
| ws_star LBRACE nodes unterm_node ws_star RBRACE { [*val[2], val[3]] }
|
40
|
+
| ws_star LBRACE linespaces unterm_node ws_star RBRACE { [val[3]] }
|
41
|
+
empty_children : slashdash node_children
|
42
|
+
| ws_plus empty_children
|
43
|
+
empty_childrens: empty_children | empty_childrens empty_children
|
38
44
|
node_term: linespaces | semicolon_term
|
39
45
|
semicolon_term: SEMICOLON | SEMICOLON linespaces
|
46
|
+
slashdash: SLASHDASH | slashdash ws_plus | slashdash linespaces
|
40
47
|
|
41
|
-
type : LPAREN identifier RPAREN { val[
|
48
|
+
type : LPAREN ws_star identifier ws_star RPAREN ws_star { val[2] }
|
42
49
|
|
43
|
-
identifier: IDENT { val[0].value }
|
44
|
-
|
45
|
-
|
50
|
+
identifier : IDENT { val[0].value }
|
51
|
+
| STRING { val[0].value }
|
52
|
+
| RAWSTRING { val[0].value }
|
46
53
|
|
47
|
-
property: identifier EQUALS value { [val[0], val[2]] }
|
54
|
+
property : identifier EQUALS value { [val[0], val[2]] }
|
48
55
|
|
49
56
|
value : untyped_value
|
50
57
|
| type untyped_value { val[1].as_type(val[0], @type_parsers.fetch(val[0], nil)) }
|
51
58
|
|
52
|
-
untyped_value :
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
untyped_value : IDENT { @output_module::Value::String.new(val[0].value) }
|
60
|
+
| STRING { @output_module::Value::String.new(val[0].value) }
|
61
|
+
| RAWSTRING { @output_module::Value::String.new(val[0].value) }
|
62
|
+
| INTEGER { @output_module::Value::Int.new(val[0].value) }
|
63
|
+
| FLOAT { @output_module::Value::Float.new(val[0].value, format: val[0].meta[:format]) }
|
64
|
+
| boolean { @output_module::Value::Boolean.new(val[0]) }
|
65
|
+
| NULL { @output_module::Value::Null }
|
58
66
|
|
59
67
|
boolean : TRUE { true }
|
60
68
|
| FALSE { false }
|
61
69
|
|
62
|
-
|
63
|
-
ws_star: none |
|
70
|
+
ws_plus: WS | WS ws_plus
|
71
|
+
ws_star: none | ws_plus
|
64
72
|
linespace: WS | NEWLINE | EOF
|
65
73
|
linespaces: linespace | linespaces linespace
|
66
74
|
|
@@ -68,18 +76,18 @@ nodes : none { [] }
|
|
68
76
|
|
69
77
|
---- inner
|
70
78
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
@type_parsers = {}
|
76
|
-
end
|
77
|
-
@tokenizer = ::KDL::Tokenizer.new(str)
|
78
|
-
do_parse
|
79
|
+
include KDL::ParserCommon
|
80
|
+
|
81
|
+
def initialize(**options)
|
82
|
+
init(**options)
|
79
83
|
end
|
80
84
|
|
81
|
-
|
85
|
+
def parser_version
|
86
|
+
2
|
87
|
+
end
|
82
88
|
|
83
|
-
def
|
84
|
-
@tokenizer.
|
89
|
+
def parse(str)
|
90
|
+
@tokenizer = ::KDL::Tokenizer.new(str)
|
91
|
+
check_version
|
92
|
+
do_parse
|
85
93
|
end
|
data/lib/kdl/node.rb
CHANGED
@@ -1,32 +1,114 @@
|
|
1
1
|
module KDL
|
2
2
|
class Node
|
3
|
+
class Custom < Node
|
4
|
+
def self.call(node, type)
|
5
|
+
new(node.name, arguments: node.arguments, properties: node.properties, children: node.children, type:)
|
6
|
+
end
|
7
|
+
|
8
|
+
def version
|
9
|
+
nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_v1
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_v2
|
17
|
+
self
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
include Enumerable
|
22
|
+
|
3
23
|
attr_accessor :name, :arguments, :properties, :children, :type
|
4
24
|
|
5
|
-
def initialize(name,
|
25
|
+
def initialize(name, _args = [], _props = {}, _children = [],
|
26
|
+
arguments: _args,
|
27
|
+
properties: _props,
|
28
|
+
children: _children,
|
29
|
+
type: nil
|
30
|
+
)
|
6
31
|
@name = name
|
7
32
|
@arguments = arguments
|
8
|
-
@properties = properties
|
33
|
+
@properties = properties.transform_keys(&:to_s)
|
9
34
|
@children = children
|
10
35
|
@type = type
|
11
36
|
end
|
12
37
|
|
13
|
-
def
|
38
|
+
def [](key)
|
39
|
+
case key
|
40
|
+
when Integer
|
41
|
+
arguments[key]&.value
|
42
|
+
when String, Symbol
|
43
|
+
properties[key.to_s]&.value
|
44
|
+
else
|
45
|
+
raise ArgumentError, "node can only be indexed by Integer, String, or Symbol"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def child(key)
|
50
|
+
case key
|
51
|
+
when Integer
|
52
|
+
children[key]
|
53
|
+
when String, Symbol
|
54
|
+
children.find { _1.name == key.to_s }
|
55
|
+
else
|
56
|
+
raise ArgumentError, "node can only be indexed by Integer, String, or Symbol"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def arg(key)
|
61
|
+
child(key)&.arguments&.first&.value
|
62
|
+
end
|
63
|
+
|
64
|
+
def args(key)
|
65
|
+
child(key)&.arguments&.map(&:value)
|
66
|
+
end
|
67
|
+
|
68
|
+
def each_arg(key, &block)
|
69
|
+
args(key)&.each(&block)
|
70
|
+
end
|
71
|
+
|
72
|
+
def dash_vals(key)
|
73
|
+
child(key)
|
74
|
+
&.children
|
75
|
+
&.select { _1.name == "-" }
|
76
|
+
&.map { _1.arguments.first&.value }
|
77
|
+
end
|
78
|
+
|
79
|
+
def each_dash_val(key, &block)
|
80
|
+
dash_vals(key)&.each(&block)
|
81
|
+
end
|
82
|
+
|
83
|
+
def each(&block)
|
84
|
+
children.each(&block)
|
85
|
+
end
|
86
|
+
|
87
|
+
def <=>(other)
|
88
|
+
name <=> other.name
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_s(level = 0, m = :to_s)
|
14
92
|
indent = ' ' * level
|
15
|
-
s = "#{indent}#{type ? "(#{id_to_s type})" : ''}#{id_to_s name}"
|
93
|
+
s = "#{indent}#{type ? "(#{id_to_s type, m })" : ''}#{id_to_s name, m}"
|
16
94
|
unless arguments.empty?
|
17
|
-
s += " #{arguments.map(
|
95
|
+
s += " #{arguments.map(&m).join(' ')}"
|
18
96
|
end
|
19
97
|
unless properties.empty?
|
20
|
-
s += " #{properties.map { |k, v| "#{id_to_s k}=#{v}" }.join(' ')}"
|
98
|
+
s += " #{properties.map { |k, v| "#{id_to_s k, m}=#{v.public_send(m)}" }.join(' ')}"
|
21
99
|
end
|
22
100
|
unless children.empty?
|
23
101
|
s += " {\n"
|
24
|
-
s += children.map { |c| "#{c.
|
25
|
-
s += "#{indent}}"
|
102
|
+
s += children.map { |c| "#{c.public_send(m, level + 1)}" }.join("\n")
|
103
|
+
s += "\n#{indent}}"
|
26
104
|
end
|
27
105
|
s
|
28
106
|
end
|
29
107
|
|
108
|
+
def inspect(level = 0)
|
109
|
+
to_s(level, :inspect)
|
110
|
+
end
|
111
|
+
|
30
112
|
def ==(other)
|
31
113
|
return false unless other.is_a?(Node)
|
32
114
|
|
@@ -45,18 +127,37 @@ module KDL
|
|
45
127
|
|
46
128
|
return self.as_type(type) if result.nil?
|
47
129
|
|
48
|
-
unless result.is_a?(::KDL::Node)
|
49
|
-
raise ArgumentError, "expected parser to return an instance of ::KDL::Node, got `#{result.class}'"
|
130
|
+
unless result.is_a?(::KDL::Node::Custom)
|
131
|
+
raise ArgumentError, "expected parser to return an instance of ::KDL::Node::Custom, got `#{result.class}'"
|
50
132
|
end
|
51
133
|
|
52
134
|
result
|
53
135
|
end
|
54
136
|
end
|
55
137
|
|
138
|
+
def version
|
139
|
+
2
|
140
|
+
end
|
141
|
+
|
142
|
+
def to_v2
|
143
|
+
self
|
144
|
+
end
|
145
|
+
|
146
|
+
def to_v1
|
147
|
+
::KDL::V1::Node.new(name,
|
148
|
+
arguments: arguments.map(&:to_v1),
|
149
|
+
properties: properties.transform_values(&:to_v1),
|
150
|
+
children: children.map(&:to_v1),
|
151
|
+
type: type
|
152
|
+
)
|
153
|
+
end
|
154
|
+
|
56
155
|
private
|
57
156
|
|
58
|
-
def id_to_s(id)
|
59
|
-
|
157
|
+
def id_to_s(id, m = :to_s)
|
158
|
+
return id.public_send(m) unless m == :to_s
|
159
|
+
|
160
|
+
StringDumper.call(id.to_s)
|
60
161
|
end
|
61
162
|
end
|
62
163
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module KDL
|
2
|
+
module ParserCommon
|
3
|
+
|
4
|
+
private
|
5
|
+
|
6
|
+
def init(parse_types: true, type_parsers: {}, output_module: ::KDL)
|
7
|
+
@output_module = output_module
|
8
|
+
if parse_types
|
9
|
+
@type_parsers = ::KDL::Types::MAPPING.merge(type_parsers)
|
10
|
+
else
|
11
|
+
@type_parsers = {}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def next_token
|
16
|
+
@tokenizer.next_token
|
17
|
+
end
|
18
|
+
|
19
|
+
def check_version
|
20
|
+
return unless doc_version = @tokenizer.version_directive
|
21
|
+
if doc_version != parser_version
|
22
|
+
raise ParseError, "Version mismatch, document specified v#{doc_version}, but this is a v#{parser_version} parser"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/kdl/string_dumper.rb
CHANGED
@@ -1,45 +1,42 @@
|
|
1
1
|
module KDL
|
2
2
|
module StringDumper
|
3
|
-
|
4
|
-
|
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
|
3
|
+
def call(string)
|
4
|
+
return string if bare_identifier?(string)
|
15
5
|
|
16
|
-
|
6
|
+
%("#{string.each_char.map { |char| escape(char) }.join}")
|
7
|
+
end
|
17
8
|
|
18
|
-
|
19
|
-
' ' <= char && char <= '\x7e'
|
20
|
-
end
|
9
|
+
private
|
21
10
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
end
|
11
|
+
def escape(char)
|
12
|
+
case char
|
13
|
+
when "\n" then '\n'
|
14
|
+
when "\r" then '\r'
|
15
|
+
when "\t" then '\t'
|
16
|
+
when '\\' then '\\\\'
|
17
|
+
when '"' then '\"'
|
18
|
+
when "\b" then '\b'
|
19
|
+
when "\f" then '\f'
|
20
|
+
else char
|
33
21
|
end
|
22
|
+
end
|
34
23
|
|
35
|
-
|
36
|
-
|
37
|
-
|
24
|
+
FORBIDDEN =
|
25
|
+
Tokenizer::SYMBOLS.keys +
|
26
|
+
Tokenizer::WHITESPACE +
|
27
|
+
Tokenizer::NEWLINES +
|
28
|
+
"()[]/\\\"#".chars +
|
29
|
+
("\x0".."\x20").to_a
|
38
30
|
|
39
|
-
|
40
|
-
|
41
|
-
|
31
|
+
def bare_identifier?(name)
|
32
|
+
case name
|
33
|
+
when '', 'true', 'fase', 'null', '#true', '#false', '#null', /\A\.?\d/
|
34
|
+
false
|
35
|
+
else
|
36
|
+
!name.each_char.any? { |c| FORBIDDEN.include?(c) }
|
42
37
|
end
|
43
38
|
end
|
39
|
+
|
40
|
+
extend self
|
44
41
|
end
|
45
42
|
end
|