d02ec34dc16c0e2bd2622460327fea1891f68f1b
[packages/precise/mcollective.git] / lib / mcollective / vendor / json / lib / json / pure / parser.rb
1 require 'strscan'
2
3 module JSON
4   module Pure
5     # This class implements the JSON parser that is used to parse a JSON string
6     # into a Ruby data structure.
7     class Parser < StringScanner
8       STRING                = /" ((?:[^\x0-\x1f"\\] |
9                                    # escaped special characters:
10                                   \\["\\\/bfnrt] |
11                                   \\u[0-9a-fA-F]{4} |
12                                    # match all but escaped special characters:
13                                   \\[\x20-\x21\x23-\x2e\x30-\x5b\x5d-\x61\x63-\x65\x67-\x6d\x6f-\x71\x73\x75-\xff])*)
14                               "/nx
15       INTEGER               = /(-?0|-?[1-9]\d*)/
16       FLOAT                 = /(-?
17                                 (?:0|[1-9]\d*)
18                                 (?:
19                                   \.\d+(?i:e[+-]?\d+) |
20                                   \.\d+ |
21                                   (?i:e[+-]?\d+)
22                                 )
23                                 )/x
24       NAN                   = /NaN/
25       INFINITY              = /Infinity/
26       MINUS_INFINITY        = /-Infinity/
27       OBJECT_OPEN           = /\{/
28       OBJECT_CLOSE          = /\}/
29       ARRAY_OPEN            = /\[/
30       ARRAY_CLOSE           = /\]/
31       PAIR_DELIMITER        = /:/
32       COLLECTION_DELIMITER  = /,/
33       TRUE                  = /true/
34       FALSE                 = /false/
35       NULL                  = /null/
36       IGNORE                = %r(
37         (?:
38          //[^\n\r]*[\n\r]| # line comments
39          /\*               # c-style comments
40          (?:
41           [^*/]|        # normal chars
42           /[^*]|        # slashes that do not start a nested comment
43           \*[^/]|       # asterisks that do not end this comment
44           /(?=\*/)      # single slash before this comment's end
45          )*
46            \*/               # the End of this comment
47            |[ \t\r\n]+       # whitespaces: space, horicontal tab, lf, cr
48         )+
49       )mx
50
51       UNPARSED = Object.new
52
53       # Creates a new JSON::Pure::Parser instance for the string _source_.
54       #
55       # It will be configured by the _opts_ hash. _opts_ can have the following
56       # keys:
57       # * *max_nesting*: The maximum depth of nesting allowed in the parsed data
58       #   structures. Disable depth checking with :max_nesting => false|nil|0,
59       #   it defaults to 19.
60       # * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in
61       #   defiance of RFC 4627 to be parsed by the Parser. This option defaults
62       #   to false.
63       # * *symbolize_names*: If set to true, returns symbols for the names
64       #   (keys) in a JSON object. Otherwise strings are returned, which is also
65       #   the default.
66       # * *create_additions*: If set to true, the Parser creates
67       #   additions when if a matching class and create_id was found. This
68       #   option defaults to false.
69       # * *object_class*: Defaults to Hash
70       # * *array_class*: Defaults to Array
71       # * *quirks_mode*: Enables quirks_mode for parser, that is for example
72       #   parsing single JSON values instead of documents is possible.
73       def initialize(source, opts = {})
74         opts ||= {}
75         unless @quirks_mode = opts[:quirks_mode]
76           source = determine_encoding source
77         end
78         super source
79         if !opts.key?(:max_nesting) # defaults to 19
80           @max_nesting = 19
81         elsif opts[:max_nesting]
82           @max_nesting = opts[:max_nesting]
83         else
84           @max_nesting = 0
85         end
86         @allow_nan = !!opts[:allow_nan]
87         @symbolize_names = !!opts[:symbolize_names]
88         if opts.key?(:create_additions)
89           @create_additions = !!opts[:create_additions]
90         else
91           @create_additions = false
92         end
93         @create_id = @create_additions ? JSON.create_id : nil
94         @object_class = opts[:object_class] || Hash
95         @array_class  = opts[:array_class] || Array
96         @match_string = opts[:match_string]
97       end
98
99       alias source string
100
101       def quirks_mode?
102         !!@quirks_mode
103       end
104
105       def reset
106         super
107         @current_nesting = 0
108       end
109
110       # Parses the current JSON string _source_ and returns the complete data
111       # structure as a result.
112       def parse
113         reset
114         obj = nil
115         if @quirks_mode
116           while !eos? && skip(IGNORE)
117           end
118           if eos?
119             raise ParserError, "source did not contain any JSON!"
120           else
121             obj = parse_value
122             obj == UNPARSED and raise ParserError, "source did not contain any JSON!"
123           end
124         else
125           until eos?
126             case
127             when scan(OBJECT_OPEN)
128               obj and raise ParserError, "source '#{peek(20)}' not in JSON!"
129               @current_nesting = 1
130               obj = parse_object
131             when scan(ARRAY_OPEN)
132               obj and raise ParserError, "source '#{peek(20)}' not in JSON!"
133               @current_nesting = 1
134               obj = parse_array
135             when skip(IGNORE)
136               ;
137             else
138               raise ParserError, "source '#{peek(20)}' not in JSON!"
139             end
140           end
141           obj or raise ParserError, "source did not contain any JSON!"
142         end
143         obj
144       end
145
146       private
147
148       def determine_encoding(source)
149         if defined?(::Encoding)
150           if source.encoding == ::Encoding::ASCII_8BIT
151             b = source[0, 4].bytes.to_a
152             source =
153               case
154               when b.size >= 4 && b[0] == 0 && b[1] == 0 && b[2] == 0
155                 source.dup.force_encoding(::Encoding::UTF_32BE).encode!(::Encoding::UTF_8)
156               when b.size >= 4 && b[0] == 0 && b[2] == 0
157                 source.dup.force_encoding(::Encoding::UTF_16BE).encode!(::Encoding::UTF_8)
158               when b.size >= 4 && b[1] == 0 && b[2] == 0 && b[3] == 0
159                 source.dup.force_encoding(::Encoding::UTF_32LE).encode!(::Encoding::UTF_8)
160               when b.size >= 4 && b[1] == 0 && b[3] == 0
161                 source.dup.force_encoding(::Encoding::UTF_16LE).encode!(::Encoding::UTF_8)
162               else
163                 source.dup
164               end
165           else
166             source = source.encode(::Encoding::UTF_8)
167           end
168           source.force_encoding(::Encoding::ASCII_8BIT)
169         else
170           b = source
171           source =
172             case
173             when b.size >= 4 && b[0] == 0 && b[1] == 0 && b[2] == 0
174               JSON.iconv('utf-8', 'utf-32be', b)
175             when b.size >= 4 && b[0] == 0 && b[2] == 0
176               JSON.iconv('utf-8', 'utf-16be', b)
177             when b.size >= 4 && b[1] == 0 && b[2] == 0 && b[3] == 0
178               JSON.iconv('utf-8', 'utf-32le', b)
179             when b.size >= 4 && b[1] == 0 && b[3] == 0
180               JSON.iconv('utf-8', 'utf-16le', b)
181             else
182               b
183             end
184         end
185         source
186       end
187
188       # Unescape characters in strings.
189       UNESCAPE_MAP = Hash.new { |h, k| h[k] = k.chr }
190       UNESCAPE_MAP.update({
191         ?"  => '"',
192         ?\\ => '\\',
193         ?/  => '/',
194         ?b  => "\b",
195         ?f  => "\f",
196         ?n  => "\n",
197         ?r  => "\r",
198         ?t  => "\t",
199         ?u  => nil,
200       })
201
202       EMPTY_8BIT_STRING = ''
203       if ::String.method_defined?(:encode)
204         EMPTY_8BIT_STRING.force_encoding Encoding::ASCII_8BIT
205       end
206
207       def parse_string
208         if scan(STRING)
209           return '' if self[1].empty?
210           string = self[1].gsub(%r((?:\\[\\bfnrt"/]|(?:\\u(?:[A-Fa-f\d]{4}))+|\\[\x20-\xff]))n) do |c|
211             if u = UNESCAPE_MAP[$&[1]]
212               u
213             else # \uXXXX
214               bytes = EMPTY_8BIT_STRING.dup
215               i = 0
216               while c[6 * i] == ?\\ && c[6 * i + 1] == ?u
217                 bytes << c[6 * i + 2, 2].to_i(16) << c[6 * i + 4, 2].to_i(16)
218                 i += 1
219               end
220               JSON.iconv('utf-8', 'utf-16be', bytes)
221             end
222           end
223           if string.respond_to?(:force_encoding)
224             string.force_encoding(::Encoding::UTF_8)
225           end
226           if @create_additions and @match_string
227             for (regexp, klass) in @match_string
228               klass.json_creatable? or next
229               string =~ regexp and return klass.json_create(string)
230             end
231           end
232           string
233         else
234           UNPARSED
235         end
236       rescue => e
237         raise ParserError, "Caught #{e.class} at '#{peek(20)}': #{e}"
238       end
239
240       def parse_value
241         case
242         when scan(FLOAT)
243           Float(self[1])
244         when scan(INTEGER)
245           Integer(self[1])
246         when scan(TRUE)
247           true
248         when scan(FALSE)
249           false
250         when scan(NULL)
251           nil
252         when (string = parse_string) != UNPARSED
253           string
254         when scan(ARRAY_OPEN)
255           @current_nesting += 1
256           ary = parse_array
257           @current_nesting -= 1
258           ary
259         when scan(OBJECT_OPEN)
260           @current_nesting += 1
261           obj = parse_object
262           @current_nesting -= 1
263           obj
264         when @allow_nan && scan(NAN)
265           NaN
266         when @allow_nan && scan(INFINITY)
267           Infinity
268         when @allow_nan && scan(MINUS_INFINITY)
269           MinusInfinity
270         else
271           UNPARSED
272         end
273       end
274
275       def parse_array
276         raise NestingError, "nesting of #@current_nesting is too deep" if
277           @max_nesting.nonzero? && @current_nesting > @max_nesting
278         result = @array_class.new
279         delim = false
280         until eos?
281           case
282           when (value = parse_value) != UNPARSED
283             delim = false
284             result << value
285             skip(IGNORE)
286             if scan(COLLECTION_DELIMITER)
287               delim = true
288             elsif match?(ARRAY_CLOSE)
289               ;
290             else
291               raise ParserError, "expected ',' or ']' in array at '#{peek(20)}'!"
292             end
293           when scan(ARRAY_CLOSE)
294             if delim
295               raise ParserError, "expected next element in array at '#{peek(20)}'!"
296             end
297             break
298           when skip(IGNORE)
299             ;
300           else
301             raise ParserError, "unexpected token in array at '#{peek(20)}'!"
302           end
303         end
304         result
305       end
306
307       def parse_object
308         raise NestingError, "nesting of #@current_nesting is too deep" if
309           @max_nesting.nonzero? && @current_nesting > @max_nesting
310         result = @object_class.new
311         delim = false
312         until eos?
313           case
314           when (string = parse_string) != UNPARSED
315             skip(IGNORE)
316             unless scan(PAIR_DELIMITER)
317               raise ParserError, "expected ':' in object at '#{peek(20)}'!"
318             end
319             skip(IGNORE)
320             unless (value = parse_value).equal? UNPARSED
321               result[@symbolize_names ? string.to_sym : string] = value
322               delim = false
323               skip(IGNORE)
324               if scan(COLLECTION_DELIMITER)
325                 delim = true
326               elsif match?(OBJECT_CLOSE)
327                 ;
328               else
329                 raise ParserError, "expected ',' or '}' in object at '#{peek(20)}'!"
330               end
331             else
332               raise ParserError, "expected value in object at '#{peek(20)}'!"
333             end
334           when scan(OBJECT_CLOSE)
335             if delim
336               raise ParserError, "expected next name, value pair in object at '#{peek(20)}'!"
337             end
338             if @create_additions and klassname = result[@create_id]
339               klass = JSON.deep_const_get klassname
340               break unless klass and klass.json_creatable?
341               result = klass.json_create(result)
342             end
343             break
344           when skip(IGNORE)
345             ;
346           else
347             raise ParserError, "unexpected token in object at '#{peek(20)}'!"
348           end
349         end
350         result
351       end
352     end
353   end
354 end