Skip to content

Instantly share code, notes, and snippets.

@eagletmt
Created June 1, 2010 03:07
Show Gist options
  • Save eagletmt/420517 to your computer and use it in GitHub Desktop.
Save eagletmt/420517 to your computer and use it in GitHub Desktop.
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