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