Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
261 changes: 193 additions & 68 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,36 @@ inspecting, and transforming arbitrary tree structures. It's based on the

[![NPM](https://nodei.co/npm/tree-walk.png?compact=true)](https://nodei.co/npm/tree-walk/)

Table of Contents
-----------------
- [Introduction](#introduction)
- [Installation](#installation)
- [Usage](#usage)
- [API Reference](#api-reference)
- [Examples](#examples)
- [Development](#development)
- [License](#license)

Introduction
------------

tree-walk is a JavaScript library providing useful functions for traversing,
inspecting, and transforming arbitrary tree structures. It's based on the
`walk` module that I wrote for [Underscore-contrib](http://documentcloud.github.io/underscore-contrib/).

Installation
------------

You can install tree-walk using npm or yarn:

```sh
npm install tree-walk
```

```sh
yarn add tree-walk
```

Usage
-----

Expand Down Expand Up @@ -44,91 +74,186 @@ the root node is visited, then all of its child nodes are recursively visited.
`postorder` does the opposite, calling the visitor function for a node
only after visiting all of its child nodes.

Collection Functions
--------------------
API Reference
-------------

This module provides versions of most of the
[Underscore collection functions](http://underscorejs.org/#collections), with
some small differences that make them better suited for operating on trees. For
example, you can use `filter` to get a list of all the strings in a tree:
### `preorder(tree, visitor, context)`

var walk = require('tree-walk');
walk.filter(walk.preorder, _.isString);
Performs a preorder traversal of the tree, calling the visitor function for each node.

Like many other functions in this module, the argument to `filter` is a function
indicating in what order the nodes should be visited. Currently, only
`preorder` and `postorder` are supported.
- `tree`: The tree to traverse.
- `visitor`: A function to call for each node.
- `context`: Optional. The context in which to call the visitor function.

Custom Walkers
--------------
### `postorder(tree, visitor, context)`

Sometimes, you have a tree structure that can't be naïvely traversed. A good
example of this is a DOM tree: because each element has a reference to its
parent, a naïve walk would encounter circular references. To handle such cases,
you can create a custom walker by invoking `walk` as a function, and passing
it a function which returns the descendants of a given node. E.g.:
Performs a postorder traversal of the tree, calling the visitor function for each node.

var walk = require('tree-walk');
var domWalker = walk(function(el) {
return el.children;
});
- `tree`: The tree to traverse.
- `visitor`: A function to call for each node.
- `context`: Optional. The context in which to call the visitor function.

The resulting object has the same functions as `walk`, but parameterized
to use the custom walking behavior:
### `filter(tree, strategy, visitor, context)`

var buttons = domWalker.filter(walk.preorder, function(el) {
return el.tagName === 'BUTTON';
});
Recursively traverses the tree and returns all the elements that pass a truth test.

However, it's not actually necessary to create custom walkers for DOM nodes --
walk handles DOM nodes specially by default.
- `tree`: The tree to traverse.
- `strategy`: The traversal function to use, e.g., `preorder` or `postorder`.
- `visitor`: A function to call for each node.
- `context`: Optional. The context in which to call the visitor function.

Parse Trees
-----------
### `map(tree, strategy, visitor, context)`

A _parse tree_ is tree that represents the syntactic structure of a formal
language. For example, the arithmetic expression `1 + (4 + 2) * 7` might have the
following parse tree:
Produces a new array of values by recursively traversing the tree and mapping each value through the transformation function.

var tree = {
- `tree`: The tree to traverse.
- `strategy`: The traversal function to use, e.g., `preorder` or `postorder`.
- `visitor`: A function to call for each node.
- `context`: Optional. The context in which to call the visitor function.

### `reduce(tree, visitor, leafMemo, context)`

Builds up a single value by doing a post-order traversal of the tree and calling the visitor function on each object in the tree.

- `tree`: The tree to traverse.
- `visitor`: A function to call for each node.
- `leafMemo`: The value to use for leaf nodes.
- `context`: Optional. The context in which to call the visitor function.

### `pluck(tree, propertyName)`

Returns the value of properties named `propertyName` reachable from the tree rooted at `tree`.

- `tree`: The tree to traverse.
- `propertyName`: The name of the property to pluck.

### `pluckRec(tree, propertyName)`

Version of `pluck` which recursively searches results for nested objects with a property named `propertyName`.

- `tree`: The tree to traverse.
- `propertyName`: The name of the property to pluck.

### `createAttribute(visitor, defaultValue, context)`

Creates an attribute that is calculated by invoking a visitor function on a node.

- `visitor`: A function to call for each node.
- `defaultValue`: The default value to use for the attribute.
- `context`: Optional. The context in which to call the visitor function.

Examples
--------

### Traversing Different Types of Trees

#### JSON Objects

```js
var tree = {
'name': { 'first': 'Bucky', 'last': 'Fuller' },
'occupations': ['designer', 'inventor']
};

var walk = require('tree-walk');

walk.preorder(tree, function(value, key, parent) {
console.log(key + ': ' + value);
});
```

#### DOM Trees

```js
var walk = require('tree-walk');
var domWalker = walk(function(el) {
return el.children;
});

var buttons = domWalker.filter(walk.preorder, function(el) {
return el.tagName === 'BUTTON';
});
```

### Custom Traversal Strategies

#### Custom Walker for Parse Trees

```js
var walk = require('tree-walk');
var parseTreeWalker = walk(function(node) {
return _.pick(node, 'left', 'right');
});

var tree = {
'type': 'Addition',
'left': { 'type': 'Value', 'value': 1 },
'right': {
'type': 'Multiplication',
'left': {
'type': 'Addition',
'left': { 'type': 'Value', 'value': 1 },
'right': {
'type': 'Multiplication',
'left': {
'type': 'Addition',
'left': { 'type': 'Value', 'value': 4 },
'right': { 'type': 'Value', 'value': 2 }
},
'right': { 'type': 'Value', 'value': 7 }
}
};
'left': { 'type': 'Value', 'value': 4 },
'right': { 'type': 'Value', 'value': 2 }
},
'right': { 'type': 'Value', 'value': 7 }
}
};

parseTreeWalker.reduce(tree, function(memo, node) {
if (node.type === 'Value') return node.value;
if (node.type === 'Addition') return memo.left + memo.right;
if (node.type === 'Multiplication') return memo.left * memo.right;
});
```

Development
-----------

We can create a custom walker for this parse tree:
### Setting Up the Development Environment

var walk = require('tree-walk');
var parseTreeWalker = walk(function(node) {
return _.pick(node, 'left', 'right');
});
1. Clone the repository:

Using the `find` function, we could find the first occurrence of the addition
operator. It uses a pre-order traversal of the tree, so the following code
will produce the root node (`tree`):
```sh
gh repo clone pdubroy/tree-walk
```

parseTreeWalker.find(tree, function(node) {
return node.type === 'Addition';
});
2. Install the dependencies:

We could use the `reduce` function to evaluate the arithmetic expression
represented by the tree. The following code will produce `43`:
```sh
npm install
```

parseTreeWalker.reduce(tree, function(memo, node) {
if (node.type === 'Value') return node.value;
if (node.type === 'Addition') return memo.left + memo.right;
if (node.type === 'Multiplication') return memo.left * memo.right;
});
or

```sh
yarn install
```

### Running Tests

To run the tests, use the following command:

```sh
npm test
```

### Building the Project

To build the project, use the following command:

```sh
npm run build
```

### Linting the Project

To lint the project, use the following command:

```sh
npm run lint
```

License
-------

When the visitor function is called on a node, the `memo` argument contains
the results of calling `reduce` on each of the node's subtrees. To evaluate a
node, we just need to add or multiply the results of the left and right
subtrees of the node.
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.