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