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

+23-4
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

+10
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

+2
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

+5
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

+6
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

+2
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

+81
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

+6
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

+64
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)