Production Ready Macros for SAS Application Developers
https://github.com/sasjs/core
ml_json.sas
Go to the documentation of this file.
1
/**
2
@file ml_json.sas
3
@brief Compiles the json.lua lua file
4
@details Writes json.lua to the work directory
5
and then includes it.
6
Usage:
7
8
%ml_json()
9
10
**/
11
12
%macro ml_json();
13
data _null_;
14
file
"%sysfunc(pathname(work))/ml_json.lua"
;
15
put
'-- '
;
16
put
'-- json.lua '
;
17
put
'-- '
;
18
put
'-- Copyright (c) 2019 rxi '
;
19
put
'-- '
;
20
put
'-- Permission is hereby granted, free of charge, to any person obtaining a copy of '
;
21
put
'-- this software and associated documentation files (the "Software"), to deal in '
;
22
put
'-- the Software without restriction, including without limitation the rights to '
;
23
put
'-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies '
;
24
put
'-- of the Software, and to permit persons to whom the Software is furnished to do '
;
25
put
'-- so, subject to the following conditions: '
;
26
put
'-- '
;
27
put
'-- The above copyright notice and this permission notice shall be included in all '
;
28
put
'-- copies or substantial portions of the Software. '
;
29
put
'-- '
;
30
put
'-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR '
;
31
put
'-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, '
;
32
put
'-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE '
;
33
put
'-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER '
;
34
put
'-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, '
;
35
put
'-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE '
;
36
put
'-- SOFTWARE. '
;
37
put
'-- '
;
38
put
' '
;
39
put
'json = { _version = "0.1.2" } '
;
40
put
' '
;
41
put
'------------------------------------------------------------------------------- '
;
42
put
'-- Encode '
;
43
put
'------------------------------------------------------------------------------- '
;
44
put
' '
;
45
put
'local encode '
;
46
put
' '
;
47
put
'local escape_char_map = { '
;
48
put
' [ "\\" ] = "\\\\", '
;
49
put
' [ "\"" ] = "\\\"", '
;
50
put
' [ "\b" ] = "\\b", '
;
51
put
' [ "\f" ] = "\\f", '
;
52
put
' [ "\n" ] = "\\n", '
;
53
put
' [ "\r" ] = "\\r", '
;
54
put
' [ "\t" ] = "\\t", '
;
55
put
'} '
;
56
put
' '
;
57
put
'local escape_char_map_inv = { [ "\\/" ] = "/" } '
;
58
put
'for k, v in pairs(escape_char_map) do '
;
59
put
' escape_char_map_inv[v] = k '
;
60
put
'end '
;
61
put
' '
;
62
put
'local function escape_char(c) '
;
63
put
' return escape_char_map[c] or string.format("\\u%04x", c:byte()) '
;
64
put
'end '
;
65
put
' '
;
66
put
'local function encode_nil(val) '
;
67
put
' return "null" '
;
68
put
'end '
;
69
put
' '
;
70
put
'local function encode_table(val, stack) '
;
71
put
' local res = {} '
;
72
put
' stack = stack or {} '
;
73
put
' '
;
74
put
' -- Circular reference? '
;
75
put
' if stack[val] then error("circular reference") end '
;
76
put
' '
;
77
put
' stack[val] = true '
;
78
put
' '
;
79
put
' if rawget(val, 1) ~= nil or next(val) == nil then '
;
80
put
' -- Treat as array -- check keys are valid and it is not sparse '
;
81
put
' local n = 0 '
;
82
put
' for k in pairs(val) do '
;
83
put
' if type(k) ~= "number" then '
;
84
put
' error("invalid table: mixed or invalid key types") '
;
85
put
' end '
;
86
put
' n = n + 1 '
;
87
put
' end '
;
88
put
' if n ~= #val then '
;
89
put
' error("invalid table: sparse array") '
;
90
put
' end '
;
91
put
' -- Encode '
;
92
put
' for i, v in ipairs(val) do '
;
93
put
' table.insert(res, encode(v, stack)) '
;
94
put
' end '
;
95
put
' stack[val] = nil '
;
96
put
' return "[" .. table.concat(res, ",") .. "]" '
;
97
put
' else '
;
98
put
' -- Treat as an object '
;
99
put
' for k, v in pairs(val) do '
;
100
put
' if type(k) ~= "string" then '
;
101
put
' error("invalid table: mixed or invalid key types") '
;
102
put
' end '
;
103
put
' table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) '
;
104
put
' end '
;
105
put
' stack[val] = nil '
;
106
put
' return "{" .. table.concat(res, ",") .. "}" '
;
107
put
' end '
;
108
put
'end '
;
109
put
' '
;
110
put
'local function encode_string(val) '
;
111
put
' return '
'"'
' .. val:gsub('
'[%z\1-\31\\"]'
', escape_char) .. '
'"'
' '
;
112
put
'end '
;
113
put
' '
;
114
put
'local function encode_number(val) '
;
115
put
' -- Check for NaN, -inf and inf '
;
116
put
' if val ~= val or val <= -math.huge or val >= math.huge then '
;
117
put
' error("unexpected number value '
'" .. tostring(val) .. "'
'") '
;
118
put
' end '
;
119
put
' return string.format("%.14g", val) '
;
120
put
'end '
;
121
put
' '
;
122
put
'local type_func_map = { '
;
123
put
' [ "nil" ] = encode_nil, '
;
124
put
' [ "table" ] = encode_table, '
;
125
put
' [ "string" ] = encode_string, '
;
126
put
' [ "number" ] = encode_number, '
;
127
put
' [ "boolean" ] = tostring, '
;
128
put
'} '
;
129
put
' '
;
130
put
'encode = function(val, stack) '
;
131
put
' local t = type(val) '
;
132
put
' local f = type_func_map[t] '
;
133
put
' if f then '
;
134
put
' return f(val, stack) '
;
135
put
' end '
;
136
put
' error("unexpected type '
'" .. t .. "'
'") '
;
137
put
'end '
;
138
put
' '
;
139
put
'function json.encode(val) '
;
140
put
' return ( encode(val) ) '
;
141
put
'end '
;
142
put
' '
;
143
put
'------------------------------------------------------------------------------- '
;
144
put
'-- Decode '
;
145
put
'------------------------------------------------------------------------------- '
;
146
put
'local parse '
;
147
put
'local function create_set(...) '
;
148
put
' local res = {} '
;
149
put
' for i = 1, select("#", ...) do '
;
150
put
' res[ select(i, ...) ] = true '
;
151
put
' end '
;
152
put
' return res '
;
153
put
'end '
;
154
put
' '
;
155
put
'local space_chars = create_set(" ", "\t", "\r", "\n") '
;
156
put
'local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") '
;
157
put
'local escape_chars = create_set("\\", "/", '
'"'
', "b", "f", "n", "r", "t", "u") '
;
158
put
'local literals = create_set("true", "false", "null") '
;
159
put
' '
;
160
put
'local literal_map = { '
;
161
put
' [ "true" ] = true, '
;
162
put
' [ "false" ] = false, '
;
163
put
' [ "null" ] = nil, '
;
164
put
'} '
;
165
put
' '
;
166
put
'local function next_char(str, idx, set, negate) '
;
167
put
' for i = idx, #str do '
;
168
put
' if set[str:sub(i, i)] ~= negate then '
;
169
put
' return i '
;
170
put
' end '
;
171
put
' end '
;
172
put
' return #str + 1 '
;
173
put
'end '
;
174
put
' '
;
175
put
'local function decode_error(str, idx, msg) '
;
176
put
' local line_count = 1 '
;
177
put
' local col_count = 1 '
;
178
put
' for i = 1, idx - 1 do '
;
179
put
' col_count = col_count + 1 '
;
180
put
' if str:sub(i, i) == "\n" then '
;
181
put
' line_count = line_count + 1 '
;
182
put
' col_count = 1 '
;
183
put
' end '
;
184
put
' end '
;
185
put
' error( string.format("%s at line %d col %d", msg, line_count, col_count) ) '
;
186
put
'end '
;
187
put
' '
;
188
put
'local function codepoint_to_utf8(n) '
;
189
put
' -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa '
;
190
put
' local f = math.floor '
;
191
put
' if n <= 0x7f then '
;
192
put
' return string.char(n) '
;
193
put
' elseif n <= 0x7ff then '
;
194
put
' return string.char(f(n / 64) + 192, n % 64 + 128) '
;
195
put
' elseif n <= 0xffff then '
;
196
put
' return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) '
;
197
put
' elseif n <= 0x10ffff then '
;
198
put
' return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, '
;
199
put
' f(n % 4096 / 64) + 128, n % 64 + 128) '
;
200
put
' end '
;
201
put
' error( string.format("invalid unicode codepoint '
'%x'
'", n) ) '
;
202
put
'end '
;
203
put
' '
;
204
put
'local function parse_unicode_escape(s) '
;
205
put
' local n1 = tonumber( s:sub(3, 6), 16 ) '
;
206
put
' local n2 = tonumber( s:sub(9, 12), 16 ) '
;
207
put
' -- Surrogate pair? '
;
208
put
' if n2 then '
;
209
put
' return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) '
;
210
put
' else '
;
211
put
' return codepoint_to_utf8(n1) '
;
212
put
' end '
;
213
put
'end '
;
214
put
' '
;
215
put
'local function parse_string(str, i) '
;
216
put
' local has_unicode_escape = false '
;
217
put
' local has_surrogate_escape = false '
;
218
put
' local has_escape = false '
;
219
put
' local last '
;
220
put
' for j = i + 1, #str do '
;
221
put
' local x = str:byte(j) '
;
222
put
' if x < 32 then '
;
223
put
' decode_error(str, j, "control character in string") '
;
224
put
' end '
;
225
put
' if last == 92 then -- "\\" (escape char) '
;
226
put
' if x == 117 then -- "u" (unicode escape sequence) '
;
227
put
' local hex = str:sub(j + 1, j + 5) '
;
228
put
' if not hex:find("%x%x%x%x") then '
;
229
put
' decode_error(str, j, "invalid unicode escape in string") '
;
230
put
' end '
;
231
put
' if hex:find("^[dD][89aAbB]") then '
;
232
put
' has_surrogate_escape = true '
;
233
put
' else '
;
234
put
' has_unicode_escape = true '
;
235
put
' end '
;
236
put
' else '
;
237
put
' local c = string.char(x) '
;
238
put
' if not escape_chars[c] then '
;
239
put
' decode_error(str, j, "invalid escape char '
'" .. c .. "'
' in string") '
;
240
put
' end '
;
241
put
' has_escape = true '
;
242
put
' end '
;
243
put
' last = nil '
;
244
put
' elseif x == 34 then -- '
'"'
' (end of string) '
;
245
put
' local s = str:sub(i + 1, j - 1) '
;
246
put
' if has_surrogate_escape then '
;
247
put
' s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape) '
;
248
put
' end '
;
249
put
' if has_unicode_escape then '
;
250
put
' s = s:gsub("\\u....", parse_unicode_escape) '
;
251
put
' end '
;
252
put
' if has_escape then '
;
253
put
' s = s:gsub("\\.", escape_char_map_inv) '
;
254
put
' end '
;
255
put
' return s, j + 1 '
;
256
put
' else '
;
257
put
' last = x '
;
258
put
' end '
;
259
put
' end '
;
260
put
' decode_error(str, i, "expected closing quote for string") '
;
261
put
'end '
;
262
put
' '
;
263
put
'local function parse_number(str, i) '
;
264
put
' local x = next_char(str, i, delim_chars) '
;
265
put
' local s = str:sub(i, x - 1) '
;
266
put
' local n = tonumber(s) '
;
267
put
' if not n then '
;
268
put
' decode_error(str, i, "invalid number '
'" .. s .. "'
'") '
;
269
put
' end '
;
270
put
' return n, x '
;
271
put
'end '
;
272
put
' '
;
273
put
'local function parse_literal(str, i) '
;
274
put
' local x = next_char(str, i, delim_chars) '
;
275
put
' local word = str:sub(i, x - 1) '
;
276
put
' if not literals[word] then '
;
277
put
' decode_error(str, i, "invalid literal '
'" .. word .. "'
'") '
;
278
put
' end '
;
279
put
' return literal_map[word], x '
;
280
put
'end '
;
281
put
' '
;
282
put
'local function parse_array(str, i) '
;
283
put
' local res = {} '
;
284
put
' local n = 1 '
;
285
put
' i = i + 1 '
;
286
put
' while 1 do '
;
287
put
' local x '
;
288
put
' i = next_char(str, i, space_chars, true) '
;
289
put
' -- Empty / end of array? '
;
290
put
' if str:sub(i, i) == "]" then '
;
291
put
' i = i + 1 '
;
292
put
' break '
;
293
put
' end '
;
294
put
' -- Read token '
;
295
put
' x, i = parse(str, i) '
;
296
put
' res[n] = x '
;
297
put
' n = n + 1 '
;
298
put
' -- Next token '
;
299
put
' i = next_char(str, i, space_chars, true) '
;
300
put
' local chr = str:sub(i, i) '
;
301
put
' i = i + 1 '
;
302
put
' if chr == "]" then break end '
;
303
put
' if chr ~= "," then decode_error(str, i, "expected '
']'
' or '
','
'") end '
;
304
put
' end '
;
305
put
' return res, i '
;
306
put
'end '
;
307
put
' '
;
308
put
'local function parse_object(str, i) '
;
309
put
' local res = {} '
;
310
put
' i = i + 1 '
;
311
put
' while 1 do '
;
312
put
' local key, val '
;
313
put
' i = next_char(str, i, space_chars, true) '
;
314
put
' -- Empty / end of object? '
;
315
put
' if str:sub(i, i) == "}" then '
;
316
put
' i = i + 1 '
;
317
put
' break '
;
318
put
' end '
;
319
put
' -- Read key '
;
320
put
' if str:sub(i, i) ~= '
'"'
' then '
;
321
put
' decode_error(str, i, "expected string for key") '
;
322
put
' end '
;
323
put
' key, i = parse(str, i) '
;
324
put
' -- Read '
':'
' delimiter '
;
325
put
' i = next_char(str, i, space_chars, true) '
;
326
put
' if str:sub(i, i) ~= ":" then '
;
327
put
' decode_error(str, i, "expected '
':'
' after key") '
;
328
put
' end '
;
329
put
' i = next_char(str, i + 1, space_chars, true) '
;
330
put
' -- Read value '
;
331
put
' val, i = parse(str, i) '
;
332
put
' -- Set '
;
333
put
' res[key] = val '
;
334
put
' -- Next token '
;
335
put
' i = next_char(str, i, space_chars, true) '
;
336
put
' local chr = str:sub(i, i) '
;
337
put
' i = i + 1 '
;
338
put
' if chr == "}" then break end '
;
339
put
' if chr ~= "," then decode_error(str, i, "expected '
'}'
' or '
','
'") end '
;
340
put
' end '
;
341
put
' return res, i '
;
342
put
'end '
;
343
put
' '
;
344
put
'local char_func_map = { '
;
345
put
' [ '
'"'
' ] = parse_string, '
;
346
put
' [ "0" ] = parse_number, '
;
347
put
' [ "1" ] = parse_number, '
;
348
put
' [ "2" ] = parse_number, '
;
349
put
' [ "3" ] = parse_number, '
;
350
put
' [ "4" ] = parse_number, '
;
351
put
' [ "5" ] = parse_number, '
;
352
put
' [ "6" ] = parse_number, '
;
353
put
' [ "7" ] = parse_number, '
;
354
put
' [ "8" ] = parse_number, '
;
355
put
' [ "9" ] = parse_number, '
;
356
put
' [ "-" ] = parse_number, '
;
357
put
' [ "t" ] = parse_literal, '
;
358
put
' [ "f" ] = parse_literal, '
;
359
put
' [ "n" ] = parse_literal, '
;
360
put
' [ "[" ] = parse_array, '
;
361
put
' [ "{" ] = parse_object, '
;
362
put
'} '
;
363
put
' '
;
364
put
'parse = function(str, idx) '
;
365
put
' local chr = str:sub(idx, idx) '
;
366
put
' local f = char_func_map[chr] '
;
367
put
' if f then '
;
368
put
' return f(str, idx) '
;
369
put
' end '
;
370
put
' decode_error(str, idx, "unexpected character '
'" .. chr .. "'
'") '
;
371
put
'end '
;
372
put
' '
;
373
put
'function json.decode(str) '
;
374
put
' if type(str) ~= "string" then '
;
375
put
' error("expected argument of type string, got " .. type(str)) '
;
376
put
' end '
;
377
put
' local res, idx = parse(str, next_char(str, 1, space_chars, true)) '
;
378
put
' idx = next_char(str, idx, space_chars, true) '
;
379
put
' if idx <= #str then '
;
380
put
' decode_error(str, idx, "trailing garbage") '
;
381
put
' end '
;
382
put
' return res '
;
383
put
'end '
;
384
put
' '
;
385
put
'return json '
;
386
run;
387
388
%inc
"%sysfunc(pathname(work))/ml_json.lua"
;
389
390
%mend;
lua
ml_json.sas
Generated by
1.8.18
For more information visit the
Macro Core library
.