UNPKG

4.22 kBMarkdownView Raw
1# Path Expressions
2
3It's possible to run path expressions against projects to select [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) nodes resulting from parsing using a [microgrammar](https://en.wikipedia.org/wiki/Abstract_syntax_tree) or other grammar. Atomist path expressions are inspired by [XPath](https://www.w3.org/TR/xpath/).
4
5Path expressions are a good solution for working with ASTs as they can be so noisy. (For example, the Java--and especially, the Python--ASTs contain many levels that can be surprising even to experts in those languages.) Thus navigating via properties or through explicitly traversing children can be difficult and error-prone. The "descendants" (`//`) "axis specifier" in path expressions makes it easy to skip levels, greatly simplifying expressions. Path expressions also decouple navigation from logic, fostering reuse.
6
7## Usage
8
9The `findMatches` function in `astUtils` makes it possible to execute a path expression across many files, as identified by a glob pattern:
10
11```typescript
12const mg = Microgrammar.fromString<Person>("${name}:${age}", {
13 age: Integer,
14 });
15const fpr = new DefaultFileParserRegistry().addParser(
16 new MicrogrammarBasedFileParser("people", "person", mg));
17
18findMatches(project, AllFiles, fpr, "/people/person/name")
19 .then(matches => ...
20```
21
22It's possible to update matches, simply by setting the `$value` property on a match. The project will automatically be updated on the next flush. (To understand flushing, see [project operations](ProjectOperations.md).)
23
24This programming model is consistent across all grammars.
25
26## Navigation
27All matches implement the `TreeNode` interface, exposing `$value` and `$children` properties.
28
29Additional navigation methods are added for convenience.
30
31Imagine the following tree:
32
33```
34people
35├── person
36 ├── name
37 ├── age
38├── person
39 ├── name
40 ├── age
41```
42This can be navigated via `TreeNode` method with the root `$name` = "people" and two `$children` with name `person`, each with children representing the terminal nodes.
43
44However, it can be more convenient to use properties.
45
46The `people` node will have a `persons` property that contains an array of `person` nodes. (Note that an `s` is added to the node.) Each `person` node will have a scalar terminal for `name` and `age`, as the infrastructure can determine that these must be scalar to be unique.
47
48When working with non-terminal scalar properties, use the node name _without_ the `s` suffix. For example, `people.person` will return a single `person` node. In this case it will be the last `person` node seen, which won't be very useful. But in the case of a known scalar value, it will be easier than working with an array. Both properties--with `s` suffix and without--will be added in all cases.
49
50## SPI: TreeNodes and FileParser
51Path expressions and `astUtils` are backed by implementations of the simple `TreeNode` interface:
52
53
54```typescript
55export interface TreeNode {
56
57 readonly $name: string;
58
59 $children?: TreeNode[];
60
61 /**
62 * Value, if this is a terminal node
63 */
64 $value?: string;
65
66 /** Offset from 0 in the file, if available */
67 readonly $offset?: number;
68
69}
70```
71This makes it simple to expose any parsed structure so that it has path expression support.
72
73Implementations of the `FileParser` SPI interface parse files into `TreeNodes`:
74
75```typescript
76export interface FileParser {
77
78 /**
79 * Name of the top level production: name of the root TreeNode
80 */
81 rootName: string;
82
83 /**
84 * Parse a file, returning an AST
85 * @param {File} f
86 * @return {TreeNode} root tree node
87 */
88 toAst(f: File): Promise<TreeNode>;
89}
90
91```
92The `FileParserRegistry` type makes it possible to register multiple `FileParser` instances and have automatic resolution when working with multiple files, based on the first location step of path expressions. For this to work, the first location step must specific the node name. For example, the following will work because it identifiers the `magicThing` top level AST node:
93
94```
95/magicThing//subThing
96```
97Whereas this will not, as the top level node isn't specified:
98
99```
100//otherThing/that
101```
102