diff --git a/hy/contrib/walk.hy b/hy/contrib/walk.hy new file mode 100644 index 0000000..9fc3771 --- /dev/null +++ b/hy/contrib/walk.hy @@ -0,0 +1,47 @@ +;;; Hy AST walker +;; +;; Copyright (c) 2014 Gergely Nagy +;; +;; Permission is hereby granted, free of charge, to any person obtaining a +;; copy of this software and associated documentation files (the "Software"), +;; to deal in the Software without restriction, including without limitation +;; the rights to use, copy, modify, merge, publish, distribute, sublicense, +;; and/or sell copies of the Software, and to permit persons to whom the +;; Software is furnished to do so, subject to the following conditions: +;; +;; The above copyright notice and this permission notice shall be included in +;; all copies or substantial portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +;; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +;; DEALINGS IN THE SOFTWARE. + +(import [hy [HyExpression HyDict]] + [functools [partial]]) + +(defn walk [inner outer form] + "Traverses form, an arbitrary data structure. Applies inner to each + element of form, building up a data structure of the same type. + Applies outer to the result." + (cond + [(instance? HyExpression form) + (outer (HyExpression (map inner form)))] + [(instance? HyDict form) + (HyDict (outer (HyExpression (map inner form))))] + [(instance? list form) + ((type form) (outer (HyExpression (map inner form))))] + [true (outer form)])) + +(defn postwalk [f form] + "Performs depth-first, post-order traversal of form. Calls f on each + sub-form, uses f's return value in place of the original." + (walk (partial postwalk f) f form)) + +(defn prewalk [f form] + "Performs depth-first, pre-order traversal of form. Calls f on each + sub-form, uses f's return value in place of the original." + (walk (partial prewalk f) identity (f form))) diff --git a/tests/__init__.py b/tests/__init__.py index adcb583..e36ce11 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -17,3 +17,4 @@ from .native_tests.with_test import * # noqa from .native_tests.contrib.anaphoric import * # noqa from .native_tests.contrib.loop import * # noqa from .native_tests.contrib.meth import * # noqa +from .native_tests.contrib.walk import * # noqa diff --git a/tests/native_tests/contrib/walk.hy b/tests/native_tests/contrib/walk.hy new file mode 100644 index 0000000..854dfc7 --- /dev/null +++ b/tests/native_tests/contrib/walk.hy @@ -0,0 +1,24 @@ +(import [hy.contrib.walk [*]]) + +(def walk-form '(print {"foo" "bar" + "array" [1 2 3 [4]] + "something" (+ 1 2 3 4) + "quoted?" '(foo)})) + +(defn collector [acc x] + (.append acc x) + nil) + +(defn test-walk-identity [] + (assert (= (walk identity identity walk-form) + walk-form))) + +(defn test-walk [] + (let [[acc '()]] + (assert (= (walk (partial collector acc) identity walk-form) + [nil nil])) + (assert (= acc walk-form))) + (let [[acc []]] + (assert (= (walk identity (partial collector acc) walk-form) + nil)) + (assert (= acc [walk-form]))))