@traqula/chevrotain 0.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +22 -0
- package/README.md +186 -0
- package/dist/cjs/lib/index.js +9614 -0
- package/dist/esm/lib/index.d.ts +1 -0
- package/dist/esm/lib/index.js +2 -0
- package/dist/esm/lib/index.js.map +1 -0
- package/package.json +45 -0
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright © 2024–now Jitse De Smet
|
|
4
|
+
Comunica Association and Ghent University – imec, Belgium
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
|
14
|
+
all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Traqula core package
|
|
2
|
+
|
|
3
|
+
Traqula core contains core components of Traqula.
|
|
4
|
+
Most importantly, its [lexer builder](./lib/lexer-builder/LexerBuilder.ts), [parser builder](./lib/parser-builder/parserBuilder.ts), and [generator builder](./lib/generator-builder/generatorBuilder.ts).
|
|
5
|
+
This library heavily relies on the amazing [Chevrotain package](https://chevrotain.io/docs/).
|
|
6
|
+
Knowing the basics of that package will allow you to quickly generate your own grammars.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @traqula/core
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
or
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
yarn add @traqula/core
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
Each parser contains two steps:
|
|
23
|
+
1. a lexer
|
|
24
|
+
2. a grammar + abstract syntax tree generation step.
|
|
25
|
+
|
|
26
|
+
Sometimes grammar definitions and abstract syntax tree generation is split into separate steps.
|
|
27
|
+
In this library, we choose to keep the two together when building a parser.
|
|
28
|
+
|
|
29
|
+
### Lexer Builder
|
|
30
|
+
|
|
31
|
+
To tackle the first step, a lexer should be created.
|
|
32
|
+
This is a system that separates different groups of characters into annotated groups.
|
|
33
|
+
In human language for example the sentence 'I eat apples' is lexed into different groups called **tokens** namely `words` and `spaces`:
|
|
34
|
+
`I`, ` `, `eat`, ` `, `apples`.
|
|
35
|
+
|
|
36
|
+
To create a token definition, you use the provided function `createToken` like:
|
|
37
|
+
```typescript
|
|
38
|
+
const select = createToken({ name: 'Select', pattern: /select/i, label: 'SELECT' });
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Lexer definitions are then put in a list and when a lexer is build, the lexer will match a string to the [**first token in the list**](https://chevrotain.io/docs/tutorial/step1_lexing.html#creating-the-lexer) that matches.
|
|
42
|
+
Note that the order of definitions in the list is thus essential.
|
|
43
|
+
|
|
44
|
+
We therefore use a [lexer builder](./lib/lexer-builder/LexerBuilder.ts) which allows you to easily:
|
|
45
|
+
1. change the order of lexer rules,
|
|
46
|
+
2. and create a new lexer staring from an existing one.
|
|
47
|
+
|
|
48
|
+
Creating a builder is as easy as:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
const sparql11Tokens = LexerBuilder.create(<const> [select, describe]);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
A new lexer can be created from an existing one, and altered by calling:
|
|
55
|
+
```typescript
|
|
56
|
+
const sparql11AdjustTokens = LexerBuilder.create(sparql11Tokens).addBefore(select, BuiltInAdjust);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Parser Builder
|
|
60
|
+
|
|
61
|
+
The grammar builder is used to link together grammar rules such that they can be converted into a parser.
|
|
62
|
+
Grammar rule definitions come in the form of [ParserRule](./lib/parser-builder/ruleDefTypes.ts) objects.
|
|
63
|
+
Each `ParserRule` object contains its name and its returnType.
|
|
64
|
+
Optionally, it can also contain arguments that should be provided to the SUBRULE calls.
|
|
65
|
+
A simple example of a grammar rule is the rule bellow that allows you to parse booleanLiterals.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
/**
|
|
69
|
+
* Parses a boolean literal.
|
|
70
|
+
* [[134]](https://www.w3.org/TR/sparql11-query/#rBooleanLiteral)
|
|
71
|
+
*/
|
|
72
|
+
export const booleanLiteral: ParserRule<'booleanLiteral', LiteralTerm> = <const> {
|
|
73
|
+
name: 'booleanLiteral',
|
|
74
|
+
impl: ({ CONSUME, OR, context }) => () => OR([
|
|
75
|
+
{ ALT: () => context.dataFactory.literal(
|
|
76
|
+
CONSUME(l.true_).image.toLowerCase(),
|
|
77
|
+
context.dataFactory.namedNode(CommonIRIs.BOOLEAN),
|
|
78
|
+
) },
|
|
79
|
+
{ ALT: () => context.dataFactory.literal(
|
|
80
|
+
CONSUME(l.false_).image.toLowerCase(),
|
|
81
|
+
context.dataFactory.namedNode(CommonIRIs.BOOLEAN),
|
|
82
|
+
) },
|
|
83
|
+
]),
|
|
84
|
+
};
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The `impl` member of `ParserRule` is a function that receives:
|
|
88
|
+
1. essential functions to create a grammar rule (capitalized members),
|
|
89
|
+
2. a context object that can be used by the rules,
|
|
90
|
+
3. a cache object ([WeakMap](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)) that can be used to cache the creation of long lists in the parser, [increasing parser performance](https://chevrotain.io/docs/guide/performance.html#caching-arrays-of-alternatives).
|
|
91
|
+
|
|
92
|
+
You cannot unpack the context entry in the function definition itself because the parser uses a [recording phase](https://chevrotain.io/docs/guide/internals.html#grammar-recording) to optimize itself. During this phase, the context entry will be undefined, as such, it can only be accessed within the `ACTION` function.
|
|
93
|
+
|
|
94
|
+
The result of an `impl` call is a function called a `rule`.
|
|
95
|
+
Rules can be [parameterized](https://chevrotain.io/docs/features/parameterized_rules.html), although I have not found a scenario where that is usefully.
|
|
96
|
+
Personally I create a function that can be used to create multiple `ParserRule` objects.
|
|
97
|
+
The result of a rule should match the type provided in the `ParserRule` definition, and is the result of a call of `SUBRULE` with that rule.
|
|
98
|
+
|
|
99
|
+
##### Testing the correctness of your parser
|
|
100
|
+
By default, the parser builder will construct a parser that does not perform validation (to be more speedy).
|
|
101
|
+
When creating a parser, you best enable the validation by passing a context to the parser builder like:
|
|
102
|
+
```typescript
|
|
103
|
+
const context = {
|
|
104
|
+
tokenVocabulary: myLexerVoc,
|
|
105
|
+
lexerConfig: {
|
|
106
|
+
skipValidations: false,
|
|
107
|
+
ensureOptimizations: true,
|
|
108
|
+
},
|
|
109
|
+
parserConfig: {
|
|
110
|
+
skipValidations: false,
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
#### Patching rules
|
|
116
|
+
|
|
117
|
+
When a rule definition calls to a subrule using `SUBRULE(mySub)`, the implementation itself is not necessarily called.
|
|
118
|
+
That is because the SUBRULE function will call the function with the same name as `mySub` that is present in the current grammarBuilder.
|
|
119
|
+
|
|
120
|
+
A builder is thus free to override definitions as it pleases. Doing so does however **break the types** and should thus only be done with care.
|
|
121
|
+
An example patch is:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
const myBuilder = Builder
|
|
125
|
+
.createBuilder(<const> [selectOrDescribe, selectRule, describeRule])
|
|
126
|
+
.patchRule(selectRuleAlternative);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
When `selectOrDescribe` calls what it thinks to be `selectRule`,
|
|
130
|
+
it will instead call `selectRuleAlternative` since it overwrote the function `selectRule` with the same name.
|
|
131
|
+
|
|
132
|
+
When you are creating a new parser,
|
|
133
|
+
it might be good to test your parser by setting `skipValidations: false` in the context of the `.build` function.
|
|
134
|
+
|
|
135
|
+
### Generator Builder
|
|
136
|
+
|
|
137
|
+
The generator builder function in much the same as the [parser builder](#parser-builder).
|
|
138
|
+
Your builder expects objects of type [GeneratorRule](lib/generator-builder/generatorTypes.ts),
|
|
139
|
+
containing the implementation of the generator in the `gImpl` member.
|
|
140
|
+
The `gImpl` function gets essential functions to create a generator rule (capitalized members),
|
|
141
|
+
returning a function that will get the AST and context, returning a string.
|
|
142
|
+
For generator rules, you can unpack the context since no recording phase is present in this case.
|
|
143
|
+
The idea is that GeneratorRules and ParserRules can be tied together in the same object, as such, similar behaviour is grouped together.
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
/**
|
|
147
|
+
* Parses a named node, either as an IRI or as a prefixed name.
|
|
148
|
+
* [[136]](https://www.w3.org/TR/sparql11-query/#riri)
|
|
149
|
+
*/
|
|
150
|
+
export const iri: GeneratorRule<'iri', IriTerm> = <const> {
|
|
151
|
+
name: 'iri',
|
|
152
|
+
gImpl: ({ PRINT }) => ast => { PRINT(ast.value) },
|
|
153
|
+
};
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
While implementing a generator, you can easily support pretty print indentation manipulating `traqulaIndentation` context item.
|
|
157
|
+
The key for this context item can be accessed like:
|
|
158
|
+
```typescript
|
|
159
|
+
import { traqulaIndentation } from '@traqula/core';
|
|
160
|
+
C[traqulaIndentation] += 2;
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### A word on round tripping:
|
|
164
|
+
|
|
165
|
+
The generator builder can significantly help you with creating a round tripping parser.
|
|
166
|
+
Basically what that allows you to do is to keep information that the AST finds 'unimportant' within the generated string.
|
|
167
|
+
Take for example capitalization and spaces in the sparql spec.
|
|
168
|
+
Both are ignored in the AST, but if you want to generate the same string out of your AST, yuo need to store them somewhere.
|
|
169
|
+
Traqula helps you store this information using it's `Node` `Localization`.
|
|
170
|
+
|
|
171
|
+
Localization basically allows you to remember what _portion of the original string_ a node represents.
|
|
172
|
+
Take for example the `SENTENCE`: `I Love Traqula`, If we ignore spaces and caps in the ast, a valid representation would be:
|
|
173
|
+
```
|
|
174
|
+
SENTENCE-node{ words: [ WORD-node{ value: "i" }, WORD-node{ value: "love" }, WORD-node{ value: "traqula" } ] }
|
|
175
|
+
```
|
|
176
|
+
If we generated we would loe the capitalisation and get: `i love traqula` for example.
|
|
177
|
+
Round tripping will add a `source localization` for each node,
|
|
178
|
+
we therefore register that our SENTENCE starts at 0 and ends at 19, while our words have ranges 0-1, 2-6, 12-19.
|
|
179
|
+
Using this information our generator can reconstruct the original string (given the original string).
|
|
180
|
+
The magic happens when we start manipulating the words, so imagine we want to lowercase the word 'Love',
|
|
181
|
+
we would simply annotate in the `localization` that the node should be generated (and not reconstructed),
|
|
182
|
+
and we can generate the sentence: `I love Traqula`.
|
|
183
|
+
|
|
184
|
+
To support this feature, the generator requires that your AST follows a tree structure with respect to the ranges.
|
|
185
|
+
That means that a node cannot start later, or end earlier than its children.
|
|
186
|
+
In our example: A sentence cannot start after the first word start, nor can it end before the last word ends.
|