1 | # Path Expressions
|
2 |
|
3 | It'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 |
|
5 | Path 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 |
|
9 | The `findMatches` function in `astUtils` makes it possible to execute a path expression across many files, as identified by a glob pattern:
|
10 |
|
11 | ```typescript
|
12 | const mg = Microgrammar.fromString<Person>("${name}:${age}", {
|
13 | age: Integer,
|
14 | });
|
15 | const fpr = new DefaultFileParserRegistry().addParser(
|
16 | new MicrogrammarBasedFileParser("people", "person", mg));
|
17 |
|
18 | findMatches(project, AllFiles, fpr, "/people/person/name")
|
19 | .then(matches => ...
|
20 | ```
|
21 |
|
22 | It'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 |
|
24 | This programming model is consistent across all grammars.
|
25 |
|
26 | ## Navigation
|
27 | All matches implement the `TreeNode` interface, exposing `$value` and `$children` properties.
|
28 |
|
29 | Additional navigation methods are added for convenience.
|
30 |
|
31 | Imagine the following tree:
|
32 |
|
33 | ```
|
34 | people
|
35 | ├── person
|
36 | ├── name
|
37 | ├── age
|
38 | ├── person
|
39 | ├── name
|
40 | ├── age
|
41 | ```
|
42 | This 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 |
|
44 | However, it can be more convenient to use properties.
|
45 |
|
46 | The `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 |
|
48 | When 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
|
51 | Path expressions and `astUtils` are backed by implementations of the simple `TreeNode` interface:
|
52 |
|
53 |
|
54 | ```typescript
|
55 | export 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 | ```
|
71 | This makes it simple to expose any parsed structure so that it has path expression support.
|
72 |
|
73 | Implementations of the `FileParser` SPI interface parse files into `TreeNodes`:
|
74 |
|
75 | ```typescript
|
76 | export 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 | ```
|
92 | The `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 | ```
|
97 | Whereas this will not, as the top level node isn't specified:
|
98 |
|
99 | ```
|
100 | //otherThing/that
|
101 | ```
|
102 |
|