kdl 0.1.1 → 1.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 +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
|