Skip to content

Commit 9c0aed0

Browse files
committed
first release
0 parents  commit 9c0aed0

File tree

2 files changed

+328
-0
lines changed

2 files changed

+328
-0
lines changed

jsmn.nim

Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
# Copyright 2016 Huy Doan
2+
#
3+
# This file is a Nim fork of Jsmn: https://github.com/zserge/jsmn
4+
# Copyright (c) 2010 Serge A. Zaitsev
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
18+
## This module is a fork of `Jsmn <http://zserge.com/jsmn.html>`_ - a world fastest JSON parser/tokenizer - in pure Nim
19+
##
20+
## It also supports ``PARENT LINKS`` and ``STRICTMODE``, you can enable them via command line switch
21+
##
22+
## Installation
23+
## ============
24+
##.. code-block::
25+
## nimble install jsmn
26+
##
27+
## Usage
28+
## =====
29+
##.. code-block:: nim
30+
## import jsmn
31+
##
32+
## const
33+
## json = """{
34+
## "user": "johndoe",
35+
## "admin": false,
36+
## "uid": 1000,
37+
## "groups": ["users", "wheel", "audio", "video"]}"""
38+
##
39+
## var
40+
## tokens: array[32, JsmnToken] # expect not more than 32 tokens
41+
##
42+
## let r = parseJson(json, tokens)
43+
## if r < 0:
44+
## echo "Failed to parse JSON: ", r
45+
##
46+
## for i in 1..r:
47+
## var token = addr tokens[i]
48+
## echo "Kind: ", token.kind
49+
## echo "Value: ", json[token.start..<token.stop]
50+
51+
type
52+
JsmnKind* = enum ## JSON type identifier
53+
JSMN_UNDEFINED, ## Undefined
54+
JSMN_OBJECT, ## Object, tuple
55+
JSMN_ARRAY, ## Array, seq
56+
JSMN_STRING, ## String
57+
JSMN_PRIMITIVE ## Other primitive: number, boolean (true/false) or null
58+
59+
JsmnToken* = object
60+
## JSON token description.
61+
kind*: JsmnKind
62+
start*: int ## start position in JSON data string
63+
stop*: int ## end position in JSON data string
64+
size*: int
65+
when defined(JSMN_PARENT_LINKS):
66+
parent*: int
67+
68+
JsmnParser = tuple[pos: int, toknext: int, toksuper: int]
69+
70+
const
71+
JSMN_ERROR_NOMEM* = -1 ## not enough tokens, JSON string is too large
72+
JSMN_ERROR_INVAL* = -2 ## bad token, JSON string is corrupted
73+
JSMN_ERROR_PART* = -3 ## JSON string is too short, expecting more JSON data
74+
75+
proc initToken(parser: var JsmnParser, tokens: var openarray[JsmnToken]): ptr JsmnToken =
76+
## Allocates a fresh unused token from the token pull.
77+
if parser.toknext >= tokens.len - 1:
78+
return
79+
80+
inc(parser.toknext)
81+
82+
result = addr tokens[parser.toknext]
83+
result.start = -1
84+
result.stop = - 1
85+
result.size = 0
86+
when defined(JSMN_PARENT_LINKS):
87+
result.parent = - 1
88+
89+
proc fillToken(token: ptr JsmnToken, kind: JsmnKind, start, stop: int) =
90+
## Fills token type and boundaries.
91+
token.kind = kind
92+
token.start = start
93+
token.stop = stop
94+
token.size = 0
95+
96+
when defined(JSMN_STRICT):
97+
# In strict mode primitive must be followed by "," or "}" or "]"
98+
const PRIMITIVE_DELIMITERS = ['\x09', '\x0D', '\x0A', ' ', ',', ']', '}']
99+
else:
100+
const PRIMITIVE_DELIMITERS = [':', '\x09', '\x0D', '\x0A', ' ', ',', ']', '}']
101+
102+
proc parsePrimitive(parser: var JsmnParser, json: string, len: int, tokens: var openarray[JsmnToken]): int =
103+
## Fills next available token with JSON primitive.
104+
var start = parser.pos
105+
while parser.pos < len and json[parser.pos] != '\0':
106+
case json[parser.pos]
107+
of PRIMITIVE_DELIMITERS:
108+
if tokens.len <= 0:
109+
dec(parser.pos)
110+
return 0
111+
112+
var token = initToken(parser, tokens)
113+
if token == nil:
114+
parser.pos = start
115+
return JSMN_ERROR_NOMEM
116+
117+
fillToken(token, JSMN_PRIMITIVE, start, parser.pos)
118+
when defined(JSMN_PARENT_LINKS):
119+
token.parent = parser.toksuper
120+
dec(parser.pos)
121+
return 0
122+
else:
123+
discard
124+
125+
if json[parser.pos].ord < 32 or json[parser.pos].ord >= 127:
126+
parser.pos = start
127+
return JSMN_ERROR_INVAL
128+
inc(parser.pos)
129+
when defined(JSMN_STRICT):
130+
# In strict mode primitive must be followed by a comma/object/array
131+
parser.pos = start
132+
return JSMN_ERROR_PART
133+
134+
proc parseString(parser: var JsmnParser, json: string, tokens: var openarray[JsmnToken]): int =
135+
## Fills next token with JSON string.
136+
var start = parser.pos
137+
inc(parser.pos)
138+
# Skip starting quote
139+
while parser.pos < json.len and json[parser.pos] != '\0':
140+
let c = json[parser.pos]
141+
# Quote: end of string
142+
if c == '\"':
143+
if tokens.len <= 0:
144+
return 0
145+
var token = initToken(parser, tokens)
146+
if token == nil:
147+
parser.pos = start
148+
return JSMN_ERROR_NOMEM
149+
fillToken(token, JSMN_STRING, start + 1, parser.pos)
150+
when defined(JSMN_PARENT_LINKS):
151+
token.parent = parser.toksuper
152+
return 0
153+
if c == '\x08' and parser.pos + 1 < json.len:
154+
inc(parser.pos)
155+
case json[parser.pos] # Allowed escaped symbols
156+
of '\"', '/', '\x08', 'b', 'f', 'r', 'n', 't':
157+
discard
158+
of 'u':
159+
inc(parser.pos)
160+
var i = 0
161+
while i < 4 and parser.pos < json.len and json[parser.pos] != '\0':
162+
# If it isn't a hex character we have an error
163+
if not ((json[parser.pos].ord >= 48 and json[parser.pos].ord <= 57) or
164+
(json[parser.pos].ord >= 65 and json[parser.pos].ord <= 70) or
165+
(json[parser.pos].ord >= 97 and json[parser.pos].ord <= 102)): # A-F
166+
# 0-9
167+
# a-f
168+
parser.pos = start
169+
return JSMN_ERROR_INVAL
170+
inc(parser.pos)
171+
inc(i)
172+
dec(parser.pos)
173+
else:
174+
parser.pos = start
175+
return JSMN_ERROR_INVAL
176+
inc(parser.pos)
177+
parser.pos = start
178+
return JSMN_ERROR_PART
179+
180+
proc parse(parser: var JsmnParser, json: string, tokens: var openarray[JsmnToken]): int =
181+
## Parse JSON string and fill tokens.
182+
var token: ptr JsmnToken
183+
var count = parser.toknext
184+
while parser.pos < json.len and json[parser.pos] != '\0':
185+
var kind: JsmnKind
186+
var c = json[parser.pos]
187+
case c
188+
of '{', '[':
189+
inc(count)
190+
if tokens.len <= 0:
191+
break
192+
token = initToken(parser, tokens)
193+
if token == nil: return JSMN_ERROR_NOMEM
194+
if parser.toksuper != - 1:
195+
inc(tokens[parser.toksuper].size)
196+
when defined(JSMN_PARENT_LINKS):
197+
token.parent = parser.toksuper
198+
token.kind = (if c == '{': JSMN_OBJECT else: JSMN_ARRAY)
199+
token.start = parser.pos
200+
parser.toksuper = parser.toknext - 1
201+
of '}', ']':
202+
if tokens.len <= 0: break
203+
kind = (if c == '}': JSMN_OBJECT else: JSMN_ARRAY)
204+
when defined(JSMN_PARENT_LINKS):
205+
if parser.toknext < 1:
206+
return JSMN_ERROR_INVAL
207+
token = addr(tokens[parser.toknext - 1])
208+
while true:
209+
if token.start != - 1 and token.stop == - 1:
210+
if token.kind != kind:
211+
return JSMN_ERROR_INVAL
212+
token.stop = parser.pos + 1
213+
parser.toksuper = token.parent
214+
break
215+
if token.parent == - 1:
216+
break
217+
token = addr(tokens[token.parent])
218+
else:
219+
var i = parser.toknext - 1
220+
while i >= 0:
221+
token = addr tokens[i]
222+
if token.start != - 1 and token.stop == - 1:
223+
if token.kind != kind:
224+
return JSMN_ERROR_INVAL
225+
parser.toksuper = - 1
226+
token.stop = parser.pos + 1
227+
break
228+
dec(i)
229+
# Error if unmatched closing bracket
230+
if i == - 1: return JSMN_ERROR_INVAL
231+
while i >= 0:
232+
token = addr tokens[i]
233+
if token.start != - 1 and token.stop == - 1:
234+
parser.toksuper = i
235+
break
236+
dec(i)
237+
of '\"':
238+
let r = parseString(parser, json, tokens)
239+
if r < 0: return r
240+
inc(count)
241+
if parser.toksuper != - 1 and tokens.len <= 0:
242+
inc(tokens[parser.toksuper].size)
243+
of '\t', '\r', '\x0A', ' ':
244+
discard
245+
of ':':
246+
parser.toksuper = parser.toknext - 1
247+
of ',':
248+
if tokens.len <= 0 and parser.toksuper != - 1 and
249+
tokens[parser.toksuper].kind != JSMN_ARRAY and
250+
tokens[parser.toksuper].kind != JSMN_OBJECT:
251+
when defined(JSMN_PARENT_LINKS):
252+
parser.toksuper = tokens[parser.toksuper].parent
253+
else:
254+
var i = parser.toknext - 1
255+
while i >= 0:
256+
if tokens[i].kind == JSMN_ARRAY or
257+
tokens[i].kind == JSMN_OBJECT:
258+
if tokens[i].start != - 1 and tokens[i].stop == - 1:
259+
parser.toksuper = i
260+
break
261+
dec(i)
262+
263+
else:
264+
when defined(JSMN_STRICT):
265+
# In strict mode primitives are: numbers and booleans
266+
case c
267+
of '-', '0', '1', '2', '3', '4', '5', '6', '7', '8','9', 't', 'f', 'n':
268+
# And they must not be keys of the object
269+
if tokens.len <= 0 and parser.toksuper != - 1:
270+
var t: ptr JsmnToken = addr(tokens[parser.toksuper])
271+
if t.kind == JSMN_OBJECT or (t.kind == JSMN_STRING and t.size != 0):
272+
return JSMN_ERROR_INVAL
273+
274+
let r = parsePrimitive(parser, json, json.len, tokens)
275+
if r < 0: return r
276+
inc(count)
277+
if parser.toksuper != - 1 and tokens.len <= 0:
278+
inc(tokens[parser.toksuper].size)
279+
else:
280+
return JSMN_ERROR_INVAL
281+
else:
282+
let r = parsePrimitive(parser, json, json.len, tokens)
283+
if r < 0: return r
284+
inc(count)
285+
if parser.toksuper != - 1 and tokens.len <= 0:
286+
inc(tokens[parser.toksuper].size)
287+
288+
inc(parser.pos)
289+
if tokens.len <= 0:
290+
var i = parser.toknext - 1
291+
while i >= 0:
292+
# Unmatched opened object or array
293+
if tokens[i].start != - 1 and tokens[i].stop == - 1:
294+
return JSMN_ERROR_PART
295+
dec(i)
296+
return count
297+
298+
proc parseJson*(json: string, tokens: var openarray[JsmnToken]): int =
299+
## Parse a JSON data string into and array of tokens, each describing a single JSON object.
300+
var parser: JsmnParser = (0, 0, -1)
301+
parser.parse(json, tokens)
302+
303+
when isMainModule:
304+
const
305+
json = "{\"user\": \"johndoe\", \"admin\": false, \"uid\": 1000, \"groups\": [\"users\", \"wheel\", \"audio\", \"video\"]}"
306+
307+
var
308+
tokens: array[10, JsmnToken]
309+
310+
let r = parseJson(json, tokens)
311+
if r < 0:
312+
echo "Failed to parse JSON: ", r
313+
314+
for i in 1..r:
315+
var token = addr tokens[i]
316+
echo "Kind: ", token.kind
317+
echo "Value: ", json[token.start..<token.stop]

jsmn.nimble

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Package
2+
3+
version = "0.1.0"
4+
author = "Huy Doan"
5+
description = "Jsmn - a world fastest JSON parser - in pure Nim"
6+
license = "MIT"
7+
8+
# Dependencies
9+
10+
requires "nim >= 0.13.1"
11+

0 commit comments

Comments
 (0)