@bablr/boot 0.1.9 → 0.2.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.
@@ -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,53 +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 escapeReplacer = (esc) => {
67
- if (esc === '\r') {
68
- return '\\r';
69
- } else if (esc === '\n') {
70
- return '\\n';
71
- } else if (esc === '\0') {
72
- return '\\0';
73
- } else {
74
- return `\\${esc}`;
75
- }
76
- };
77
- const printTemplateString = (str) => {
78
- return `\`${str.replace(/[`\\\0\r\n]/g, escapeReplacer)}\``;
79
- };
80
-
81
- const generateNodeChild = (child, bindings) => {
48
+ const generateBabelNodeChild = (child, exprs, bindings) => {
82
49
  if (child.type === 'Reference') {
83
- const { pathName, pathIsArray } = child.value;
84
- const printedRef = pathIsArray ? `${pathName}[]` : pathName;
85
- return expression(`%%t%%.ref\`${printedRef}\``)({ t: bindings.t });
50
+ return expression(`%%t%%.ref\`${printRef(child.value)}\``)({ t: bindings.t });
86
51
  } else if (child.type === 'Literal') {
87
- return expression(`%%t%%.lit${printTemplateString(child.value)}`)({ t: bindings.t });
88
- } else if (child.type === 'Trivia') {
89
- return expression(`%%t%%.trivia${printTemplateString(child.value)}`)({ t: bindings.t });
90
- } else if (child.type === 'Escape') {
91
- const {cooked, raw} = child.value
92
- return expression(`%%t%%.esc(${printTemplateString(raw)}, ${printTemplateString(cooked)})`)({
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%%)`)({
93
58
  t: bindings.t,
59
+ value: generateBabelNode(child.value, exprs, bindings),
94
60
  });
95
61
  } else {
96
62
  throw new Error(`Unknown child type ${child.type}`);
97
63
  }
98
64
  };
99
65
 
100
- 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) => {
101
81
  const resolver = new PathResolver(node);
102
- const { children, type, language, attributes } = node;
82
+ const { flags = {}, children, type, language, attributes } = node;
83
+
103
84
  const properties_ = {};
104
85
  const children_ = [];
105
86
 
@@ -108,64 +89,122 @@ const generateNode = (node, exprs, bindings) => {
108
89
  }
109
90
 
110
91
  for (const child of children) {
111
- children_.push(generateNodeChild(child, bindings));
112
-
113
92
  if (child.type === 'Reference') {
114
93
  const path = child.value;
115
- const { pathIsArray } = path;
94
+ const { isArray: pathIsArray } = path;
116
95
  const resolved = resolver.get(path);
117
96
 
118
- let embedded = resolved;
119
97
  if (resolved) {
120
- embedded = generateNode(resolved, exprs, bindings);
98
+ set(properties_, path, generateBabelNode(resolved, exprs, bindings));
99
+ children_.push(generateBabelNodeChild(child, exprs, bindings));
121
100
  } else {
122
- embedded = exprs.pop();
123
- const { interpolateArray, interpolateString } = bindings;
101
+ // gap
102
+ const expr = exprs.pop();
103
+ const { interpolateArray, interpolateArrayChildren, interpolateString } = bindings;
124
104
 
125
105
  if (pathIsArray) {
126
- embedded = expression('[...%%interpolateArray%%(%%embedded%%)]')({
127
- interpolateArray,
128
- embedded,
129
- }).elements[0];
130
- } else if (language === 'String' && type === 'String') {
131
- embedded = expression('%%interpolateString%%(%%embedded%%)')({
132
- interpolateString,
133
- embedded,
134
- });
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));
135
146
  }
136
147
  }
137
-
138
- set(properties_, path, embedded);
148
+ } else {
149
+ children_.push(generateBabelNodeChild(child, exprs, bindings));
139
150
  }
140
151
  }
141
152
 
142
- return expression(
143
- `%%t%%.node(%%language%%, %%type%%, %%children%%, %%properties%%, %%attributes%%)`,
144
- )({
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})`)({
145
177
  t: bindings.t,
146
- language: t.stringLiteral(language),
178
+ l: bindings.l,
179
+ language: t.identifier(namesFor[language]),
180
+ nodeType: t.identifier(nodeType),
147
181
  type: t.stringLiteral(type),
148
- children: t.arrayExpression(children_),
149
- properties: t.objectExpression(
150
- Object.entries(properties_).map(([key, value]) =>
151
- t.objectProperty(t.identifier(key), isArray(value) ? t.arrayExpression(value) : value),
152
- ),
153
- ),
154
- attributes: t.objectExpression(
155
- Object.entries(attributes).map(([key, value]) =>
156
- t.objectProperty(t.identifier(key), getASTValue(value, exprs, bindings)),
157
- ),
158
- ),
182
+ children:
183
+ nodeType === 's_node' || nodeType === 's_i_node'
184
+ ? t.stringLiteral(children[0].value)
185
+ : t.arrayExpression(children_),
186
+ ...propsAttsValue,
159
187
  });
160
188
  };
161
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
+
162
201
  const languages = {
163
- i,
164
- re,
165
- spam,
166
- str,
167
- num,
168
- 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',
169
208
  };
170
209
 
171
210
  const topTypes = {
@@ -177,10 +216,13 @@ const topTypes = {
177
216
  cst: 'Fragment',
178
217
  };
179
218
 
180
- const getTopScope = (scope) => {
181
- let top = scope;
182
- while (top.parent) top = top.parent;
183
- return top;
219
+ const miniLanguages = {
220
+ i,
221
+ re,
222
+ spam,
223
+ str: cstml,
224
+ num: cstml,
225
+ cst: cstml,
184
226
  };
185
227
 
186
228
  const shorthandMacro = ({ references }) => {
@@ -197,16 +239,30 @@ const shorthandMacro = ({ references }) => {
197
239
  references.cst,
198
240
  )) {
199
241
  if (!bindings.t) {
200
- bindings.t = addNamespace(getTopScope(ref.scope).path, '@bablr/boot-helpers/types', {
242
+ bindings.t = addNamespace(getTopScope(ref.scope).path, '@bablr/agast-helpers/shorthand', {
201
243
  nameHint: 't',
202
244
  });
203
245
  }
204
246
 
247
+ if (!bindings.l) {
248
+ bindings.l = addNamespace(getTopScope(ref.scope).path, '@bablr/agast-vm-helpers/languages', {
249
+ nameHint: 'l',
250
+ });
251
+ }
252
+
205
253
  if (!bindings.interpolateArray) {
206
254
  bindings.interpolateArray = addNamed(
207
255
  getTopScope(ref.scope).path,
208
256
  'interpolateArray',
209
- '@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',
210
266
  );
211
267
  }
212
268
 
@@ -214,7 +270,7 @@ const shorthandMacro = ({ references }) => {
214
270
  bindings.interpolateString = addNamed(
215
271
  getTopScope(ref.scope).path,
216
272
  'interpolateString',
217
- '@bablr/boot-helpers/template',
273
+ '@bablr/agast-helpers/template',
218
274
  );
219
275
  }
220
276
 
@@ -230,15 +286,55 @@ const shorthandMacro = ({ references }) => {
230
286
  ? ref.parentPath.node.property.name
231
287
  : topTypes[tagName];
232
288
 
233
- 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];
234
296
 
235
297
  const ast = new TemplateParser(
236
- language,
298
+ miniLanguage,
237
299
  quasis.map((q) => q.value.raw),
238
300
  expressions.map(() => null),
239
- ).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
+ // }
240
336
 
241
- taggedTemplate.replaceWith(generateNode(ast, expressions.reverse(), bindings));
337
+ taggedTemplate.replaceWith(generateBabelNode(agAST, expressions.reverse(), bindings));
242
338
  }
243
339
  };
244
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: ['Integer', 'Infinity'],
11
- });
12
-
13
- const grammar = class NumberMiniparserGrammar {
14
- // @Node
15
- Integer(p) {
16
- p.eatMatch('-', 'Punctuator', { path: 'negative' });
17
- p.eatProduction('Digits', { path: 'digits[]' });
18
- }
19
-
20
- // @Node
21
- Infinity(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,86 +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 = /\\u([0-9a-f]{4})/iy.exec(escape))) {
32
- //continue
33
- } else if ((hexMatch = /\\u{([0-9a-f]+)}/iy.exec(escape))) {
34
- //continue
35
- }
36
-
37
- if (hexMatch) {
38
- return String.fromCodePoint(parseInt(hexMatch[1], 16));
39
- }
40
-
41
- const litPattern = span === 'Single' ? /\\([\\nrt0'])/y : /\\([\\nrt0"])/y;
42
- const litMatch = litPattern.exec(escape);
43
-
44
- if (litMatch) {
45
- return escapables.get(litMatch[1]) || litMatch[1];
46
- }
47
-
48
- throw new Error('unable to cook string escape');
49
- };
50
-
51
- const grammar = class StringMiniparserGrammar {
52
- // @Node
53
- String(p) {
54
- const q = p.match(/['"]/y) || '"';
55
-
56
- const span = q === '"' ? 'Double' : 'Single';
57
-
58
- p.eat(q, PN, { path: 'open', startSpan: span, balanced: q });
59
- while (p.match(/./sy) || p.atExpression) {
60
- p.eatProduction('Content', { path: 'content' });
61
- }
62
- p.eat(q, PN, { path: 'close', endSpan: span, balancer: true });
63
- }
64
-
65
- // @Node
66
- Content(p) {
67
- let esc, lit;
68
- let i = 0;
69
- do {
70
- esc =
71
- p.span.type === 'Single'
72
- ? p.eatMatchEscape(/\\(u(\{\d{1,6}\}|\d{4})|[\\nrt0'])/y)
73
- : p.eatMatchEscape(/\\(u(\{\d{1,6}\}|\d{4})|[\\nrt0"])/y);
74
- lit =
75
- p.span.type === 'Single'
76
- ? p.eatMatchLiteral(/[^\r\n\0\\']+/y)
77
- : p.eatMatchLiteral(/[^\r\n\0\\"]+/y);
78
- i++;
79
- } while (esc || lit);
80
- if (i === 1 && !esc && !lit) {
81
- throw new Error('Invalid string content');
82
- }
83
- }
84
- };
85
-
86
- module.exports = { name, dependencies, covers, grammar, escapables, cookEscape };