• Stars
    star
    192
  • Rank 195,585 (Top 4 %)
  • Language
    JavaScript
  • Created over 9 years ago
  • Updated 9 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Abstract Syntax Tree (AST) Query Engine

ASTq

Abstract Syntax Tree (AST) Query Engine

Installation

$ npm install astq

About

ASTq is an Abstract Syntax Tree (AST) query engine library for JavaScript, i.e., it allows you to query nodes of an arbitary AST-style hierarchical data structure with the help of a powerful XPath-inspired query language. ASTq can operate on arbitrary AST-style data structures through the help of pluggable access adapters.

Query Language

ASTq uses an XPath-inspired Domain Specific Language (DSL) for querying the supplied AST-style hierarchical data structure.

By Example

At its simplest form, a query looks like a POSIX filesystem path:

Foo/Bar/Quux

This means: query and return all nodes of type Quux, which in turn are childs of nodes of type Bar, which in turn are childs of nodes of type Foo, which in turn has to be the start node.

A little bit more sophisticated query, showing more features, like axis, filter and optional whitespaces for padding:

// Foo [ /Bar [ @bar == 'baz1' || @bar == 'baz2' ] && /Quux ]

This means: query and return all nodes anywhere under the start node which are of type Foo and which have both childs of type Bar -- and with an attribute bar of values baz1 or baz2 -- and childs of type Quux.

By Grammar

In general, a query consists of one or more individual query paths, separated by comma. A path consists of a mandatory initial query step and optionally zero or more subsequent query steps.

The difference between initial and subsequent query steps is that an initial query step does not need an axis while all subsequent query steps require it. A query step consists of an (optional) AST node search axis, a (mandatory) AST node type match, an (optional) result marker "!" and an (optional) AST node filter expression:

query            ::= path (, path)*
path             ::= step-initial step-subsequent*
step-initial     ::= axis? match result? filter?
step-subsequent  ::= axis  match result? filter?

The search axis can be either...

  • / for direct child nodes, or
  • // for any descendant nodes, or
  • ./ for current node plus direct child nodes, or
  • .// for current node plus any descendant nodes, or
  • -/ for direct left sibling node, or
  • -// for any left sibling nodes, or
  • +/ for direct right sibling node, or
  • +// for any right sibling nodes, or
  • ~/ for direct left and right sibling nodes, or
  • ~// for all left and right sibling nodes, or
  • ../ for direct parent node, or
  • ..// for any parent nodes, or
  • <// for any preceding nodes, or
  • >// for any following nodes.

As an illustrating example: given an AST of the following particular nodes, ...

      A
      |
  +-+-+-+-+
 / /  |  \ \
B  C  D  E  F
      |
   +--+--+
  /   |   \
 G    H    I
      |
    +-+-+
   /     \
  J       K

...the following queries and their result exist:

Start Node Query Result Node(s)
D / * G, H, I
D // * G, H, J, K, I
D ./ * D, G, H, I
D .// * D, G, H, J, K, I
D -/ * C
D -// * C, B
D +/ * E
D +// * E, F
D ~/ * C, E
D ~// * B, C, E, F
H ../ * D
H ..// * D, A
H <// * G, D, C B A
H >// * J, K, I, E, F

A search axis usually walks along the references between nodes (at least in case of ASTy based AST). But in case the underlying AST and its adapter uses typed references, you can optionally constrain the search axis to take only references matching the type id into account.

axis               ::= axis-direction axis-type?
axis-direction     ::= axis-child
                     | axis-sibling-left
                     | axis-sibling-right
                     | axis-sibling
                     | axis-parent
                     | axis-preceding
                     | axis-following
axis-child         ::= ("/" | "//" | "./" | ".//")
axis-sibling-left  ::= ("-/" | "-//")
axis-sibling-right ::= ("+/" | "+//")
axis-sibling       ::= ("~/" | "~//")
axis-parent        ::= ("../" | "..//")
axis-preceding     ::= "<//"
axis-following     ::= ">//"
axis-type          ::= ":" (id | string)
result             ::= "!"
match              ::= id | string | "*"
filter             ::= "[" expr "]"

