Created
June 1, 2010 03:07
-
-
Save eagletmt/420517 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function! ParseJSON(str) | |
let obj = { 'str': a:str, 'pos': 0, 'len': len(a:str) } | |
function! obj.skip_space() dict | |
let self.pos = matchend(self.str, '^\s*', self.pos) | |
endfunction | |
function! obj.parse_string() dict | |
if self.str[self.pos] != '"' | |
throw 'not a string' | |
endif | |
let self.pos += 1 | |
let s = '' | |
while self.pos < self.len && self.str[self.pos] != '"' | |
if self.str[self.pos] == '\' | |
let self.pos += 1 | |
if self.str[self.pos] == '"' | |
\ || self.str[self.pos] == '/' | |
\ || self.str[self.pos] == '\' | |
let s .= self.str[self.pos] | |
let self.pos += 1 | |
elseif self.str[self.pos] == 'b' | |
let s .= "\b" | |
let self.pos += 1 | |
elseif self.str[self.pos] == 'f' | |
let s .= "\f" | |
let self.pos += 1 | |
elseif self.str[self.pos] == 'n' | |
let s .= "\n" | |
let self.pos += 1 | |
elseif self.str[self.pos] == 'r' | |
let s .= "\r" | |
let self.pos += 1 | |
elseif self.str[self.pos] == 't' | |
let s .= "\t" | |
let self.pos += 1 | |
elseif self.str[self.pos] == 'u' | |
let self.pos += 1 | |
let m = matchend(self.str, '\x\{4\}', self.pos) | |
if m == -1 | |
throw 'parse error at ' . self.pos | |
endif | |
let s .= eval('"\u' . self.str[self.pos : m-1] . '"') | |
let self.pos = m | |
else | |
throw 'parse error at ' . self.pos | |
endif | |
else | |
let s .= self.str[self.pos] | |
let self.pos += 1 | |
endif | |
endwhile | |
let self.pos += 1 | |
if self.pos > self.len | |
throw 'parse error at end of string' | |
endif | |
return s | |
endfunction | |
function! obj.parse_number() dict | |
let m = matchend(self.str, '^\(-\?[1-9]\d*\|0\)\(\.\d\+\)\?\([eE][+-]\?\d\+\)\?', self.pos) | |
if m == -1 | |
throw 'parse number error' | |
endif | |
let s = self.str[self.pos : m-1] | |
let t = matchlist(s, '^\([^.]\+\)\([eE]\)\(.\+\)$') | |
if !empty(t) | |
let s = t[1] . '.0' . t[2] . t[3] | |
endif | |
let n = eval(s) | |
let self.pos = m | |
return n | |
endfunction | |
function! obj.parse_object() dict | |
if self.str[self.pos] != '{' | |
throw 'not an object' | |
endif | |
let self.pos += 1 | |
call self.skip_space() | |
if self.str[self.pos] == '}' | |
let self.pos += 1 | |
return {} | |
endif | |
let obj = {} | |
while self.pos < self.len && self.str[self.pos] != '}' | |
call self.skip_space() | |
let key = self.parse_string() | |
call self.skip_space() | |
if self.str[self.pos] != ':' | |
throw 'parse error at ' . self.pos | |
endif | |
let self.pos += 1 | |
call self.skip_space() | |
let obj[key] = self.parse_value() | |
call self.skip_space() | |
if self.str[self.pos] == ',' | |
let self.pos += 1 | |
call self.skip_space() | |
elseif self.str[self.pos] == '}' | |
let self.pos += 1 | |
return obj | |
else | |
throw 'parse error at ' . self.pos | |
endif | |
endwhile | |
throw 'parse error at end of string' | |
endfunction | |
function! obj.parse_array() dict | |
if self.str[self.pos] != '[' | |
throw 'not an array' | |
endif | |
let self.pos += 1 | |
call self.skip_space() | |
if self.str[self.pos] == ']' | |
let self.pos += 1 | |
return [] | |
endif | |
let arr = [] | |
while self.pos < self.len | |
call self.skip_space() | |
call add(arr, self.parse_value()) | |
call self.skip_space() | |
if self.str[self.pos] == ',' | |
let self.pos += 1 | |
call self.skip_space() | |
elseif self.str[self.pos] == ']' | |
let self.pos += 1 | |
return arr | |
else | |
throw 'parse error at ' . self.pos | |
endif | |
endwhile | |
throw 'parse error at end of string' | |
endfunction | |
function! obj.parse_value() dict | |
if self.str[self.pos] == '"' | |
return self.parse_string() | |
elseif self.str[self.pos] == '{' | |
return self.parse_object() | |
elseif self.str[self.pos] == '[' | |
return self.parse_array() | |
elseif self.str[self.pos] =~# '-\|\d' | |
return self.parse_number() | |
elseif self.str[self.pos :] =~# '^true' | |
let self.pos += 4 | |
return 1 | |
elseif self.str[self.pos :] =~# '^false' | |
let self.pos += 5 | |
return 0 | |
elseif self.str[self.pos :] =~# '^null' | |
let self.pos += 4 | |
" XXX | |
return 0 | |
else | |
throw 'parse error at ' . self.pos | |
endif | |
endfunction | |
call obj.skip_space() | |
let result = obj.parse_value() | |
call obj.skip_space() | |
if obj.len != obj.pos | |
throw 'parse error at ' . obj.pos | |
endif | |
return result | |
endfunction | |
function! s:test_helper(input, expected, exception) | |
try | |
let output = ParseJSON(a:input) | |
if a:exception | |
echomsg printf('NG: NO EXCEPTION: input:%s, output:%s', a:input, string(output)) | |
elseif output == a:expected | |
echomsg printf('OK: input:%s', a:input) | |
else | |
echomsg printf('NG: INCORRECT: input:%s, output:%s', a:input, string(output)) | |
endif | |
catch | |
if a:exception | |
echomsg printf('OK: input:%s', a:input) | |
else | |
echomsg printf('NG: CAUGHT EXCEPTION: input:%s, message:%s', a:input, v:exception) | |
endif | |
endtry | |
endfunction | |
function! TestJSON() | |
call s:test_helper('true', 1, 0) | |
call s:test_helper('false', 0, 0) | |
call s:test_helper('42', 42, 0) | |
call s:test_helper('-7', -7, 0) | |
call s:test_helper('03', 0, 1) | |
call s:test_helper('0', 0, 0) | |
call s:test_helper('12.3', 12.3, 0) | |
call s:test_helper('1e2', 1.0e2, 0) | |
call s:test_helper('1.2e2', 1.2e2, 0) | |
call s:test_helper('1e-2', 1.0e-2, 0) | |
call s:test_helper('1E+2', 1.0e+2, 0) | |
call s:test_helper('1e-', 0, 1) | |
call s:test_helper('"foo"', 'foo', 0) | |
call s:test_helper('"foo', 0, 1) | |
call s:test_helper('foo', 0, 1) | |
call s:test_helper('"foo\"bar"', 'foo"bar', 0) | |
call s:test_helper('"\u3042\n"', "あ\n", 0) | |
call s:test_helper('"\e"', 0, 1) | |
call s:test_helper('[]', [], 0) | |
call s:test_helper('[1,2,3]', [1,2,3], 0) | |
call s:test_helper('[1,2,3', 0, 1) | |
call s:test_helper('{}', {}, 0) | |
call s:test_helper('{"foo": "bar"}', {'foo' : 'bar'}, 0) | |
call s:test_helper('{"foo"}', 0, 1) | |
call s:test_helper('{"foo": }', 0, 1) | |
call s:test_helper('{"foo: "bar", }', 0, 1) | |
call s:test_helper('{"foo": {"bar": 42}, "hoge": ["fuga", "piyo"], "baz": true}' | |
\ , {'foo': {'bar': 42}, 'hoge': ['fuga', 'piyo'], 'baz': 1}, 0) | |
endfunction |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment