grammar-well 1.1.0 → 1.1.1
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/README.md +103 -0
- package/bootstrap.ts +2 -2
- package/package.json +11 -10
- package/src/compiler/compiler.ts +7 -6
- package/src/typings.ts +2 -2
- package/src/utility/general.ts +58 -3
- package/src/utility/lr.ts +3 -4
package/README.md
CHANGED
|
@@ -1,6 +1,109 @@
|
|
|
1
1
|
# Grammar Well
|
|
2
2
|
A cross-platform grammar compiler and interpreter. That aims to facilitate a simple way to create and evaluate custom grammars on the front-end and back-end. Formerly a TypeScript port of [Nearley](https://github.com/kach/nearley).
|
|
3
3
|
|
|
4
|
+
# Quick Start
|
|
5
|
+
### Install
|
|
6
|
+
`npm i grammar-well`
|
|
7
|
+
|
|
8
|
+
### Example
|
|
9
|
+
|
|
10
|
+
```Javascript
|
|
11
|
+
import { Compile, Parse } from 'grammar-well';
|
|
12
|
+
|
|
13
|
+
async function GrammarWellRunner(source, input) {
|
|
14
|
+
function Evalr(source) {
|
|
15
|
+
const module = { exports: null };
|
|
16
|
+
eval(source);
|
|
17
|
+
return module.exports;
|
|
18
|
+
}
|
|
19
|
+
const compiled = await Compile(source, { exportName: 'grammar' });
|
|
20
|
+
return Parse(Evalr(compiled)(), input, { algorithm: 'earley' });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
const source = `lexer: {{
|
|
25
|
+
start: "json"
|
|
26
|
+
|
|
27
|
+
json ->
|
|
28
|
+
- when: /\s+/ tag: "space"
|
|
29
|
+
- when: /-?(?:[0-9]|[1-9][0-9]+)(?:\.[0-9]+)?(?:[eE][-+]?[0-9]+)?\b/ tag: "number"
|
|
30
|
+
- when: /"(?:\\["bfnrt\/\\]|\\u[a-fA-F0-9]{4}|[^"\\])*"/ tag: "string"
|
|
31
|
+
- when: "{" tag: "{"
|
|
32
|
+
- when: "}" tag: "}"
|
|
33
|
+
- when: "[" tag: "["
|
|
34
|
+
- when: "]" tag: "]"
|
|
35
|
+
- when: "," tag: ","
|
|
36
|
+
- when: ":" tag: ":"
|
|
37
|
+
- when: "true" tag: "true"
|
|
38
|
+
- when: "false" tag: "false"
|
|
39
|
+
- when: "null" tag: "null"
|
|
40
|
+
}}
|
|
41
|
+
|
|
42
|
+
grammar: {{
|
|
43
|
+
json -> _ (object | array) _ : {{ $1[0] }}
|
|
44
|
+
|
|
45
|
+
object -> "{" _ "}" : {{ {} }}
|
|
46
|
+
| "{" _ pair (_ "," _ pair)* _ "}" : \${ extractObject }
|
|
47
|
+
|
|
48
|
+
array -> "[" _ "]" : {{ [] }}
|
|
49
|
+
| "[" _ value (_ "," _ value)* _ "]" : \${ extractArray }
|
|
50
|
+
|
|
51
|
+
value : {{ $0 }} ->
|
|
52
|
+
object
|
|
53
|
+
| array
|
|
54
|
+
| number
|
|
55
|
+
| string
|
|
56
|
+
| "true" : {{ true }}
|
|
57
|
+
| "false" : {{ false }}
|
|
58
|
+
| "null" : {{ null }}
|
|
59
|
+
|
|
60
|
+
number -> $number : {{ parseFloat($0.value) }}
|
|
61
|
+
|
|
62
|
+
string -> $string : {{ JSON.parse($0.value) }}
|
|
63
|
+
|
|
64
|
+
pair -> key:k _ ":" _ value:v : {{ [$k, $v] }}
|
|
65
|
+
|
|
66
|
+
key -> string : {{ $0 }}
|
|
67
|
+
|
|
68
|
+
_ -> $space? : {{ null }}
|
|
69
|
+
}}
|
|
70
|
+
|
|
71
|
+
head: \${
|
|
72
|
+
|
|
73
|
+
function extractPair(kv, output) {
|
|
74
|
+
if(kv[0]) { output[kv[0]] = kv[1]; }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function extractObject({data}) {
|
|
78
|
+
let output = {};
|
|
79
|
+
|
|
80
|
+
extractPair(data[2], output);
|
|
81
|
+
|
|
82
|
+
for (let i in data[3]) {
|
|
83
|
+
extractPair(data[3][i][3], output);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return output;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function extractArray({data}) {
|
|
90
|
+
let output = [data[2]];
|
|
91
|
+
|
|
92
|
+
for (let i in data[3]) {
|
|
93
|
+
output.push(data[3][i][3]);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return output;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
`
|
|
100
|
+
|
|
101
|
+
const input = `{"a":"string","b":true,"c":2}`
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
console.log(await GrammarWellRunner(source, input))
|
|
105
|
+
```
|
|
106
|
+
|
|
4
107
|
# Warning
|
|
5
108
|
A lot has changed and documentation needs to be written. For now here's the Grammar Well grammar file that parses Garmmar Well's syntax.
|
|
6
109
|
|
package/bootstrap.ts
CHANGED
|
@@ -9,8 +9,8 @@ const BaseDir = './src/grammars';
|
|
|
9
9
|
try {
|
|
10
10
|
console.log(fullpath(file))
|
|
11
11
|
if (/\.gwell$/.test(file)) {
|
|
12
|
-
const json = await Compile(read(file), {
|
|
13
|
-
const js = await Compile(read(file), { exportName: 'grammar',
|
|
12
|
+
const json = await Compile(read(file), { template: 'json' });
|
|
13
|
+
const js = await Compile(read(file), { exportName: 'grammar', template: 'esmodule' });
|
|
14
14
|
write(file.replace(/.gwell$/, '.json'), json);
|
|
15
15
|
write(file.replace(/.gwell$/, '.js'), js);
|
|
16
16
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grammar-well",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Cross-platform Parser written in TypeScript",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"prebuild": "tsc --build --clean",
|
|
8
8
|
"build": "tsc --build",
|
|
9
|
-
"bootstrap": "
|
|
10
|
-
"testing": "
|
|
9
|
+
"bootstrap": "tsx bootstrap.ts",
|
|
10
|
+
"testing": "tsx testing.ts",
|
|
11
11
|
"benchmark": "node tests/performance/benchmark.js",
|
|
12
12
|
"test": "nyc mocha tests/**/*.spec.ts",
|
|
13
13
|
"profile": "node tests/performance/profile.js"
|
|
@@ -25,19 +25,20 @@
|
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/chai": "^4.3.4",
|
|
27
27
|
"@types/mocha": "^10.0.1",
|
|
28
|
-
"@types/node": "^18.
|
|
29
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
30
|
-
"@typescript-eslint/parser": "^5.
|
|
28
|
+
"@types/node": "^18.15.0",
|
|
29
|
+
"@typescript-eslint/eslint-plugin": "^5.54.1",
|
|
30
|
+
"@typescript-eslint/parser": "^5.54.1",
|
|
31
31
|
"babel-cli": "^6.26.0",
|
|
32
32
|
"babel-preset-env": "^1.7.0",
|
|
33
33
|
"benny": "^3.7.1",
|
|
34
34
|
"chai": "^4.3.7",
|
|
35
|
-
"eslint": "^8.
|
|
36
|
-
"expect": "^29.
|
|
35
|
+
"eslint": "^8.36.0",
|
|
36
|
+
"expect": "^29.5.0",
|
|
37
37
|
"mocha": "^10.2.0",
|
|
38
38
|
"nyc": "^15.1.0",
|
|
39
39
|
"ts-node": "^10.9.1",
|
|
40
|
-
"
|
|
40
|
+
"tsx": "^3.12.3",
|
|
41
|
+
"typescript": "^4.9.5",
|
|
41
42
|
"yaml": "^2.2.1"
|
|
42
43
|
},
|
|
43
44
|
"keywords": [
|
|
@@ -49,4 +50,4 @@
|
|
|
49
50
|
"grammar",
|
|
50
51
|
"language"
|
|
51
52
|
]
|
|
52
|
-
}
|
|
53
|
+
}
|
package/src/compiler/compiler.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CompileOptions, GrammarBuilderContext,
|
|
1
|
+
import { CompileOptions, GrammarBuilderContext, TemplateFormat, LanguageDirective, ConfigDirective, GrammarBuilderSymbolRepeat, GrammarBuilderExpression, GeneratorGrammarRule, GrammarDirective, ImportDirective, LexerDirective, GrammarBuilderSymbolSubexpression, GrammarTypeLiteral, GeneratorGrammarSymbol, GrammarBuilderSymbol, GrammarBuilderRule } from "../typings";
|
|
2
2
|
|
|
3
3
|
import { Parser } from "../parser/parser";
|
|
4
4
|
import { FileSystemResolver } from "./import-resolver";
|
|
@@ -18,7 +18,7 @@ const BuiltInRegistry = {
|
|
|
18
18
|
string,
|
|
19
19
|
whitespace,
|
|
20
20
|
}
|
|
21
|
-
const
|
|
21
|
+
const TemplateFormats = {
|
|
22
22
|
_default: JavascriptOutput,
|
|
23
23
|
object: (grammar, exportName) => ({ grammar, exportName }),
|
|
24
24
|
json: JSONFormatter,
|
|
@@ -26,6 +26,7 @@ const OutputFormats = {
|
|
|
26
26
|
javascript: JavascriptOutput,
|
|
27
27
|
module: ESMOutput,
|
|
28
28
|
esmodule: ESMOutput,
|
|
29
|
+
esm: ESMOutput,
|
|
29
30
|
ts: TypescriptFormat,
|
|
30
31
|
typescript: TypescriptFormat
|
|
31
32
|
}
|
|
@@ -33,7 +34,7 @@ const OutputFormats = {
|
|
|
33
34
|
export async function Compile(rules: string | LanguageDirective | (LanguageDirective[]), config: CompileOptions = {}) {
|
|
34
35
|
const builder = new GrammarBuilder(config);
|
|
35
36
|
await builder.import(rules as any);
|
|
36
|
-
return builder.export(config.
|
|
37
|
+
return builder.export(config.template);
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
export class GrammarBuilder {
|
|
@@ -51,11 +52,11 @@ export class GrammarBuilder {
|
|
|
51
52
|
this.generator.state.grammar.uuids = this.context.uuids;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
export<T extends
|
|
55
|
+
export<T extends TemplateFormat = '_default'>(format: T, name: string = 'GWLanguage'): ReturnType<typeof TemplateFormats[T]> {
|
|
55
56
|
const grammar = this.generator.state;
|
|
56
57
|
const output = format || grammar.config.preprocessor || '_default';
|
|
57
|
-
if (
|
|
58
|
-
return
|
|
58
|
+
if (TemplateFormats[output]) {
|
|
59
|
+
return TemplateFormats[output](this.generator, name);
|
|
59
60
|
}
|
|
60
61
|
throw new Error("No such preprocessor: " + output)
|
|
61
62
|
}
|
package/src/typings.ts
CHANGED
|
@@ -12,10 +12,10 @@ export interface CompileOptions {
|
|
|
12
12
|
resolver?: ImportResolverConstructor;
|
|
13
13
|
resolverInstance?: ImportResolver;
|
|
14
14
|
exportName?: string;
|
|
15
|
-
|
|
15
|
+
template?: TemplateFormat;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export type
|
|
18
|
+
export type TemplateFormat = '_default' | 'object' | 'json' | 'js' | 'javascript' | 'module' | 'esmodule' | 'esm' | 'ts' | 'typescript'
|
|
19
19
|
|
|
20
20
|
export interface GrammarBuilderContext {
|
|
21
21
|
alreadyCompiled: Set<string>;
|
package/src/utility/general.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { Dictionary, GrammarRuleSymbol } from "../typings";
|
|
|
4
4
|
export class Collection<T> {
|
|
5
5
|
categorized: Dictionary<Dictionary<number>> = {};
|
|
6
6
|
private uncategorized = new Map<T, number>();
|
|
7
|
-
|
|
7
|
+
items: T[] = [];
|
|
8
8
|
|
|
9
9
|
constructor(ref: T[] = []) {
|
|
10
10
|
for (const s of ref) {
|
|
@@ -23,6 +23,17 @@ export class Collection<T> {
|
|
|
23
23
|
return this.items[typeof id == 'string' ? parseInt(id) : id];
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
has(ref: T) {
|
|
27
|
+
const c = this.resolve(ref);
|
|
28
|
+
if (c)
|
|
29
|
+
return (c.key in this.categorized[c.category])
|
|
30
|
+
return this.uncategorized.has(ref);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
redirect(source: T, target: T) {
|
|
34
|
+
this.items[this.encode(source)] = target;
|
|
35
|
+
}
|
|
36
|
+
|
|
26
37
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
|
|
27
38
|
resolve(_: T): { category: keyof Collection<T>['categorized'], key: string } | void { }
|
|
28
39
|
|
|
@@ -63,7 +74,7 @@ export class SymbolCollection extends Collection<GrammarRuleSymbol>{
|
|
|
63
74
|
return { category: 'literalS', key: symbol.literal }
|
|
64
75
|
} else if ('token' in symbol) {
|
|
65
76
|
return { category: 'token', key: symbol.token }
|
|
66
|
-
} else if (
|
|
77
|
+
} else if (symbol instanceof RegExp) {
|
|
67
78
|
return { category: 'regex', key: symbol.toString() }
|
|
68
79
|
} else if (typeof symbol == 'function') {
|
|
69
80
|
return { category: 'function', key: symbol.toString() }
|
|
@@ -117,4 +128,48 @@ export class Matrix<T> {
|
|
|
117
128
|
}
|
|
118
129
|
}
|
|
119
130
|
|
|
120
|
-
|
|
131
|
+
export function Flatten(obj: any[] | { [key: string]: any }): FlatObject {
|
|
132
|
+
const collection = new Collection();
|
|
133
|
+
function Traverse(ref: any) {
|
|
134
|
+
if (collection.has(ref)) {
|
|
135
|
+
return collection.encode(ref)
|
|
136
|
+
}
|
|
137
|
+
if (Array.isArray(ref)) {
|
|
138
|
+
collection.redirect(ref, ref.map(v => Traverse(v)));
|
|
139
|
+
} else if (typeof ref === 'object') {
|
|
140
|
+
const o = {};
|
|
141
|
+
for (const k in ref) {
|
|
142
|
+
o[k] = Traverse(ref[k])
|
|
143
|
+
}
|
|
144
|
+
collection.redirect(ref, o);
|
|
145
|
+
} else if (typeof ref === 'function') {
|
|
146
|
+
return collection.encode(ref.toString());
|
|
147
|
+
}
|
|
148
|
+
return collection.encode(ref);
|
|
149
|
+
}
|
|
150
|
+
Traverse(obj);
|
|
151
|
+
return collection.items as any;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function Unflatten(items: FlatObject) {
|
|
155
|
+
const visited = new Set();
|
|
156
|
+
function Traverse(id: number) {
|
|
157
|
+
if (visited.has(id)) {
|
|
158
|
+
return items[id];
|
|
159
|
+
}
|
|
160
|
+
visited.add(id);
|
|
161
|
+
if (Array.isArray(items[id])) {
|
|
162
|
+
return (items[id] as any[]).map(v => Traverse(id));
|
|
163
|
+
} else if (typeof items[id] === 'object') {
|
|
164
|
+
for (const k in items[id] as { [key: string]: any }) {
|
|
165
|
+
items[id][k] = Traverse(id[k])
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return items[id];
|
|
169
|
+
}
|
|
170
|
+
return Traverse(0);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
type FlatObject = (boolean | number | string | (number[]) | { [key: string]: number })[];
|
|
174
|
+
|
|
175
|
+
type GetCallbackOrValue<T> = T extends (...args: any) => any ? ReturnType<T> : T;
|
package/src/utility/lr.ts
CHANGED
|
@@ -5,10 +5,9 @@ import { Collection, SymbolCollection } from "./general";
|
|
|
5
5
|
export class CanonicalCollection {
|
|
6
6
|
rules: Collection<GrammarRule> = new Collection();
|
|
7
7
|
states: { [key: string]: State } = Object.create(null)
|
|
8
|
-
symbols: SymbolCollection;
|
|
9
|
-
|
|
10
|
-
constructor(public grammar: LanguageGrammar
|
|
11
|
-
this.symbols = grammar.symbols || new SymbolCollection();
|
|
8
|
+
symbols: SymbolCollection = new SymbolCollection();
|
|
9
|
+
|
|
10
|
+
constructor(public grammar: LanguageGrammar) {
|
|
12
11
|
const augmented = { name: Symbol() as unknown as string, symbols: [grammar.start] }
|
|
13
12
|
grammar.rules[augmented.name] = [augmented];
|
|
14
13
|
this.addState([{ rule: augmented, dot: 0 }]);
|