parseman 0.23.0 → 0.24.0
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/dist/combinators/keywords.d.ts.map +1 -1
- package/dist/combinators/literal.d.ts.map +1 -1
- package/dist/combinators/parser.d.ts +6 -0
- package/dist/combinators/parser.d.ts.map +1 -1
- package/dist/combinators/regex.d.ts.map +1 -1
- package/dist/combinators/trivia-skip.d.ts.map +1 -1
- package/dist/index.cjs +156 -39
- package/dist/index.cjs.map +3 -3
- package/dist/index.js +156 -39
- package/dist/index.js.map +3 -3
- package/dist/plugin/index.cjs +156 -39
- package/dist/plugin/index.cjs.map +3 -3
- package/dist/plugin/index.js +156 -39
- package/dist/plugin/index.js.map +3 -3
- package/dist/spec/ebnf.d.ts +13 -0
- package/dist/spec/ebnf.d.ts.map +1 -0
- package/dist/spec/index.cjs +977 -0
- package/dist/spec/index.cjs.map +7 -0
- package/dist/spec/index.d.ts +15 -0
- package/dist/spec/index.d.ts.map +1 -0
- package/dist/spec/index.js +954 -0
- package/dist/spec/index.js.map +7 -0
- package/dist/spec/model.d.ts +117 -0
- package/dist/spec/model.d.ts.map +1 -0
- package/dist/spec/railroad-lib.d.ts +11 -0
- package/dist/spec/railroad-lib.d.ts.map +1 -0
- package/dist/spec/railroad.d.ts +18 -0
- package/dist/spec/railroad.d.ts.map +1 -0
- package/package.json +6 -1
|
@@ -0,0 +1,977 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/spec/index.ts
|
|
21
|
+
var spec_exports = {};
|
|
22
|
+
__export(spec_exports, {
|
|
23
|
+
buildSpecModel: () => buildSpecModel,
|
|
24
|
+
renderEBNF: () => renderEBNF,
|
|
25
|
+
renderExpr: () => renderExpr,
|
|
26
|
+
renderRailroadHtml: () => renderRailroadHtml,
|
|
27
|
+
toEBNF: () => toEBNF,
|
|
28
|
+
toRailroadHtml: () => toRailroadHtml
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(spec_exports);
|
|
31
|
+
|
|
32
|
+
// src/combinators/parser.ts
|
|
33
|
+
var RULE_ORDER = "__parsemanRuleOrder";
|
|
34
|
+
|
|
35
|
+
// src/spec/model.ts
|
|
36
|
+
function ruleNameOf(c) {
|
|
37
|
+
return c._ruleName;
|
|
38
|
+
}
|
|
39
|
+
function resolveLazy(c) {
|
|
40
|
+
const def = c._def;
|
|
41
|
+
if (def.tag !== "lazy") return void 0;
|
|
42
|
+
try {
|
|
43
|
+
return def.thunk();
|
|
44
|
+
} catch {
|
|
45
|
+
return void 0;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function defaultRegexDisplay(source) {
|
|
49
|
+
return `/${source}/`;
|
|
50
|
+
}
|
|
51
|
+
var Builder = class {
|
|
52
|
+
seen = /* @__PURE__ */ new Set();
|
|
53
|
+
pending = [];
|
|
54
|
+
pendingNames = /* @__PURE__ */ new Set();
|
|
55
|
+
record;
|
|
56
|
+
opts;
|
|
57
|
+
productions = [];
|
|
58
|
+
constructor(record, opts) {
|
|
59
|
+
this.record = record;
|
|
60
|
+
this.opts = opts;
|
|
61
|
+
}
|
|
62
|
+
enqueue(name, comb) {
|
|
63
|
+
if (this.seen.has(name)) return;
|
|
64
|
+
if (this.pendingNames.has(name)) return;
|
|
65
|
+
this.pendingNames.add(name);
|
|
66
|
+
this.pending.push({ name, comb });
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Seed and drain in phases. Each phase seeds its names, then BFS-drains the
|
|
70
|
+
* queue (following references) before the next phase seeds. This lets callers
|
|
71
|
+
* express "entry first, then its reachable closure, then any leftovers" by
|
|
72
|
+
* splitting the seed across phases; a single phase with every rule yields plain
|
|
73
|
+
* declaration order (references, already queued, keep their declared slot).
|
|
74
|
+
*/
|
|
75
|
+
run(phases) {
|
|
76
|
+
for (const phase of phases) {
|
|
77
|
+
for (const name of phase) {
|
|
78
|
+
const comb = this.record[name];
|
|
79
|
+
if (comb) this.enqueue(name, comb);
|
|
80
|
+
}
|
|
81
|
+
this.drain();
|
|
82
|
+
}
|
|
83
|
+
return { productions: this.productions };
|
|
84
|
+
}
|
|
85
|
+
drain() {
|
|
86
|
+
while (this.pending.length > 0) {
|
|
87
|
+
const { name, comb } = this.pending.shift();
|
|
88
|
+
this.pendingNames.delete(name);
|
|
89
|
+
if (this.seen.has(name)) continue;
|
|
90
|
+
this.seen.add(name);
|
|
91
|
+
const expr = this.ruleBody(comb, name);
|
|
92
|
+
this.productions.push({ name, expr, trivia: comb._meta.isTrivia === true });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** Walk a rule's top-level combinator, transparently unwrapping its own self-ref. */
|
|
96
|
+
ruleBody(comb, name) {
|
|
97
|
+
const pinned = this.opts.terminals?.[name];
|
|
98
|
+
if (pinned !== void 0) return { kind: "terminal", text: pinned, literal: false };
|
|
99
|
+
let c = comb;
|
|
100
|
+
const guard = /* @__PURE__ */ new Set();
|
|
101
|
+
while (c._def.tag === "lazy" && ruleNameOf(c) === name && !guard.has(c)) {
|
|
102
|
+
guard.add(c);
|
|
103
|
+
const next = resolveLazy(c);
|
|
104
|
+
if (!next) break;
|
|
105
|
+
c = next;
|
|
106
|
+
}
|
|
107
|
+
return this.walk(c, true);
|
|
108
|
+
}
|
|
109
|
+
/** Walk any combinator. `isRuleRoot` suppresses treating it as a self-reference. */
|
|
110
|
+
walk(c, isRuleRoot = false) {
|
|
111
|
+
const rn = ruleNameOf(c);
|
|
112
|
+
if (rn !== void 0 && !isRuleRoot) {
|
|
113
|
+
if (!this.record[rn]) this.record[rn] = c;
|
|
114
|
+
this.enqueue(rn, c);
|
|
115
|
+
return { kind: "ref", name: rn };
|
|
116
|
+
}
|
|
117
|
+
return this.walkDef(c._def, c);
|
|
118
|
+
}
|
|
119
|
+
walkDef(def, self) {
|
|
120
|
+
switch (def.tag) {
|
|
121
|
+
case "literal":
|
|
122
|
+
return { kind: "terminal", text: def.value, literal: true };
|
|
123
|
+
case "regex": {
|
|
124
|
+
const shown = this.opts.regexDisplay?.(def.source, def.flags) ?? defaultRegexDisplay(def.source);
|
|
125
|
+
return { kind: "terminal", text: shown, literal: false };
|
|
126
|
+
}
|
|
127
|
+
case "keywords":
|
|
128
|
+
return {
|
|
129
|
+
kind: "choice",
|
|
130
|
+
items: def.words.map((w) => ({ kind: "terminal", text: w, literal: true }))
|
|
131
|
+
};
|
|
132
|
+
case "sequence": {
|
|
133
|
+
const items = def.parsers.map((p) => this.walk(p)).filter(nonEmpty);
|
|
134
|
+
return flattenSeq(items);
|
|
135
|
+
}
|
|
136
|
+
case "choice": {
|
|
137
|
+
const items = def.parsers.map((p) => this.walk(p)).filter(nonEmpty);
|
|
138
|
+
return flattenChoice(items);
|
|
139
|
+
}
|
|
140
|
+
case "many":
|
|
141
|
+
return { kind: "star", item: this.walk(def.parser) };
|
|
142
|
+
case "oneOrMore":
|
|
143
|
+
return { kind: "plus", item: this.walk(def.parser) };
|
|
144
|
+
case "optional":
|
|
145
|
+
return { kind: "opt", item: this.walk(def.parser) };
|
|
146
|
+
case "sepBy":
|
|
147
|
+
return { kind: "sepBy", item: this.walk(def.parser), sep: this.walk(def.separator) };
|
|
148
|
+
case "not":
|
|
149
|
+
return { kind: "not", item: this.walk(def.parser) };
|
|
150
|
+
// Transparent semantic wrappers — render the inner syntax.
|
|
151
|
+
case "transform":
|
|
152
|
+
case "token":
|
|
153
|
+
case "label":
|
|
154
|
+
case "field":
|
|
155
|
+
case "expect":
|
|
156
|
+
case "withCtx":
|
|
157
|
+
case "node":
|
|
158
|
+
return this.walk(def.parser);
|
|
159
|
+
case "skip":
|
|
160
|
+
return this.walk(def.main);
|
|
161
|
+
case "grammar":
|
|
162
|
+
return this.walk(def.parser);
|
|
163
|
+
// Error recovery — render the intended sub-parser, drop the sentinel.
|
|
164
|
+
case "recover":
|
|
165
|
+
return this.walk(def.parser);
|
|
166
|
+
case "scanTo":
|
|
167
|
+
return { kind: "annotation", text: "\u2026" };
|
|
168
|
+
// Trivia — elided by default; included as an annotation when asked.
|
|
169
|
+
case "trivia":
|
|
170
|
+
return this.opts.includeTrivia ? this.walk(def.parser) : { kind: "empty" };
|
|
171
|
+
case "guard":
|
|
172
|
+
return { kind: "empty" };
|
|
173
|
+
case "lazy": {
|
|
174
|
+
const inner = resolveLazy(self);
|
|
175
|
+
return inner ? this.walk(inner) : { kind: "annotation", text: "?" };
|
|
176
|
+
}
|
|
177
|
+
case "unknown":
|
|
178
|
+
default:
|
|
179
|
+
return { kind: "annotation", text: "?" };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
function nonEmpty(n) {
|
|
184
|
+
return n.kind !== "empty";
|
|
185
|
+
}
|
|
186
|
+
function flattenSeq(items) {
|
|
187
|
+
const flat = [];
|
|
188
|
+
for (const it of items) {
|
|
189
|
+
if (it.kind === "seq") flat.push(...it.items);
|
|
190
|
+
else flat.push(it);
|
|
191
|
+
}
|
|
192
|
+
if (flat.length === 0) return { kind: "empty" };
|
|
193
|
+
if (flat.length === 1) return flat[0];
|
|
194
|
+
return { kind: "seq", items: flat };
|
|
195
|
+
}
|
|
196
|
+
function flattenChoice(items) {
|
|
197
|
+
const flat = [];
|
|
198
|
+
for (const it of items) {
|
|
199
|
+
if (it.kind === "choice") flat.push(...it.items);
|
|
200
|
+
else flat.push(it);
|
|
201
|
+
}
|
|
202
|
+
if (flat.length === 0) return { kind: "empty" };
|
|
203
|
+
if (flat.length === 1) return flat[0];
|
|
204
|
+
return { kind: "choice", items: flat };
|
|
205
|
+
}
|
|
206
|
+
function toRecord(grammar) {
|
|
207
|
+
if (isCombinator(grammar)) {
|
|
208
|
+
const name = ruleNameOf(grammar) ?? "start";
|
|
209
|
+
return { [name]: grammar };
|
|
210
|
+
}
|
|
211
|
+
return grammar;
|
|
212
|
+
}
|
|
213
|
+
function isCombinator(x) {
|
|
214
|
+
return typeof x === "object" && x !== null && typeof x.parse === "function" && "_def" in x;
|
|
215
|
+
}
|
|
216
|
+
function declarationOrder(record) {
|
|
217
|
+
const declared = record[RULE_ORDER];
|
|
218
|
+
if (Array.isArray(declared)) return declared.filter((k) => k in record);
|
|
219
|
+
return Object.keys(record);
|
|
220
|
+
}
|
|
221
|
+
function buildSpecModel(grammar, options = {}) {
|
|
222
|
+
const record = toRecord(grammar);
|
|
223
|
+
const decl = declarationOrder(record);
|
|
224
|
+
let phases;
|
|
225
|
+
if (options.order) {
|
|
226
|
+
phases = [options.order];
|
|
227
|
+
} else if (options.root !== void 0) {
|
|
228
|
+
phases = [Array.isArray(options.root) ? options.root : [options.root]];
|
|
229
|
+
} else if (options.sort === "reachable") {
|
|
230
|
+
phases = decl.length > 0 ? [[decl[0]], decl.slice(1)] : [decl];
|
|
231
|
+
} else {
|
|
232
|
+
phases = [decl];
|
|
233
|
+
}
|
|
234
|
+
const model = new Builder({ ...record }, options).run(phases);
|
|
235
|
+
if (!options.includeTrivia) {
|
|
236
|
+
model.productions = model.productions.filter((p) => !p.trivia);
|
|
237
|
+
}
|
|
238
|
+
return model;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/spec/ebnf.ts
|
|
242
|
+
var ALT = 1;
|
|
243
|
+
var CAT = 2;
|
|
244
|
+
var ATOM = 3;
|
|
245
|
+
function quote(s) {
|
|
246
|
+
if (s.includes('"') && !s.includes("'")) return `'${s}'`;
|
|
247
|
+
return `"${s.replace(/"/g, '\\"')}"`;
|
|
248
|
+
}
|
|
249
|
+
function wrap(inner, innerLevel, minLevel) {
|
|
250
|
+
return innerLevel < minLevel ? `(${inner})` : inner;
|
|
251
|
+
}
|
|
252
|
+
function render(node) {
|
|
253
|
+
switch (node.kind) {
|
|
254
|
+
case "terminal":
|
|
255
|
+
return { text: node.literal ? quote(node.text) : node.text, level: ATOM };
|
|
256
|
+
case "ref":
|
|
257
|
+
return { text: node.name, level: ATOM };
|
|
258
|
+
case "empty":
|
|
259
|
+
return { text: "", level: ATOM };
|
|
260
|
+
case "annotation":
|
|
261
|
+
return { text: `/* ${node.text} */`, level: ATOM };
|
|
262
|
+
case "seq": {
|
|
263
|
+
const text = node.items.map((it) => {
|
|
264
|
+
const r = render(it);
|
|
265
|
+
return wrap(r.text, r.level, CAT);
|
|
266
|
+
}).filter((t) => t.length > 0).join(" ");
|
|
267
|
+
return { text, level: CAT };
|
|
268
|
+
}
|
|
269
|
+
case "choice": {
|
|
270
|
+
const text = node.items.map((it) => {
|
|
271
|
+
const r = render(it);
|
|
272
|
+
return wrap(r.text, r.level, ALT);
|
|
273
|
+
}).join(" | ");
|
|
274
|
+
return { text, level: ALT };
|
|
275
|
+
}
|
|
276
|
+
case "star":
|
|
277
|
+
case "plus":
|
|
278
|
+
case "opt": {
|
|
279
|
+
const r = render(node.item);
|
|
280
|
+
const op = node.kind === "star" ? "*" : node.kind === "plus" ? "+" : "?";
|
|
281
|
+
return { text: `${wrap(r.text, r.level, ATOM)}${op}`, level: ATOM };
|
|
282
|
+
}
|
|
283
|
+
case "sepBy": {
|
|
284
|
+
const item = render(node.item);
|
|
285
|
+
const sep = render(node.sep);
|
|
286
|
+
const itemA = wrap(item.text, item.level, ATOM);
|
|
287
|
+
const sepA = wrap(sep.text, sep.level, CAT);
|
|
288
|
+
return { text: `${itemA} (${sepA} ${itemA})*`, level: CAT };
|
|
289
|
+
}
|
|
290
|
+
case "not": {
|
|
291
|
+
const r = render(node.item);
|
|
292
|
+
return { text: `!${wrap(r.text, r.level, ATOM)}`, level: ATOM };
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function renderProduction(p) {
|
|
297
|
+
const { text } = render(p.expr);
|
|
298
|
+
return `${p.name} ::= ${text || "/* empty */"}`;
|
|
299
|
+
}
|
|
300
|
+
function renderEBNF(model) {
|
|
301
|
+
return model.productions.map(renderProduction).join("\n") + "\n";
|
|
302
|
+
}
|
|
303
|
+
function renderExpr(node) {
|
|
304
|
+
return render(node).text;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// src/spec/railroad-lib.ts
|
|
308
|
+
var RAILROAD_JS = `/*
|
|
309
|
+
Railroad Diagrams
|
|
310
|
+
by Tab Atkins Jr. (and others)
|
|
311
|
+
http://xanthir.com
|
|
312
|
+
http://twitter.com/tabatkins
|
|
313
|
+
http://github.com/tabatkins/railroad-diagrams
|
|
314
|
+
|
|
315
|
+
This document and all associated files in the github project are licensed under CC0: http://creativecommons.org/publicdomain/zero/1.0/
|
|
316
|
+
This means you can reuse, remix, or otherwise appropriate this project for your own use WITHOUT RESTRICTION.
|
|
317
|
+
(The actual legal meaning can be found at the above link.)
|
|
318
|
+
Don't ask me for permission to use any part of this project, JUST USE IT.
|
|
319
|
+
I would appreciate attribution, but that is not required by the license.
|
|
320
|
+
*/
|
|
321
|
+
|
|
322
|
+
/*
|
|
323
|
+
This file uses a module pattern to avoid leaking names into the global scope.
|
|
324
|
+
The only accidental leakage is the name "temp".
|
|
325
|
+
The exported names can be found at the bottom of this file;
|
|
326
|
+
simply change the names in the array of strings to change what they are called in your application.
|
|
327
|
+
|
|
328
|
+
As well, several configuration constants are passed into the module function at the bottom of this file.
|
|
329
|
+
At runtime, these constants can be found on the Diagram class.
|
|
330
|
+
*/
|
|
331
|
+
|
|
332
|
+
(function(options) {
|
|
333
|
+
function subclassOf(baseClass, superClass) {
|
|
334
|
+
baseClass.prototype = Object.create(superClass.prototype);
|
|
335
|
+
baseClass.prototype.$super = superClass.prototype;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function unnull(/* children */) {
|
|
339
|
+
return [].slice.call(arguments).reduce(function(sofar, x) { return sofar !== undefined ? sofar : x; });
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function determineGaps(outer, inner) {
|
|
343
|
+
var diff = outer - inner;
|
|
344
|
+
switch(Diagram.INTERNAL_ALIGNMENT) {
|
|
345
|
+
case 'left': return [0, diff]; break;
|
|
346
|
+
case 'right': return [diff, 0]; break;
|
|
347
|
+
case 'center':
|
|
348
|
+
default: return [diff/2, diff/2]; break;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function wrapString(value) {
|
|
353
|
+
return ((typeof value) == 'string') ? new Terminal(value) : value;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
function SVG(name, attrs, text) {
|
|
358
|
+
attrs = attrs || {};
|
|
359
|
+
text = text || '';
|
|
360
|
+
var el = document.createElementNS("http://www.w3.org/2000/svg",name);
|
|
361
|
+
for(var attr in attrs) {
|
|
362
|
+
el.setAttribute(attr, attrs[attr]);
|
|
363
|
+
}
|
|
364
|
+
el.textContent = text;
|
|
365
|
+
return el;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function FakeSVG(tagName, attrs, text){
|
|
369
|
+
if(!(this instanceof FakeSVG)) return new FakeSVG(tagName, attrs, text);
|
|
370
|
+
if(text) this.children = text;
|
|
371
|
+
else this.children = [];
|
|
372
|
+
this.tagName = tagName;
|
|
373
|
+
this.attrs = unnull(attrs, {});
|
|
374
|
+
return this;
|
|
375
|
+
};
|
|
376
|
+
FakeSVG.prototype.format = function(x, y, width) {
|
|
377
|
+
// Virtual
|
|
378
|
+
};
|
|
379
|
+
FakeSVG.prototype.addTo = function(parent) {
|
|
380
|
+
if(parent instanceof FakeSVG) {
|
|
381
|
+
parent.children.push(this);
|
|
382
|
+
return this;
|
|
383
|
+
} else {
|
|
384
|
+
var svg = this.toSVG();
|
|
385
|
+
parent.appendChild(svg);
|
|
386
|
+
return svg;
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
FakeSVG.prototype.escapeString = function(string) {
|
|
390
|
+
// Escape markdown and HTML special characters
|
|
391
|
+
return string.replace(/[*_\\\`\\[\\]<&]/g, function(charString) {
|
|
392
|
+
return '&#' + charString.charCodeAt(0) + ';';
|
|
393
|
+
});
|
|
394
|
+
};
|
|
395
|
+
FakeSVG.prototype.toSVG = function() {
|
|
396
|
+
var el = SVG(this.tagName, this.attrs);
|
|
397
|
+
if(typeof this.children == 'string') {
|
|
398
|
+
el.innerHTML = FakeSVG.prototype.escapeString(this.children);
|
|
399
|
+
} else {
|
|
400
|
+
this.children.forEach(function(e) {
|
|
401
|
+
el.appendChild(e.toSVG());
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
return el;
|
|
405
|
+
};
|
|
406
|
+
FakeSVG.prototype.toString = function() {
|
|
407
|
+
var str = '<' + this.tagName;
|
|
408
|
+
var group = this.tagName == "g" || this.tagName == "svg";
|
|
409
|
+
for(var attr in this.attrs) {
|
|
410
|
+
str += ' ' + attr + '="' + (this.attrs[attr]+'').replace(/&/g, '&').replace(/"/g, '"') + '"';
|
|
411
|
+
}
|
|
412
|
+
str += '>';
|
|
413
|
+
if(group) str += "\\n";
|
|
414
|
+
if(typeof this.children == 'string') {
|
|
415
|
+
str += FakeSVG.prototype.escapeString(this.children);
|
|
416
|
+
} else {
|
|
417
|
+
this.children.forEach(function(e) {
|
|
418
|
+
str += e;
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
str += '</' + this.tagName + '>\\n';
|
|
422
|
+
return str;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function Path(x,y) {
|
|
426
|
+
if(!(this instanceof Path)) return new Path(x,y);
|
|
427
|
+
FakeSVG.call(this, 'path');
|
|
428
|
+
this.attrs.d = "M"+x+' '+y;
|
|
429
|
+
}
|
|
430
|
+
subclassOf(Path, FakeSVG);
|
|
431
|
+
Path.prototype.m = function(x,y) {
|
|
432
|
+
this.attrs.d += 'm'+x+' '+y;
|
|
433
|
+
return this;
|
|
434
|
+
}
|
|
435
|
+
Path.prototype.h = function(val) {
|
|
436
|
+
this.attrs.d += 'h'+val;
|
|
437
|
+
return this;
|
|
438
|
+
}
|
|
439
|
+
Path.prototype.right = Path.prototype.h;
|
|
440
|
+
Path.prototype.left = function(val) { return this.h(-val); }
|
|
441
|
+
Path.prototype.v = function(val) {
|
|
442
|
+
this.attrs.d += 'v'+val;
|
|
443
|
+
return this;
|
|
444
|
+
}
|
|
445
|
+
Path.prototype.down = Path.prototype.v;
|
|
446
|
+
Path.prototype.up = function(val) { return this.v(-val); }
|
|
447
|
+
Path.prototype.arc = function(sweep){
|
|
448
|
+
var x = Diagram.ARC_RADIUS;
|
|
449
|
+
var y = Diagram.ARC_RADIUS;
|
|
450
|
+
if(sweep[0] == 'e' || sweep[1] == 'w') {
|
|
451
|
+
x *= -1;
|
|
452
|
+
}
|
|
453
|
+
if(sweep[0] == 's' || sweep[1] == 'n') {
|
|
454
|
+
y *= -1;
|
|
455
|
+
}
|
|
456
|
+
if(sweep == 'ne' || sweep == 'es' || sweep == 'sw' || sweep == 'wn') {
|
|
457
|
+
var cw = 1;
|
|
458
|
+
} else {
|
|
459
|
+
var cw = 0;
|
|
460
|
+
}
|
|
461
|
+
this.attrs.d += "a"+Diagram.ARC_RADIUS+" "+Diagram.ARC_RADIUS+" 0 0 "+cw+' '+x+' '+y;
|
|
462
|
+
return this;
|
|
463
|
+
}
|
|
464
|
+
Path.prototype.format = function() {
|
|
465
|
+
// All paths in this library start/end horizontally.
|
|
466
|
+
// The extra .5 ensures a minor overlap, so there's no seams in bad rasterizers.
|
|
467
|
+
this.attrs.d += 'h.5';
|
|
468
|
+
return this;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function Diagram(items) {
|
|
472
|
+
if(!(this instanceof Diagram)) return new Diagram([].slice.call(arguments));
|
|
473
|
+
FakeSVG.call(this, 'svg', {class: Diagram.DIAGRAM_CLASS});
|
|
474
|
+
this.items = items.map(wrapString);
|
|
475
|
+
this.items.unshift(new Start);
|
|
476
|
+
this.items.push(new End);
|
|
477
|
+
this.width = this.items.reduce(function(sofar, el) { return sofar + el.width + (el.needsSpace?20:0)}, 0)+1;
|
|
478
|
+
this.up = Math.max.apply(null, this.items.map(function (x) { return x.up; }));
|
|
479
|
+
this.down = Math.max.apply(null, this.items.map(function (x) { return x.down; }));
|
|
480
|
+
this.formatted = false;
|
|
481
|
+
}
|
|
482
|
+
subclassOf(Diagram, FakeSVG);
|
|
483
|
+
for(var option in options) {
|
|
484
|
+
Diagram[option] = options[option];
|
|
485
|
+
}
|
|
486
|
+
Diagram.prototype.format = function(paddingt, paddingr, paddingb, paddingl) {
|
|
487
|
+
paddingt = unnull(paddingt, 20);
|
|
488
|
+
paddingr = unnull(paddingr, paddingt, 20);
|
|
489
|
+
paddingb = unnull(paddingb, paddingt, 20);
|
|
490
|
+
paddingl = unnull(paddingl, paddingr, 20);
|
|
491
|
+
var x = paddingl;
|
|
492
|
+
var y = paddingt;
|
|
493
|
+
y += this.up;
|
|
494
|
+
var g = FakeSVG('g', Diagram.STROKE_ODD_PIXEL_LENGTH ? {transform:'translate(.5 .5)'} : {});
|
|
495
|
+
for(var i = 0; i < this.items.length; i++) {
|
|
496
|
+
var item = this.items[i];
|
|
497
|
+
if(item.needsSpace) {
|
|
498
|
+
Path(x,y).h(10).addTo(g);
|
|
499
|
+
x += 10;
|
|
500
|
+
}
|
|
501
|
+
item.format(x, y, item.width).addTo(g);
|
|
502
|
+
x += item.width;
|
|
503
|
+
if(item.needsSpace) {
|
|
504
|
+
Path(x,y).h(10).addTo(g);
|
|
505
|
+
x += 10;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
this.attrs.width = this.width + paddingl + paddingr;
|
|
509
|
+
this.attrs.height = this.up + this.down + paddingt + paddingb;
|
|
510
|
+
this.attrs.viewBox = "0 0 " + this.attrs.width + " " + this.attrs.height;
|
|
511
|
+
g.addTo(this);
|
|
512
|
+
this.formatted = true;
|
|
513
|
+
return this;
|
|
514
|
+
}
|
|
515
|
+
Diagram.prototype.addTo = function(parent) {
|
|
516
|
+
var scriptTag = document.getElementsByTagName('script');
|
|
517
|
+
scriptTag = scriptTag[scriptTag.length - 1];
|
|
518
|
+
var parentTag = scriptTag.parentNode;
|
|
519
|
+
parent = parent || parentTag;
|
|
520
|
+
return this.$super.addTo.call(this, parent);
|
|
521
|
+
}
|
|
522
|
+
Diagram.prototype.toSVG = function() {
|
|
523
|
+
if (!this.formatted) {
|
|
524
|
+
this.format();
|
|
525
|
+
}
|
|
526
|
+
return this.$super.toSVG.call(this);
|
|
527
|
+
}
|
|
528
|
+
Diagram.prototype.toString = function() {
|
|
529
|
+
if (!this.formatted) {
|
|
530
|
+
this.format();
|
|
531
|
+
}
|
|
532
|
+
return this.$super.toString.call(this);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function ComplexDiagram() {
|
|
536
|
+
var diagram = new Diagram([].slice.call(arguments));
|
|
537
|
+
var items = diagram.items;
|
|
538
|
+
items.shift();
|
|
539
|
+
items.pop();
|
|
540
|
+
items.unshift(new Start(false));
|
|
541
|
+
items.push(new End(false));
|
|
542
|
+
diagram.items = items;
|
|
543
|
+
return diagram;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function Sequence(items) {
|
|
547
|
+
if(!(this instanceof Sequence)) return new Sequence([].slice.call(arguments));
|
|
548
|
+
FakeSVG.call(this, 'g');
|
|
549
|
+
this.items = items.map(wrapString);
|
|
550
|
+
this.width = this.items.reduce(function(sofar, el) { return sofar + el.width + (el.needsSpace?20:0)}, 0);
|
|
551
|
+
this.up = this.items.reduce(function(sofar,el) { return Math.max(sofar, el.up)}, 0);
|
|
552
|
+
this.down = this.items.reduce(function(sofar,el) { return Math.max(sofar, el.down)}, 0);
|
|
553
|
+
}
|
|
554
|
+
subclassOf(Sequence, FakeSVG);
|
|
555
|
+
Sequence.prototype.format = function(x,y,width) {
|
|
556
|
+
// Hook up the two sides if this is narrower than its stated width.
|
|
557
|
+
var gaps = determineGaps(width, this.width);
|
|
558
|
+
Path(x,y).h(gaps[0]).addTo(this);
|
|
559
|
+
Path(x+gaps[0]+this.width,y).h(gaps[1]).addTo(this);
|
|
560
|
+
x += gaps[0];
|
|
561
|
+
|
|
562
|
+
for(var i = 0; i < this.items.length; i++) {
|
|
563
|
+
var item = this.items[i];
|
|
564
|
+
if(item.needsSpace) {
|
|
565
|
+
Path(x,y).h(10).addTo(this);
|
|
566
|
+
x += 10;
|
|
567
|
+
}
|
|
568
|
+
item.format(x, y, item.width).addTo(this);
|
|
569
|
+
x += item.width;
|
|
570
|
+
if(item.needsSpace) {
|
|
571
|
+
Path(x,y).h(10).addTo(this);
|
|
572
|
+
x += 10;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return this;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function Choice(normal, items) {
|
|
579
|
+
if(!(this instanceof Choice)) return new Choice(normal, [].slice.call(arguments,1));
|
|
580
|
+
FakeSVG.call(this, 'g');
|
|
581
|
+
if( typeof normal !== "number" || normal !== Math.floor(normal) ) {
|
|
582
|
+
throw new TypeError("The first argument of Choice() must be an integer.");
|
|
583
|
+
} else if(normal < 0 || normal >= items.length) {
|
|
584
|
+
throw new RangeError("The first argument of Choice() must be an index for one of the items.");
|
|
585
|
+
} else {
|
|
586
|
+
this.normal = normal;
|
|
587
|
+
}
|
|
588
|
+
this.items = items.map(wrapString);
|
|
589
|
+
this.width = this.items.reduce(function(sofar, el){return Math.max(sofar, el.width)},0) + Diagram.ARC_RADIUS*4;
|
|
590
|
+
this.up = this.down = 0;
|
|
591
|
+
for(var i = 0; i < this.items.length; i++) {
|
|
592
|
+
var item = this.items[i];
|
|
593
|
+
if(i < normal) { this.up += Math.max(Diagram.ARC_RADIUS,item.up + item.down + Diagram.VERTICAL_SEPARATION); }
|
|
594
|
+
if(i == normal) { this.up += Math.max(Diagram.ARC_RADIUS, item.up); this.down += Math.max(Diagram.ARC_RADIUS, item.down); }
|
|
595
|
+
if(i > normal) { this.down += Math.max(Diagram.ARC_RADIUS,Diagram.VERTICAL_SEPARATION + item.up + item.down); }
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
subclassOf(Choice, FakeSVG);
|
|
599
|
+
Choice.prototype.format = function(x,y,width) {
|
|
600
|
+
// Hook up the two sides if this is narrower than its stated width.
|
|
601
|
+
var gaps = determineGaps(width, this.width);
|
|
602
|
+
Path(x,y).h(gaps[0]).addTo(this);
|
|
603
|
+
Path(x+gaps[0]+this.width,y).h(gaps[1]).addTo(this);
|
|
604
|
+
x += gaps[0];
|
|
605
|
+
|
|
606
|
+
var last = this.items.length -1;
|
|
607
|
+
var innerWidth = this.width - Diagram.ARC_RADIUS*4;
|
|
608
|
+
|
|
609
|
+
// Do the elements that curve above
|
|
610
|
+
for(var i = this.normal - 1; i >= 0; i--) {
|
|
611
|
+
var item = this.items[i];
|
|
612
|
+
if( i == this.normal - 1 ) {
|
|
613
|
+
var distanceFromY = Math.max(Diagram.ARC_RADIUS*2, this.items[i+1].up + Diagram.VERTICAL_SEPARATION + item.down);
|
|
614
|
+
}
|
|
615
|
+
Path(x,y).arc('se').up(distanceFromY - Diagram.ARC_RADIUS*2).arc('wn').addTo(this);
|
|
616
|
+
item.format(x+Diagram.ARC_RADIUS*2,y - distanceFromY,innerWidth).addTo(this);
|
|
617
|
+
Path(x+Diagram.ARC_RADIUS*2+innerWidth, y-distanceFromY).arc('ne').down(distanceFromY - Diagram.ARC_RADIUS*2).arc('ws').addTo(this);
|
|
618
|
+
distanceFromY += Math.max(Diagram.ARC_RADIUS, item.up + Diagram.VERTICAL_SEPARATION + (i == 0 ? 0 : this.items[i-1].down));
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Do the straight-line path.
|
|
622
|
+
Path(x,y).right(Diagram.ARC_RADIUS*2).addTo(this);
|
|
623
|
+
this.items[this.normal].format(x+Diagram.ARC_RADIUS*2, y, innerWidth).addTo(this);
|
|
624
|
+
Path(x+Diagram.ARC_RADIUS*2+innerWidth, y).right(Diagram.ARC_RADIUS*2).addTo(this);
|
|
625
|
+
|
|
626
|
+
// Do the elements that curve below
|
|
627
|
+
for(var i = this.normal+1; i <= last; i++) {
|
|
628
|
+
var item = this.items[i];
|
|
629
|
+
if( i == this.normal + 1 ) {
|
|
630
|
+
var distanceFromY = Math.max(Diagram.ARC_RADIUS*2, this.items[i-1].down + Diagram.VERTICAL_SEPARATION + item.up);
|
|
631
|
+
}
|
|
632
|
+
Path(x,y).arc('ne').down(distanceFromY - Diagram.ARC_RADIUS*2).arc('ws').addTo(this);
|
|
633
|
+
item.format(x+Diagram.ARC_RADIUS*2, y+distanceFromY, innerWidth).addTo(this);
|
|
634
|
+
Path(x+Diagram.ARC_RADIUS*2+innerWidth, y+distanceFromY).arc('se').up(distanceFromY - Diagram.ARC_RADIUS*2).arc('wn').addTo(this);
|
|
635
|
+
distanceFromY += Math.max(Diagram.ARC_RADIUS, item.down + Diagram.VERTICAL_SEPARATION + (i == last ? 0 : this.items[i+1].up));
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
return this;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function Optional(item, skip) {
|
|
642
|
+
if( skip === undefined )
|
|
643
|
+
return Choice(1, Skip(), item);
|
|
644
|
+
else if ( skip === "skip" )
|
|
645
|
+
return Choice(0, Skip(), item);
|
|
646
|
+
else
|
|
647
|
+
throw "Unknown value for Optional()'s 'skip' argument.";
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function OneOrMore(item, rep) {
|
|
651
|
+
if(!(this instanceof OneOrMore)) return new OneOrMore(item, rep);
|
|
652
|
+
FakeSVG.call(this, 'g');
|
|
653
|
+
rep = rep || (new Skip);
|
|
654
|
+
this.item = wrapString(item);
|
|
655
|
+
this.rep = wrapString(rep);
|
|
656
|
+
this.width = Math.max(this.item.width, this.rep.width) + Diagram.ARC_RADIUS*2;
|
|
657
|
+
this.up = this.item.up;
|
|
658
|
+
this.down = Math.max(Diagram.ARC_RADIUS*2, this.item.down + Diagram.VERTICAL_SEPARATION + this.rep.up + this.rep.down);
|
|
659
|
+
}
|
|
660
|
+
subclassOf(OneOrMore, FakeSVG);
|
|
661
|
+
OneOrMore.prototype.needsSpace = true;
|
|
662
|
+
OneOrMore.prototype.format = function(x,y,width) {
|
|
663
|
+
// Hook up the two sides if this is narrower than its stated width.
|
|
664
|
+
var gaps = determineGaps(width, this.width);
|
|
665
|
+
Path(x,y).h(gaps[0]).addTo(this);
|
|
666
|
+
Path(x+gaps[0]+this.width,y).h(gaps[1]).addTo(this);
|
|
667
|
+
x += gaps[0];
|
|
668
|
+
|
|
669
|
+
// Draw item
|
|
670
|
+
Path(x,y).right(Diagram.ARC_RADIUS).addTo(this);
|
|
671
|
+
this.item.format(x+Diagram.ARC_RADIUS,y,this.width-Diagram.ARC_RADIUS*2).addTo(this);
|
|
672
|
+
Path(x+this.width-Diagram.ARC_RADIUS,y).right(Diagram.ARC_RADIUS).addTo(this);
|
|
673
|
+
|
|
674
|
+
// Draw repeat arc
|
|
675
|
+
var distanceFromY = Math.max(Diagram.ARC_RADIUS*2, this.item.down+Diagram.VERTICAL_SEPARATION+this.rep.up);
|
|
676
|
+
Path(x+Diagram.ARC_RADIUS,y).arc('nw').down(distanceFromY-Diagram.ARC_RADIUS*2).arc('ws').addTo(this);
|
|
677
|
+
this.rep.format(x+Diagram.ARC_RADIUS, y+distanceFromY, this.width - Diagram.ARC_RADIUS*2).addTo(this);
|
|
678
|
+
Path(x+this.width-Diagram.ARC_RADIUS, y+distanceFromY).arc('se').up(distanceFromY-Diagram.ARC_RADIUS*2).arc('en').addTo(this);
|
|
679
|
+
|
|
680
|
+
return this;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function ZeroOrMore(item, rep, skip) {
|
|
684
|
+
return Optional(OneOrMore(item, rep), skip);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function Start(simpleType) {
|
|
688
|
+
if(!(this instanceof Start)) return new Start();
|
|
689
|
+
FakeSVG.call(this, 'path');
|
|
690
|
+
this.width = 20;
|
|
691
|
+
this.up = 10;
|
|
692
|
+
this.down = 10;
|
|
693
|
+
this.simpleType = simpleType;
|
|
694
|
+
}
|
|
695
|
+
subclassOf(Start, FakeSVG);
|
|
696
|
+
Start.prototype.format = function(x,y) {
|
|
697
|
+
if (this.simpleType === false) {
|
|
698
|
+
this.attrs.d = 'M '+x+' '+(y-10)+' v 20 m 0 -10 h 20.5';
|
|
699
|
+
} else {
|
|
700
|
+
this.attrs.d = 'M '+x+' '+(y-10)+' v 20 m 10 -20 v 20 m -10 -10 h 20.5';
|
|
701
|
+
}
|
|
702
|
+
return this;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function End(simpleType) {
|
|
706
|
+
if(!(this instanceof End)) return new End();
|
|
707
|
+
FakeSVG.call(this, 'path');
|
|
708
|
+
this.width = 20;
|
|
709
|
+
this.up = 10;
|
|
710
|
+
this.down = 10;
|
|
711
|
+
this.simpleType = simpleType;
|
|
712
|
+
}
|
|
713
|
+
subclassOf(End, FakeSVG);
|
|
714
|
+
End.prototype.format = function(x,y) {
|
|
715
|
+
if (this.simpleType === false) {
|
|
716
|
+
this.attrs.d = 'M '+x+' '+y+' h 20 m 0 -10 v 20';
|
|
717
|
+
} else {
|
|
718
|
+
this.attrs.d = 'M '+x+' '+y+' h 20 m -10 -10 v 20 m 10 -20 v 20';
|
|
719
|
+
}
|
|
720
|
+
return this;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function Terminal(text) {
|
|
724
|
+
if(!(this instanceof Terminal)) return new Terminal(text);
|
|
725
|
+
FakeSVG.call(this, 'g');
|
|
726
|
+
this.text = text;
|
|
727
|
+
this.width = text.length * 8 + 20; /* Assume that each char is .5em, and that the em is 16px */
|
|
728
|
+
this.up = 11;
|
|
729
|
+
this.down = 11;
|
|
730
|
+
}
|
|
731
|
+
subclassOf(Terminal, FakeSVG);
|
|
732
|
+
Terminal.prototype.needsSpace = true;
|
|
733
|
+
Terminal.prototype.format = function(x, y, width) {
|
|
734
|
+
// Hook up the two sides if this is narrower than its stated width.
|
|
735
|
+
var gaps = determineGaps(width, this.width);
|
|
736
|
+
Path(x,y).h(gaps[0]).addTo(this);
|
|
737
|
+
Path(x+gaps[0]+this.width,y).h(gaps[1]).addTo(this);
|
|
738
|
+
x += gaps[0];
|
|
739
|
+
|
|
740
|
+
FakeSVG('rect', {x:x, y:y-11, width:this.width, height:this.up+this.down, rx:10, ry:10}).addTo(this);
|
|
741
|
+
FakeSVG('text', {x:x+this.width/2, y:y+4}, this.text).addTo(this);
|
|
742
|
+
return this;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function NonTerminal(text) {
|
|
746
|
+
if(!(this instanceof NonTerminal)) return new NonTerminal(text);
|
|
747
|
+
FakeSVG.call(this, 'g');
|
|
748
|
+
this.text = text;
|
|
749
|
+
this.width = text.length * 8 + 20;
|
|
750
|
+
this.up = 11;
|
|
751
|
+
this.down = 11;
|
|
752
|
+
}
|
|
753
|
+
subclassOf(NonTerminal, FakeSVG);
|
|
754
|
+
NonTerminal.prototype.needsSpace = true;
|
|
755
|
+
NonTerminal.prototype.format = function(x, y, width) {
|
|
756
|
+
// Hook up the two sides if this is narrower than its stated width.
|
|
757
|
+
var gaps = determineGaps(width, this.width);
|
|
758
|
+
Path(x,y).h(gaps[0]).addTo(this);
|
|
759
|
+
Path(x+gaps[0]+this.width,y).h(gaps[1]).addTo(this);
|
|
760
|
+
x += gaps[0];
|
|
761
|
+
|
|
762
|
+
FakeSVG('rect', {x:x, y:y-11, width:this.width, height:this.up+this.down}).addTo(this);
|
|
763
|
+
FakeSVG('text', {x:x+this.width/2, y:y+4}, this.text).addTo(this);
|
|
764
|
+
return this;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function Comment(text) {
|
|
768
|
+
if(!(this instanceof Comment)) return new Comment(text);
|
|
769
|
+
FakeSVG.call(this, 'g');
|
|
770
|
+
this.text = text;
|
|
771
|
+
this.width = text.length * 7 + 10;
|
|
772
|
+
this.up = 11;
|
|
773
|
+
this.down = 11;
|
|
774
|
+
}
|
|
775
|
+
subclassOf(Comment, FakeSVG);
|
|
776
|
+
Comment.prototype.needsSpace = true;
|
|
777
|
+
Comment.prototype.format = function(x, y, width) {
|
|
778
|
+
// Hook up the two sides if this is narrower than its stated width.
|
|
779
|
+
var gaps = determineGaps(width, this.width);
|
|
780
|
+
Path(x,y).h(gaps[0]).addTo(this);
|
|
781
|
+
Path(x+gaps[0]+this.width,y).h(gaps[1]).addTo(this);
|
|
782
|
+
x += gaps[0];
|
|
783
|
+
|
|
784
|
+
FakeSVG('text', {x:x+this.width/2, y:y+5, class:'comment'}, this.text).addTo(this);
|
|
785
|
+
return this;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function Skip() {
|
|
789
|
+
if(!(this instanceof Skip)) return new Skip();
|
|
790
|
+
FakeSVG.call(this, 'g');
|
|
791
|
+
this.width = 0;
|
|
792
|
+
this.up = 0;
|
|
793
|
+
this.down = 0;
|
|
794
|
+
}
|
|
795
|
+
subclassOf(Skip, FakeSVG);
|
|
796
|
+
Skip.prototype.format = function(x, y, width) {
|
|
797
|
+
Path(x,y).right(width).addTo(this);
|
|
798
|
+
return this;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
var root;
|
|
802
|
+
if (typeof define === 'function' && define.amd) {
|
|
803
|
+
// AMD. Register as an anonymous module.
|
|
804
|
+
root = {};
|
|
805
|
+
define([], function() {
|
|
806
|
+
return root;
|
|
807
|
+
});
|
|
808
|
+
} else if (typeof exports === 'object') {
|
|
809
|
+
// CommonJS for node
|
|
810
|
+
root = exports;
|
|
811
|
+
} else {
|
|
812
|
+
// Browser globals (root is window)
|
|
813
|
+
root = this;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
var temp = [Diagram, ComplexDiagram, Sequence, Choice, Optional, OneOrMore, ZeroOrMore, Terminal, NonTerminal, Comment, Skip];
|
|
817
|
+
/*
|
|
818
|
+
These are the names that the internal classes are exported as.
|
|
819
|
+
If you would like different names, adjust them here.
|
|
820
|
+
*/
|
|
821
|
+
['Diagram', 'ComplexDiagram', 'Sequence', 'Choice', 'Optional', 'OneOrMore', 'ZeroOrMore', 'Terminal', 'NonTerminal', 'Comment', 'Skip']
|
|
822
|
+
.forEach(function(e,i) { root[e] = temp[i]; });
|
|
823
|
+
}).call(this,
|
|
824
|
+
{
|
|
825
|
+
VERTICAL_SEPARATION: 8,
|
|
826
|
+
ARC_RADIUS: 10,
|
|
827
|
+
DIAGRAM_CLASS: 'railroad-diagram',
|
|
828
|
+
STROKE_ODD_PIXEL_LENGTH: true,
|
|
829
|
+
INTERNAL_ALIGNMENT: 'center',
|
|
830
|
+
}
|
|
831
|
+
);
|
|
832
|
+
`;
|
|
833
|
+
var RAILROAD_CSS = `svg.railroad-diagram {
|
|
834
|
+
background-color: hsl(30,20%,95%);
|
|
835
|
+
}
|
|
836
|
+
svg.railroad-diagram path {
|
|
837
|
+
stroke-width: 3;
|
|
838
|
+
stroke: black;
|
|
839
|
+
fill: rgba(0,0,0,0);
|
|
840
|
+
}
|
|
841
|
+
svg.railroad-diagram text {
|
|
842
|
+
font: bold 14px monospace;
|
|
843
|
+
text-anchor: middle;
|
|
844
|
+
}
|
|
845
|
+
svg.railroad-diagram text.label {
|
|
846
|
+
text-anchor: start;
|
|
847
|
+
}
|
|
848
|
+
svg.railroad-diagram text.comment {
|
|
849
|
+
font: italic 12px monospace;
|
|
850
|
+
}
|
|
851
|
+
svg.railroad-diagram rect {
|
|
852
|
+
stroke-width: 3;
|
|
853
|
+
stroke: black;
|
|
854
|
+
fill: hsl(120,100%,90%);
|
|
855
|
+
}
|
|
856
|
+
`;
|
|
857
|
+
|
|
858
|
+
// src/spec/railroad.ts
|
|
859
|
+
var j = (s) => JSON.stringify(s);
|
|
860
|
+
function toDsl(node) {
|
|
861
|
+
switch (node.kind) {
|
|
862
|
+
case "terminal":
|
|
863
|
+
return `Terminal(${j(node.text)})`;
|
|
864
|
+
case "ref":
|
|
865
|
+
return `NonTerminal(${j(node.name)})`;
|
|
866
|
+
case "empty":
|
|
867
|
+
return `Skip()`;
|
|
868
|
+
case "annotation":
|
|
869
|
+
return `Comment(${j(node.text)})`;
|
|
870
|
+
case "seq":
|
|
871
|
+
return `Sequence(${node.items.map(toDsl).join(", ")})`;
|
|
872
|
+
case "choice":
|
|
873
|
+
return `Choice(0, ${node.items.map(toDsl).join(", ")})`;
|
|
874
|
+
case "star":
|
|
875
|
+
return `ZeroOrMore(${toDsl(node.item)})`;
|
|
876
|
+
case "plus":
|
|
877
|
+
return `OneOrMore(${toDsl(node.item)})`;
|
|
878
|
+
case "opt":
|
|
879
|
+
return `Optional(${toDsl(node.item)})`;
|
|
880
|
+
case "sepBy":
|
|
881
|
+
return `OneOrMore(${toDsl(node.item)}, ${toDsl(node.sep)})`;
|
|
882
|
+
case "not":
|
|
883
|
+
return `Sequence(Comment("not"), ${toDsl(node.item)})`;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
function escapeHtml(s) {
|
|
887
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
888
|
+
}
|
|
889
|
+
function slug(name) {
|
|
890
|
+
return "rule-" + name.replace(/[^\w-]/g, "_");
|
|
891
|
+
}
|
|
892
|
+
var PAGE_CSS = `
|
|
893
|
+
/* The railroad SVGs ship a fixed light background, so the page commits to a
|
|
894
|
+
light theme for a consistent look regardless of the viewer's OS setting. */
|
|
895
|
+
body { font: 14px/1.5 system-ui, sans-serif; margin: 0; padding: 2rem 2.5rem;
|
|
896
|
+
color: #1a1a1a; background: #fff; }
|
|
897
|
+
h1 { font-size: 1.6rem; margin: 0 0 0.25rem; }
|
|
898
|
+
.subtitle { color: #666; margin: 0 0 2rem; }
|
|
899
|
+
.toc { columns: 3 12rem; margin: 0 0 2.5rem; padding: 1rem 1.25rem; background: #f6f4f1; border-radius: 8px; }
|
|
900
|
+
.toc a { display: block; color: #0b6; text-decoration: none; font-family: monospace; padding: 1px 0; }
|
|
901
|
+
.toc a:hover { text-decoration: underline; }
|
|
902
|
+
.rule { margin: 0 0 2.75rem; padding-top: 0.5rem; border-top: 1px solid #eee; }
|
|
903
|
+
.rule h2 { font-size: 1.1rem; font-family: monospace; margin: 0 0 0.75rem; }
|
|
904
|
+
.rule h2 a { color: #bbb; text-decoration: none; font-weight: normal; }
|
|
905
|
+
.rule h2 a:hover { color: #0b6; }
|
|
906
|
+
.ebnf { margin: 0.5rem 0 0; padding: 0.5rem 0.75rem; background: #f6f4f1; border-radius: 6px;
|
|
907
|
+
font-family: monospace; font-size: 12px; white-space: pre-wrap; overflow-x: auto; }
|
|
908
|
+
.diagram { overflow-x: auto; }
|
|
909
|
+
/* The vendored library sizes each shape as (chars * 8 + padding), which assumes
|
|
910
|
+
an 8px glyph advance (0.5em at 16px). Its own 14px-bold CSS actually advances
|
|
911
|
+
~8.4px/char, so long terminals crowd the box edges. Drop the text to 13px
|
|
912
|
+
(advance ~7.8px) to restore comfortable side padding, and nudge the baked
|
|
913
|
+
baseline (y+4) down slightly so glyphs sit vertically centered in the shapes. */
|
|
914
|
+
svg.railroad-diagram text { font-size: 13px; }
|
|
915
|
+
svg.railroad-diagram text:not(.comment) { transform: translateY(0.75px); }
|
|
916
|
+
`;
|
|
917
|
+
function renderRailroadHtml(model, options = {}) {
|
|
918
|
+
const title = options.title ?? "Grammar";
|
|
919
|
+
const showEbnf = options.showEbnf ?? true;
|
|
920
|
+
const toc = model.productions.map((p) => `<a href="#${slug(p.name)}">${escapeHtml(p.name)}</a>`).join("\n ");
|
|
921
|
+
const sections = model.productions.map((p) => {
|
|
922
|
+
const ebnf = showEbnf ? `<div class="ebnf">${escapeHtml(`${p.name} ::= ${renderExpr(p.expr) || "/* empty */"}`)}</div>` : "";
|
|
923
|
+
return ` <section class="rule" id="${slug(p.name)}">
|
|
924
|
+
<h2><a href="#${slug(p.name)}">\xA7</a> ${escapeHtml(p.name)}</h2>
|
|
925
|
+
<div class="diagram" data-rule="${escapeHtml(p.name)}"></div>
|
|
926
|
+
${ebnf}
|
|
927
|
+
</section>`;
|
|
928
|
+
}).join("\n");
|
|
929
|
+
const builders = model.productions.map((p) => ` { name: ${j(p.name)}, dsl: function(){ return Diagram(${toDsl(p.expr)}); } }`).join(",\n");
|
|
930
|
+
return `<!doctype html>
|
|
931
|
+
<html lang="en">
|
|
932
|
+
<head>
|
|
933
|
+
<meta charset="utf-8">
|
|
934
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
935
|
+
<title>${escapeHtml(title)}</title>
|
|
936
|
+
<style>
|
|
937
|
+
${RAILROAD_CSS}
|
|
938
|
+
${PAGE_CSS}
|
|
939
|
+
</style>
|
|
940
|
+
</head>
|
|
941
|
+
<body>
|
|
942
|
+
<h1>${escapeHtml(title)}</h1>
|
|
943
|
+
<p class="subtitle">Syntax diagrams generated from the parser grammar.</p>
|
|
944
|
+
<nav class="toc">
|
|
945
|
+
${toc}
|
|
946
|
+
</nav>
|
|
947
|
+
${sections}
|
|
948
|
+
<script>
|
|
949
|
+
${RAILROAD_JS}
|
|
950
|
+
<\/script>
|
|
951
|
+
<script>
|
|
952
|
+
(function () {
|
|
953
|
+
var rules = [
|
|
954
|
+
${builders}
|
|
955
|
+
];
|
|
956
|
+
rules.forEach(function (r) {
|
|
957
|
+
var host = document.querySelector('.diagram[data-rule=' + CSS.escape(r.name) + ']');
|
|
958
|
+
if (!host) return;
|
|
959
|
+
try { r.dsl().addTo(host); }
|
|
960
|
+
catch (e) { host.textContent = 'diagram error: ' + e.message; }
|
|
961
|
+
});
|
|
962
|
+
})();
|
|
963
|
+
<\/script>
|
|
964
|
+
</body>
|
|
965
|
+
</html>
|
|
966
|
+
`;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// src/spec/index.ts
|
|
970
|
+
function toEBNF(grammar, options) {
|
|
971
|
+
return renderEBNF(buildSpecModel(grammar, options));
|
|
972
|
+
}
|
|
973
|
+
function toRailroadHtml(grammar, options) {
|
|
974
|
+
const model = buildSpecModel(grammar, options);
|
|
975
|
+
return renderRailroadHtml(model, options);
|
|
976
|
+
}
|
|
977
|
+
//# sourceMappingURL=index.cjs.map
|