Skip to content

Commit 20186f4

Browse files
author
Christopher Doris
committed
remove pyeval() and puts all its logic straight into @py etc. (saves memory allocations)
1 parent 43794a8 commit 20186f4

File tree

1 file changed

+57
-86
lines changed

1 file changed

+57
-86
lines changed

src/eval.jl

Lines changed: 57 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,3 @@
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-
761
module CompiledCode
772
import ..Python: PyCode
783
stash(co::PyCode) = begin
@@ -138,31 +63,77 @@ pyeval_macro(src, mode, args...) = begin
13863
# for LHS interpolation, extract out type annotations
13964
if any(islhs)
14065
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]...)}})
14366
end
14467
# make the code object
14568
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+
)
151125
end
152-
ret
153126
end
154127

155128
"""
156-
@py [rettype] `...` [locals] [var=val, ...]
129+
@py `...` [locals] [var=val, ...]
157130
158131
Executes the given Python code.
159132
160133
Julia values can be interpolated using the usual `\$(...)` syntax.
161134
Additionally, assignment to interpolations is supported: e.g. `\$(x::T) = ...` will convert the right hand side to a `T` and assign it to `x`.
162135
163136
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.
166137
"""
167138
macro py(args...)
168139
pyeval_macro(__source__, :exec, args...)

0 commit comments

Comments
 (0)