@bablr/boot 0.1.8 → 0.2.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.
@@ -1,47 +1,29 @@
1
+ const { spawnSync } = require('node:child_process');
1
2
  const t = require('@babel/types');
2
3
  const { expression } = require('@babel/template');
4
+ const { diff } = require('jest-diff');
3
5
  const isObject = require('iter-tools-es/methods/is-object');
4
6
  const isUndefined = require('iter-tools-es/methods/is-undefined');
5
7
  const isNull = require('iter-tools-es/methods/is-null');
6
8
  const isString = require('iter-tools-es/methods/is-string');
7
9
  const concat = require('iter-tools-es/methods/concat');
8
10
  const { createMacro } = require('babel-plugin-macros');
9
- const { TemplateParser } = require('./lib/miniparser.js');
11
+ const { TemplateParser, set, getAgASTValue } = require('./lib/index.js');
10
12
  const i = require('./lib/languages/instruction.js');
11
13
  const re = require('./lib/languages/regex.js');
12
14
  const spam = require('./lib/languages/spamex.js');
13
- const str = require('./lib/languages/string.js');
14
- const num = require('./lib/languages/number.js');
15
15
  const cstml = require('./lib/languages/cstml.js');
16
16
  const { addNamespace, addNamed } = require('@babel/helper-module-imports');
17
17
  const { PathResolver } = require('@bablr/boot-helpers/path');
18
+ const { printPrettyCSTML } = require('./lib/print.js');
18
19
 
19
- const { hasOwn } = Object;
20
20
  const { isArray } = Array;
21
21
  const isNumber = (v) => typeof v === 'number';
22
22
  const isBoolean = (v) => typeof v === 'boolean';
23
-
24
23
  const isPlainObject = (v) => isObject(v) && !isArray(v);
24
+ const printRef = (ref) => (ref.isArray ? `${ref.name}[]` : ref.name);
25
25
 
