From 55986b20331a6401d00aae5175d56b83da7de01a Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Tue, 27 Jun 2017 15:09:31 -0600 Subject: [PATCH] Auto-promote values to HyObjects in the compiler --- NEWS | 3 +++ hy/compiler.py | 20 ++++++++++++--- hy/core/language.hy | 15 +++-------- hy/models.py | 3 +++ tests/compilers/test_ast.py | 4 ++- tests/native_tests/language.hy | 40 ++++++++++++++++++++++++++--- tests/native_tests/native_macros.hy | 8 ++++++ 7 files changed, 73 insertions(+), 20 deletions(-) diff --git a/NEWS b/NEWS index f8a209a..a21c514 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,9 @@ Changes from 0.13.0 longer names * Periods are no longer allowed in keywords * `eval` is now a function instead of a special form + * The compiler now automatically promotes values to Hy model objects + as necessary, so you can write ``(eval `(+ 1 ~n))`` instead of + ``(eval `(+ 1 ~(HyInteger n)))`` [ Bug Fixes ] * Numeric literals are no longer parsed as symbols when followed by a dot diff --git a/hy/compiler.py b/hy/compiler.py index 3325b35..adc2647 100755 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -5,7 +5,7 @@ from hy.models import (HyObject, HyExpression, HyKeyword, HyInteger, HyComplex, HyString, HyBytes, HySymbol, HyFloat, HyList, HySet, - HyDict, HyCons) + HyDict, HyCons, wrap_value) from hy.errors import HyCompileError, HyTypeError from hy.lex.parser import hy_symbol_mangle @@ -112,6 +112,8 @@ def builds_if(_type, condition): def spoof_positions(obj): + if not isinstance(obj, HyObject) or isinstance(obj, HyCons): + return if not hasattr(obj, "start_column"): obj.start_column = 0 if not hasattr(obj, "start_line"): @@ -416,6 +418,11 @@ class HyASTCompiler(object): if not isinstance(ret, Result): ret = Result() + ret return ret + if not isinstance(atom, HyObject): + atom = wrap_value(atom) + if isinstance(atom, HyObject): + spoof_positions(atom) + return self.compile_atom(type(atom), atom) def compile(self, tree): try: @@ -1840,6 +1847,7 @@ class HyASTCompiler(object): op = ops[expression.pop(0)] right_associative = op == ast.Pow + lineno, col_offset = expression.start_line, expression.start_column if right_associative: expression = expression[::-1] ret = self.compile(expression.pop(0)) @@ -1852,8 +1860,8 @@ class HyASTCompiler(object): ret += ast.BinOp(left=left_expr, op=op(), right=right_expr, - lineno=child.start_line, - col_offset=child.start_column) + lineno=lineno, + col_offset=col_offset) return ret @builds("**") @@ -2602,7 +2610,11 @@ def hy_compile(tree, module_name, root=ast.Module, get_expr=False): expr = None if not isinstance(tree, HyObject): - raise HyCompileError("tree must be a HyObject") + tree = wrap_value(tree) + if not isinstance(tree, HyObject): + raise HyCompileError("`tree` must be a HyObject or capable of " + "being promoted to one") + spoof_positions(tree) compiler = HyASTCompiler(module_name) result = compiler.compile(tree) diff --git a/hy/core/language.hy b/hy/core/language.hy index 25fadd7..6bcdb79 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -18,7 +18,7 @@ (import [hy._compat [long-type]]) ; long for python2, int for python3 (import [hy.models [HyCons HySymbol HyKeyword]]) (import [hy.lex [LexException PrematureEndOfInput tokenize]]) -(import [hy.compiler [HyASTCompiler]]) +(import [hy.compiler [HyASTCompiler spoof-positions]]) (import [hy.importer [hy-eval :as eval]]) (defn butlast [coll] @@ -75,8 +75,8 @@ (import astor) (import hy.compiler) - (fake-source-positions tree) - (setv compiled (hy.compiler.hy_compile tree (calling-module-name))) + (spoof-positions tree) + (setv compiled (hy.compiler.hy-compile tree (calling-module-name))) ((if codegen astor.codegen.to_source astor.dump) @@ -175,15 +175,6 @@ "Return true if (pred x) is logical true for every x in coll, else false" (all (map pred coll))) -(defn fake-source-positions [tree] - "Fake the source positions for a given tree" - (if (coll? tree) - (for* [subtree tree] - (fake-source-positions subtree))) - (for* [attr '[start-line end-line start-column end-column]] - (if (not (hasattr tree attr)) - (setattr tree attr 1)))) - (defn flatten [coll] "Return a single flat list expanding all members of coll" (if (coll? coll) diff --git a/hy/models.py b/hy/models.py index 412f5d1..93cce18 100644 --- a/hy/models.py +++ b/hy/models.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals from hy._compat import PY3, str_type, bytes_type, long_type, string_types +from fractions import Fraction class HyObject(object): @@ -229,6 +230,8 @@ class HyExpression(HyList): return "(%s)" % (" ".join([repr(x) for x in self])) _wrappers[HyExpression] = lambda e: HyExpression(wrap_value(x) for x in e) +_wrappers[Fraction] = lambda e: HyExpression( + [HySymbol("fraction"), wrap_value(e.numerator), wrap_value(e.denominator)]) class HySet(HyList): diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index 0fe7485..1bacdf0 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -48,8 +48,10 @@ def cant_compile(expr): def test_ast_bad_type(): "Make sure AST breakage can happen" + class C: + pass try: - hy_compile("foo", "__main__") + hy_compile(C(), "__main__") assert True is False except HyCompileError: pass diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 4d7c262..b4f222d 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -5,6 +5,7 @@ (import [tests.resources [kwtest function-with-a-dash]] [os.path [exists isdir isfile]] [sys :as systest] + re [operator [or_]] [hy.errors [HyTypeError]] pytest) @@ -1087,13 +1088,44 @@ (defn test-eval-failure [] "NATIVE: test eval failure modes" ; yo dawg - (import [hy.compiler [HyCompileError]]) (try (eval '(eval)) (except [e TypeError]) (else (assert False))) - (try (eval '(eval "snafu")) (except [e HyCompileError]) (else (assert False))) + (defclass C) + (try (eval (C)) (except [e TypeError]) (else (assert False))) (try (eval 'False []) (except [e HyTypeError]) (else (assert False))) (try (eval 'False {} 1) (except [e TypeError]) (else (assert False)))) +(defn test-eval-quasiquote [] + ; https://github.com/hylang/hy/issues/1174 + + (for [x [ + None False True + 5 5.1 + 1/2 + 5j 5.1j 2+1j 1.2+3.4j + "" b"" + "apple bloom" b"apple bloom" "⚘" b"\x00" + :mykeyword + [] #{} {} + [1 2 3] #{1 2 3} {"a" 1 "b" 2}]] + (assert (= (eval `(identity ~x)) x)) + (assert (= (eval x) x))) + + ; Tuples wrap to HyLists, not HyExpressions. + (assert (= (eval (,)) [])) + (assert (= (eval (, 1 2 3)) [1 2 3])) + + (assert (= (eval `(+ "a" ~(+ "b" "c"))) "abc")) + + (setv l ["a" "b"]) + (setv n 1) + (assert (= (eval `(get ~l ~n) "b"))) + + (setv d {"a" 1 "b" 2}) + (setv k "b") + (assert (= (eval `(get ~d ~k)) 2))) + + (defn test-import-syntax [] "NATIVE: test the import syntax." @@ -1367,7 +1399,9 @@ (assert (= (disassemble '(do (leaky) (leaky) (macros))) "Module(\n body=[\n Expr(value=Call(func=Name(id='leaky'), args=[], keywords=[], starargs=None, kwargs=None)),\n Expr(value=Call(func=Name(id='leaky'), args=[], keywords=[], starargs=None, kwargs=None)),\n Expr(value=Call(func=Name(id='macros'), args=[], keywords=[], starargs=None, kwargs=None))])"))) (assert (= (disassemble '(do (leaky) (leaky) (macros)) True) - "leaky()\nleaky()\nmacros()"))) + "leaky()\nleaky()\nmacros()")) + (assert (= (re.sub r"[L() ]" "" (disassemble `(+ ~(+ 1 1) 40) True)) + "2+40"))) (defn test-attribute-access [] diff --git a/tests/native_tests/native_macros.hy b/tests/native_tests/native_macros.hy index 6089f32..6baa7a4 100644 --- a/tests/native_tests/native_macros.hy +++ b/tests/native_tests/native_macros.hy @@ -84,6 +84,14 @@ "NATIVE: test macro calling a plain function" (assert (= 3 (bar 1 2)))) +(defn test-optional-and-apply-in-macro [] + ; https://github.com/hylang/hy/issues/1154 + (defn f [&rest args] + (+ "f:" (repr args))) + (defmacro mac [&optional x] + `(apply f [~x])) + (assert (= (mac) "f:(None,)"))) + (defn test-midtree-yield [] "NATIVE: test yielding with a returnable" (defn kruft [] (yield) (+ 1 1)))