The real power comes through the optional filter expression: it can be applied to each query step and it recursively(!) can contain sub-queries with the help of embedded query paths! An illustrating combined example is:

// Foo / Bar [ / Baz [ @bar == 'baz' ] && / Quux ], // Foo2
+---------------------------------------------------------+  query
+------------------------------------------------+  +-----+  path
               +---------------------+    +-----+            path
+----+ +-----------------------------------------+  +-----+  step
++     +       +                          +         ++       axis
   +-+   +-+     +-+                        +--+       +--+  match
             +-----------------------------------+           filter
               +-------------------------------+             expr
                     +---------------+                       filter
                       +----------+                          expr

The result of a query is always all nodes which match against the last query step of any path (in case of no result marker on any step in the path) or all nodes of matched steps with a result marker. The queries in filter expressions just lead to a boolean decision for the filter, but never cause any resulting nodes theirself.

An expression can be either a ternary/binary conditional expression, logical expression, bitwise expression, relational expression, arithmethical expression, functional call, attribute reference, query parameter, literal value, parenthesis expression or path of a sub-query.

expr             ::= conditional
                   | logical
                   | bitwise
                   | relational
                   | arithmentical
                   | function-call
                   | attribute-ref
                   | query-parameter
                   | literal
                   | parenthesis
                   | sub-query
conditional      ::= expr "?" expr ":" expr
                   | expr "?:" expr
logical          ::= expr ("&&" | "||") expr
                   | "!" expr
bitwise          ::= expr ("&" | "|" | "<<" | ">>") expr
                   | "~" expr
