-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy path.Elixir.Zixir.Compiler.PythonFFI.zig
More file actions
440 lines (384 loc) · 13.4 KB
/
.Elixir.Zixir.Compiler.PythonFFI.zig
File metadata and controls
440 lines (384 loc) · 13.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
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
// this code is autogenerated, do not check it into to your code repository
// ref lib/zixir/compiler/python_ffi.ex:11
const std = @import("std");
// Python C API bindings (forward declarations - will be linked at runtime)
extern fn Py_Initialize() void;
extern fn Py_IsInitialized() c_int;
extern fn Py_Finalize() void;
extern fn Py_IncRef(*anyopaque) void;
extern fn Py_DecRef(*anyopaque) void;
extern fn PyImport_ImportModule([*c]const u8) ?*anyopaque;
extern fn PyObject_GetAttrString(*anyopaque, [*c]const u8) ?*anyopaque;
extern fn PyCallable_Check(*anyopaque) c_int;
extern fn PyTuple_New(isize) ?*anyopaque;
extern fn PyTuple_SetItem(*anyopaque, isize, *anyopaque) c_int;
extern fn PyList_New(isize) ?*anyopaque;
extern fn PyList_SetItem(*anyopaque, isize, *anyopaque) c_int;
extern fn PyList_GetItem(*anyopaque, isize) ?*anyopaque;
extern fn PyList_Size(*anyopaque) isize;
extern fn PyLong_FromLongLong(i64) ?*anyopaque;
extern fn PyLong_AsLongLong(*anyopaque) i64;
extern fn PyFloat_FromDouble(f64) ?*anyopaque;
extern fn PyFloat_AsDouble(*anyopaque) f64;
extern fn PyUnicode_FromStringAndSize([*c]const u8, isize) ?*anyopaque;
extern fn PyUnicode_AsUTF8(*anyopaque) ?[*c]const u8;
extern fn PyUnicode_GET_LENGTH(*anyopaque) isize;
extern fn PyBool_Check(*anyopaque) c_int;
extern fn PyLong_Check(*anyopaque) c_int;
extern fn PyFloat_Check(*anyopaque) c_int;
extern fn PyUnicode_Check(*anyopaque) c_int;
extern fn PyList_Check(*anyopaque) c_int;
extern fn PyObject_CallObject(*anyopaque, *anyopaque) ?*anyopaque;
extern fn PyObject_Repr(*anyopaque) ?*anyopaque;
extern fn PyErr_Clear() void;
extern fn PyErr_Occurred() ?*anyopaque;
extern var Py_None: *anyopaque;
extern var Py_True: *anyopaque;
extern var Py_False: *anyopaque;
// Track initialization state
var python_initialized: bool = false;
pub const PythonValue = union(enum) {
none,
bool: bool,
int: i64,
float: f64,
string: []const u8,
list: []PythonValue,
dict: void,
object: *anyopaque,
pub fn deinit(self: PythonValue, allocator: std.mem.Allocator) void {
switch (self) {
.string => |s| allocator.free(s),
.list => |l| {
for (l) |item| {
item.deinit(allocator);
}
allocator.free(l);
},
.object => |obj| {
Py_DecRef(obj);
},
else => {},
}
}
};
pub const PythonError = error{
NotInitialized,
AlreadyInitialized,
ModuleNotFound,
FunctionNotFound,
CallFailed,
ConversionFailed,
};
/// Initialize Python interpreter. Must be called before any Python operations.
pub fn init_python() i32 {
if (python_initialized) {
return 1; // Already initialized is OK
}
Py_Initialize();
if (Py_IsInitialized() == 0) {
return 0;
}
python_initialized = true;
return 1;
}
/// Check if Python interpreter is initialized.
pub fn python_initialized() i32 {
return if (python_initialized) 1 else 0;
}
/// Cleanup Python interpreter.
pub fn finalize_python() void {
if (python_initialized) {
Py_Finalize();
python_initialized = false;
}
}
/// Check if Python module is available
pub fn has_module(name: []const u8) i32 {
if (!python_initialized) {
return 0;
}
const name_c = std.heap.c_allocator.dupeZ(u8, name) catch {
return 0;
};
defer std.heap.c_allocator.free(name_c);
const module = PyImport_ImportModule(name_c.ptr);
if (module == null) {
PyErr_Clear();
return 0;
}
Py_DecRef(module.?);
return 1;
}
/// Call Python function: module, function, args (as JSON string for simplicity in Phase 2)
/// Returns result as JSON string.
pub fn python_call(module: []const u8, function: []const u8, args_json: []const u8, result_buf: []u8) i32 {
if (!python_initialized) {
return -4; // Not initialized
}
// Parse args from JSON
const args = parse_args_json(args_json) catch return -1;
defer free_args(args);
// Get module
const module_c = std.heap.c_allocator.dupeZ(u8, module) catch return -5;
defer std.heap.c_allocator.free(module_c);
const py_module = PyImport_ImportModule(module_c.ptr);
if (py_module == null) {
PyErr_Clear();
return -5; // Module not found
}
defer Py_DecRef(py_module.?);
// Get function
const function_c = std.heap.c_allocator.dupeZ(u8, function) catch return -6;
defer std.heap.c_allocator.free(function_c);
const py_func = PyObject_GetAttrString(py_module.?, function_c.ptr);
if (py_func == null) {
PyErr_Clear();
return -6; // Function not found
}
defer Py_DecRef(py_func.?);
if (PyCallable_Check(py_func.?) == 0) {
return -6; // Not callable
}
// Convert arguments to Python tuple
const py_args = zig_args_to_python(args) catch return -1;
defer Py_DecRef(py_args);
// Call function
const result = PyObject_CallObject(py_func.?, py_args);
if (result == null) {
PyErr_Clear();
return -2; // Call failed
}
defer Py_DecRef(result.?);
// Convert result back to Zig and serialize to JSON
const zig_result = python_to_zig(result.?) catch return -3;
defer zig_result.deinit(std.heap.c_allocator);
const result_str = value_to_json(zig_result) catch return -3;
defer std.heap.c_allocator.free(result_str);
// Copy to buffer
const len = @min(result_str.len, result_buf.len);
@memcpy(result_buf[0..len], result_str[0..len]);
return @intCast(len);
}
/// Create NumPy array from f64 slice (stub - requires NumPy C API)
pub fn numpy_array_nif(data: []const f64, result_buf: []u8) i32 {
if (!python_initialized) {
return -1; // Not initialized
}
// For now, return a simple JSON representation
// Full NumPy support would require importing numpy C API
_ = data;
const result_str = "{\"type\": \"numpy_array\", \"status\": \"stub\"}";
const len = @min(result_str.len, result_buf.len);
@memcpy(result_buf[0..len], result_str);
return @intCast(len);
}
// Helper: Parse simple JSON array
fn parse_args_json(json: []const u8) ![]PythonValue {
var args: std.ArrayList(PythonValue) = .{};
errdefer {
for (args.items) |item| {
item.deinit(std.heap.c_allocator);
}
args.deinit(std.heap.c_allocator);
}
// Very basic parser for [1.0, 2.0, 3.0] format
var i: usize = 0;
while (i < json.len) : (i += 1) {
if (json[i] >= '0' and json[i] <= '9' or json[i] == '-' or json[i] == '.') {
const start = i;
while (i < json.len and (json[i] >= '0' and json[i] <= '9' or json[i] == '.' or json[i] == 'e' or json[i] == 'E' or json[i] == '-' or json[i] == '+')) : (i += 1) {}
const num_str = json[start..i];
const num = std.fmt.parseFloat(f64, num_str) catch continue;
try args.append(std.heap.c_allocator, PythonValue{ .float = num });
} else if (json[i] == '"') {
i += 1;
const start = i;
while (i < json.len and json[i] != '"') : (i += 1) {}
const str = try std.heap.c_allocator.dupe(u8, json[start..i]);
try args.append(std.heap.c_allocator, PythonValue{ .string = str });
}
}
return args.toOwnedSlice(std.heap.c_allocator);
}
fn free_args(args: []PythonValue) void {
for (args) |arg| {
arg.deinit(std.heap.c_allocator);
}
std.heap.c_allocator.free(args);
}
// Helper: Convert Zig values to Python tuple
fn zig_args_to_python(args: []PythonValue) !*anyopaque {
const tuple = PyTuple_New(@intCast(args.len));
if (tuple == null) {
return PythonError.ConversionFailed;
}
for (args, 0..) |arg, i| {
const py_val = try zig_to_python(arg);
_ = PyTuple_SetItem(tuple.?, @intCast(i), py_val);
}
return tuple.?;
}
// Helper: Convert single Zig value to Python object
fn zig_to_python(value: PythonValue) !*anyopaque {
switch (value) {
.none => {
Py_IncRef(Py_None);
return Py_None;
},
.bool => |b| {
const py_bool = if (b) Py_True else Py_False;
Py_IncRef(py_bool);
return py_bool;
},
.int => |n| {
const py_int = PyLong_FromLongLong(n);
if (py_int == null) {
return PythonError.ConversionFailed;
}
return py_int.?;
},
.float => |f| {
const py_float = PyFloat_FromDouble(f);
if (py_float == null) {
return PythonError.ConversionFailed;
}
return py_float.?;
},
.string => |s| {
const py_str = PyUnicode_FromStringAndSize(s.ptr, @intCast(s.len));
if (py_str == null) {
return PythonError.ConversionFailed;
}
return py_str.?;
},
.list => |l| {
const py_list = PyList_New(@intCast(l.len));
if (py_list == null) {
return PythonError.ConversionFailed;
}
for (l, 0..) |item, i| {
const py_item = try zig_to_python(item);
_ = PyList_SetItem(py_list.?, @intCast(i), py_item);
}
return py_list.?;
},
.dict => {
// Return None for now
Py_IncRef(Py_None);
return Py_None;
},
.object => |obj| {
Py_IncRef(obj);
return obj;
},
}
}
// Helper: Convert Python object to Zig value
fn python_to_zig(obj: *anyopaque) !PythonValue {
// Check for None
if (obj == Py_None) {
return PythonValue{ .none = {} };
}
// Check for bool
if (PyBool_Check(obj) != 0) {
return PythonValue{ .bool = (obj == Py_True) };
}
// Check for int
if (PyLong_Check(obj) != 0) {
const val = PyLong_AsLongLong(obj);
if (val == -1 and PyErr_Occurred() != null) {
PyErr_Clear();
return PythonError.ConversionFailed;
}
return PythonValue{ .int = val };
}
// Check for float
if (PyFloat_Check(obj) != 0) {
const val = PyFloat_AsDouble(obj);
if (val == -1.0 and PyErr_Occurred() != null) {
PyErr_Clear();
return PythonError.ConversionFailed;
}
return PythonValue{ .float = val };
}
// Check for string
if (PyUnicode_Check(obj) != 0) {
const str_ptr = PyUnicode_AsUTF8(obj);
if (str_ptr == null) {
return PythonError.ConversionFailed;
}
const str_len = PyUnicode_GET_LENGTH(obj);
const str = std.heap.c_allocator.dupe(u8, str_ptr.?[0..@intCast(str_len)]) catch {
return PythonError.ConversionFailed;
};
return PythonValue{ .string = str };
}
// Check for list
if (PyList_Check(obj) != 0) {
const len = PyList_Size(obj);
if (len < 0) {
return PythonError.ConversionFailed;
}
var list = std.ArrayList(PythonValue).init(std.heap.c_allocator);
defer list.deinit();
var i: isize = 0;
while (i < len) : (i += 1) {
const item = PyList_GetItem(obj, i);
if (item == null) {
return PythonError.ConversionFailed;
}
const zig_item = try python_to_zig(item.?);
try list.append(zig_item);
}
return PythonValue{ .list = list.toOwnedSlice() catch {
return PythonError.ConversionFailed;
} };
}
// Return as opaque object
Py_IncRef(obj);
return PythonValue{ .object = obj };
}
// Helper: Convert PythonValue to JSON string
fn value_to_json(value: PythonValue) ![]const u8 {
var buf: std.ArrayList(u8) = .{};
defer {
buf.clearAndFree(std.heap.c_allocator);
}
switch (value) {
.none => try buf.appendSlice(std.heap.c_allocator, "null"),
.bool => |b| try buf.appendSlice(std.heap.c_allocator, if (b) "true" else "false"),
.int => |n| {
var temp_buf: [64]u8 = undefined;
const str = try std.fmt.bufPrint(&temp_buf, "{d}", .{n});
try buf.appendSlice(std.heap.c_allocator, str);
},
.float => |f| {
var temp_buf: [64]u8 = undefined;
const str = try std.fmt.bufPrint(&temp_buf, "{d}", .{f});
try buf.appendSlice(std.heap.c_allocator, str);
},
.string => |s| {
try buf.append(std.heap.c_allocator, '"');
try buf.appendSlice(std.heap.c_allocator, s);
try buf.append(std.heap.c_allocator, '"');
},
.list => |l| {
try buf.append(std.heap.c_allocator, '[');
for (l, 0..) |item, i| {
if (i > 0) try buf.append(std.heap.c_allocator, ',');
const item_json = try value_to_json(item);
defer std.heap.c_allocator.free(item_json);
try buf.appendSlice(std.heap.c_allocator, item_json);
}
try buf.append(std.heap.c_allocator, ']');
},
.dict => {
try buf.appendSlice(std.heap.c_allocator, "{}");
},
.object => {
try buf.appendSlice(std.heap.c_allocator, "null");
},
}
return buf.toOwnedSlice(std.heap.c_allocator);
}