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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +4 -3
- data/.gitmodules +3 -0
- data/README.md +16 -2
- data/Rakefile +11 -6
- data/bin/console +2 -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 +237 -167
- data/lib/kdl/kdl.yy +40 -24
- data/lib/kdl/node.rb +33 -8
- data/lib/kdl/string_dumper.rb +45 -0
- data/lib/kdl/tokenizer.rb +163 -71
- 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 +47 -8
- data/.travis.yml +0 -6
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
node
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
37
|
-
|
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 :
|
42
|
-
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
-
|
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.
|
22
|
+
unless children.nil?
|
22
23
|
s += " {\n"
|
23
|
-
|
24
|
-
|
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
|
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
|
-
'{' => :
|
9
|
-
'}' => :
|
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
|
-
|
86
|
+
traverse(1)
|
49
87
|
when 'r'
|
50
88
|
if @str[@index + 1] == '"'
|
51
89
|
self.context = :rawstring
|
52
|
-
|
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
|
-
|
110
|
+
traverse(1)
|
73
111
|
when /[0-9\-+]/
|
74
112
|
n = @str[@index + 1]
|
75
113
|
if c == '0' && n =~ /[box]/
|
76
|
-
|
114
|
+
traverse(2)
|
77
115
|
@buffer = ''
|
78
116
|
self.context = case n
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
-
|
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
|
-
|
136
|
+
raise_error "Unexpected '\\'"
|
97
137
|
end
|
98
138
|
when *SYMBOLS.keys
|
99
|
-
|
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
|
-
|
105
|
-
|
143
|
+
return token(:NEWLINE, "#{c}#{n}").tap do
|
144
|
+
traverse(2)
|
145
|
+
new_line
|
146
|
+
end
|
106
147
|
else
|
107
|
-
|
108
|
-
|
148
|
+
return token(:NEWLINE, c).tap do
|
149
|
+
traverse(1)
|
150
|
+
new_line
|
151
|
+
end
|
109
152
|
end
|
110
153
|
when *NEWLINES
|
111
|
-
|
112
|
-
|
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
|
-
|
161
|
+
traverse(2)
|
117
162
|
elsif @str[@index + 1] == '*'
|
118
163
|
self.context = :multi_line_comment
|
119
164
|
@comment_nesting = 1
|
120
|
-
|
165
|
+
traverse(2)
|
121
166
|
elsif @str[@index + 1] == '-'
|
122
|
-
|
123
|
-
return [:SLASHDASH, '/-']
|
167
|
+
return token(:SLASHDASH, '/-').tap { traverse(2) }
|
124
168
|
else
|
125
169
|
self.context = :ident
|
126
170
|
@buffer = c
|
127
|
-
|
171
|
+
traverse(1)
|
128
172
|
end
|
129
173
|
when *WHITEPACE
|
130
174
|
self.context = :whitespace
|
131
175
|
@buffer = c
|
132
|
-
|
176
|
+
traverse(1)
|
133
177
|
when nil
|
134
|
-
return [false,
|
178
|
+
return [false, token(:EOF, :EOF)[1]] if @done
|
135
179
|
@done = true
|
136
|
-
return
|
180
|
+
return token(:EOF, :EOF)
|
137
181
|
when INITIAL_IDENTIFIER_CHARS
|
138
182
|
self.context = :ident
|
139
183
|
@buffer = c
|
140
|
-
|
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
|
-
|
192
|
+
raise_error "Unexpected character #{c.inspect}"
|
143
193
|
end
|
144
194
|
when :ident
|
145
195
|
case c
|
146
196
|
when IDENTIFIER_CHARS
|
147
|
-
|
197
|
+
traverse(1)
|
148
198
|
@buffer += c
|
149
199
|
else
|
150
200
|
case @buffer
|
151
|
-
when 'true' then return
|
152
|
-
when 'false' then return
|
153
|
-
when 'null' then return
|
154
|
-
else return
|
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
|
-
|
212
|
+
traverse(2)
|
163
213
|
when '"'
|
164
|
-
@
|
165
|
-
return [:STRING, convert_escapes(@buffer)]
|
214
|
+
return token(:STRING, convert_escapes(@buffer)).tap { traverse(1) }
|
166
215
|
when nil
|
167
|
-
|
216
|
+
raise_error "Unterminated string literal"
|
168
217
|
else
|
169
218
|
@buffer += c
|
170
|
-
|
219
|
+
traverse(1)
|
171
220
|
end
|
172
221
|
when :rawstring
|
173
|
-
|
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
|
-
@
|
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
|
-
|
235
|
+
traverse(1)
|
188
236
|
when :decimal
|
189
237
|
case c
|
190
238
|
when /[0-9.\-+_eE]/
|
191
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
275
|
+
return token(:EOF, :EOF)
|
227
276
|
else
|
228
|
-
|
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
|
-
|
282
|
+
traverse(2)
|
234
283
|
elsif c == '*' && @str[@index + 1] == '/'
|
235
284
|
@comment_nesting -= 1
|
236
|
-
|
285
|
+
traverse(2)
|
237
286
|
if @comment_nesting == 0
|
238
287
|
revert_context
|
239
288
|
end
|
240
289
|
else
|
241
|
-
|
290
|
+
traverse(1)
|
242
291
|
end
|
243
292
|
when :whitespace
|
244
293
|
if WHITEPACE.include?(c)
|
245
|
-
|
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
|
-
|
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
|
-
|
311
|
+
traverse(2)
|
261
312
|
else
|
262
|
-
return
|
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
|
282
|
-
|
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
|
-
|
377
|
+
token(:INTEGER, Integer(munch_underscores(s), 16))
|
287
378
|
end
|
288
|
-
|
379
|
+
|
289
380
|
def parse_octal(s)
|
290
|
-
|
381
|
+
token(:INTEGER, Integer(munch_underscores(s), 8))
|
291
382
|
end
|
292
|
-
|
383
|
+
|
293
384
|
def parse_binary(s)
|
294
|
-
|
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
|
-
|
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
|
-
|
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
|