|
1 |
| -function pyeval(::Type{R}, co::PyCode, globals::PyDict, locals::Union{PyDict,Nothing}=globals, extras::Union{NamedTuple,Nothing,Tuple{}}=nothing) where {R} |
2 |
| - # get code |
3 |
| - cptr = pyptr(co) |
4 |
| - # get globals & ensure __builtins__ is set |
5 |
| - gptr = pyptr(globals) |
6 |
| - if !globals.hasbuiltins |
7 |
| - if C.PyMapping_HasKeyString(gptr, "__builtins__") == 0 |
8 |
| - err = C.PyMapping_SetItemString(gptr, "__builtins__", C.PyEval_GetBuiltins()) |
9 |
| - ism1(err) && pythrow() |
10 |
| - end |
11 |
| - globals.hasbuiltins = true |
12 |
| - end |
13 |
| - # get locals (ALLOCATES lptr if locals===nothing) |
14 |
| - if locals === nothing |
15 |
| - lptr = C.PyDict_New() |
16 |
| - isnull(lptr) && pythrow() |
17 |
| - else |
18 |
| - lptr = pyptr(locals) |
19 |
| - end |
20 |
| - # insert extra locals |
21 |
| - if extras isa NamedTuple |
22 |
| - for (k,v) in pairs(extras) |
23 |
| - vo = C.PyObject_From(v) |
24 |
| - if isnull(vo) |
25 |
| - locals===nothing && C.Py_DecRef(lptr) |
26 |
| - pythrow() |
27 |
| - end |
28 |
| - err = C.PyMapping_SetItemString(lptr, string(k), vo) |
29 |
| - C.Py_DecRef(vo) |
30 |
| - if ism1(err) |
31 |
| - locals===nothing && C.Py_DecRef(lptr) |
32 |
| - pythrow() |
33 |
| - end |
34 |
| - end |
35 |
| - end |
36 |
| - # Call eval (ALLOCATES rptr) |
37 |
| - rptr = C.PyEval_EvalCode(cptr, gptr, lptr) |
38 |
| - if isnull(rptr) |
39 |
| - locals === nothing && C.Py_DecRef(lptr) |
40 |
| - pythrow() |
41 |
| - end |
42 |
| - # TODO: convert rptr using PyObject_As |
43 |
| - if co.mode == :exec |
44 |
| - if R <: Nothing |
45 |
| - C.Py_DecRef(rptr) |
46 |
| - locals===nothing && C.Py_DecRef(lptr) |
47 |
| - return nothing |
48 |
| - elseif R <: NamedTuple && isconcretetype(R) |
49 |
| - C.Py_DecRef(rptr) |
50 |
| - ret = C.PyMapping_ExtractAs(lptr, R) |
51 |
| - locals===nothing && C.Py_DecRef(lptr) |
52 |
| - ret === PYERR() && pythrow() |
53 |
| - ret === NOTIMPLEMENTED() && pythrow() |
54 |
| - return ret::R |
55 |
| - else |
56 |
| - C.Py_DecRef(rptr) |
57 |
| - locals===nothing && C.Py_DecRef(lptr) |
58 |
| - error("invalid return type $(R)") |
59 |
| - end |
60 |
| - elseif co.mode == :eval |
61 |
| - ret = C.PyObject_As(rptr, R) |
62 |
| - ret === NOTIMPLEMENTED() && C.PyErr_SetString(C.PyExc_TypeError(), "Cannot convert this '$(C.PyType_Name(C.Py_Type(rptr)))' to a Julia '$R'") |
63 |
| - C.Py_DecRef(rptr) |
64 |
| - locals===nothing && C.Py_DecRef(lptr) |
65 |
| - ret === PYERR() && pythrow() |
66 |
| - ret === NOTIMPLEMENTED() && pythrow() |
67 |
| - return ret::R |
68 |
| - else |
69 |
| - C.Py_DecRef(rptr) |
70 |
| - locals===nothing && C.Py_DecRef(lptr) |
71 |
| - error("invalid mode $(repr(co.mode))") |
72 |
| - end |
73 |
| -end |
74 |
| -export pyeval |
75 |
| - |
76 | 1 | module CompiledCode
|
77 | 2 | import ..Python: PyCode
|
78 | 3 | stash(co::PyCode) = begin
|
@@ -138,31 +63,77 @@ pyeval_macro(src, mode, args...) = begin
|
138 | 63 | # for LHS interpolation, extract out type annotations
|
139 | 64 | if any(islhs)
|
140 | 65 | mode == :exec || error("interpolation on LHS only allowed in exec mode")
|
141 |
| - icode == 1 || error("cannot specify return type when interpolation on LHS is used") |
142 |
| - rettypearg = :($NamedTuple{($([QuoteNode(Symbol(n)) for (n,lhs) in zip(intvars,islhs) if lhs]...),),Tuple{$([(ex isa Expr && ex.head == :(::)) ? ex.args[2] : Any for (ex,lhs) in zip(interps,islhs) if lhs]...)}}) |
143 | 66 | end
|
144 | 67 | # make the code object
|
145 | 68 | co = CompiledCode.stash(newcode, "<julia $(src.file):$(src.line)>", mode)
|
146 |
| - # call pyeval |
147 |
| - ret = :(pyeval($(esc(rettypearg)), $(co), $(esc(:pyglobals)), $(esc(locals)), ($([:($k = $(esc(v))) for (k,v) in kvs]...),))) |
148 |
| - # assign |
149 |
| - if any(islhs) |
150 |
| - ret = :(($([(ex isa Expr && ex.head == :(::)) ? esc(ex.args[1]) : esc(ex) for (ex,lhs) in zip(interps,islhs) if lhs]...),) = $ret; nothing) |
| 69 | + # go |
| 70 | + freelocals = locals === nothing ? :(GC.@preserve locals C.Py_DecRef(lptr)) : nothing |
| 71 | + quote |
| 72 | + # get the code pointer |
| 73 | + cptr = pyptr($co) |
| 74 | + # get the globals pointer |
| 75 | + globals = $(esc(:pyglobals)) |
| 76 | + gptr = pyptr(globals) |
| 77 | + # ensure globals includes builtins |
| 78 | + if !globals.hasbuiltins |
| 79 | + if C.PyMapping_HasKeyString(gptr, "__builtins__") == 0 |
| 80 | + err = C.PyMapping_SetItemString(gptr, "__builtins__", C.PyEval_GetBuiltins()) |
| 81 | + ism1(err) && pythrow() |
| 82 | + end |
| 83 | + globals.hasbuiltins = true |
| 84 | + end |
| 85 | + # get locals (ALLOCATES lptr if locals===nothing) |
| 86 | + $(locals === nothing ? :(lptr = C.PyDict_New(); isnull(lptr) && pythrow()) : :(locals = $(esc(locals)); lptr = pyptr(locals))) |
| 87 | + # insert extra locals |
| 88 | + $([:(let; vo=C.PyObject_From($(esc(v))); isnull(vo) && ($freelocals; pythrow()); err=C.PyMapping_SetItemString(lptr, $(string(k)), vo); C.Py_DecRef(vo); ism1(err) && ($freelocals; pythrow()); end) for (k,v) in kvs]...) |
| 89 | + # Call eval (ALLOCATES rptr) |
| 90 | + rptr = GC.@preserve globals C.PyEval_EvalCode(cptr, gptr, lptr) |
| 91 | + isnull(rptr) && ($freelocals; pythrow()) |
| 92 | + # extract values |
| 93 | + $( |
| 94 | + if mode == :eval |
| 95 | + quote |
| 96 | + $freelocals |
| 97 | + res = C.PyObject_As(rptr, $(esc(rettypearg))) |
| 98 | + C.Py_DecRef(rptr) |
| 99 | + res == PYERR() && pythrow() |
| 100 | + res == NOTIMPLEMENTED() && (C.PyErr_SetString(C.PyExc_TypeError(), "Cannot convert return value of type '$(C.PyType_Name(C.Py_Type(rptr)))' to a Julia '$($(esc(rettypearg)))'"); pythrow()) |
| 101 | + return res::$(esc(rettypearg)) |
| 102 | + end |
| 103 | + elseif mode == :exec |
| 104 | + quote |
| 105 | + C.Py_DecRef(rptr) |
| 106 | + $((((jv,jt) = (ex isa Expr && ex.head == :(::)) ? (ex.args[1], esc(ex.args[2])) : (ex, Any); quote |
| 107 | + $(esc(jv)) = let |
| 108 | + xo = C.PyMapping_GetItemString(lptr, $v) |
| 109 | + isnull(xo) && ($freelocals; pythrow()) |
| 110 | + x = C.PyObject_As(xo, $jt) |
| 111 | + x===NOTIMPLEMENTED() && C.PyErr_SetString(C.PyExc_TypeError(), "Cannot convert return value '$($(string(jv)))' of type '$(C.PyType_Name(C.Py_Type(xo)))' to a Julia '$($jt)'") |
| 112 | + C.Py_DecRef(xo) |
| 113 | + x===PYERR() && ($freelocals; pythrow()) |
| 114 | + x===NOTIMPLEMENTED() && ($freelocals; pythrow()) |
| 115 | + x::$jt; |
| 116 | + end |
| 117 | + end) for (ex,v,lhs) in zip(interps,intvars,islhs) if lhs)...) |
| 118 | + $freelocals |
| 119 | + nothing |
| 120 | + end |
| 121 | + else |
| 122 | + error() |
| 123 | + end |
| 124 | + ) |
151 | 125 | end
|
152 |
| - ret |
153 | 126 | end
|
154 | 127 |
|
155 | 128 | """
|
156 |
| - @py [rettype] `...` [locals] [var=val, ...] |
| 129 | + @py `...` [locals] [var=val, ...] |
157 | 130 |
|
158 | 131 | Executes the given Python code.
|
159 | 132 |
|
160 | 133 | Julia values can be interpolated using the usual `\$(...)` syntax.
|
161 | 134 | Additionally, assignment to interpolations is supported: e.g. `\$(x::T) = ...` will convert the right hand side to a `T` and assign it to `x`.
|
162 | 135 |
|
163 | 136 | The globals are `pyglobals`. The locals are `locals`, if given, otherwise a temporary scope is created. Extra values to be interted into the scope can be given with extra `var=val` arguments.
|
164 |
| -
|
165 |
| -The resulting expression has type `rettype`, which may be `Nothing` (the default) or `NamedTuple{names,types}` to extract variables with the given names as a named tuple. |
166 | 137 | """
|
167 | 138 | macro py(args...)
|
168 | 139 | pyeval_macro(__source__, :exec, args...)
|
|
0 commit comments