Chris@210
|
1 module CodeRay
|
Chris@210
|
2 module Scanners
|
Chris@210
|
3
|
Chris@210
|
4 class JSON < Scanner
|
Chris@210
|
5
|
Chris@210
|
6 include Streamable
|
Chris@210
|
7
|
Chris@210
|
8 register_for :json
|
Chris@210
|
9 file_extension 'json'
|
Chris@210
|
10
|
Chris@210
|
11 KINDS_NOT_LOC = [
|
Chris@210
|
12 :float, :char, :content, :delimiter,
|
Chris@210
|
13 :error, :integer, :operator, :value,
|
Chris@210
|
14 ]
|
Chris@210
|
15
|
Chris@210
|
16 ESCAPE = / [bfnrt\\"\/] /x
|
Chris@210
|
17 UNICODE_ESCAPE = / u[a-fA-F0-9]{4} /x
|
Chris@210
|
18
|
Chris@210
|
19 def scan_tokens tokens, options
|
Chris@210
|
20
|
Chris@210
|
21 state = :initial
|
Chris@210
|
22 stack = []
|
Chris@210
|
23 key_expected = false
|
Chris@210
|
24
|
Chris@210
|
25 until eos?
|
Chris@210
|
26
|
Chris@210
|
27 kind = nil
|
Chris@210
|
28 match = nil
|
Chris@210
|
29
|
Chris@210
|
30 case state
|
Chris@210
|
31
|
Chris@210
|
32 when :initial
|
Chris@210
|
33 if match = scan(/ \s+ | \\\n /x)
|
Chris@210
|
34 tokens << [match, :space]
|
Chris@210
|
35 next
|
Chris@210
|
36 elsif match = scan(/ [:,\[{\]}] /x)
|
Chris@210
|
37 kind = :operator
|
Chris@210
|
38 case match
|
Chris@210
|
39 when '{' then stack << :object; key_expected = true
|
Chris@210
|
40 when '[' then stack << :array
|
Chris@210
|
41 when ':' then key_expected = false
|
Chris@210
|
42 when ',' then key_expected = true if stack.last == :object
|
Chris@210
|
43 when '}', ']' then stack.pop # no error recovery, but works for valid JSON
|
Chris@210
|
44 end
|
Chris@210
|
45 elsif match = scan(/ true | false | null /x)
|
Chris@210
|
46 kind = :value
|
Chris@210
|
47 elsif match = scan(/-?(?:0|[1-9]\d*)/)
|
Chris@210
|
48 kind = :integer
|
Chris@210
|
49 if scan(/\.\d+(?:[eE][-+]?\d+)?|[eE][-+]?\d+/)
|
Chris@210
|
50 match << matched
|
Chris@210
|
51 kind = :float
|
Chris@210
|
52 end
|
Chris@210
|
53 elsif match = scan(/"/)
|
Chris@210
|
54 state = key_expected ? :key : :string
|
Chris@210
|
55 tokens << [:open, state]
|
Chris@210
|
56 kind = :delimiter
|
Chris@210
|
57 else
|
Chris@210
|
58 getch
|
Chris@210
|
59 kind = :error
|
Chris@210
|
60 end
|
Chris@210
|
61
|
Chris@210
|
62 when :string, :key
|
Chris@210
|
63 if scan(/[^\\"]+/)
|
Chris@210
|
64 kind = :content
|
Chris@210
|
65 elsif scan(/"/)
|
Chris@210
|
66 tokens << ['"', :delimiter]
|
Chris@210
|
67 tokens << [:close, state]
|
Chris@210
|
68 state = :initial
|
Chris@210
|
69 next
|
Chris@210
|
70 elsif scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox)
|
Chris@210
|
71 kind = :char
|
Chris@210
|
72 elsif scan(/\\./m)
|
Chris@210
|
73 kind = :content
|
Chris@210
|
74 elsif scan(/ \\ | $ /x)
|
Chris@210
|
75 tokens << [:close, state]
|
Chris@210
|
76 kind = :error
|
Chris@210
|
77 state = :initial
|
Chris@210
|
78 else
|
Chris@210
|
79 raise_inspect "else case \" reached; %p not handled." % peek(1), tokens
|
Chris@210
|
80 end
|
Chris@210
|
81
|
Chris@210
|
82 else
|
Chris@210
|
83 raise_inspect 'Unknown state', tokens
|
Chris@210
|
84
|
Chris@210
|
85 end
|
Chris@210
|
86
|
Chris@210
|
87 match ||= matched
|
Chris@210
|
88 if $CODERAY_DEBUG and not kind
|
Chris@210
|
89 raise_inspect 'Error token %p in line %d' %
|
Chris@210
|
90 [[match, kind], line], tokens
|
Chris@210
|
91 end
|
Chris@210
|
92 raise_inspect 'Empty token', tokens unless match
|
Chris@210
|
93
|
Chris@210
|
94 tokens << [match, kind]
|
Chris@210
|
95
|
Chris@210
|
96 end
|
Chris@210
|
97
|
Chris@210
|
98 if [:string, :key].include? state
|
Chris@210
|
99 tokens << [:close, state]
|
Chris@210
|
100 end
|
Chris@210
|
101
|
Chris@210
|
102 tokens
|
Chris@210
|
103 end
|
Chris@210
|
104
|
Chris@210
|
105 end
|
Chris@210
|
106
|
Chris@210
|
107 end
|
Chris@210
|
108 end
|