Skip to content

Commit 20fb671

Browse files
committed
builtin: implement exec() and eval()
1 parent d0c72a9 commit 20fb671

File tree

9 files changed

+199
-4
lines changed

9 files changed

+199
-4
lines changed

builtin/builtin.go

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77

88
"github.com/ncw/gpython/compile"
99
"github.com/ncw/gpython/py"
10-
"github.com/ncw/gpython/vm"
1110
)
1211

1312
const builtin_doc = `Built-in functions, exceptions, and other objects.
@@ -30,8 +29,8 @@ func init() {
3029
// py.MustNewMethod("delattr", builtin_delattr, 0, delattr_doc),
3130
// py.MustNewMethod("dir", builtin_dir, 0, dir_doc),
3231
py.MustNewMethod("divmod", builtin_divmod, 0, divmod_doc),
33-
// py.MustNewMethod("eval", builtin_eval, 0, eval_doc),
34-
// py.MustNewMethod("exec", builtin_exec, 0, exec_doc),
32+
py.MustNewMethod("eval", py.InternalMethodEval, 0, eval_doc),
33+
py.MustNewMethod("exec", py.InternalMethodExec, 0, exec_doc),
3534
// py.MustNewMethod("format", builtin_format, 0, format_doc),
3635
py.MustNewMethod("getattr", builtin_getattr, 0, getattr_doc),
3736
py.MustNewMethod("globals", py.InternalMethodGlobals, 0, globals_doc),
@@ -323,7 +322,7 @@ func builtin___build_class__(self py.Object, args py.Tuple, kwargs py.StringDict
323322
}
324323
// fmt.Printf("Calling %v with %v and %v\n", fn.Name, fn.Globals, ns)
325324
// fmt.Printf("Code = %#v\n", fn.Code)
326-
cell, err = vm.Run(fn.Globals, ns, fn.Code, fn.Closure)
325+
cell, err = py.VmRun(fn.Globals, ns, fn.Code, fn.Closure)
327326
if err != nil {
328327
return nil, err
329328
}
@@ -599,6 +598,26 @@ func builtin_divmod(self py.Object, args py.Tuple) (py.Object, error) {
599598
return py.Tuple{q, r}, nil
600599
}
601600

601+
const eval_doc = `"eval(source[, globals[, locals]]) -> value
602+
603+
Evaluate the source in the context of globals and locals.
604+
The source may be a string representing a Python expression
605+
or a code object as returned by compile().
606+
The globals must be a dictionary and locals can be any mapping,
607+
defaulting to the current globals and locals.
608+
If only globals is given, locals defaults to it.`
609+
610+
// For code see vm/builtin.go
611+
612+
const exec_doc = `exec(object[, globals[, locals]])
613+
614+
Read and execute code from an object, which can be a string or a code
615+
object.
616+
The globals and locals are dictionaries, defaulting to the current
617+
globals and locals. If only globals is given, locals defaults to it.`
618+
619+
// For code see vm/builtin.go
620+
602621
const len_doc = `len(object) -> integer
603622
604623
Return the number of items of a sequence or mapping.`

builtin/tests/builtin.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@
1616
doc="divmod"
1717
assert divmod(34,7) == (4, 6)
1818

19+
doc="eval"
20+
# smoke test only - see vm/tests/builtin.py for more tests
21+
assert eval("1+2") == 3
22+
23+
doc="exec"
24+
# smoke test only - see vm/tests/builtin.py for more tests
25+
glob = {"a":100}
26+
assert exec("b = a+100", glob) == None
27+
assert glob["b"] == 200
28+
1929
doc="getattr"
2030
class C:
2131
def __init__(self):

notes.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ Limitations & Missing parts
3737
* repr/str
3838
* subclassing built in types - how? Need to make sure we have called the appropriate method everywhere rather than just .(String)
3939
* FIXME how do mapping types work?
40+
* PyMapping_Check
41+
* is it just an interface?
4042

4143
Type ideas
4244
==========

py/code.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,11 @@ func NewCode(argcount int32, kwonlyargcount int32,
214214
}
215215
}
216216

217+
// Return number of free variables
218+
func (co *Code) GetNumFree() int {
219+
return len(co.Freevars)
220+
}
221+
217222
// Use co_lnotab to compute the line number from a bytecode index,
218223
// addrq. See lnotab_notes.txt for the details of the lnotab
219224
// representation.

py/dict.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ func DictCheckExact(obj Object) (StringDict, error) {
5252
return dict, nil
5353
}
5454

55+
// Checks that obj is exactly a dictionary and returns an error if not
56+
func DictCheck(obj Object) (StringDict, error) {
57+
// FIXME should be checking subclasses
58+
return DictCheckExact(obj)
59+
}
60+
5561
// Copy a dictionary
5662
func (d StringDict) Copy() StringDict {
5763
e := make(StringDict, len(d))

py/method.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ const (
7272
InternalMethodGlobals
7373
InternalMethodLocals
7474
InternalMethodImport
75+
InternalMethodEval
76+
InternalMethodExec
7577
)
7678

7779
var MethodType = NewType("method", "method object")

vm/builtin.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Implement builtin functions eval and exec
2+
3+
package vm
4+
5+
import (
6+
"strings"
7+
8+
"github.com/ncw/gpython/py"
9+
)
10+
11+
func builtinEvalOrExec(self py.Object, args py.Tuple, kwargs, currentLocals, currentGlobals, builtins py.StringDict, mode string) (py.Object, error) {
12+
var (
13+
cmd py.Object
14+
globals py.Object = py.None
15+
locals py.Object = py.None
16+
)
17+
err := py.UnpackTuple(args, kwargs, mode, 1, 3, &cmd, &globals, &locals)
18+
if err != nil {
19+
return nil, err
20+
}
21+
if globals == py.None {
22+
globals = currentGlobals
23+
if locals == py.None {
24+
locals = currentLocals
25+
}
26+
} else if locals == py.None {
27+
locals = globals
28+
}
29+
// FIXME this can be a mapping too
30+
globalsDict, err := py.DictCheck(globals)
31+
if err != nil {
32+
return nil, py.ExceptionNewf(py.TypeError, "globals must be a dict")
33+
}
34+
localsDict, err := py.DictCheck(locals)
35+
if err != nil {
36+
return nil, py.ExceptionNewf(py.TypeError, "locals must be a dict")
37+
}
38+
39+
// Set __builtins__ if not set
40+
if _, ok := globalsDict["__builtins__"]; !ok {
41+
globalsDict["__builtins__"] = builtins
42+
}
43+
44+
var codeStr string
45+
var code *py.Code
46+
switch x := cmd.(type) {
47+
case *py.Code:
48+
code = x
49+
case py.String:
50+
codeStr = string(x)
51+
case py.Bytes:
52+
codeStr = string(x)
53+
default:
54+
return nil, py.ExceptionNewf(py.TypeError, "%s() arg 1 must be a string, bytes or code object", mode)
55+
56+
}
57+
if code == nil {
58+
codeStr = strings.TrimLeft(codeStr, " \t")
59+
obj, err := py.Compile(codeStr, "<string>", mode, 0, true)
60+
if err != nil {
61+
return nil, err
62+
}
63+
code = obj.(*py.Code)
64+
}
65+
if code.GetNumFree() > 0 {
66+
return nil, py.ExceptionNewf(py.TypeError, "code passed to %s() may not contain free variables", mode)
67+
}
68+
return EvalCode(code, globalsDict, localsDict)
69+
}
70+
71+
func builtinEval(self py.Object, args py.Tuple, kwargs, currentLocals, currentGlobals, builtins py.StringDict) (py.Object, error) {
72+
return builtinEvalOrExec(self, args, kwargs, currentLocals, currentGlobals, builtins, "eval")
73+
}
74+
75+
func builtinExec(self py.Object, args py.Tuple, kwargs, currentLocals, currentGlobals, builtins py.StringDict) (py.Object, error) {
76+
_, err := builtinEvalOrExec(self, args, kwargs, currentLocals, currentGlobals, builtins, "exec")
77+
if err != nil {
78+
return nil, err
79+
}
80+
return py.None, nil
81+
}

vm/eval.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1582,6 +1582,12 @@ func callInternal(fn py.Object, args py.Tuple, kwargs py.StringDict, f *py.Frame
15821582
return f.Locals, nil
15831583
case py.InternalMethodImport:
15841584
return py.BuiltinImport(nil, args, kwargs, f.Globals)
1585+
case py.InternalMethodEval:
1586+
f.FastToLocals()
1587+
return builtinEval(nil, args, kwargs, f.Locals, f.Globals, f.Builtins)
1588+
case py.InternalMethodExec:
1589+
f.FastToLocals()
1590+
return builtinExec(nil, args, kwargs, f.Locals, f.Globals, f.Builtins)
15851591
default:
15861592
return nil, py.ExceptionNewf(py.SystemError, "Internal method %v not found", x)
15871593
}

vm/tests/builtin.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
doc="eval"
2+
assert eval("1+2") == 3
3+
glob ={'a':1}
4+
assert eval("a+2", glob) == 3
5+
loc ={'b':2}
6+
assert eval("a+b", glob, loc) == 3
7+
co = compile("a+b+1", "s", "eval")
8+
assert eval(co, glob, loc) == 4
9+
assert eval(b"2+3") == 5
10+
11+
try:
12+
eval(())
13+
except TypeError as e:
14+
pass
15+
else:
16+
assert False, "SyntaxError not raised"
17+
18+
try:
19+
eval("a = 26")
20+
except SyntaxError as e:
21+
pass
22+
else:
23+
assert False, "SyntaxError not raised"
24+
25+
try:
26+
eval(1,2,3,4)
27+
except TypeError as e:
28+
pass
29+
else:
30+
assert False, "TypeError not raised"
31+
32+
try:
33+
eval("1", ())
34+
except TypeError as e:
35+
pass
36+
else:
37+
assert False, "TypeError not raised"
38+
39+
try:
40+
eval("1", {}, ())
41+
except TypeError as e:
42+
pass
43+
else:
44+
assert False, "TypeError not raised"
45+
46+
doc="exec"
47+
glob = {"a":100}
48+
assert exec("b = a+100", glob) == None
49+
assert glob["b"] == 200
50+
loc = {"c":23}
51+
assert exec("d = a+b+c", glob, loc) == None
52+
assert loc["d"] == 323
53+
co = compile("d = a+b+c+1", "s", "exec")
54+
assert eval(co, glob, loc) == None
55+
assert loc["d"] == 324
56+
57+
try:
58+
exec("if")
59+
except SyntaxError as e:
60+
pass
61+
else:
62+
assert False, "SyntaxError not raised"
63+
64+
doc="finished"

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy