-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Expand file tree
/
Copy pathjs2python.js
More file actions
369 lines (352 loc) · 10.4 KB
/
Copy pathjs2python.js
File metadata and controls
369 lines (352 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
import { _PropagatePythonError as PropagateError } from "generated/error_handling";
function js2python_string(value) {
// The general idea here is to allocate a Python string and then
// have JavaScript write directly into its buffer. We first need
// to determine if is needs to be a 1-, 2- or 4-byte string, since
// Python handles all 3.
let max_code_point = 0;
// `value.length` counts UTF-16 code units, which is an upper bound on the
// number of code points (a surrogate pair is two units but one code point),
// so a typed array of that length is large enough to hold every code point.
const length = value.length;
const code_points = new Uint32Array(length);
let num_code_points = 0;
for (let i = 0; i < length; i++) {
const unit = value.charCodeAt(i);
let code_point;
// A high surrogate (0xD800-0xDBFF) followed by a low surrogate
// (0xDC00-0xDFFF) is a surrogate pair encoding a single code point. We
// combine them with the standard UTF-16 decoding formula and then skip
// the trailing low surrogate. Lone surrogates (high without a following
// low, or a low surrogate) are left as-is as their bare code-unit value,
// matching the behavior of the previous string-iterator implementation.
// See:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt
if (unit >= 0xd800 && unit <= 0xdbff) {
const next = value.charCodeAt(i + 1);
if (next >= 0xdc00 && next <= 0xdfff) {
code_point = (unit - 0xd800) * 0x400 + (next - 0xdc00) + 0x10000;
i++;
} else {
code_point = unit;
}
} else {
code_point = unit;
}
code_points[num_code_points++] = code_point;
max_code_point = code_point > max_code_point ? code_point : max_code_point;
}
let result = _PyUnicode_New(num_code_points, max_code_point);
if (result === 0) {
throw new PropagateError();
}
let ptr = _PyUnicode_Data(result);
if (max_code_point > 0xffff) {
for (let i = 0; i < num_code_points; i++) {
ASSIGN_U32(ptr, i, code_points[i]);
}
} else if (max_code_point > 0xff) {
for (let i = 0; i < num_code_points; i++) {
ASSIGN_U16(ptr, i, code_points[i]);
}
} else {
for (let i = 0; i < num_code_points; i++) {
ASSIGN_U8(ptr, i, code_points[i]);
}
}
return result;
}
function js2python_bigint(value) {
let value_orig = value;
let length = 0;
if (value < 0) {
value = -value;
}
value <<= BigInt(1);
while (value) {
length++;
value >>= BigInt(32);
}
const orig = stackSave();
const ptr = stackAlloc(length * 4);
value = value_orig;
for (let i = 0; i < length; i++) {
ASSIGN_U32(ptr, i, Number(value & BigInt(0xffffffff)));
value >>= BigInt(32);
}
const resInt = __PyLong_FromByteArray(
ptr,
length * 4 /* length in bytes */,
true /* little endian */,
true /* signed? */,
);
stackRestore(orig);
const res = __js2python_bigint(resInt);
_Py_DecRef(resInt);
return res;
}
/**
* This function converts immutable types. numbers, bigints, strings,
* booleans, undefined, and null are converted. PyProxies are unwrapped.
*
* If `value` is of any other type then `undefined` is returned.
*
* If `value` is one of those types but an error is raised during conversion,
* we throw a PropagateError to propagate the error out to C. This causes
* special handling in the EM_JS wrapper.
*/
function js2python_convertImmutable(value) {
let result = js2python_convertImmutableInner(value);
if (result === 0) {
throw new PropagateError();
}
return result;
}
// js2python_convertImmutable is used from js2python.c so we need to add it
// to Module.
Module.js2python_convertImmutable = js2python_convertImmutable;
/**
* Returns a pointer to a Python object, 0, or undefined.
*
* If we return 0 it means we tried to convert but an error occurred, if we
* return undefined, no conversion was attempted.
*/
function js2python_convertImmutableInner(value) {
let type = typeof value;
if (type === "string") {
return js2python_string(value);
} else if (type === "number") {
if (Number.isSafeInteger(value)) {
return _PyLong_FromDouble(value);
} else {
return _PyFloat_FromDouble(value);
}
} else if (type === "bigint") {
return js2python_bigint(value);
} else if (value === undefined) {
return __js2python_none();
} else if (value === null) {
return __js2python_null();
} else if (value === true) {
return __js2python_true();
} else if (value === false) {
return __js2python_false();
} else if (API.isPyProxy(value)) {
const { props, shared } = Module.PyProxy_getAttrs(value);
if (props.roundtrip) {
return _JsProxy_create(value);
} else {
return __js2python_pyproxy(shared.ptr);
}
}
return undefined;
}
function js2python_convertList(obj, context) {
let list = _PyList_New(obj.length);
if (list === 0) {
return 0;
}
let item = 0;
try {
context.cache.set(obj, list);
for (let i = 0; i < obj.length; i++) {
item = js2python_convert_with_context(obj[i], context);
// PyList_SetItem steals a reference to item no matter what
_Py_IncRef(item);
if (_PyList_SetItem(list, i, item) === -1) {
throw new PropagateError();
}
_Py_DecRef(item);
item = 0;
}
} catch (e) {
_Py_DecRef(item);
_Py_DecRef(list);
throw e;
}
return list;
}
function js2python_convertMap(obj, entries, context) {
let dict = _PyDict_New();
if (dict === 0) {
return 0;
}
let key_py = 0;
let value_py = 0;
try {
context.cache.set(obj, dict);
for (let [key_js, value_js] of entries) {
key_py = js2python_convertImmutable(key_js);
if (key_py === undefined) {
let key_type =
(key_js.constructor && key_js.constructor.name) || typeof key_js;
throw new Error(
`Cannot use key of type ${key_type} as a key to a Python dict`,
);
}
value_py = js2python_convert_with_context(value_js, context);
if (_PyDict_SetItem(dict, key_py, value_py) === -1) {
throw new PropagateError();
}
_Py_DecRef(key_py);
key_py = 0;
_Py_DecRef(value_py);
value_py = 0;
}
} catch (e) {
_Py_DecRef(key_py);
_Py_DecRef(value_py);
_Py_DecRef(dict);
throw e;
}
return dict;
}
function js2python_convertSet(obj, context) {
let set = _PySet_New(0);
if (set === 0) {
return 0;
}
let key_py = 0;
try {
context.cache.set(obj, set);
for (let key_js of obj) {
key_py = js2python_convertImmutable(key_js);
if (key_py === undefined) {
let key_type =
(key_js.constructor && key_js.constructor.name) || typeof key_js;
throw new Error(
`Cannot use key of type ${key_type} as a key to a Python set`,
);
}
const err = _PySet_Add(set, key_py);
if (err === -1) {
throw new PropagateError();
}
_Py_DecRef(key_py);
key_py = 0;
}
} catch (e) {
_Py_DecRef(key_py);
_Py_DecRef(set);
throw e;
}
return set;
}
function checkBoolIntCollision(obj, ty) {
if (obj.has(1) && obj.has(true)) {
throw new Error(
`Cannot faithfully convert ${ty} into Python since it ` +
"contains both 1 and true as keys.",
);
}
if (obj.has(0) && obj.has(false)) {
throw new Error(
`Cannot faithfully convert ${ty} into Python since it ` +
"contains both 0 and false as keys.",
);
}
}
/**
* Convert mutable types: Array, Map, Set, and Objects whose prototype is
* either null or the default. Anything else is wrapped in a Proxy. This
* should only be used on values for which js2python_convertImmutable
* returned `undefined`.
*/
function js2python_convertOther(value, context) {
let typeTag = getTypeTag(value);
if (
Array.isArray(value) ||
value === "[object HTMLCollection]" ||
value === "[object NodeList]"
) {
return js2python_convertList(value, context);
}
if (typeTag === "[object Map]" || value instanceof Map) {
checkBoolIntCollision(value, "Map");
return js2python_convertMap(value, value.entries(), context);
}
if (typeTag === "[object Set]" || value instanceof Set) {
checkBoolIntCollision(value, "Set");
return js2python_convertSet(value, context);
}
if (
typeTag === "[object Object]" &&
(value.constructor === undefined || value.constructor.name === "Object")
) {
return js2python_convertMap(value, Object.entries(value), context);
}
if (typeTag === "[object ArrayBuffer]" || ArrayBuffer.isView(value)) {
let [format_utf8, itemsize] = Module.get_buffer_datatype(value);
return _JsBuffer_CopyIntoMemoryView(
value,
value.byteLength,
format_utf8,
itemsize,
);
}
return undefined;
}
/**
* Convert a JavaScript object to Python to a given depth.
*/
function js2python_convert_with_context(value, context) {
let result = js2python_convertImmutable(value);
if (result !== undefined) {
return result;
}
if (context.depth === 0) {
return _JsProxy_create(value);
}
result = context.cache.get(value);
if (result !== undefined) {
_Py_IncRef(result);
return result;
}
context.depth--;
try {
result = js2python_convertOther(value, context);
if (result !== undefined) {
return result;
}
if (!context.defaultConverter) {
return _JsProxy_create(value);
}
let result_js = context.defaultConverter(
value,
context.converter,
context.cacheConversion,
);
result = js2python_convertImmutable(result_js);
if (API.isPyProxy(result_js)) {
Module.pyproxy_destroy(result_js, "", false);
}
if (result !== undefined) {
return result;
}
return _JsProxy_create(result_js);
} finally {
context.depth++;
}
}
/**
* Convert a JavaScript object to Python to a given depth.
*/
function js2python_convert(val, { depth, defaultConverter }) {
let context = {
cache: new Map(),
depth,
defaultConverter,
// arguments for defaultConverter
converter: (x) =>
Module.pyproxy_new(js2python_convert_with_context(x, context)),
cacheConversion(input, output) {
if (API.isPyProxy(output)) {
context.cache.set(input, Module.PyProxy_getPtr(output));
} else {
throw new Error("Second argument should be a PyProxy!");
}
},
};
return js2python_convert_with_context(val, context);
}
Module.js2python_convert = js2python_convert;