relational       ::= expr ("==" | "!=" | "<=" | ">=" | "<" | ">" | "=~" | "!~") expr
arithmethical    ::= expr ("+" | "-" | "*" | "/" | "%" | "**") expr
function-call    ::= id "(" (expr ("," expr)*)? ")"
attribute-ref    ::= "@" (id | string)
query-parameter  ::= "{" id "}"
id               ::= /[a-zA-Z_][a-zA-Z0-9_-]*/
literal          ::= string | regexp | number | value
string           ::= /"(\\"|.)*"/ | /'(\\'|.)*'/
regexp           ::= /`(\\`|.)*`/
number           ::= /\d+(\.\d+)?$/
value            ::= "true" | "false" | "null" | "NaN" | "undefined"
parenthesis      ::= "(" expr ")"
sub-query        ::= path           // <-- ESSENTIAL RECURSION !!

Notice that the function call parameters can be full expressions theirself, including (through the recursion over sub-query above) full query paths. The available pre-defined standard functions are:

  • type(): String:
    Return type of current node. Example: type() == "foo"

  • attrs(sep: String): String:
    Return the sep-separated concatenation of all attribute names of current node. The sep string is alway also prepended and appended for easier comparison of the result string. Example: attr(",") == ",foo,bar,"

  • depth(): Number:
    Return depth in AST of current node (counting from 1 for the root node). Example: depth() <= 3

  • pos(): Number:
    Return position of current node among sibling (counting from 1 for the first sibling). Example: pos() == 2

  • nth(pos: Number): Boolean:
    Check whether position of current node among sibling is pos (counting from 1 for the first sibling). Negative values for pos count from the last sibling backward, i.e., -1 is the last sibling. Example: nth(3)

  • first(): Boolean:
    Shorthand for nth(1).

  • last(): Boolean:
    Shorthand for nth(-1).

  • count(array: Object[]): Number:
    Return the number of elements in array. The array usually is either an externally passed-in parameter or a sub-query. Example: count({nodes}) <= count(// *)

  • below(node: Node): Boolean:
    Checks whether current node is somewhere below node, i.e., whether current node is a child or descendant of node. Usually, this makes sense only if node is an externally passed-in parameter. Example: below({node}).

  • follows(node: Node): Boolean:
    Checks whether current node is following node, i.e., whether current node comes after node in a standard depth-first tree visit (where parents are visited before childs). Usually, this makes sense only if node is an externally passed-in parameter. Example: follows({node}).

  • in(nodes: Node[]): Number:
    Checks whether current node is in nodes. Usually, nodes is either an externally passed-in parameter or a sub-query. Example: in({nodes}).

  • substr(str: String, pos: Number, len: Number): String:
    Returns the sub-string of str, starting at pos with length len. Negative values for pos count from the end of the string, i.e., -1 is the last character. Example: substr(@foo, 0, 1) == "A"

  • index(str: String, sub: String, pos: Number): Number:
    Returns the index position of sub-string sub in string str, starting at pos. Example: indexof(@foo, "bar", 0) >= 0

  • trim(str: String): String:
    Returns the string str with whitespaces removed from begin and end. Example: trim(@foo) == "bar"

  • lc(str: String): String:
    Returns the lower-case variant of str. Example: lc(@foo) == "bar"

  • uc(str: String): String:
    Returns the upper-case variant of str. Example: uc(@foo) == "BAR"

Application Programming Interface (API)

The ASTq API, here assumed to be exposed through the variable ASTQ, provides the following methods (in a notation somewhat resembling TypeScript type definitions):

ASTQ API

  • new ASTQ(): ASTQ:
    Create a new ASTQ instance.

  • ASTQ#adapter(adapter: (ASTQAdapter | ASTQAdapter[]), force: Boolean): ASTQ:
    Register one or more custom tree access adapter(s) to support arbitrary AST-style data structures. The ASTQAdapter has to conform to a particular duck-typed interface. See below for more information. By default ASTq has built-in adapters for ASTy, XML DOM, Parse5, Cheerio, UniST, JSON and Mozilla AST. All those "taste" the node passed to ASTQ#query and hence are auto-selected. Calling adapter() causes these to be replaced with a single custom adapter. Its "tasting" can be disabled with option force set to true. The ASTQ#adapter teturns the API itself.

      /*  the built-in implementation for supporting ASTy  */
      astq.adapter({
          taste:            function (node)       { return (typeof node === "object" && node.ASTy) },
          getParentNode:    function (node, type) { return node.parent()  },
          getChildNodes:    function (node, type) { return node.childs()  },
          getNodeType:      function (node)       { return node.type()    },
          getNodeAttrNames: function (node)       { return node.attrs()   },
          getNodeAttrValue: function (node, attr) { return node.get(attr) }
      })
    
  • ASTQ#version(): { major: Number, minor: Number, micro: Number, date: Number }:
    Return the current ASTq library version details.

  • ASTQ#func(name: String, func: (adapter: Adapter, node: Object, [...]) => Any): ASTQ:
    Register function named name by providing the callback func which has to return an arbitrary value and optionally can access the current node with the help of the selected adapter. Returns the API itself.

      /*  the built-in implementation for "depth"  */
      astq.func("depth", function (adapter, node) => {
          var depth = 1
          while ((node = adapter.getParentNode(node)) !== null)
              depth++
          return depth
      })
    
  • ASTQ#cache(num: Number): ASTQ:
    Set the upper limit for the internal query cache to num, i.e., up to num ASTs of parsed queries will be cached. Set num to 0 to disable the cache at all. Returns the API itself.

  • ASTQ#compile(selector: String, trace?: Boolean): ASTQQuery { Compile selectorDSL into an internal query object for subsequent processing byASTQ#execute. If traceistrue` the compiling is dumped to the console. Returns the query object.

  • ASTQ#execute(node: Object, query: ASTQQuery, params?: Object, trace?: Boolean): Object[]:
    Execute the previously compiled query (see compile above) at node. The optional params object can provide parameters for the {name} query constructs. If trace is true the execution is dumped to the console. Returns an array of zero or more matching AST nodes.

  • ASTQ#query(node: Object, selector: String, params?: Object, trace?: Boolean): Object[]:
    Just the convenient combination of compile and execute: execute(node, compile(selector, trace), params, trace). Use this as the standard query method except you need more control. The optional params object can provide parameters for the {name} query constructs. If trace is true the compiling and execution is dumped to the console. Returns an array of zero or more matching AST nodes.

