| Module | ActiveSupport::OkJson |
| In: |
vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb
|
Some parts adapted from golang.org/src/pkg/json/decode.go and golang.org/src/pkg/utf8/utf8.go
| Upstream | = | 'LTD7LBKLZWFF7OZK' |
| Utagx | = | 0x80 |
| Utag2 | = | 0xc0 |
| Utag3 | = | 0xe0 |
| Utag4 | = | 0xf0 |
| Utag5 | = | 0xF8 |
| Umaskx | = | 0x3f |
| Umask2 | = | 0x1f |
| Umask3 | = | 0x0f |
| Umask4 | = | 0x07 |
| Uchar1max | = | (1<<7) - 1 |
| Uchar2max | = | (1<<11) - 1 |
| Uchar3max | = | (1<<16) - 1 |
| Ucharerr | = | 0xFFFD |
| Ustrerr | = | "\xef\xbf\xbd" |
| Usurrself | = | 0x10000 |
| Usurr1 | = | 0xd800 |
| Usurr2 | = | 0xdc00 |
| Usurr3 | = | 0xe000 |
| Spc | = | ' '[0] |
| Unesc | = | {?b=>?\b, ?f=>?\f, ?n=>?\n, ?r=>?\r, ?t=>?\t} |
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 255
255: def abbrev(s)
256: t = s[0,10]
257: p = t['`']
258: t = t[0,p] if p
259: t = t + '...' if t.length < s.length
260: '`' + t + '`'
261: end
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 437
437: def arrenc(a)
438: '[' + a.map{|x| valenc(x)}.join(',') + ']'
439: end
Parses an "array" in the sense of RFC 4627. Returns the parsed value and any trailing tokens.
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 137
137: def arrparse(ts)
138: ts = eat('[', ts)
139: arr = []
140:
141: if ts[0][0] == ']'
142: return arr, ts[1..-1]
143: end
144:
145: v, ts = valparse(ts)
146: arr << v
147:
148: if ts[0][0] == ']'
149: return arr, ts[1..-1]
150: end
151:
152: loop do
153: ts = eat(',', ts)
154:
155: v, ts = valparse(ts)
156: arr << v
157:
158: if ts[0][0] == ']'
159: return arr, ts[1..-1]
160: end
161: end
162: end
Decodes a json document in string s and returns the corresponding ruby value. String s must be valid UTF-8. If you have a string in some other encoding, convert it first.
String values in the resulting structure will be UTF-8.
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 45
45: def decode(s)
46: ts = lex(s)
47: v, ts = textparse(ts)
48: if ts.length > 0
49: raise Error, 'trailing garbage'
50: end
51: v
52: end
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 165
165: def eat(typ, ts)
166: if ts[0][0] != typ
167: raise Error, "expected #{typ} (got #{ts[0].inspect})"
168: end
169: ts[1..-1]
170: end
Encodes x into a json text. It may contain only Array, Hash, String, Numeric, true, false, nil. (Note, this list excludes Symbol.) X itself must be an Array or a Hash. No other value can be encoded, and an error will be raised if x contains any other value, such as Nan, Infinity, Symbol, and Proc, or if a Hash key is not a String. Strings contained in x must be valid UTF-8.
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 407
407: def encode(x)
408: case x
409: when Hash then objenc(x)
410: when Array then arrenc(x)
411: else
412: raise Error, 'root value must be an Array or a Hash'
413: end
414: end
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 227
227: def falsetok(s); s[0,5] == 'false' ? [:val, 'false', false] : [] end
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 366
366: def hexdec4(s)
367: if s.length != 4
368: raise Error, 'short'
369: end
370: (nibble(s[0])<<12) | (nibble(s[1])<<8) | (nibble(s[2])<<4) | nibble(s[3])
371: end
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 442
442: def keyenc(k)
443: case k
444: when String then strenc(k)
445: else
446: raise Error, "Hash key is not a string: #{k.inspect}"
447: end
448: end
Scans s and returns a list of json tokens, excluding white space (as defined in RFC 4627).
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 175
175: def lex(s)
176: ts = []
177: while s.length > 0
178: typ, lexeme, val = tok(s)
179: if typ == nil
180: raise Error, "invalid character at #{s[0,10].inspect}"
181: end
182: if typ != :space
183: ts << [typ, lexeme, val]
184: end
185: s = s[lexeme.length..-1]
186: end
187: ts
188: end
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 387
387: def nibble(c)
388: case true
389: when ?0 <= c && c <= ?9 then c.ord - ?0.ord
390: when ?a <= c && c <= ?z then c.ord - ?a.ord + 10
391: when ?A <= c && c <= ?Z then c.ord - ?A.ord + 10
392: else
393: raise Error, "invalid hex code #{c}"
394: end
395: end
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 225
225: def nulltok(s); s[0,4] == 'null' ? [:val, 'null', nil] : [] end
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 492
492: def numenc(x)
493: if ((x.nan? || x.infinite?) rescue false)
494: raise Error, "Numeric cannot be represented: #{x}"
495: end
496: "#{x}"
497: end
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 230
230: def numtok(s)
231: m = /-?([1-9][0-9]+|[0-9])([.][0-9]+)?([eE][+-]?[0-9]+)?/.match(s)
232: if m && m.begin(0) == 0
233: if m[3] && !m[2]
234: [:val, m[0], Integer(m[1])*(10**Integer(m[3][1..-1]))]
235: elsif m[2]
236: [:val, m[0], Float(m[0])]
237: else
238: [:val, m[0], Integer(m[0])]
239: end
240: else
241: []
242: end
243: end
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 432
432: def objenc(x)
433: '{' + x.map{|k,v| keyenc(k) + ':' + valenc(v)}.join(',') + '}'
434: end
Parses an "object" in the sense of RFC 4627. Returns the parsed value and any trailing tokens.
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 94
94: def objparse(ts)
95: ts = eat('{', ts)
96: obj = {}
97:
98: if ts[0][0] == '}'
99: return obj, ts[1..-1]
100: end
101:
102: k, v, ts = pairparse(ts)
103: obj[k] = v
104:
105: if ts[0][0] == '}'
106: return obj, ts[1..-1]
107: end
108:
109: loop do
110: ts = eat(',', ts)
111:
112: k, v, ts = pairparse(ts)
113: obj[k] = v
114:
115: if ts[0][0] == '}'
116: return obj, ts[1..-1]
117: end
118: end
119: end
Parses a "member" in the sense of RFC 4627. Returns the parsed values and any trailing tokens.
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 124
124: def pairparse(ts)
125: (typ, _, k), ts = ts[0], ts[1..-1]
126: if typ != :str
127: raise Error, "unexpected #{k.inspect}"
128: end
129: ts = eat(':', ts)
130: v, ts = valparse(ts)
131: [k, v, ts]
132: end
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 451
451: def strenc(s)
452: t = StringIO.new
453: t.putc(?")
454: r = 0
455:
456: # In ruby >= 1.9, s[r] is a codepoint, not a byte.
457: rubydoesenc = s.class.method_defined?(:encoding)
458:
459: while r < s.length
460: case s[r]
461: when ?" then t.print('\\"')
462: when ?\\ then t.print('\\\\')
463: when ?\b then t.print('\\b')
464: when ?\f then t.print('\\f')
465: when ?\n then t.print('\\n')
466: when ?\r then t.print('\\r')
467: when ?\t then t.print('\\t')
468: else
469: c = s[r]
470: case true
471: when rubydoesenc
472: begin
473: c.ord # will raise an error if c is invalid UTF-8
474: t.write(c)
475: rescue
476: t.write(Ustrerr)
477: end
478: when Spc <= c && c <= ?~
479: t.putc(c)
480: else
481: n = ucharcopy(t, s, r) # ensure valid UTF-8 output
482: r += n - 1 # r is incremented below
483: end
484: end
485: r += 1
486: end
487: t.putc(?")
488: t.string
489: end
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 246
246: def strtok(s)
247: m = /"([^"\\]|\\["\/\\bfnrt]|\\u[0-9a-fA-F]{4})*"/.match(s)
248: if ! m
249: raise Error, "invalid string literal at #{abbrev(s)}"
250: end
251: [:str, m[0], unquote(m[0])]
252: end
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 374
374: def subst(u1, u2)
375: if Usurr1 <= u1 && u1 < Usurr2 && Usurr2 <= u2 && u2 < Usurr3
376: return ((u1-Usurr1)<<10) | (u2-Usurr2) + Usurrself
377: end
378: return Ucharerr
379: end
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 382
382: def surrogate?(u)
383: Usurr1 <= u && u < Usurr3
384: end
Parses a "json text" in the sense of RFC 4627. Returns the parsed value and any trailing tokens. Note: this is almost the same as valparse, except that it does not accept atomic values.
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 59
59: def textparse(ts)
60: if ts.length < 0
61: raise Error, 'empty'
62: end
63:
64: typ, _, val = ts[0]
65: case typ
66: when '{' then objparse(ts)
67: when '[' then arrparse(ts)
68: else
69: raise Error, "unexpected #{val.inspect}"
70: end
71: end
Scans the first token in s and returns a 3-element list, or nil if s does not begin with a valid token.
The first list element is one of ’{’, ’}’, ’:’, ’,’, ’[’, ’]’, :val, :str, and :space.
The second element is the lexeme.
The third element is the value of the token for :val and :str, otherwise it is the lexeme.
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 204
204: def tok(s)
205: case s[0]
206: when ?{ then ['{', s[0,1], s[0,1]]
207: when ?} then ['}', s[0,1], s[0,1]]
208: when ?: then [':', s[0,1], s[0,1]]
209: when ?, then [',', s[0,1], s[0,1]]
210: when ?[ then ['[', s[0,1], s[0,1]]
211: when ?] then [']', s[0,1], s[0,1]]
212: when ?n then nulltok(s)
213: when ?t then truetok(s)
214: when ?f then falsetok(s)
215: when ?" then strtok(s)
216: when Spc then [:space, s[0,1], s[0,1]]
217: when ?\t then [:space, s[0,1], s[0,1]]
218: when ?\n then [:space, s[0,1], s[0,1]]
219: when ?\r then [:space, s[0,1], s[0,1]]
220: else numtok(s)
221: end
222: end
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 226
226: def truetok(s); s[0,4] == 'true' ? [:val, 'true', true] : [] end
Copies the valid UTF-8 bytes of a single character from string s at position i to I/O object t, and returns the number of bytes copied. If no valid UTF-8 char exists at position i, ucharcopy writes Ustrerr and returns 1.
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 505
505: def ucharcopy(t, s, i)
506: n = s.length - i
507: raise Utf8Error if n < 1
508:
509: c0 = s[i].ord
510:
511: # 1-byte, 7-bit sequence?
512: if c0 < Utagx
513: t.putc(c0)
514: return 1
515: end
516:
517: raise Utf8Error if c0 < Utag2 # unexpected continuation byte?
518:
519: raise Utf8Error if n < 2 # need continuation byte
520: c1 = s[i+1].ord
521: raise Utf8Error if c1 < Utagx || Utag2 <= c1
522:
523: # 2-byte, 11-bit sequence?
524: if c0 < Utag3
525: raise Utf8Error if ((c0&Umask2)<<6 | (c1&Umaskx)) <= Uchar1max
526: t.putc(c0)
527: t.putc(c1)
528: return 2
529: end
530:
531: # need second continuation byte
532: raise Utf8Error if n < 3
533:
534: c2 = s[i+2].ord
535: raise Utf8Error if c2 < Utagx || Utag2 <= c2
536:
537: # 3-byte, 16-bit sequence?
538: if c0 < Utag4
539: u = (c0&Umask3)<<12 | (c1&Umaskx)<<6 | (c2&Umaskx)
540: raise Utf8Error if u <= Uchar2max
541: t.putc(c0)
542: t.putc(c1)
543: t.putc(c2)
544: return 3
545: end
546:
547: # need third continuation byte
548: raise Utf8Error if n < 4
549: c3 = s[i+3].ord
550: raise Utf8Error if c3 < Utagx || Utag2 <= c3
551:
552: # 4-byte, 21-bit sequence?
553: if c0 < Utag5
554: u = (c0&Umask4)<<18 | (c1&Umaskx)<<12 | (c2&Umaskx)<<6 | (c3&Umaskx)
555: raise Utf8Error if u <= Uchar3max
556: t.putc(c0)
557: t.putc(c1)
558: t.putc(c2)
559: t.putc(c3)
560: return 4
561: end
562:
563: raise Utf8Error
564: rescue Utf8Error
565: t.write(Ustrerr)
566: return 1
567: end
Encodes unicode character u as UTF-8 bytes in string a at position i. Returns the number of bytes written.
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 342
342: def ucharenc(a, i, u)
343: case true
344: when u <= Uchar1max
345: a[i] = (u & 0xff).chr
346: 1
347: when u <= Uchar2max
348: a[i+0] = (Utag2 | ((u>>6)&0xff)).chr
349: a[i+1] = (Utagx | (u&Umaskx)).chr
350: 2
351: when u <= Uchar3max
352: a[i+0] = (Utag3 | ((u>>12)&0xff)).chr
353: a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr
354: a[i+2] = (Utagx | (u&Umaskx)).chr
355: 3
356: else
357: a[i+0] = (Utag4 | ((u>>18)&0xff)).chr
358: a[i+1] = (Utagx | ((u>>12)&Umaskx)).chr
359: a[i+2] = (Utagx | ((u>>6)&Umaskx)).chr
360: a[i+3] = (Utagx | (u&Umaskx)).chr
361: 4
362: end
363: end
Converts a quoted json string literal q into a UTF-8-encoded string. The rules are different than for Ruby, so we cannot use eval. Unquote will raise an error if q contains control characters.
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 267
267: def unquote(q)
268: q = q[1...-1]
269: a = q.dup # allocate a big enough string
270: rubydoesenc = false
271: # In ruby >= 1.9, a[w] is a codepoint, not a byte.
272: if a.class.method_defined?(:force_encoding)
273: a.force_encoding('UTF-8')
274: rubydoesenc = true
275: end
276: r, w = 0, 0
277: while r < q.length
278: c = q[r]
279: case true
280: when c == ?\\
281: r += 1
282: if r >= q.length
283: raise Error, "string literal ends with a \"\\\": \"#{q}\""
284: end
285:
286: case q[r]
287: when ?",?\\,?/,?'
288: a[w] = q[r]
289: r += 1
290: w += 1
291: when ?b,?f,?n,?r,?t
292: a[w] = Unesc[q[r]]
293: r += 1
294: w += 1
295: when ?u
296: r += 1
297: uchar = begin
298: hexdec4(q[r,4])
299: rescue RuntimeError => e
300: raise Error, "invalid escape sequence \\u#{q[r,4]}: #{e}"
301: end
302: r += 4
303: if surrogate? uchar
304: if q.length >= r+6
305: uchar1 = hexdec4(q[r+2,4])
306: uchar = subst(uchar, uchar1)
307: if uchar != Ucharerr
308: # A valid pair; consume.
309: r += 6
310: end
311: end
312: end
313: if rubydoesenc
314: a[w] = '' << uchar
315: w += 1
316: else
317: w += ucharenc(a, w, uchar)
318: end
319: else
320: raise Error, "invalid escape char #{q[r]} in \"#{q}\""
321: end
322: when c == ?", c < Spc
323: raise Error, "invalid character in string literal \"#{q}\""
324: else
325: # Copy anything else byte-for-byte.
326: # Valid UTF-8 will remain valid UTF-8.
327: # Invalid UTF-8 will remain invalid UTF-8.
328: # In ruby >= 1.9, c is a codepoint, not a byte,
329: # in which case this is still what we want.
330: a[w] = c
331: r += 1
332: w += 1
333: end
334: end
335: a[0,w]
336: end
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 417
417: def valenc(x)
418: case x
419: when Hash then objenc(x)
420: when Array then arrenc(x)
421: when String then strenc(x)
422: when Numeric then numenc(x)
423: when true then "true"
424: when false then "false"
425: when nil then "null"
426: else
427: raise Error, "cannot encode #{x.class}: #{x.inspect}"
428: end
429: end
Parses a "value" in the sense of RFC 4627. Returns the parsed value and any trailing tokens.
# File vendor/rails/activesupport/lib/active_support/json/backends/okjson.rb, line 76
76: def valparse(ts)
77: if ts.length < 0
78: raise Error, 'empty'
79: end
80:
81: typ, _, val = ts[0]
82: case typ
83: when '{' then objparse(ts)
84: when '[' then arrparse(ts)
85: when :val,:str then [val, ts[1..-1]]
86: else
87: raise Error, "unexpected #{val.inspect}"
88: end
89: end