class PSON::Pure::Parser

This class implements the PSON parser that is used to parse a PSON string into a Ruby data structure.

Constants

ARRAY_CLOSE
ARRAY_OPEN
COLLECTION_DELIMITER
FALSE
FLOAT
IGNORE
INFINITY
INTEGER
MINUS_INFINITY
NAN
NULL
OBJECT_CLOSE
OBJECT_OPEN
PAIR_DELIMITER
STRING
TRUE
UNESCAPE_MAP

Unescape characters in strings.

UNPARSED

Public Class Methods

new(source, opts = {}) click to toggle source

Creates a new PSON::Pure::Parser instance for the string source.

It will be configured by the opts hash. opts can have the following keys:

  • max_nesting: The maximum depth of nesting allowed in the parsed data structures. Disable depth checking with :max_nesting => false|nil|0, it defaults to 19.

  • allow_nan: If set to true, allow NaN, Infinity and -Infinity in defiance of RFC 4627 to be parsed by the Parser. This option defaults to false.

  • object_class: Defaults to Hash

  • array_class: Defaults to Array

Calls superclass method
   # File lib/puppet/external/pson/pure/parser.rb
65 def initialize(source, opts = {})
66   source = convert_encoding source
67   super source
68   if !opts.key?(:max_nesting) # defaults to 19
69     @max_nesting = 19
70   elsif opts[:max_nesting]
71     @max_nesting = opts[:max_nesting]
72   else
73     @max_nesting = 0
74   end
75   @allow_nan = !!opts[:allow_nan]
76   @object_class = opts[:object_class] || Hash
77   @array_class = opts[:array_class] || Array
78 end

Public Instance Methods

parse() click to toggle source

Parses the current PSON string source and returns the complete data structure as a result.

    # File lib/puppet/external/pson/pure/parser.rb
 84 def parse
 85   reset
 86   obj = nil
 87   until eos?
 88     case
 89     when scan(OBJECT_OPEN)
 90       obj and raise ParserError, "source '#{peek(20)}' not in PSON!"
 91       @current_nesting = 1
 92       obj = parse_object
 93     when scan(ARRAY_OPEN)
 94       obj and raise ParserError, "source '#{peek(20)}' not in PSON!"
 95       @current_nesting = 1
 96       obj = parse_array
 97     when skip(IGNORE)
 98       ;
 99     else
100       raise ParserError, "source '#{peek(20)}' not in PSON!"
101     end
102   end
103   obj or raise ParserError, "source did not contain any PSON!"
104   obj
105 end

Private Instance Methods

convert_encoding(source) click to toggle source
    # File lib/puppet/external/pson/pure/parser.rb
109 def convert_encoding(source)
110   if source.respond_to?(:to_str)
111     source = source.to_str
112   else
113     raise TypeError, "#{source.inspect} is not like a string"
114   end
115   if supports_encodings?(source)
116     if source.encoding == ::Encoding::ASCII_8BIT
117       b = source[0, 4].bytes.to_a
118       source =
119         case
120         when b.size >= 4 && b[0] == 0 && b[1] == 0 && b[2] == 0
121           source.dup.force_encoding(::Encoding::UTF_32BE).encode!(::Encoding::UTF_8)
122         when b.size >= 4 && b[0] == 0 && b[2] == 0
123           source.dup.force_encoding(::Encoding::UTF_16BE).encode!(::Encoding::UTF_8)
124         when b.size >= 4 && b[1] == 0 && b[2] == 0 && b[3] == 0
125           source.dup.force_encoding(::Encoding::UTF_32LE).encode!(::Encoding::UTF_8)
126         when b.size >= 4 && b[1] == 0 && b[3] == 0
127           source.dup.force_encoding(::Encoding::UTF_16LE).encode!(::Encoding::UTF_8)
128         else
129           source.dup
130         end
131     else
132       source = source.encode(::Encoding::UTF_8)
133     end
134     source.force_encoding(::Encoding::ASCII_8BIT)
135   else
136     b = source
137     source =
138       case
139       when b.size >= 4 && b[0] == 0 && b[1] == 0 && b[2] == 0
140         PSON.encode('utf-8', 'utf-32be', b)
141       when b.size >= 4 && b[0] == 0 && b[2] == 0
142         PSON.encode('utf-8', 'utf-16be', b)
143       when b.size >= 4 && b[1] == 0 && b[2] == 0 && b[3] == 0
144         PSON.encode('utf-8', 'utf-32le', b)
145       when b.size >= 4 && b[1] == 0 && b[3] == 0
146         PSON.encode('utf-8', 'utf-16le', b)
147       else
148         b
149       end
150   end
151   source
152 end
parse_array() click to toggle source
    # File lib/puppet/external/pson/pure/parser.rb