ASTQAdapter API

For accessing arbitrary AST-style data structures, an adapter has to be provided. By default ASTq has adapters for use with ASTy, XML DOM, Parse5, Cheerio, UniST, JSON and Mozilla AST. The ASTQAdapter interface is:

  • ASTQAdapter#taste(node: Object): Boolean:
    Taste node to be sure this adapter is intended to handle it.

  • ASTQAdapter#getParentNode(node: Object): Object:
    Return parent node of node. In case the underyling data structure does not support traversing to parent nodes, throw an exception.

  • ASTQAdapter#getChildNodes(node: Object): Object[]:
    Return the list of all child nodes of node.

  • ASTQAdapter#getNodeType(node: Object): String:
    Return the type of node.

  • ASTQAdapter#getNodeAttrNames(node: Object): String[]:
    Return the list of all attribute names of node.

  • ASTQAdapter#getNodeAttrValue(node: Object, attr: String): Any:
    Return the value of attribute attr of node.

Example

$ cat sample.js
const acorn = require("acorn")
const ASTQ  = require("astq")

let source = `
    class Foo {
        foo () {
            const bar = "quux"
            let baz = 42
        }
    }
`

let ast = acorn.parse(source, { ecmaVersion: 6 })

let astq = new ASTQ()
astq.adapter("mozast")
astq.query(ast, `
    // VariableDeclarator [
           /:id   Identifier [ @name  ]
        && /:init Literal    [ @value ]
    ]
`).forEach(function (node) {
    console.log(`${node.id.name}: ${node.init.value}`)
})

$ babel-node sample.js
bar: quux
baz: 42

Implementation Notice

Although ASTq is written in ECMAScript 2018, it is transpiled to older environments and this way runs in really all current (as of 2018) JavaScript environments, of course.

Additionally, there are two transpilation results: first, there is a compressed astq.browser.js for Browser environments. Second, there is an uncompressed astq.node.js for Node.js environments.

The Browser variant astq.browser.js has all external dependencies asty, pegjs-otf, pegjs-util, and cache-lru directly embedded. The Node.js variant astq.node.js still requires the external dependencies asty, pegjs-otf, pegjs-util, and cache-lru.

License