26
- const set = (obj, path, value) => {
27
- const { pathName, pathIsArray } = path;
28
- if (pathIsArray) {
29
- if (!obj[pathName]) {
30
- obj[pathName] = [];
31
- }
32
-
33
- if (!isArray(obj[pathName])) throw new Error('bad array value');
34
-
35
- obj[pathName].push(value);
36
- } else {
37
- if (hasOwn(obj, pathName)) {
38
- throw new Error('duplicate child name');
39
- }
40
- obj[pathName] = value;
41
- }
42
- };
43
-
44
- const getASTValue = (v, exprs, bindings) => {
26
+ const getBabelASTValue = (v, exprs, bindings) => {
45
27
  return isNull(v)
46
28
  ? t.nullLiteral()
47
29
  : isUndefined(v)
@@ -53,39 +35,52 @@ const getASTValue = (v, exprs, bindings) => {
53
35
  : isBoolean(v)
54
36
  ? t.booleanLiteral(v)
55
37
  : isArray(v)
56
- ? t.arrayExpression(v.map((v) => getASTValue(v, exprs, bindings)))
38
+ ? t.arrayExpression(v.map((v) => getBabelASTValue(v, exprs, bindings)))
57
39
  : isPlainObject(v) && !v.language
58
40
  ? t.objectExpression(
59
41
  Object.entries(v).map(([k, v]) =>
60
- t.objectProperty(t.identifier(k), getASTValue(v, exprs, bindings)),
42
+ t.objectProperty(t.identifier(k), getBabelASTValue(v, exprs, bindings)),
61
43
  ),
62
44
  )
63
- : generateNode(v, exprs, bindings);
45
+ : generateBabelNode(v, exprs, bindings);
64
46
  };
65
47
 
66
- const generateNodeChild = (child, bindings) => {
48
+ const generateBabelNodeChild = (child, exprs, bindings) => {
67
49
  if (child.type === 'Reference') {
68
- const { pathName, pathIsArray } = child.value;
69
- const printedRef = pathIsArray ? `${pathName}[]` : pathName;
70
- return expression(`%%t%%.ref\`${printedRef}\``)({ t: bindings.t });
50
+ return expression(`%%t%%.ref\`${printRef(child.value)}\``)({ t: bindings.t });
71
51
  } else if (child.type === 'Literal') {
72
- return expression(`%%t%%.lit\`${child.value.replace(/\\/g, '\\\\')}\``)({ t: bindings.t });
73
- } else if (child.type === 'Trivia') {
74
- return expression(`%%t%%.trivia\` \``)({ t: bindings.t });
75
- } else if (child.type === 'Escape') {
76
- return expression(`%%t%%.esc(%%cooked%%, %%raw%%)`)({
52
+ return expression(`%%t%%.lit(%%value%%)`)({
53
+ t: bindings.t,
54
+ value: getBabelASTValue(child.value, exprs, bindings),
55
+ });
56
+ } else if (child.type === 'Embedded') {
57
+ return expression(`%%t%%.embedded(%%value%%)`)({
77
58
  t: bindings.t,
78
- cooked: child.cooked,
79
- raw: child.raw,
59
+ value: generateBabelNode(child.value, exprs, bindings),
80
60
  });
81
61
  } else {
82
62
  throw new Error(`Unknown child type ${child.type}`);
83
63
  }
84
64
  };
85
65
 
86
- const generateNode = (node, exprs, bindings) => {
66
+ const getAgastNodeType = (flags) => {
67
+ if (flags.intrinsic && flags.token) {
68
+ return 's_i_node';
69
+ } else if (flags.token && flags.trivia) {
70
+ return 's_t_node';
71
+ } else if (flags.token && flags.escape) {
72
+ return 's_e_node';
73
+ } else if (flags.token) {
74
+ return 's_node';
75
+ } else {
76
+ return 'node';
77
+ }
78
+ };
79
+
80
+ const generateBabelNode = (node, exprs, bindings) => {
87
81
  const resolver = new PathResolver(node);
88
- const { children, type, language, attributes } = node;
82
+ const { flags = {}, children, type, language, attributes } = node;
83
+
89
84
  const properties_ = {};
90
85
  const children_ = [];
91
86
 
@@ -94,64 +89,122 @@ const generateNode = (node, exprs, bindings) => {
94
89
  }
95
90
 
96
91
  for (const child of children) {
97
- children_.push(generateNodeChild(child, bindings));
98
-
99
92
  if (child.type === 'Reference') {
100
93
  const path = child.value;
101
- const { pathIsArray } = path;
94
+ const { isArray: pathIsArray } = path;
102
95
  const resolved = resolver.get(path);
103
96
 
104
- let embedded = resolved;
105
97
  if (resolved) {
106
- embedded = generateNode(resolved, exprs, bindings);
98
+ set(properties_, path, generateBabelNode(resolved, exprs, bindings));
99
+ children_.push(generateBabelNodeChild(child, exprs, bindings));
107
100
  } else {
108
- embedded = exprs.pop();
109
- const { interpolateArray, interpolateString } = bindings;
101
+ // gap
102
+ const expr = exprs.pop();
103
+ const { interpolateArray, interpolateArrayChildren, interpolateString } = bindings;
110
104
 
111
105
  if (pathIsArray) {
112
- embedded = expression('[...%%interpolateArray%%(%%embedded%%)]')({
113
- interpolateArray,
114
- embedded,
115
- }).elements[0];
116
- } else if (language === 'String' && type === 'String') {
117
- embedded = expression('%%interpolateString%%(%%embedded%%)')({
118
- interpolateString,
119
- embedded,
120
- });
106
+ set(
107
+ properties_,
108
+ path,
109
+ expression('[...%%interpolateArray%%(%%expr%%)]')({
110
+ interpolateArray,
111
+ expr,
112
+ }).elements[0],
113
+ );
114
+
115
+ children_.push(
116
+ t.spreadElement(
117
+ expression('%%interpolateArrayChildren%%(%%expr%%, %%ref%%, %%sep%%)')({
118
+ interpolateArrayChildren,
119
+ expr,
120
+ ref: expression(`%%t%%.ref\`${printRef(child.value)}\``)({ t: bindings.t }),
121
+
122
+ // Really really awful unsafe-as-heck hack, to be removed ASAP
123
+ // Fixing this requires having interpolation happen during parsing
124
+ // That way the grammar can deal with the separators!
125
+ sep: expression(
126
+ "%%t%%.embedded(%%t%%.t_node(%%l%%.Comment, null, [%%t%%.embedded(%%t%%.t_node('Space', 'Space', [%%t%%.lit(' ')]))]))",
127
+ )({ t: bindings.t, l: bindings.l }),
128
+ }),
129
+ ),
130
+ );
131
+ } else if (language === cstml.canonicalURL && type === 'String') {
132
+ set(
133
+ properties_,
134
+ path,
135
+ expression('%%interpolateString%%(%%expr%%)')({
136
+ interpolateString,
137
+ expr,
138
+ }),
139
+ );
140
+
141
+ children_.push(generateBabelNodeChild(child, exprs, bindings));
142
+ } else {
143
+ set(properties_, path, expr);
144
+
145
+ children_.push(generateBabelNodeChild(child, exprs, bindings));
121
146
  }
122
147
  }
123
-
124
- set(properties_, path, embedded);
148
+ } else {
149
+ children_.push(generateBabelNodeChild(child, exprs, bindings));
125
150
  }
126
151
  }
127
152
 
128
- return expression(
129
- `%%t%%.node(%%language%%, %%type%%, %%children%%, %%properties%%, %%attributes%%)`,
130
- )({
153
+ const nodeType = getAgastNodeType(flags);
154
+
155
+ const propsAtts =
156
+ nodeType === 's_node' || nodeType === 's_i_node' ? '' : ', %%properties%%, %%attributes%%';
157
+ const propsAttsValue =
158
+ nodeType === 's_node' || nodeType === 's_i_node'
159
+ ? {}
160
+ : {
161
+ properties: t.objectExpression(
162
+ Object.entries(properties_).map(([key, value]) =>
163
+ t.objectProperty(
164
+ t.identifier(key),
165
+ isArray(value) ? t.arrayExpression(value) : value,
166
+ ),
167
+ ),
168
+ ),
169
+ attributes: t.objectExpression(
170
+ Object.entries(attributes).map(([key, value]) =>
171
+ t.objectProperty(t.identifier(key), getBabelASTValue(value, exprs, bindings)),
172
+ ),
173
+ ),
174
+ };
175
+
176
+ return expression(`%%t%%.%%nodeType%%(%%l%%.%%language%%, %%type%%, %%children%%${propsAtts})`)({
131
177
  t: bindings.t,
132
- language: t.stringLiteral(language),
178
+ l: bindings.l,
179
+ language: t.identifier(namesFor[language]),
180
+ nodeType: t.identifier(nodeType),
133
181
  type: t.stringLiteral(type),
134
- children: t.arrayExpression(children_),
135
- properties: t.objectExpression(
136
- Object.entries(properties_).map(([key, value]) =>
137
- t.objectProperty(t.identifier(key), isArray(value) ? t.arrayExpression(value) : value),
138
- ),
139
- ),
140
- attributes: t.objectExpression(
141
- Object.entries(attributes).map(([key, value]) =>
142
- t.objectProperty(t.identifier(key), getASTValue(value, exprs, bindings)),
143
- ),
144
- ),
182
+ children:
183
+ nodeType === 's_node' || nodeType === 's_i_node'
184
+ ? t.stringLiteral(children[0].value)
185
+ : t.arrayExpression(children_),
186
+ ...propsAttsValue,
145
187
  });
146
188
  };
147
189
 
190
+ const getTopScope = (scope) => {
191
+ let top = scope;
192
+ while (top.parent) top = top.parent;
193
+ return top;
194
+ };
195
+
196
+ const namesFor = Object.fromEntries([
197
+ ...[i, re, spam, cstml].map((l) => [l.canonicalURL, l.name]),
198
+ ['https://bablr.org/languages/core/space-tab-newline', 'Space'],
199
+ ]);
200
+
148
201
  const languages = {
149
- i,
150
- re,
151
- spam,
152
- str,
153
- num,
154
- cst: cstml,
202
+ i: '@bablr/language-bablr-vm-instruction',
203
+ re: '@bablr/language-regex-vm-pattern',
204
+ spam: '@bablr/language-spamex',
205
+ str: '@bablr/language-cstml',
206
+ num: '@bablr/language-cstml',
207
+ cst: '@bablr/language-cstml',
155
208
  };
156
209
 
157
210
  const topTypes = {
@@ -159,14 +212,17 @@ const topTypes = {
159
212
  re: 'Pattern',
160
213
  spam: 'Matcher',
161
214
  str: 'String',
162
- num: 'Number',
215
+ num: 'Integer',
163
216
  cst: 'Fragment',
164
217
  };
165
218
 
166
- const getTopScope = (scope) => {
167
- let top = scope;
168
- while (top.parent) top = top.parent;
169
- return top;
219
+ const miniLanguages = {
220
+ i,
221
+ re,
222
+ spam,
223
+ str: cstml,
224
+ num: cstml,
225
+ cst: cstml,
170
226
  };
171
227
 
172
228
  const shorthandMacro = ({ references }) => {
@@ -183,16 +239,30 @@ const shorthandMacro = ({ references }) => {
183
239
  references.cst,
184
240
  )) {
185
241
  if (!bindings.t) {
186
- bindings.t = addNamespace(getTopScope(ref.scope).path, '@bablr/boot-helpers/types', {
242
+ bindings.t = addNamespace(getTopScope(ref.scope).path, '@bablr/agast-helpers/shorthand', {
187
243
  nameHint: 't',
188
244
  });
189
245
  }
190
246
 
247
+ if (!bindings.l) {
248
+ bindings.l = addNamespace(getTopScope(ref.scope).path, '@bablr/agast-vm-helpers/languages', {
249
+ nameHint: 'l',
250
+ });
251
+ }
252
+
191
253
  if (!bindings.interpolateArray) {
192
254
  bindings.interpolateArray = addNamed(
193
255
  getTopScope(ref.scope).path,
194
256
  'interpolateArray',
195
- '@bablr/boot-helpers/template',
257
+ '@bablr/agast-helpers/template',
258
+ );
259
+ }
260
+
261
+ if (!bindings.interpolateArrayChildren) {
262
+ bindings.interpolateArrayChildren = addNamed(
263
+ getTopScope(ref.scope).path,
264
+ 'interpolateArrayChildren',
265
+ '@bablr/agast-helpers/template',
196
266
  );
197
267
  }
198
268
 
@@ -200,7 +270,7 @@ const shorthandMacro = ({ references }) => {
200
270
  bindings.interpolateString = addNamed(
201
271
  getTopScope(ref.scope).path,
202
272
  'interpolateString',
203
- '@bablr/boot-helpers/template',
273
+ '@bablr/agast-helpers/template',
204
274
  );
205
275
  }
206
276
 
@@ -216,15 +286,55 @@ const shorthandMacro = ({ references }) => {
216
286
  ? ref.parentPath.node.property.name
217
287
  : topTypes[tagName];
218
288
 
219
- if (!language) throw new Error();
289
+ const streamText = quasis
290
+ .map((q) => `"${q.value.raw.replace(/[\\"]/g, '\\$&')}"`)
291
+ .join(' <//> ');
292
+
293
+ // console.log(streamText);
294
+
295
+ const miniLanguage = miniLanguages[tagName];
220
296
 
221
297
  const ast = new TemplateParser(
222
- language,
298
+ miniLanguage,
223
299
  quasis.map((q) => q.value.raw),
224
300
  expressions.map(() => null),
225
- ).eval({ language: language.name, type });
301
+ ).eval({ language: miniLanguage.name, type });
302
+
303
+ const agAST = getAgASTValue(miniLanguage, ast);
304
+ let referenceDocument = null;
305
+
306
+ const document = printPrettyCSTML(agAST);
307
+
308
+ // try {
309
+ // const documentResult = spawnSync(
310
+ // '../bablr-cli/bin/index.js',
311
+ // ['-l', language, '-p', type, '-f'],
312
+ // {
313
+ // input: streamText,
314
+ // encoding: 'utf8',
315
+ // },
316
+ // );
317
+
318
+ // if (documentResult.status > 0) {
319
+ // throw new Error('bablr CLI parse return non-zero exit');
320
+ // }
321
+
322
+ // if (documentResult.error) {
323
+ // throw new Error(documentResult.error);
324
+ // }
325
+
326
+ // referenceDocument = documentResult.stdout.slice(0, -1);
327
+
328
+ // if (!referenceDocument.length) {
329
+ // throw new Error();
330
+ // }
331
+
332
+ // // secondaryAst = parse(cstml, 'Document', document);
333
+ // } catch (e) {
334
+ // console.warn(' parse failure');
335
+ // }
226
336
 
227
- taggedTemplate.replaceWith(generateNode(ast, expressions.reverse(), bindings));
337
+ taggedTemplate.replaceWith(generateBabelNode(agAST, expressions.reverse(), bindings));
228
338
  }
229
339
  };
230
340
 
@@ -1,38 +0,0 @@
1
- const { buildCovers } = require('../utils.js');
2
- const sym = require('../symbols.js');
3
-
4
- const name = 'Number';
5
-
6
- const dependencies = {};
7
-
8
- const covers = buildCovers({
9
- [sym.node]: ['Number', 'Digit'],
10
- Number: ['LiteralNumber', 'SymbolicNumber'],
11
- });
12
-
13
- const grammar = class NumberMiniparserGrammar {
14
- // @Node
15
- LiteralNumber(p) {
16
- p.eatMatch('-', 'Punctuator', { path: 'negative' });
17
- p.eatProduction('Digits', { path: 'digits[]' });
18
- }
19
-
20
- // @Node
21
- SymbolicNumber(p) {
22
- p.eatMatch('-', 'Punctuator', { path: 'negative' });
23
- p.eat('Infinity', 'Keyword', { path: 'value' });
24
- }
25
-
26
- Digits(p) {
27
- while (p.match(/\d/y)) {
28
- p.eatProduction('Digit');
29
- }
30
- }
31
-
32
- // @Node
33
- Digit(p) {
34
- p.eatLiteral(/\d/y);
35
- }
36
- };
37
-
38
- module.exports = { name, dependencies, covers, grammar };
@@ -1,88 +0,0 @@
1
- const objectEntries = require('iter-tools-es/methods/object-entries');
2
- const { buildCovers } = require('../utils.js');
3
- const sym = require('../symbols.js');
4
-
5
- const name = 'String';
6
-
7
- const dependencies = {};
8
-
9
- const covers = buildCovers({
10
- [sym.node]: ['String', 'Content'],
11
- });
12
-
13
- const PN = 'Punctuator';
14
-
15
- const escapables = new Map(
16
- objectEntries({
17
- n: '\n',
18
- r: '\r',
19
- t: '\t',
20
- 0: '\0',
21
- }),
22
- );
23
-
24
- const cookEscape = (escape, span) => {
25
- let hexMatch;
26
-
27
- if (!escape.startsWith('\\')) {
28
- throw new Error('string escape must start with \\');
29
- }
30
-
31
- if ((hexMatch = /\\x([0-9a-f]{2})/iy.exec(escape))) {
32
- //continue
33
- } else if ((hexMatch = /\\u([0-9a-f]{4})/iy.exec(escape))) {
34
- //continue
35
- } else if ((hexMatch = /\\u{([0-9a-f]+)}/iy.exec(escape))) {
36
- //continue
37
- }
38
-
39
- if (hexMatch) {
40
- return String.fromCodePoint(parseInt(hexMatch[1], 16));
41
- }
42
-
43
- const litPattern = span === 'Single' ? /\\([\\nrt0'])/y : /\\([\\nrt0"])/y;
44
- const litMatch = litPattern.exec(escape);
45
-
46
- if (litMatch) {
47
- return escapables.get(litMatch[1]) || litMatch[1];
48
- }
49
-
50
- throw new Error('unable to cook string escape');
51
- };
52
-
53
- const grammar = class StringMiniparserGrammar {
54
- // @Node
55
- String(p) {
56
- const q = p.match(/['"]/y) || '"';
57
-
58
- const span = q === '"' ? 'Double' : 'Single';
59
-
60
- p.eat(q, PN, { path: 'open', startSpan: span, balanced: q });
61
- while (p.match(/./sy) || p.atExpression) {
62
- p.eatProduction('Content', { path: 'content' });
63
- }
64
- p.eat(q, PN, { path: 'close', endSpan: span, balancer: true });
65
- }
66
-
67
- // @Node
68
- Content(p) {
69
- let esc, lit;
70
- let i = 0;
71
- do {
72
- esc =
73
- p.span.type === 'Single'
74
- ? p.eatMatchEscape(/\\(u(\{\d{1,6}\}|\d{4})|x[0-9a-fA-F]{2}|[\\nrt0'])/y)
75
- : p.eatMatchEscape(/\\(u(\{\d{1,6}\}|\d{4})|x[0-9a-fA-F]{2}|[\\nrt0"])/y);
76
- lit =
77
- p.span.type === 'Single'
78
- ? p.eatMatchLiteral(/[^\r\n\0\\']+/y)
79
- : p.eatMatchLiteral(/[^\r\n\0\\"]+/y);
80
- i++;
81
- } while (esc || lit);
82
- if (i === 1 && !esc && !lit) {
83
- throw new Error('Invalid string content');
84
- }
85
- }
86
- };
87
-
88
- module.exports = { name, dependencies, covers, grammar, escapables, cookEscape };