Skip to content

Commit a3e7276

Browse files
committed
feat(builtins): eval
1 parent 3c852e5 commit a3e7276

File tree

5 files changed

+81
-20
lines changed

5 files changed

+81
-20
lines changed

Parser/lexer.nim

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -733,8 +733,6 @@ proc lexOneLine(lexer: Lexer, line: string, mode: Mode) {.inline, raises: [Synta
733733
lexer.add(Token.NEWLINE, idx)
734734

735735
proc lexString*(lexer: Lexer, input: string, mode=Mode.File) =
736-
assert mode != Mode.Eval # eval not tested
737-
738736
# interactive mode and an empty line
739737
if mode == Mode.Single and input.len == 0 and not lexer.tripleStr.within:
740738
lexer.dedentAll

Python/ast.nim

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -320,11 +320,18 @@ ast file_input, [AstModule]:
320320
if child.tokenNode.token == Token.stmt:
321321
result.body.addCompat astStmt(child)
322322

323-
#[
324-
ast eval_input, []:
325-
discard
326-
327-
]#
323+
# eval_input: testlist NEWLINE* ENDMARKER
324+
ast eval_input, [AstExpression]:
325+
result = newAstExpression()
326+
let child = parseNode.children[0]
327+
if child.tokenNode.token != Token.testlist:
328+
raiseSyntaxError("expected testlist in eval input", child)
329+
let e = astTestlist(child)
330+
result.body = e
331+
for i in 1..<parseNode.children.len:
332+
let child = parseNode.children[i]
333+
if child.tokenNode.token not_in {Token.NEWLINE, Token.ENDMARKER}:
334+
raiseSyntaxError("unexpected token in eval input", child)
328335

329336
# decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
330337
ast decorator, [AsdlExpr]:
@@ -1747,7 +1754,7 @@ proc ast*(root: ParseNode, tfileName: string): AsdlModl =
17471754
of Token.single_input:
17481755
result = astSingleInput(root)
17491756
of Token.eval_input:
1750-
unreachable # currently no eval mode
1757+
result = astEvalInput(root)
17511758
else:
17521759
unreachable
17531760
when defined(debug_ast):

Python/bltinmodule/compile_eval_exec.nim

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
2+
from std/strutils import strip
33
import ../../Objects/[
44
stringobject/codec,
55
noneobject,
@@ -46,9 +46,10 @@ proc compile*(
4646

4747
Py_CompileStringObject(str, filename, emode, cf, optimize)
4848

49-
proc exec*(
50-
source: PyObject, globals: PyObject = pyNone, locals: PyObject = pyNone,
51-
closure: PyObject = nil): PyObject{.bltin_clinicGen.} =
49+
template init_globals_locals_ensure_bltin(no_globals_err,
50+
globals_invalid, locals_invalid
51+
){.dirty.} =
52+
5253
var globals = globals
5354
var fromframe = false
5455
if globals.isPyNone:
@@ -59,7 +60,7 @@ proc exec*(
5960
else:
6061
globals = PyEval_GetGlobalsFromRunningMain()
6162
if globals.isNil:
62-
return newSystemError newPyAscii "globals and locals cannot be NULL"
63+
return newSystemError newPyAscii no_globals_err
6364

6465
var locals = locals
6566
if locals.isPyNone:
@@ -70,16 +71,68 @@ proc exec*(
7071
locals = globals
7172

7273
if not globals.ofPyDictObject:
73-
return type_errorn("exec() globals must be a dict, not $#", globals)
74+
return type_error globals_invalid
7475
if not locals.ofPyMapping:
75-
return type_errorn("exec() locals must be a mapping or None, not $#", locals)
76+
return type_error locals_invalid
7677

7778
locals = nil
7879
TODO_locals locals
7980

8081
let dglobals = PyDictObject globals
8182
retIfExc PyEval_EnsureBuiltins(dglobals)
8283

84+
template asStringAndInitCf(source, funName){.dirty.} =
85+
var cf = initPyCompilerFlags()
86+
cf.flags = typeof(cf.flags) PyCF.SOURCE_IS_UTF8
87+
var source_copy: PyObject
88+
var str: string
89+
retIfExc Py_SourceAsString(source, funName, "string, bytes or AST", cf, source_copy, str)
90+
91+
proc eval*(
92+
source: PyObject, globals: PyObject = pyNone, locals: PyObject = pyNone,
93+
): PyObject{.bltin_clinicGen.} =
94+
var globalsIsDict = globals.ofPyDictObject
95+
# if not globals.isPyNone and not (globalsIsDict = globals.ofPyDictObject; globalsIsDict):
96+
# return type_error(
97+
# )
98+
# if not locals.isPyNone and not locals.ofPyMapping:
99+
# return type_error
100+
101+
init_globals_locals_ensure_bltin(
102+
"eval must be given globals and locals when called without a frame",
103+
if globalsIsDict: "globals must be a real dict; try eval(expr, {}, mapping)"
104+
else: "globals must be a dict"
105+
,
106+
"locals must be a mapping"
107+
)
108+
if source.ofPyCodeObject:
109+
let co = PyCodeObject source
110+
retIfExc audit("exec", source)
111+
if co.freeVars.len > 0:
112+
return type_error "code object passed to eval() may not have free variables"
113+
114+
result = evalCode(co, dglobals, locals)
115+
else:
116+
asStringAndInitCf source, "eval"
117+
118+
str = str.strip(chars={' ', '\t'}, trailing=false)
119+
120+
discard PyEval_MergeCompilerFlags(cf)
121+
122+
return PyRun_StringFlags(str, Mode.Eval, dglobals, locals, cf)
123+
124+
125+
proc exec*(
126+
source: PyObject, globals: PyObject = pyNone, locals: PyObject = pyNone,
127+
closure: PyObject = nil): PyObject{.bltin_clinicGen.} =
128+
129+
init_globals_locals_ensure_bltin(
130+
"globals and locals cannot be NULL",
131+
"exec() globals must be a dict, not " & globals.typeName,
132+
"exec() locals must be a mapping or None, not " & locals.typeName
133+
134+
)
135+
83136
var closure = closure
84137
if closure.isPyNone:
85138
closure = nil
@@ -113,11 +166,7 @@ proc exec*(
113166
if not closure.isNil:
114167
return type_error "closure can only be used when source is a code object"
115168

116-
var cf = initPyCompilerFlags()
117-
cf.flags = typeof(cf.flags) PyCF.SOURCE_IS_UTF8
118-
var source_copy: PyObject
119-
var str: string
120-
retIfExc Py_SourceAsString(source, "exec", "string, bytes or AST", cf, source_copy, str)
169+
asStringAndInitCf source, "exec"
121170
result = if PyEval_MergeCompilerFlags(cf):
122171
PyRun_StringFlags(str, Mode.File, dglobals, locals, cf)
123172
else:
@@ -130,3 +179,4 @@ template reg(f) =
130179
template register_compile_eval_exec* =
131180
reg compile
132181
reg exec
182+
reg eval

Python/compile.nim

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,9 @@ compileMethod Interactive:
447447
c.interactive = true
448448
c.compileSeq(astNode.body)
449449

450+
compileMethod Expression:
451+
c.compile(astNode.body)
452+
c.addOp(newInstr(OpCode.ReturnValue, astNode.body.lineNo.value))
450453

451454
proc compileArguments(c: Compiler; astNode: auto; argsNode: AstArguments,
452455
codeName: PyStrObject): int =

Python/symtable.nim

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,9 @@ proc collectDeclaration*(st: SymTable, astRoot: AsdlModl){.raises: [SyntaxError]
213213
elif astNode of AstInteractive:
214214
ste.kind = SteKind.Module
215215
addBodies(AstInteractive)
216+
elif astNode of AstExpression:
217+
ste.kind = SteKind.Module #TODO:eval: right?
218+
visit AstExpression(astNode).body
216219
elif astNode of AstFunctionDef:
217220
ste.kind = SteKind.Function
218221
# deal with function args

0 commit comments

Comments
 (0)