diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6b8710a --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +.git diff --git a/.mailmap b/.mailmap index 678f585..e5d76d6 100644 --- a/.mailmap +++ b/.mailmap @@ -2,11 +2,10 @@ Paul R. Tagliamonte Paul Tagliamonte Paul R. Tagliamonte Paul Tagliamonte Paul R. Tagliamonte Paul Tagliamonte Paul R. Tagliamonte Paul Tagliamonte -Morten Linderud Foxboron -Morten Linderud +Morten Linderud Foxboron James King agentultra James King J Kenneth King Abhishek L Bob Tolbert Bob Tolbert Guillermo Vaya Guillermo Vaya -Gergely Nagy Gergely Nagy \ No newline at end of file +Gergely Nagy Gergely Nagy diff --git a/.travis.yml b/.travis.yml index 0009569..9e51591 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,15 +8,9 @@ python: - "3.4" cache: - $HOME/.pip-cache -# command to install dependencies -install: - - pip install -r requirements-travis.txt --download-cache $HOME/.pip-cache - - pip install coveralls --download-cache $HOME/.pip-cache - - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi # needs for running tests - - pip install --allow-all-external -e . # command to run tests script: make travis -after_success: coveralls +after_success: make coveralls notifications: email: - paultag@gmail.com diff --git a/AUTHORS b/AUTHORS index 42f89c1..c596a53 100644 --- a/AUTHORS +++ b/AUTHORS @@ -42,9 +42,12 @@ * Halit Alptekin * Richard Parsons * han semaj -* kirbyfan64 +* Ryan Gonzalez * Brendan Curran-Johnson * Ivan Kozik * Allison Kaptur * Matthew Wampler-Doty * Tianon Gravi +* Ian Denhardt +* Ruslan Prokopiev +* Alexander Artemenko diff --git a/Makefile b/Makefile index e1f3ea9..374a48f 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,21 @@ +pypy_url=http://buildbot.pypy.org/nightly/trunk/pypy-c-jit-latest-linux64.tar.bz2 +pip_url=https://bootstrap.pypa.io/get-pip.py +python=python +pip=pip +coveralls=coveralls +nose=nosetests +pcache=$(HOME)/.pip-cache + +ifeq (PyPy 2.4,$(findstring PyPy 2.4,$(shell python -V 2>&1 | tail -1))) + bad_pypy=1 + python=./pypy + pip=./pip + coveralls=./coveralls + nose=./nosetests +else + bad_pypy= +endif + all: @echo "No default step. Use setup.py" @echo "" @@ -53,14 +71,42 @@ diff: r: d tox diff -travis: - nosetests -s --with-coverage --cover-package hy +python: +ifeq ($(bad_pypy),1) + # Due to stupid PyPy 2.4 bugs, a custom version needs to be downloaded + curl $(pypy_url) -o pypy.tbz2 + tar xf pypy.tbz2 + ln -sf `pwd`/pypy-*/bin/pypy $(python) + curl $(pip_url) | $(python) + ln -sf `pwd`/pypy-*/bin/pip $(pip) + sudo $(pip) install nose + ln -sf `pwd`/pypy-*/bin/nosetests $(nose) +endif +ifeq (Python 2.6,$(findstring Python 2.6,$(shell python -V 2>&1))) + $(pip) install unittest2 +endif + $(pip) install -r requirements-travis.txt --download-cache $(pcache) + $(pip) install coveralls --download-cache $(pcache) + $(pip) install --allow-all-external -e . +ifeq ($(bad_pypy),1) + ln -sf `pwd`/pypy-*/bin/coveralls $(coveralls) +endif + +travis: python +ifeq ($(bad_pypy),1) + HY_DIR=`pwd`/pypy-*/bin $(nose) -s --with-coverage --cover-package hy +else + $(nose) -s --with-coverage --cover-package hy +endif ifeq (PyPy,$(findstring PyPy,$(shell python -V 2>&1 | tail -1))) @echo "skipping flake8 on pypy" else flake8 hy bin tests endif +coveralls: + $(coveralls) + clean: @find . -name "*.pyc" -exec rm {} \; @find -name __pycache__ -delete diff --git a/NEWS b/NEWS index ed5c534..08e6b98 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,49 @@ +Changes from 0.10.0 + + This release took some time (sorry, all my fault) but it's got a bunch of + really nice features. We hope you enjoy hacking with Hy as much as we enjoy + hacking on Hy. + + In other news, we're Dockerized as an official library image! + + + $ docker run -it --rm hylang + hy 0.10.0 using CPython(default) 3.4.1 on Linux + => ((lambda [] (print "Hello, World!"))) + Hello, World! + + - Hy Society + + [ Language Changes ] + * Implement raise :from, Python 3 only. + * defmain macro + * name & keyword functions added to core + * (read) added to core + * shadow added to core + * New functions interleave interpose zip_longest added to core + * nth returns default value when out of bounds + * merge-with added + * doto macro added + * keyword? to findout keywords + * setv no longer allows "." in names + + [Internals ] + * Builtins reimplemented in terms of python stdlib + * gensyms (defmacro/g!) handles non-string types better + + [Tools] + * Added hy2py to installed scripts + + [ Misc. Fixes ] + * Symbols like true, false, none can't be assigned + * Set sys.argv default to [''] like Python does + * REPL displays the the python version and platform at startup + * Dockerfile added for https://registry.hub.docker.com/_/hylang/ + + [ Contrib changes ] + * Fix ap-first and ap-last for failure conditions + + Changes from 0.9.12 0.10.0 - the "oh man I'm late for PyCon" release diff --git a/docs/contrib/multi.rst b/docs/contrib/multi.rst index dc4bf64..91d8cfa 100644 --- a/docs/contrib/multi.rst +++ b/docs/contrib/multi.rst @@ -11,13 +11,13 @@ args and/or kwargs. Inspired by clojures take on `defn`. => (require hy.contrib.multi) => (defmulti fun - ... ([a] a) + ... ([a] "a") ... ([a b] "a b") ... ([a b c] "a b c")) - => (fun 1 2 3) - 'a b c' - => (fun a b) - "a b" => (fun 1) - 1 + "a" + => (fun 1 2) + "a b" + => (fun 1 2 3) + "a b c" diff --git a/docs/coreteam.rst b/docs/coreteam.rst index 982df9d..7800b25 100644 --- a/docs/coreteam.rst +++ b/docs/coreteam.rst @@ -13,3 +13,4 @@ * `Bob Tolbert `_ * `Berker Peksag `_ * `Clinton N. Dreisbach `_ +* `han semaj `_ diff --git a/docs/language/api.rst b/docs/language/api.rst index 9097910..72d5b6b 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -818,14 +818,18 @@ any numeric type, empty sequence and empty dictionary are considered `False`. Everything else is considered `True`. -lisp-if / lif -------------- +lisp-if / lif and lisp-if-not / lif-not +--------------------------------------- .. versionadded:: 0.10.0 +.. versionadded:: 0.10.2 + lisp-if-not / lif-not + For those that prefer a more lisp-y if clause, we have lisp-if, or lif. This *only* considers None/nil as false! All other values of python -"falseiness" are considered true. +"falseiness" are considered true. Conversely, we have lisp-if-not or lif-not, +in parallel to if / if-not, which reverses the comparison. .. code-block:: clj @@ -840,12 +844,20 @@ For those that prefer a more lisp-y if clause, we have lisp-if, or lif. This "false" => (lisp-if None "true" "false") "false" + => (lisp-if-not nil "true" "false") + "true" + => (lisp-if-not None "true" "false") + "true" + => (lisp-if-not False "true" "false") + "false" ; And, same thing => (lif True "true" "false") "true" => (lif nil "true" "false") "false" + => (lif-not None "true" "false") + "true" import diff --git a/docs/language/core.rst b/docs/language/core.rst index 14da12c..fa9dc80 100644 --- a/docs/language/core.rst +++ b/docs/language/core.rst @@ -474,6 +474,27 @@ Returns the single step macro expansion of form. => (macroexpand-1 '(-> (a b) (-> (c d) (e f)))) (u'_>' (u'a' u'b') (u'c' u'd') (u'e' u'f')) + +.. _merge-with-fn: + +merge-with +---------- + +.. versionadded:: 0.10.1 + +Usage: ``(merge-with f &rest maps) + +Returns a map that consist of the rest of the maps joined onto first. +If a key occurs in more than one map, the mapping(s) from the latter +(left-to-right) will be combined with the mapping in the result by +calling ``(f val-in-result val-in-latter)``. + +.. code-block:: clojure + + => (merge-with (fn [x y] (+ x y)) {"a" 10 "b" 20} {"a" 1 "c" 30}) + {u'a': 11L, u'c': 30L, u'b': 20L} + + .. _neg?-fn: neg? @@ -557,11 +578,11 @@ Return True if x is None. nth --- -Usage: ``(nth coll n)`` +Usage: ``(nth coll n &optional [default nil])`` -Return the `nth` item in a collection, counting from 0. Unlike -``get``, ``nth`` works on both iterators and iterables. Raises ``IndexError`` -if the `n` is outside the range of ``coll`` or ``ValueError`` if it's negative. +Return the `nth` item in a collection, counting from 0. Return the +default value, ``nil``, if out of bounds (unless specified otherwise). +Raise ``ValueError`` if ``n`` is negative. .. code-block:: hy @@ -571,14 +592,21 @@ if the `n` is outside the range of ``coll`` or ``ValueError`` if it's negative. => (nth [1 2 4 7] 3) 7 - => (nth [1 2 4 7] 5) - Traceback (most recent call last): - ... - IndexError: 5 + => (nil? (nth [1 2 4 7] 5)) + True + + => (nth [1 2 4 7] 5 "default") + 'default' => (nth (take 3 (drop 2 [1 2 3 4 5 6])) 2)) 5 + => (nth [1 2 4 7] -1) + Traceback (most recent call last): + ... + ValueError: Indices for islice() must be None or an integer: 0 <= x <= sys.maxsize. + + .. _numeric?-fn: numeric? @@ -672,21 +700,25 @@ some Usage: ``(some pred coll)`` -Return True if ``(pred x)`` is logical true for any ``x`` in ``coll``, otherwise False. Return False if ``coll`` is empty. +Return the first logical true value of ``(pred x)`` for any ``x`` in +``coll``, otherwise ``nil``. Return ``nil`` if ``coll`` is empty. .. code-block:: hy => (some even? [2 4 6]) True - => (some even? [1 3 5]) - False - - => (some even? [1 3 6]) + => (nil? (some even? [1 3 5])) True - => (some even? []) - False + => (nil? (some identity [0 "" []])) + True + + => (some identity [0 "non-empty-string" []]) + 'non-empty-string' + + => (nil? (some even? [])) + True .. _string?-fn: @@ -958,7 +990,7 @@ Return an iterator of `x`, `fn(x)`, `fn(fn(x))`. read ---- -Usage: ``(read [from-file eof])`` +Usage: ``(read &optional [from-file eof])`` Reads the next hy expression from `from-file` (defaults to `sys.stdin`), and can take a single byte as EOF (defaults to an empty string). diff --git a/hy/_compat.py b/hy/_compat.py index 096282a..af710f2 100644 --- a/hy/_compat.py +++ b/hy/_compat.py @@ -52,3 +52,9 @@ if PY3: long_type = int else: long_type = long # NOQA + +if PY3: + exec('def raise_empty(t, *args): raise t(*args) from None') +else: + def raise_empty(t, *args): + raise t(*args) diff --git a/hy/compiler.py b/hy/compiler.py index 7d275c3..7c7925e 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -38,7 +38,7 @@ from hy.models.cons import HyCons from hy.errors import HyCompileError, HyTypeError import hy.macros -from hy._compat import str_type, long_type, PY27, PY33, PY3, PY34 +from hy._compat import str_type, long_type, PY27, PY33, PY3, PY34, raise_empty from hy.macros import require, macroexpand, reader_macroexpand import hy.importer @@ -429,7 +429,7 @@ class HyASTCompiler(object): except HyTypeError as e: raise except Exception as e: - raise HyCompileError(e, sys.exc_info()[2]) + raise_empty(HyCompileError, e, sys.exc_info()[2]) raise HyCompileError(Exception("Unknown type: `%s'" % _type)) @@ -1268,7 +1268,8 @@ class HyASTCompiler(object): def compile_decorate_expression(self, expr): expr.pop(0) # with-decorator fn = self.compile(expr.pop(-1)) - if not fn.stmts or not isinstance(fn.stmts[-1], ast.FunctionDef): + if not fn.stmts or not (isinstance(fn.stmts[-1], ast.FunctionDef) or + isinstance(fn.stmts[-1], ast.ClassDef)): raise HyTypeError(expr, "Decorated a non-function") decorators, ret = self._compile_collect(expr) fn.stmts[-1].decorator_list = decorators @@ -2003,12 +2004,12 @@ class HyASTCompiler(object): except TypeError: raise HyTypeError( expression, - "Wrong argument type for defclass slots definition.") + "Wrong argument type for defclass attributes definition.") for b in body_expression: if len(b) != 2: raise HyTypeError( expression, - "Wrong number of argument in defclass slot.") + "Wrong number of argument in defclass attribute.") body += self._compile_assign(b[0], b[1], b.start_line, b.start_column) body += body.expr_as_stmt() diff --git a/hy/core/language.hy b/hy/core/language.hy index 99441a4..4df6ef4 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -26,12 +26,12 @@ (import itertools) (import functools) (import collections) -(import sys) +(import sys) (import [hy._compat [long-type]]) ; long for python2, int for python3 -(import [hy.models.cons [HyCons]]) +(import [hy.models.cons [HyCons]] + [hy.models.keyword [HyKeyword *keyword-prefix*]]) (import [hy.lex [LexException PrematureEndOfInput tokenize]]) - (defn _numeric-check [x] (if (not (numeric? x)) (raise (TypeError (.format "{0!r} is not a number" x))))) @@ -258,6 +258,22 @@ (setv name (calling-module-name)) (hy.macros.macroexpand-1 form name)) +(defn merge-with [f &rest maps] + "Returns a map that consists of the rest of the maps joined onto + the first. If a key occurs in more than one map, the mapping(s) + from the latter (left-to-right) will be combined with the mapping in + the result by calling (f val-in-result val-in-latter)." + (if (any maps) + (let [[merge-entry (fn [m e] + (let [[k (get e 0)] [v (get e 1)]] + (if (in k m) + (assoc m k (f (get m k) v)) + (assoc m k v))) + m)] + [merge2 (fn [m1 m2] + (reduce merge-entry (.items m2) (or m1 {})))]] + (reduce merge2 maps)))) + (defn neg? [n] "Return true if n is < 0" (_numeric-check n) @@ -275,11 +291,10 @@ (import numbers) (instance? numbers.Number x)) -(defn nth [coll index] - "Return nth item in collection or sequence, counting from 0" - (try - (next (drop index coll)) - (catch [e StopIteration] (raise (IndexError index))))) +(defn nth [coll n &optional [default nil]] + "Return nth item in collection or sequence, counting from 0. + Return nil if out of bounds unless specified otherwise." + (next (drop n coll) default)) (defn odd? [n] "Return true if n is an odd number" @@ -305,8 +320,8 @@ (nth coll 1)) (defn some [pred coll] - "Return true if (pred x) is logical true for any x in coll, else false" - (any (map pred coll))) + "Return the first logical true value of (pred x) for any x in coll, else nil" + (first (filter nil (map pred coll)))) (defn string [x] "Cast x as current string implementation" @@ -353,17 +368,51 @@ (setv buff (+ buff inn)) (try (def parsed (first (tokenize buff))) - (except [e [LexException PrematureEndOfInput IndexError]]) + (except [e [LexException PrematureEndOfInput IndexError]]) (else (if parsed (break))))) parsed) +(defun Botsbuildbots () (Botsbuildbots)) -(def *exports* '[butlast calling-module-name coll? cons cons? cycle +(defn zipwith [func &rest lists] + "Zip the contents of several lists and map a function to the result" + (do + (import functools) + (map (functools.partial (fn [f args] (apply f args)) func) (apply zip lists)))) + +(defn hyify [text] + "Convert text to match hy identifier" + (.replace (string text) "_" "-")) + +(defn keyword [value] + "Create a keyword from the given value. Strings numbers and even objects + with the __name__ magic will work" + (if (and (string? value) (value.startswith *keyword-prefix*)) + (hyify value) + (if (string? value) + (HyKeyword (+ ":" (hyify value))) + (try + (hyify (.__name__ value)) + (catch [] (HyKeyword (+ ":" (string value)))))))) + +(defn name [value] + "Convert the given value to a string. Keyword special character will be stripped. + String will be used as is. Even objects with the __name__ magic will work" + (if (and (string? value) (value.startswith *keyword-prefix*)) + (hyify (slice value 2)) + (if (string? value) + (hyify value) + (try + (hyify (. value __name__)) + (catch [] (string value)))))) + +(def *exports* '[Botsbuildbots + butlast calling-module-name coll? cons cons? cycle dec distinct disassemble drop drop-last drop-while empty? even? every? first filter filterfalse flatten float? gensym identity inc input instance? integer integer? integer-char? interleave - interpose iterable? iterate iterator? keyword? list* - macroexpand macroexpand-1 map neg? nil? none? nth - numeric? odd? pos? range read remove repeat repeatedly + interpose iterable? iterate iterator? keyword keyword? list* + macroexpand macroexpand-1 map merge-with name neg? nil? none? + nth numeric? odd? pos? range read remove repeat repeatedly rest reduce second some string string? take take-nth take-while zero? zip zip_longest zipwith]) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index c6071ba..38a45ee 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -153,6 +153,10 @@ "Like `if`, but anything that is not None/nil is considered true." `(if (is-not ~test nil) ~@branches)) +(defmacro-alias [lisp-if-not lif-not] [test &rest branches] + "Like `if-not`, but anything that is not None/nil is considered true." + `(if (is ~test nil) ~@branches)) + (defmacro when [test &rest body] "Execute `body` when `test` is true" @@ -198,3 +202,15 @@ (.append ret `(setv ~name ~main))) ret)) + +(defmacro Botsbuildbots [] + "Build bots, repeatedly.^W^W^WPrint the AUTHORS, forever." + `(try + (do + (import [requests]) + + (let [[r (requests.get + "https://raw.githubusercontent.com/hylang/hy/master/AUTHORS")]] + (repeat r.text))) + (catch [e ImportError] + (repeat "Botsbuildbots requires `requests' to function.")))) diff --git a/hy/core/shadow.hy b/hy/core/shadow.hy index f59f0ca..e68c4ec 100644 --- a/hy/core/shadow.hy +++ b/hy/core/shadow.hy @@ -26,9 +26,12 @@ (defn + [&rest args] "Shadow + operator for when we need to import / map it against something" - (if (= (len args) 0) - 0 - (sum args))) ; shortcut here. + (let [[count (len args)]] + (if (zero? count) + (raise (TypeError "Need at least 1 argument to add/concatenate")) + (if (= count 1) + (get args 0) + (reduce operator.add args))))) (defn - [&rest args] diff --git a/hy/macros.py b/hy/macros.py index 937e7b8..57fcea6 100644 --- a/hy/macros.py +++ b/hy/macros.py @@ -117,6 +117,7 @@ _wrappers = { list: lambda l: HyList(_wrap_value(x) for x in l), tuple: lambda t: HyList(_wrap_value(x) for x in t), type(None): lambda foo: HySymbol("None"), + HyExpression: lambda e: HyExpression(_wrap_value(x) for x in e), } if sys.version_info[0] < 3: # do not add long on python3 diff --git a/hy/models/keyword.py b/hy/models/keyword.py index 6d25633..32b2306 100644 --- a/hy/models/keyword.py +++ b/hy/models/keyword.py @@ -23,14 +23,17 @@ from hy.models import HyObject from hy._compat import str_type +KEYWORD_PREFIX = "\uFDD0" + + class HyKeyword(HyObject, str_type): """Generic Hy Keyword object. It's either a ``str`` or a ``unicode``, depending on the Python version. """ def __new__(cls, value): - if not value.startswith("\uFDD0"): - value = "\uFDD0" + value + if not value.startswith(KEYWORD_PREFIX): + value = KEYWORD_PREFIX + value obj = str_type.__new__(cls, value) return obj diff --git a/hy/version.py b/hy/version.py index 23a31bc..d1f5edb 100644 --- a/hy/version.py +++ b/hy/version.py @@ -20,4 +20,4 @@ __appname__ = "hy" -__version__ = "0.10.0" +__version__ = "0.10.1" diff --git a/requirements-travis.txt b/requirements-travis.txt index 80be0ce..9216e4f 100644 --- a/requirements-travis.txt +++ b/requirements-travis.txt @@ -1,3 +1,5 @@ +# for Botsbuildbots +requests # code quality flake8 coverage diff --git a/scripts/reformat-changelog b/scripts/reformat-changelog new file mode 100755 index 0000000..2c74480 --- /dev/null +++ b/scripts/reformat-changelog @@ -0,0 +1,66 @@ +#!/usr/bin/env hy + +(import re) +(import pdb) +(import codecs) + +(setv *maintainer-line* + " -- Alexander Artemenko Thu, 30 Sep 2014 13:06:09 +0400") + +(defun read-lines-from-file [filename] + (let [[f (codecs.open filename "r" "utf-8")]] + (fn [] (let [[line (.readline f) ]] + line)))) + + +(defun get-version-number [line] + (let [[match (re.search r"Changes from.*(\d+\.\d+\.\d+)$" line)]] + (if match + (let [[version (.group match (int 1))] + [numbered (list (map int (.split version "."))) ] + [explicit-mapping {"0.9.12" "0.10.0" + "0.8.2" "0.9.0"}]] + (assoc numbered 2 (+ (get numbered 2) 1)) + (.get explicit-mapping + version + (.join "." (map str numbered))))))) + + +(defun read-version-content [reader] + (setv line (reader)) + (setv content []) + (while (and line (not (get-version-number line))) + (.append content (.strip line)) + (setv line (reader))) + [content line]) + + +(defun read-versions-from-file [filename] + (let [[reader (read-lines-from-file filename)]] + (read-versions-rec (reader) + reader))) + +(defun read-versions-rec [line reader] + (if line + (let [[version (get-version-number line)] + [[content next-line] (read-version-content reader)]] + + (+ [{"from" version + "content" content}] + (read-versions-rec next-line reader))) + [])) + +(defun format-deb-version [version] + (setv result [(.format "hy ({}) unstable; urgency=low" + (get version "from"))]) + (for [line (get version "content")] + (.append result (+ " " line))) + (.append result *maintainer-line*) + (.append result "") + (.join "\n" result)) + + +(defmain [&rest args] + (let ((versions (read-versions-from-file "NEWS"))) + (for [version versions] + (print (.encode (format-deb-version version) "utf-8"))))) diff --git a/tests/__init__.py b/tests/__init__.py index 56ab9fc..eb292ac 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -12,6 +12,7 @@ from .native_tests.when import * # noqa from .native_tests.with_decorator import * # noqa from .native_tests.core import * # noqa from .native_tests.reader_macros import * # noqa +from .native_tests.shadow import * # noqa from .native_tests.with_test import * # noqa from .native_tests.contrib.anaphoric import * # noqa from .native_tests.contrib.loop import * # noqa diff --git a/tests/macros/test_wrap_value.py b/tests/macros/test_wrap_value.py new file mode 100644 index 0000000..0a6e7d5 --- /dev/null +++ b/tests/macros/test_wrap_value.py @@ -0,0 +1,28 @@ +from hy._compat import long_type +from hy.models.integer import HyInteger +from hy.models.list import HyList +from hy.models.expression import HyExpression + +from hy.macros import _wrap_value + + +def test_wrap_long_type(): + """ Test conversion of integers.""" + wrapped = _wrap_value(long_type(0)) + assert type(wrapped) == HyInteger + + +def test_wrap_tuple(): + """ Test conversion of tuples.""" + wrapped = _wrap_value((HyInteger(0),)) + assert type(wrapped) == HyList + assert type(wrapped[0]) == HyInteger + assert wrapped == HyList([HyInteger(0)]) + + +def test_wrap_nested_expr(): + """ Test conversion of HyExpressions with embedded non-HyObjects.""" + wrapped = _wrap_value(HyExpression([long_type(0)])) + assert type(wrapped) == HyExpression + assert type(wrapped[0]) == HyInteger + assert wrapped == HyExpression([HyInteger(0)]) diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index f96d309..dcdb59b 100644 --- a/tests/native_tests/core.hy +++ b/tests/native_tests/core.hy @@ -30,6 +30,9 @@ (defn assert-equal [x y] (assert (= x y))) +(defn assert-nil [x] + (assert (is x nil))) + (defn test-coll? [] "NATIVE: testing coll?" (assert-true (coll? [1 2 3])) @@ -416,13 +419,17 @@ "NATIVE: testing the nth function" (assert-equal 2 (nth [1 2 4 7] 1)) (assert-equal 7 (nth [1 2 4 7] 3)) - (try (do (nth [1 2 4 7] 5) (assert False)) - (catch [e [IndexError]] nil)) + (assert-nil (nth [1 2 4 7] 5)) + (assert-equal (nth [1 2 4 7] 5 "some default value") + "some default value") ; with default specified (try (do (nth [1 2 4 7] -1) (assert False)) (catch [e [ValueError]] nil)) ;; now for iterators (assert-equal 2 (nth (iter [1 2 4 7]) 1)) (assert-equal 7 (nth (iter [1 2 4 7]) 3)) + (assert-nil (nth (iter [1 2 4 7]) 5)) + (assert-equal (nth (iter [1 2 4 7]) 5 "some default value") + "some default value") ; with default specified (try (do (nth (iter [1 2 4 7]) -1) (assert False)) (catch [e [ValueError]] nil)) (assert-equal 5 (nth (take 3 (drop 2 [1 2 3 4 5 6])) 2))) @@ -493,9 +500,16 @@ (defn test-some [] "NATIVE: testing the some function" (assert-true (some even? [2 4 6])) - (assert-false (some even? [1 3 5])) - (assert-true (some even? [1 3 6])) - (assert-false (some even? []))) + (assert-nil (some even? [1 3 5])) + (assert-true (some even? [1 2 3])) + (assert-nil (some even? [])) + ; 0, "" (empty string) and [] (empty list) are all logical false + (assert-nil (some identity [0 "" []])) + ; non-empty string is logical true + (assert-equal (some identity [0 "this string is non-empty" []]) + "this string is non-empty") + ; nil if collection is empty + (assert-nil (some even? []))) (defn test-string? [] "NATIVE: testing string?" diff --git a/tests/native_tests/defclass.hy b/tests/native_tests/defclass.hy index c1ebe5b..f303c4c 100644 --- a/tests/native_tests/defclass.hy +++ b/tests/native_tests/defclass.hy @@ -20,16 +20,16 @@ (assert (not (isinstance (A) D)))) -(defn test-defclass-slots [] - "NATIVE: test defclass slots" +(defn test-defclass-attrs [] + "NATIVE: test defclass attributes" (defclass A [] [[x 42]]) (assert (= A.x 42)) (assert (= (getattr (A) "x") 42))) -(defn test-defclass-slots-fn [] - "NATIVE: test defclass slots with fn" +(defn test-defclass-attrs-fn [] + "NATIVE: test defclass attributes with fn" (defclass B [] [[x 42] [y (fn [self value] @@ -52,7 +52,7 @@ (defn test-defclass-no-fn-leak [] - "NATIVE: test defclass slots with fn" + "NATIVE: test defclass attributes with fn" (defclass A [] [[x (fn [] 1)]]) (try diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index cc104fa..4c73455 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -1,6 +1,7 @@ (import [tests.resources [kwtest function-with-a-dash]] [os.path [exists isdir isfile]] - [sys :as systest]) + [sys :as systest] + [operator [or_]]) (import sys) (import [hy._compat [PY33 PY34]]) @@ -495,9 +496,11 @@ (setv output (list (gen))) (assert (= [1] output))) + (defn test-first [] "NATIVE: test firsty things" (assert (= (first [1 2 3 4 5]) 1)) + (assert (is (first []) nil)) (assert (= (car [1 2 3 4 5]) 1))) @@ -983,6 +986,24 @@ (assert (= (macroexpand-1 '(-> (a b) (-> (c d) (e f)))) '(-> (a b) (c d) (e f))))) +(defn test-merge-with [] + "NATIVE: test merge-with" + (assert (= (merge-with + {} {}) nil)) + (assert (= (merge-with + {"a" 10 "b" 20} {}) {"a" 10 "b" 20})) + (assert (= (merge-with + {} {"a" 10 "b" 20}) {"a" 10 "b" 20})) + (assert (= (merge-with + {"a" 10 "b" 20} {"a" 1 "c" 30}) + {"a" 11 "b" 20 "c" 30})) + (assert (= (merge-with + + {:a 1 :b 2} + {:a 9 :b 98 :c 0} + {:a 10 :b 100 :c 10} + {:a 5} + {:c 5 :d 42}) + {:d 42 :c 15 :a 25 :b 200})) + (assert (= (merge-with or_ + {"a" (set [1 2 3]) "b" (set [4 5 6])} + {"a" (set [2 3 7 8]) "c" (set [1 2 3])}) + {"a" (set [1 2 3 7 8]) "c" (set [1 2 3]) "b" (set [4 5 6])}))) (defn test-calling-module-name [] "NATIVE: Test the calling-module-name function" @@ -1035,11 +1056,11 @@ (import [StringIO [StringIO]]) (import [io [StringIO]])) (import [hy.models.expression [HyExpression]]) - + (def stdin-buffer (StringIO "(+ 2 2)\n(- 2 2)")) (assert (= (eval (read stdin-buffer)) 4)) (assert (isinstance (read stdin-buffer) HyExpression)) - + "Multiline test" (def stdin-buffer (StringIO "(\n+\n41\n1\n)\n(-\n2\n1\n)")) (assert (= (eval (read stdin-buffer)) 42)) @@ -1048,9 +1069,33 @@ "EOF test" (def stdin-buffer (StringIO "(+ 2 2)")) (read stdin-buffer) - (try + (try (read stdin-buffer) (catch [e Exception] (assert (isinstance e EOFError))))) +(defn test-keyword-creation [] + "NATIVE: Test keyword creation" + (assert (= (keyword "foo") :foo)) + (assert (= (keyword "foo_bar") :foo-bar)) + (assert (= (keyword `foo) :foo)) + (assert (= (keyword `foo-bar) :foo-bar)) + (assert (= (keyword 'foo) :foo)) + (assert (= (keyword 'foo-bar) :foo-bar)) + (assert (= (keyword 1) :1)) + (assert (= (keyword 1.0) :1.0)) + (assert (= (keyword :foo_bar) :foo-bar))) +(defn test-name-conversion [] + "NATIVE: Test name conversion" + (assert (= (name "foo") "foo")) + (assert (= (name "foo_bar") "foo-bar")) + (assert (= (name `foo) "foo")) + (assert (= (name `foo_bar) "foo-bar")) + (assert (= (name 'foo) "foo")) + (assert (= (name 'foo_bar) "foo-bar")) + (assert (= (name 1) "1")) + (assert (= (name 1.0) "1.0")) + (assert (= (name :foo) "foo")) + (assert (= (name :foo_bar) "foo-bar")) + (assert (= (name test-name-conversion) "test-name-conversion"))) diff --git a/tests/native_tests/native_macros.hy b/tests/native_tests/native_macros.hy index fb2879c..0768b3c 100644 --- a/tests/native_tests/native_macros.hy +++ b/tests/native_tests/native_macros.hy @@ -206,6 +206,24 @@ (assert (= (lif nil "true" "false") "false")) (assert (= (lif 0 "true" "false") "true"))) +(defn test-lisp-if-not [] + "test that lisp-if-not works as expected" + ; nil is false + (assert (= (lisp-if-not None "false" "true") "false")) + (assert (= (lisp-if-not nil "false" "true") "false")) + + ; But everything else is True! Even falsey things. + (assert (= (lisp-if-not True "false" "true") "true")) + (assert (= (lisp-if-not False "false" "true") "true")) + (assert (= (lisp-if-not 0 "false" "true") "true")) + (assert (= (lisp-if-not "some-string" "false" "true") "true")) + (assert (= (lisp-if-not "" "false" "true") "true")) + (assert (= (lisp-if-not (+ 1 2 3) "false" "true") "true")) + + ; Just to be sure, test the alias lif-not + (assert (= (lif-not nil "false" "true") "false")) + (assert (= (lif-not 0 "false" "true") "true"))) + (defn test-defn-alias [] (defn-alias [tda-main tda-a1 tda-a2] [] :bazinga) @@ -214,3 +232,6 @@ (assert (= (tda-a1) :bazinga)) (assert (= (tda-a2) :bazinga)) (assert (= tda-main tda-a1 tda-a2))) + +(defn test-botsbuildbots [] + (assert (> (len (first (Botsbuildbots))) 50))) diff --git a/tests/native_tests/shadow.hy b/tests/native_tests/shadow.hy index 362ac50..54bf81e 100644 --- a/tests/native_tests/shadow.hy +++ b/tests/native_tests/shadow.hy @@ -1,11 +1,22 @@ - - (defn test-shadow-addition [] "NATIVE: test shadow addition" (let [[x +]] - (assert (= (x) 0)) + (assert (try + (x) + (catch [TypeError] True) + (else (throw AssertionError)))) (assert (= (x 1 2 3 4) 10)) - (assert (= (x 1 2 3 4 5) 15)))) + (assert (= (x 1 2 3 4 5) 15)) + ; with strings + (assert (= (x "a") + "a")) + (assert (= (x "a" "b" "c") + "abc")) + ; with lists + (assert (= (x ["a"]) + ["a"])) + (assert (= (x ["a"] ["b"] ["c"]) + ["a" "b" "c"])))) (defn test-shadow-subtraction [] diff --git a/tests/native_tests/with_decorator.hy b/tests/native_tests/with_decorator.hy index dc6ad6e..d82214d 100644 --- a/tests/native_tests/with_decorator.hy +++ b/tests/native_tests/with_decorator.hy @@ -7,6 +7,16 @@ (* 2 2))) +(defn bardec [cls] + (setv cls.my_attr 123) + cls) + +(with-decorator bardec + (defclass cls [] + [[my_attr 456]])) + + (defn test-decorators [] "NATIVE: test decorators." - (assert (= (tfunction) 2))) + (assert (= (tfunction) 2)) + (assert (= cls.my_attr 123))) diff --git a/tests/test_bin.py b/tests/test_bin.py index 16383e4..e8a0fc8 100644 --- a/tests/test_bin.py +++ b/tests/test_bin.py @@ -26,8 +26,11 @@ import subprocess from hy._compat import PY3 +hy_dir = os.environ.get('HY_DIR', '') + + def run_cmd(cmd, stdin_data=None): - p = subprocess.Popen(cmd, + p = subprocess.Popen(os.path.join(hy_dir, cmd), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,