https://github.com/0e9b061f/ghast.js
An abstract syntax tree for use with PEG.js/Peggy
https://github.com/0e9b061f/ghast.js
abstract-syntax-tree ast parsing peg peggy pegjs static-analysis
Last synced: 3 months ago
JSON representation
An abstract syntax tree for use with PEG.js/Peggy
- Host: GitHub
- URL: https://github.com/0e9b061f/ghast.js
- Owner: 0E9B061F
- License: mit
- Created: 2019-12-03T04:47:37.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2023-03-24T20:59:39.000Z (about 2 years ago)
- Last Synced: 2024-05-10T03:22:46.434Z (about 1 year ago)
- Topics: abstract-syntax-tree, ast, parsing, peg, peggy, pegjs, static-analysis
- Language: JavaScript
- Homepage:
- Size: 62.5 KB
- Stars: 1
- Watchers: 1
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# :european_castle: **ghast.js** AST v0.6.9 'FLAY'
[![Version][icon-ver]][repo]
[![Series][icon-ser]][gh]
[![License][icon-lic]][license]
[![Documentation][icon-doc]][docs]
[![Maintenance][icon-mnt]][commits]
[![NPM][icon-npm]][pkg]**ghast.js** is an abstract syntax tree designed for use with
[Peggy][peggy]/[PEG.js][pegjs].# Usage
## Installation
```sh
npm install ghast.js
```## The `ast` function
**ghast.js** provides the [`AST`][doc-AST] class and [`ast`][doc-helper] helper function:
```javascript
const { AST, ast } = require('ghast.js')
```You probably won't need to interact with the [`AST`][doc-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`][doc-AST] node or an array of these.
Example:```javascript
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:```javascript
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:```pegjs
{
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`][doc-helper] helper
function in the parser's options. For example:```javascript
const peggy = require('peggy')
const { ast } = require('ghast.js')const parser = peggy.generate(GRAMMAR)
const tree = parser.parse(INPUT, {ast})
```The [`ast`][doc-helper] function will be available in your grammar:
```pegjs
{
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`][doc-AST] which can be used to manipulate the
parsed syntax. This will remove all B elements directly below an A element:```javascript
const tree = parser.parse(INPUT, {ast})
tree.select("A", {id: "B", depth: 0}, b=> b.remove())
```## API
[Complete API documentation][docs] is available. Below is an overview of common methods:
The [`each`][doc-each] method is used to query the tree:
```javascript
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`][doc-select] method creates complex selections from multiple [traverses][doc-trv],
similar to CSS selectors. The following is similar to `A .foo > B`:```javascript
node.select('A', {tag: 'foo'}, {id: 'B', depth: 0})
```The [`when`][doc-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:```javascript
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:
```javascript
// 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:
```javascript
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:
```javascript
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`][doc-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:```javascript
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`][doc-loc]:
```javascript
node.loc({start: 1, end: 2})
node.location // read set location data
```# Examples
Two examples are provided:
* **[ab][ex-ab]** - the nonsense example used in this README
* **[ini][ex-ini]** - a simplistic ini parser# License
Available under the terms of the [MIT license.][license]
Copyright 2023 **[0E9B061F][gh]**
[gh]:https://github.com/0E9B061F
[repo]:https://github.com/0E9B061F/ghast.js
[commits]:https://github.com/0E9B061F/ghast.js/commits/master
[license]:https://github.com/0E9B061F/ghast.js/blob/master/LICENSE
[pkg]:https://www.npmjs.com/package/ghast.js
[ex-ab]:https://github.com/0E9B061F/ghast.js/blob/master/example/ab
[ex-ini]:https://github.com/0E9B061F/ghast.js/blob/master/example/ini
[docs]:https://0e9b061f.github.io/docs/ghast.js[doc-AST]:https://0e9b061f.github.io/docs/ghast.js/AST.html
[doc-helper]:https://0e9b061f.github.io/docs/ghast.js/global.html#ast
[doc-each]:https://0e9b061f.github.io/docs/ghast.js/AST.html#each
[doc-select]:https://0e9b061f.github.io/docs/ghast.js/AST.html#select
[doc-when]:https://0e9b061f.github.io/docs/ghast.js/AST.html#when
[doc-read]:https://0e9b061f.github.io/docs/ghast.js/AST.html#read
[doc-loc]:https://0e9b061f.github.io/docs/ghast.js/AST.html#loc
[doc-trv]:https://0e9b061f.github.io/docs/ghast.js/Traverse.html[peggy]:https://github.com/peggyjs/peggy
[pegjs]:https://github.com/pegjs/pegjs[icon-ver]:https://img.shields.io/github/package-json/v/0E9B061F/ghast.js.svg?style=flat-square&logo=github&color=%236e7fd2
[icon-ser]:https://img.shields.io/badge/dynamic/json?color=%236e7fd2&label=series&prefix=%27&query=series&suffix=%27&url=https%3A%2F%2Fraw.githubusercontent.com%2F0E9B061F%2Fghast.js%2Fmaster%2Fpackage.json&style=flat-square
[icon-lic]:https://img.shields.io/github/license/0E9B061F/ghast.js.svg?style=flat-square&color=%236e7fd2
[icon-doc]:https://img.shields.io/badge/dynamic/json?color=%236e7fd2&label=docs&prefix=v&query=version&url=https%3A%2F%2F0e9b061f.github.io%2Fdocs%2Fghast.js%2Fpackage.json
[icon-npm]:https://img.shields.io/npm/v/ghast.js.svg?style=flat-square&logo=npm&color=%23de2657
[icon-mnt]:https://img.shields.io/maintenance/yes/2024.svg?style=flat-square