1'''
2CFFI-Wrapper for YAJL C library version 2.x.
3'''
4
5from cffi import FFI
6import functools
7
8from ijson import common, backends, utils
9from ijson.compat import b2s
10
11
12ffi = FFI()
13ffi.cdef("""
14typedef void * (*yajl_malloc_func)(void *ctx, size_t sz);
15typedef void (*yajl_free_func)(void *ctx, void * ptr);
16typedef void * (*yajl_realloc_func)(void *ctx, void * ptr, size_t sz);
17typedef struct
18{
19 yajl_malloc_func malloc;
20 yajl_realloc_func realloc;
21 yajl_free_func free;
22 void * ctx;
23} yajl_alloc_funcs;
24typedef struct yajl_handle_t * yajl_handle;
25typedef enum {
26 yajl_status_ok,
27 yajl_status_client_canceled,
28 yajl_status_error
29} yajl_status;
30typedef enum {
31 yajl_allow_comments = 0x01,
32 yajl_dont_validate_strings = 0x02,
33 yajl_allow_trailing_garbage = 0x04,
34 yajl_allow_multiple_values = 0x08,
35 yajl_allow_partial_values = 0x10
36} yajl_option;
37typedef struct {
38 int (* yajl_null)(void * ctx);
39 int (* yajl_boolean)(void * ctx, int boolVal);
40 int (* yajl_integer)(void * ctx, long long integerVal);
41 int (* yajl_double)(void * ctx, double doubleVal);
42 int (* yajl_number)(void * ctx, const char * numberVal,
43 size_t numberLen);
44 int (* yajl_string)(void * ctx, const unsigned char * stringVal,
45 size_t stringLen);
46 int (* yajl_start_map)(void * ctx);
47 int (* yajl_map_key)(void * ctx, const unsigned char * key,
48 size_t stringLen);
49 int (* yajl_end_map)(void * ctx);
50 int (* yajl_start_array)(void * ctx);
51 int (* yajl_end_array)(void * ctx);
52} yajl_callbacks;
53int yajl_version(void);
54yajl_handle yajl_alloc(const yajl_callbacks *callbacks, yajl_alloc_funcs *afs, void *ctx);
55int yajl_config(yajl_handle h, yajl_option opt, ...);
56yajl_status yajl_parse(yajl_handle hand, const unsigned char *jsonText, size_t jsonTextLength);
57yajl_status yajl_complete_parse(yajl_handle hand);
58unsigned char* yajl_get_error(yajl_handle hand, int verbose, const unsigned char *jsonText, size_t jsonTextLength);
59void yajl_free_error(yajl_handle hand, unsigned char * str);
60void yajl_free(yajl_handle handle);
61""")
62
63
64yajl = backends.find_yajl_cffi(ffi, 2)
65
66YAJL_OK = 0
67YAJL_CANCELLED = 1
68YAJL_INSUFFICIENT_DATA = 2
69YAJL_ERROR = 3
70
71# constants defined in yajl_parse.h
72YAJL_ALLOW_COMMENTS = 1
73YAJL_MULTIPLE_VALUES = 8
74
75
76def append_event_to_ctx(event):
77 def wrapper(func):
78 @functools.wraps(func)
79 def wrapped(ctx, *args, **kwargs):
80 value = func(*args, **kwargs)
81 send = ffi.from_handle(ctx)
82 send((event, value))
83 return 1
84 return wrapped
85 return wrapper
86
87
88@ffi.callback('int(void *ctx)')
89@append_event_to_ctx('null')
90def null():
91 return None
92
93
94@ffi.callback('int(void *ctx, int val)')
95@append_event_to_ctx('boolean')
96def boolean(val):
97 return bool(val)
98
99
100@ffi.callback('int(void *ctx, long long integerVal)')
101@append_event_to_ctx('number')
102def integer(val):
103 return int(val)
104
105
106@ffi.callback('int(void * ctx, double doubleVal)')
107@append_event_to_ctx('number')
108def double(val):
109 return val
110
111
112@ffi.callback('int(void *ctx, const char *numberVal, size_t numberLen)')
113@append_event_to_ctx('number')
114def number(val, length):
115 return common.integer_or_decimal(b2s(ffi.string(val, maxlen=length)))
116
117
118@ffi.callback('int(void *ctx, const unsigned char *stringVal, size_t stringLen)')
119@append_event_to_ctx('string')
120def string(val, length):
121 return ffi.string(val, maxlen=length).decode('utf-8')
122
123
124@ffi.callback('int(void *ctx)')
125@append_event_to_ctx('start_map')
126def start_map():
127 return None
128
129
130@ffi.callback('int(void *ctx, const unsigned char *key, size_t stringLen)')
131@append_event_to_ctx('map_key')
132def map_key(key, length):
133 return ffi.string(key, maxlen=length).decode('utf-8')
134
135
136@ffi.callback('int(void *ctx)')
137@append_event_to_ctx('end_map')
138def end_map():
139 return None
140
141
142@ffi.callback('int(void *ctx)')
143@append_event_to_ctx('start_array')
144def start_array():
145 return None
146
147
148@ffi.callback('int(void *ctx)')
149@append_event_to_ctx('end_array')
150def end_array():
151 return None
152
153
154_decimal_callback_data = (
155 null, boolean, ffi.NULL, ffi.NULL, number, string,
156 start_map, map_key, end_map, start_array, end_array
157)
158
159_float_callback_data = (
160 null, boolean, integer, double, ffi.NULL, string,
161 start_map, map_key, end_map, start_array, end_array
162)
163
164
165def yajl_init(scope, send, allow_comments=False, multiple_values=False, use_float=False):
166 scope.ctx = ffi.new_handle(send)
167 if use_float:
168 scope.callbacks = ffi.new('yajl_callbacks*', _float_callback_data)
169 else:
170 scope.callbacks = ffi.new('yajl_callbacks*', _decimal_callback_data)
171 handle = yajl.yajl_alloc(scope.callbacks, ffi.NULL, scope.ctx)
172
173 if allow_comments:
174 yajl.yajl_config(handle, YAJL_ALLOW_COMMENTS, ffi.cast('int', 1))
175 if multiple_values:
176 yajl.yajl_config(handle, YAJL_MULTIPLE_VALUES, ffi.cast('int', 1))
177
178 return handle
179
180
181def yajl_parse(handle, buffer):
182 if buffer:
183 result = yajl.yajl_parse(handle, buffer, len(buffer))
184 else:
185 result = yajl.yajl_complete_parse(handle)
186
187 if result != YAJL_OK:
188 perror = yajl.yajl_get_error(handle, 1, buffer, len(buffer))
189 error = ffi.string(perror)
190 try:
191 error = error.decode('utf8')
192 except UnicodeDecodeError:
193 pass
194 yajl.yajl_free_error(handle, perror)
195 exception = common.IncompleteJSONError if result == YAJL_INSUFFICIENT_DATA else common.JSONError
196 raise exception(error)
197
198
199class Container(object):
200 pass
201
202
203@utils.coroutine
204def basic_parse_basecoro(target, **config):
205 '''
206 Coroutine dispatching unprefixed events.
207
208 Parameters:
209
210 - allow_comments: tells parser to allow comments in JSON input
211 - multiple_values: allows the parser to parse multiple JSON objects
212 '''
213
214 # the scope objects makes sure the C objects allocated in _yajl.init
215 # are kept alive until this function is done
216 scope = Container()
217
218 handle = yajl_init(scope, target.send, **config)
219 try:
220 while True:
221 try:
222 buffer = (yield)
223 except GeneratorExit:
224 buffer = b''
225 yajl_parse(handle, buffer)
226 if not buffer:
227 break
228 finally:
229 yajl.yajl_free(handle)
230
231
232common.enrich_backend(globals())