240 def parse_array
241   raise NestingError, "nesting of #@current_nesting is too deep" if
242     @max_nesting.nonzero? && @current_nesting > @max_nesting
243   result = @array_class.new
244   delim = false
245   until eos?
246     case
247     when (value = parse_value) != UNPARSED
248       delim = false
249       result << value
250       skip(IGNORE)
251       if scan(COLLECTION_DELIMITER)
252         delim = true
253       elsif match?(ARRAY_CLOSE)
254         ;
255       else
256         raise ParserError, "expected ',' or ']' in array at '#{peek(20)}'!"
257       end
258     when scan(ARRAY_CLOSE)
259       raise ParserError, "expected next element in array at '#{peek(20)}'!" if delim
260       break
261     when skip(IGNORE)
262       ;
263     else
264       raise ParserError, "unexpected token in array at '#{peek(20)}'!"
265     end
266   end
267   result
268 end
parse_object() click to toggle source
    # File lib/puppet/external/pson/pure/parser.rb
270 def parse_object
271   raise NestingError, "nesting of #@current_nesting is too deep" if
272     @max_nesting.nonzero? && @current_nesting > @max_nesting
273   result = @object_class.new
274   delim = false
275   until eos?
276     case
277     when (string = parse_string) != UNPARSED
278       skip(IGNORE)
279       raise ParserError, "expected ':' in object at '#{peek(20)}'!" unless scan(PAIR_DELIMITER)
280       skip(IGNORE)
281       unless (value = parse_value).equal? UNPARSED
282         result[string] = value
283         delim = false
284         skip(IGNORE)
285         if scan(COLLECTION_DELIMITER)
286           delim = true
287         elsif match?(OBJECT_CLOSE)
288           ;
289         else
290           raise ParserError, "expected ',' or '}' in object at '#{peek(20)}'!"
291         end
292       else
293         raise ParserError, "expected value in object at '#{peek(20)}'!"
294       end
295     when scan(OBJECT_CLOSE)
296       raise ParserError, "expected next name, value pair in object at '#{peek(20)}'!" if delim
297       break
298     when skip(IGNORE)
299       ;
300     else
301       raise ParserError, "unexpected token in object at '#{peek(20)}'!"
302     end
303   end
304   result
305 end
parse_string() click to toggle source
    # File lib/puppet/external/pson/pure/parser.rb
179 def parse_string
180   if scan(STRING)
181     return '' if self[1].empty?
182     string = self[1].gsub(%r{(?:\\[\\bfnrt"/]|(?:\\u(?:[A-Fa-f\d]{4}))+|\\[\x20-\xff])}n) do |c|
183       u = UNESCAPE_MAP[$MATCH[1]]
184       if u
185         u
186       else # \uXXXX
187         bytes = ''
188         i = 0
189         while c[6 * i] == ?\\ && c[6 * i + 1] == ?u
190           bytes << c[6 * i + 2, 2].to_i(16) << c[6 * i + 4, 2].to_i(16)
191           i += 1
192         end
193         PSON.encode('utf-8', 'utf-16be', bytes)
194       end
195     end
196     string.force_encoding(Encoding::UTF_8) if string.respond_to?(:force_encoding)
197     string
198   else
199     UNPARSED
200   end
201 rescue => e
202   raise GeneratorError, "Caught #{e.class}: #{e}", e.backtrace
203 end
parse_value() click to toggle source
    # File lib/puppet/external/pson/pure/parser.rb
205 def parse_value
206   case
207   when scan(FLOAT)
208     Float(self[1])
209   when scan(INTEGER)
210     Integer(self[1])
211   when scan(TRUE)
212     true
213   when scan(FALSE)
214     false
215   when scan(NULL)
216     nil
217   when (string = parse_string) != UNPARSED
218     string
219   when scan(ARRAY_OPEN)
220     @current_nesting += 1
221     ary = parse_array
222     @current_nesting -= 1
223     ary
224   when scan(OBJECT_OPEN)
225     @current_nesting += 1
226     obj = parse_object
227     @current_nesting -= 1
228     obj
229   when @allow_nan && scan(NAN)
230     NaN
231   when @allow_nan && scan(INFINITY)
232     Infinity
233   when @allow_nan && scan(MINUS_INFINITY)
234     MinusInfinity
235   else
236     UNPARSED
237   end
238 end
supports_encodings?(string) click to toggle source
    # File lib/puppet/external/pson/pure/parser.rb
154 def supports_encodings?(string)
155   # Some modules, such as REXML on 1.8.7 (see #22804) can actually create
156   # a top-level Encoding constant when they are misused. Therefore
157   # checking for just that constant is not enough, so we'll be a bit more
158   # robust about if we can actually support encoding transformations.
159   string.respond_to?(:encoding) && defined?(::Encoding)
160 end