npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ghast.js

v0.6.9

Published

An abstract syntax tree designed for use with peg.js

Readme

:european_castle: ghast.js AST v0.6.9 'FLAY'

Version Series License Documentation Maintenance NPM

ghast.js is an abstract syntax tree designed for use with Peggy/PEG.js.

Usage

Installation

npm install ghast.js

The ast function

ghast.js provides the AST class and ast helper function:

const { AST, ast } = require('ghast.js')

You probably won't need to interact with the AST class itself. The helper is a wrapper around new AST(). It takes an ID and zero-or-more syntax elements. A syntax element may be a string, another AST node or an array of these. Example:

ast('Function',
  ast('Ident', 'foo'),
  "(", ast('String', '"', 'bar', '"'), ")"
)

This will return a small tree representing a call to function foo with one string as its argument, "bar".

The classify function

The ast.classify function takes a number of tags and returns a new helper function that automatically applies these tags to created nodes. For example:

const foo = ast.classify('foo')
const n1 = foo('Int', '55') // n1 will have the tag 'foo'

const bar = foo.classify('bar')
const n2 = bar('Str', 'foo') // n2 will be tagged 'foo bar'

The locate function

ast.locate takes a location function and returns a new helper function that automatically adds location data to any created nodes. In a grammar you would use it like this:

{
  const ast = options.ast.locate(location)
}

Any created nodes will capture location data from the rule where they're created, available as node.location. Note that all nodes created in an action will share the same location information; to get information on a portion of the match, create another rule for just that portion.

Using ghast.js in a Grammar File

To use ghast in a grammar file, create a parser and place the ast helper function in the parser's options. For example:

const peggy = require('peggy')
const { ast } = require('ghast.js')

const parser = peggy.generate(GRAMMAR)

const tree = parser.parse(INPUT, {ast})

The ast function will be available in your grammar:

{
  const ast = options.ast.locate(location)
  const node = ast.classify('Node')
  const val = ast.classify('Value')
}

Example = ex:Atom* { return ast('Example', ex) }

Atom = A / B / N / S / [ \n]
Sub = "(" Atom* ")"

A = x:("a" Sub / "a") { return node('A', x) }
B = x:("b" Sub / "b") { return node('B', x) }
N = n:$[0-9]+ { return val('Number', n) }
S = "'" C "'"
C = c:$[^']+ { return val('String', c) }

The parser will now return a ghast AST which can be used to manipulate the parsed syntax. This will remove all B elements directly below an A element:

const tree = parser.parse(INPUT, {ast})
tree.select("A", {id: "B", depth: 0}, b=> b.remove())

API

Complete API documentation is available. Below is an overview of common methods:

The each method is used to query the tree:

node.each()                       // return all descendants of `node`
node.each({self: true})           // return `node` and all of its descendants
node.each('Section')              // return all descendants with id `Section`
node.each({id: 'Section'})        // same as above
node.each({id: 'X', tag: 'y'})    // return all descendants with both id `X` and tag `y`
node.each({tag: 'val key'})       // return all descendants tagged `val` and `key`
node.each({id: 'A', first: true}) // return the first descendant with id `A`
node.each({leaf: true})           // return all descendant leaf nodes
node.each({stem: true})           // return all non-leaf descendant nodes
node.each({depth: 0})             // return all direct children of `node`
node.each({depth: 1})             // return all direct children and grandchildren
node.each({up: true})             // return all ancestors of `node`
node.ancestor()                   // same as above
node.each({up: true, tag: 'x'})   // return all ancestors of `node` tagged `x`
node.ancestor({tag: 'x'})         // same as above
node.climb(3)                     // return nth ancestor of `node`

The select method creates complex selections from multiple traverses, similar to CSS selectors. The following is similar to A .foo > B:

node.select('A', {tag: 'foo'}, {id: 'B', depth: 0})

The when method is used to visit nodes. Each visitor is an array of queries followed by a callback which will be called for each node matching any of its associated queries:

node.when(
  [{id: 'A', tag: 'foo'}, n=> n.foo()],
  ['T', 'V', n=> n.bar()],
  [{tag: 'bar'}, {leaf: true}, n=> n.baz()],
)

The following methods exist to modify the tree:

// replace a child node with another:
node.replace(node.first(), ast('Test', 'test'))
// self-replace a node with another:
node.replace(ast('Test', 'test'))

// transform nodes in-place:
node.mutate({id: 'Foo', attrs: {x: 1}})
node.mutate({tags: 'x y z', syntax: ['foo']})

// remove a child node:
node.remove(node.first())
// self-remove a node:
node.remove()

Nodes can be tagged:

node.tag('foo')         // tag a node
node.tag('foo bar baz') // apply multiple tags at once
node.hasTags            // true if the node has any tags
node.hasTag('foo')      // true if the node has the tag `foo`
node.hasTag('bar baz')  // true if the node has all of the given tags

Nodes have attributes:

node.attr('a', 100)         // set a single attribute
node.attr({foo: 1, bar: 2}) // set one or more attributes
node.attrs.foo              // accessing attributes
node.attrs['foo']           // accessing attributes

The read method deep-reads attributes; it merges the attributes of this node with all of its descendants and returns the value of the given property:

const node = ast('Function',
  ast('Ident', 'foo').attr('foo', 1),
  "(", ast('String', '"', 'bar', '"').attr('bar', 2), ")"
)
node.read('foo') // returns 1
node.read('bar') // returns 2

Location data for a node can be set with loc:

node.loc({start: 1, end: 2})
node.location // read set location data

Examples

Two examples are provided:

  • ab - the nonsense example used in this README
  • ini - a simplistic ini parser

License

Available under the terms of the MIT license.

Copyright 2023 0E9B061F