Repository: hylang/hy

Issue:
Official support for evaluating strings of Hy code from Python
Is it possible to embed some hy code inside a python file? As opposed to having the whole file be full on hy?


Discussion:
I first read this issue as being about embedding Python code in Hy (which is something that's been mentioned and I'd like to see, but I don't know if we have an issue for it), but now I see you're talking about the reverse. Well, we can't alter Python's syntax, so true embedded Hy in Python is out of the question. But you can put your Hy code in a string literal, and then use some of Hy's internal functions to read and evaluate it:

    >>> from hy.lex import tokenize; from hy.importer import hy_eval; hy_eval(tokenize("(+ 1 1)")[0], globals(), "__cmdline__")
    2

One might reasonably expect documented, public functions for this.
I'd like to implement this. Does the following seem like the best way to expose it in Python?
```python
import hy
hy.eval_str("(+ 1 1)")
```
I'd recommend providing separate functions for reading (i.e., turning a string into a HyModel) and evaluating a HyModel.
Alright, would you rather the reading function be called `read` or `parse`?
I think you should be able to export `read_str` or `read` (or both) from `hy.core.language`, in which case you should use the same name.
Oh gotcha, your original example used `hy.lex.tokenize` so I thought I had to wrap that! So this is just a matter of exporting `read`, `read_str`, and `hy_eval`?
Yes, if it doesn't lead to some kind of circular-dependency issue. Try it out.

Resolving patch:
diff --git a/hy/__init__.py b/hy/__init__.py
--- a/hy/__init__.py
+++ b/hy/__init__.py
@@ -10,3 +10,7 @@
 
 import hy.importer  # NOQA
 # we import for side-effects.
+
+
+from hy.core.language import read, read_str  # NOQA
+from hy.importer import hy_eval as eval  # NOQA


Test patch:
diff --git a/tests/importer/test_importer.py b/tests/importer/test_importer.py
--- a/tests/importer/test_importer.py
+++ b/tests/importer/test_importer.py
@@ -2,12 +2,14 @@
 # This file is part of Hy, which is free software licensed under the Expat
 # license. See the LICENSE.
 
+import hy
 from hy.importer import (import_file_to_module, import_buffer_to_ast,
                          MetaLoader, get_bytecode_path)
 from hy.errors import HyTypeError
 import os
 import ast
 import tempfile
+from fractions import Fraction
 
 
 def test_basics():
@@ -64,3 +66,20 @@ def test_import_autocompiles():
 
     os.remove(f.name)
     os.remove(get_bytecode_path(f.name))
+
+
+def test_eval():
+    def eval_str(s):
+        return hy.eval(hy.read_str(s))
+
+    assert eval_str('[1 2 3]') == [1, 2, 3]
+    assert eval_str('{"dog" "bark" "cat" "meow"}') == {
+        'dog': 'bark', 'cat': 'meow'}
+    assert eval_str('(, 1 2 3)') == (1, 2, 3)
+    assert eval_str('#{3 1 2}') == {1, 2, 3}
+    assert eval_str('1/2') == Fraction(1, 2)
+    assert eval_str('(.strip " fooooo   ")') == 'fooooo'
+    assert eval_str(
+        '(if True "this is if true" "this is if false")') == "this is if true"
+    assert eval_str('(list-comp (pow num 2) (num (range 100)) (= (% num 2) 1))') == [
+        pow(num, 2) for num in range(100) if num % 2 == 1]