kdl 0.1.2 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
- 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] }
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
- | WS empty_children
37
+ | ws empty_children
33
38
  node_term: linespaces | semicolon_term
34
39
  semicolon_term: SEMICOLON | SEMICOLON linespaces
35
40
 
36
- identifier: IDENT { val[0].value }
37
- | STRING { val[0].value }
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 : STRING { KDL::Value::String.new(val[0].value) }
42
- | RAWSTRING { KDL::Value::String.new(val[0].value) }
43
- | INTEGER { KDL::Value::Int.new(val[0].value) }
44
- | FLOAT { KDL::Value::Float.new(val[0].value) }
45
- | boolean { KDL::Value::Boolean.new(val[0]) }
46
- | NULL { KDL::Value::Null }
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
- ws_star: none | WS
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
- def parse(str)
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 += "\n#{indent}}"
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
- '{' => :LPAREN,
35
- '}' => :RPAREN,
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[0]
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 token(:FLOAT, Float(munch_underscores(s))) if s =~ /[.eE]/
337
- token(:INTEGER, Integer(munch_underscores(s), 10))
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