Copyright (c) 2014-2022 Dr. Ralf S. Engelschall (http://engelschall.com/)

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.

More Repositories

1

es6-features

ECMAScript 6: Feature Overview & Comparison
HTML
6,228
star
2

stmux

Simple Terminal Multiplexer for Node.js Environments
JavaScript
499
star
3

vingester

Ingest Web Contents as Video Streams
JavaScript
178
star
4

typopro

Fonts for Professional Typography
129
star
5

tokenizr

String Tokenization Library for JavaScript
JavaScript
93
star
6

graphql-tools-sequelize

Integration of GraphQL-Tools and Sequelize ORM
JavaScript
90
star
7

componentjs

ComponentJS -- Powerful run-time Component System for structuring HTML5-based Rich Clients
JavaScript
85
star
8

slideshow

Slideshow -- Observe and Control Slideshow Applications
AppleScript
82
star
9

ducky

Duck-Typed Value Handling for JavaScript
JavaScript
72
star
10

extraction

Tree Extraction for JavaScript Object Graphs
JavaScript
71
star
11

es6-support

ECMAScript 6: Support Hints & Examples
JavaScript
71
star
12

thenable

Embeddable Minimum Strictly-Compliant Promises/A+ 1.1.1 Thenable
JavaScript
66
star
13

pure-uuid

Pure JavaScript Based Universally Unique Identifiers (UUID)
JavaScript
64
star
14

aggregation

Aggregation of Base Class and Mixin Classes
JavaScript
59
star
15

jquery-markup

jQuery Template Based Markup Generation
JavaScript
55
star
16

hapi-plugin-websocket

HAPI plugin for seamless WebSocket integration
JavaScript
53
star
17

graphql-tools-types

Custom Scalar Types for GraphQL-Tools
JavaScript
47
star
18

vue-i18next

Vue plugin for integrating I18Next
JavaScript
46
star
19

node-prince

Node API for executing PrinceXML via prince(1) CLI
Shell
46
star
20

blessed-xterm

XTerm Widget for Blessed Curses Environment
JavaScript
46
star
21

graphql-io

GraphQL Network Communication Framework
JavaScript
41
star
22

graphql-query-compress

Compress GraphQL Query String
JavaScript
38
star
23

vmix-scripts

vMix Scripts Collection
Visual Basic .NET
38
star
24

asty

Abstract Syntax Tree (AST) Data Structure
JavaScript
34
star
25

mqtt-json-rpc

JSON-RPC protocol over MQTT communication
JavaScript
33
star
26

json-asty

Lossless JSON-to-AST Parser and AST-to-JSON Generator
JavaScript
31
star
27

pegjs-util

Utility Class for PEG.js
JavaScript
31
star
28

node-enigmavirtualbox

Node API for executing Enigma Virtual Box
JavaScript
30
star
29

unix-under-windows

Unix Environment under Windows
29
star
30

vdon-call

Remote Caller Ingest for Live Video Productions with VDO.Ninja & OBS Studio
HTML
29
star
31

microkernel

Microkernel for Server Applications
JavaScript
28
star
32

graphql-tutorial

Tutorial for GraphQL
JavaScript
27
star
33

glyphs2font

SVG Glyph Icons to Web Font Generation
JavaScript
27
star
34

grunt-bower-install-simple

Grunt Task for Installing Bower Dependencies
JavaScript
26
star
35

vdo-ninja-trampoline

VDO.Ninja Trampoline
HTML
25
star
36

obs-setup

OBS Studio Setup Information
25
star
37

hapi-plugin-graphiql

HAPI plugin for GraphiQL integration
CSS
22
star
38

typopro-web

TypoPRO (Web Font Formats)
CSS
19
star
39

node-inline-assets

Node API, CLI and Grunt Task for inlining external assets of HTML/CSS files
JavaScript
18
star
40

grunt-merge-json

Grunt Task for Merging Multiple JSON Files
JavaScript
18
star
41

huds

Head-Up-Display Server (HUDS)
JavaScript
17
star
42

node-http-proxy-simple

Simple HTTP proxy extension module for Node.js, allowing protocol and payload interception
JavaScript
17
star
43

jquery-stage

jQuery Stage Information
JavaScript
15
star
44

obs-scripts

OBS Studio Lua Scripts
Lua
15
star
45

html5-spa-essentials

Essential Functionalities for HTML5 Single-Page-Apps (SPA)
JavaScript
15
star
46

tika-server

Apache Tika Server as a Background Service in Node.js
JavaScript
14
star
47

audio-node-suite

Web Audio API AudioNode Suite
TypeScript
13
star
48

apollo-client-ws

GraphQL WebSocket Network Interface for Apollo Client
JavaScript
13
star
49

pdfbox-simple

Simple PDFBox Wrapper
JavaScript
11
star
50

jquery-page

jQuery Page Transitions
JavaScript
11
star
51

huds-hud-training

Training HUD for HUDS
Vue
11
star
52

cache-lru

In-Memory Cache with O(1) Operations and LRU Purging Strategy
JavaScript
10
star
53

fasttext-lid

Language Identification with Facebook FastText for Node.js
JavaScript
10
star
54

browserify-derequire

Browserify Plugin for Renaming require() Calls
JavaScript
10
star
55

encodr

Encoding/Decoding to/from CBOR/MsgPack/JSON for Node.js and Browser
JavaScript
9
star
56

graphql-io-server

GraphQL Network Communication Framework (Server)
JavaScript
8
star
57

stanford-postagger

Stanford Log-linear Part-Of-Speech (PoS) Tagger for Node.js
JavaScript
8
star
58

oset

Ordered Set Data Structure for JavaScript
JavaScript
8
star
59

dotfiles

Small Opinionated Collection of Unix Configuration Files for User Shell Environment
Shell
8
star
60

wordnet-lmf

WordNet Lexical Markup Framework (LMF)
JavaScript
7
star
61

lowerthird

Lower Thirds for OBS Studio
JavaScript
7
star
62

upd

Upgrade Package Dependencies (UPD)
JavaScript
7
star
63

hapi-plugin-traffic

HAPI plugin for network traffic accounting
JavaScript
7
star
64

hapi-plugin-co

HAPI plugin for Co-Routine handlers
JavaScript
7
star
65

sublime-scheme-rse

Sublime Text Color Scheme RSE
7
star
66

soundfx

Sound Effect Collection
JavaScript
7
star
67

syntax

Unobtrusive Syntax Highlighting
JavaScript
7
star
68

bash-fzf

Enhance GNU Bash with FZF Matching
Makefile
7
star
69

psd2pptx

Convert Photoshop (PSD) layers to PowerPoint (PPTX) slides
JavaScript
7
star
70

asty-astq

Abstract Syntax Tree With Integrated Query Engine
JavaScript
6
star
71

grunt-newer-explicit

Grunt Task for running tasks if source files are newer only.
JavaScript
6
star
72

node-unix

Unix operating system integration for Node.js-based services
JavaScript
6
star
73

live-receiver

Live Video Experience (LiVE) Receiver
Vue
6
star
74

jquery-schedule

jQuery plugin for performing scheduled/deferred actions on DOM elements.
JavaScript
6
star
75

node-xmlhttprequest-cookie

Cookie-aware XMLHttpRequest Wrapper
JavaScript
6
star
76

websocket-framed

Framed WebSocket Communication
JavaScript
6
star
77

ffmpeg

FFmpeg distribution for NPM
JavaScript
5
star
78

ipc-pubsub

Inter-Process-Communication (IPC) Publish-Subscribe (PubSub) Abstraction Layer
JavaScript
5
star
79

style-scope

PostCSS and PostHTML plugins for locally scoping styles
JavaScript
5
star
80

sysload

System Load Determination
JavaScript
5
star
81

audio-setup

Windows Audio Setup
5
star
82

sprintfjs

POSIX sprintf(3)-style Formatting for JavaScript
JavaScript
5
star
83

browserify-replace

Browserify Transform For Replacing Strings
JavaScript
5
star
84

gridless

Grid System for LESS CSS
CSS
4
star
85

mostlikely

Most-Likely Classification Through Bloom-Filtering
JavaScript
4
star
86

obs-crop-control

Remote Crop-Filter Control for OBS Studio
JavaScript
4
star
87

k8s-sample

Kubernetes (K8S) Sample Application
JavaScript
4
star
88

studio-canvas

Real-Time Virtual Studio Canvas Rendering
TypeScript
4
star
89

obs-banner

Simple Banner for OBS Studio
JavaScript
3
star
90

obs-cam-control

Camera Control for OBS Studio
JavaScript
3
star
91

vue-params

Vue plugin for global parameters triggering data-binding updates
JavaScript
3
star
92

pdf-scrape

PDF Text Scraping
JavaScript
3
star
93

hapi-plugin-header

HAPI plugin to always send a Server identification header
JavaScript
3
star
94

grunt-extend-config

Grunt Plugin for Extending Grunt Configuration
JavaScript
3
star
95

graphql-io-client

GraphQL Network Communication Framework (Client)
JavaScript
3
star
96

huds-hud-hello

Hello World HUD for HUDS
HTML
3
star
97

ael

Advanced Expression Language
JavaScript
3
star
98

gdo

Group- and Dependency-based Ordering
JavaScript
3
star
99

grunt-i18next-yaml

Assemble language-separated i18next JSON output files from language-merged YAML input files
JavaScript
3
star
100

componentjs-tracing

ComponentJS Tracing
JavaScript
2
star