Repository: hylang/hy

Issue:
exec not defined on hy2
on hy3 works:

```
$ hy3
hy 0.10.1 using CPython(default) 3.4.2 on Linux
=> (exec "a = 2")
=>
```

on hy2:

```
$ hy2
hy 0.10.1 using CPython(default) 2.7.8 on Linux
=> (exec "a = 2")
Traceback (most recent call last):
  File "<input>", line 1, in <module>
NameError: name 'exec' is not defined
=> 
```



Discussion:
Yes, this is a bug.

``` py
$ python
Python 2.7.3 (default, Feb 27 2014, 19:58:35) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> exec("a=2")
>>> a
2
```

Is `exec` the de facto way to evaluate python code from hy (without using a separate module)? Or should we change it to be more consistent with `eval`?


Resolving patch:
diff --git a/docs/conf.py b/docs/conf.py
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -5,6 +5,8 @@
 import re, os, sys, time, cgi
 sys.path.append(os.path.abspath(".."))
 
+extensions = ['sphinx.ext.intersphinx']
+
 from get_version import __version__ as hy_version
 # Read the Docs might dirty its checkout, so strip the dirty flag.
 hy_version = re.sub('[+.]dirty\Z', '', hy_version)
@@ -48,3 +50,7 @@
 
 html_context = dict(
     hy_descriptive_version = hy_descriptive_version)
+
+intersphinx_mapping = dict(
+    py2 = ('https://docs.python.org/2', None),
+    py  = ('https://docs.python.org/3', None))
diff --git a/hy/compiler.py b/hy/compiler.py
--- a/hy/compiler.py
+++ b/hy/compiler.py
@@ -778,6 +778,19 @@ def compile_unpack_iterable(self, expr):
     def compile_unpack_mapping(self, expr):
         raise HyTypeError(expr, "`unpack-mapping` isn't allowed here")
 
+    @builds_if("exec*", not PY3)
+    # Under Python 3, `exec` is a function rather than a statement type, so Hy
+    # doesn't need a special form for it.
+    @checkargs(min=1, max=3)
+    def compile_exec(self, expr):
+        expr.pop(0)
+        return ast.Exec(
+            lineno=expr.start_line,
+            col_offset=expr.start_column,
+            body=self.compile(expr.pop(0)).force_expr,
+            globals=self.compile(expr.pop(0)).force_expr if expr else None,
+            locals=self.compile(expr.pop(0)).force_expr if expr else None)
+
     @builds("do")
     def compile_do(self, expression):
         expression.pop(0)


Test patch:
diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py
--- a/tests/compilers/test_ast.py
+++ b/tests/compilers/test_ast.py
@@ -593,3 +593,27 @@ def test_setv_builtins():
 def test_lots_of_comment_lines():
     # https://github.com/hylang/hy/issues/1313
     can_compile(1000 * ";\n")
+
+
+def test_exec_star():
+
+    code = can_compile('(exec* "print(5)")').body[0]
+    assert type(code) == (ast.Expr if PY3 else ast.Exec)
+    if not PY3:
+        assert code.body.s == "print(5)"
+        assert code.globals is None
+        assert code.locals is None
+
+    code = can_compile('(exec* "print(a)" {"a" 3})').body[0]
+    assert type(code) == (ast.Expr if PY3 else ast.Exec)
+    if not PY3:
+        assert code.body.s == "print(a)"
+        assert code.globals.keys[0].s == "a"
+        assert code.locals is None
+
+    code = can_compile('(exec* "print(a + b)" {"a" "x"} {"b" "y"})').body[0]
+    assert type(code) == (ast.Expr if PY3 else ast.Exec)
+    if not PY3:
+        assert code.body.s == "print(a + b)"
+        assert code.globals.keys[0].s == "a"
+        assert code.locals.keys[0].s == "b"
diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy
--- a/tests/native_tests/core.hy
+++ b/tests/native_tests/core.hy
@@ -170,6 +170,49 @@
   (assert-false (every? even? [2 4 5]))
   (assert-true (every? even? [])))
 
+(setv globalvar 1)
+(defn test-exec []
+  (setv localvar 1)
+  (setv code "
+result['localvar in locals'] = 'localvar' in locals()
+result['localvar in globals'] = 'localvar' in globals()
+result['globalvar in locals'] = 'globalvar' in locals()
+result['globalvar in globals'] = 'globalvar' in globals()
+result['x in locals'] = 'x' in locals()
+result['x in globals'] = 'x' in globals()
+result['y in locals'] = 'y' in locals()
+result['y in globals'] = 'y' in globals()")
+
+  (setv result {})
+  (exec code)
+  (assert-true (get result "localvar in locals"))
+  (assert-false (get result "localvar in globals"))
+  (assert-false (get result "globalvar in locals"))
+  (assert-true (get result "globalvar in globals"))
+  (assert-false (or
+    (get result "x in locals") (get result "x in globals")
+    (get result "y in locals") (get result "y in globals")))
+
+  (setv result {})
+  (exec code {"x" 1 "result" result})
+  (assert-false (or
+    (get result "localvar in locals") (get result "localvar in globals")
+    (get result "globalvar in locals") (get result "globalvar in globals")))
+  (assert-true (and
+    (get result "x in locals") (get result "x in globals")))
+  (assert-false (or
+    (get result "y in locals") (get result "y in globals")))
+
+  (setv result {})
+  (exec code {"x" 1 "result" result} {"y" 1})
+  (assert-false (or
+    (get result "localvar in locals") (get result "localvar in globals")
+    (get result "globalvar in locals") (get result "globalvar in globals")))
+  (assert-false (get result "x in locals"))
+  (assert-true (get result "x in globals"))
+  (assert-true (get result "y in locals"))
+  (assert-false (get result "y in globals")))
+
 (defn test-filter []
   "NATIVE: testing the filter function"
   (setv res (list (filter pos? [ 1 2 3 -4 5])))