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