@bablr/boot 0.1.1 → 0.1.2

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.
Files changed (2) hide show
  1. package/package.json +3 -2
  2. package/shorthand.macro.js +224 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bablr/boot",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Compile-time tools for bootstrapping BABLR VM",
5
5
  "engines": {
6
6
  "node": ">=12.0.0"
@@ -10,7 +10,8 @@
10
10
  "./shorthand.macro": "./shorthand.macro.js"
11
11
  },
12
12
  "files": [
13
- "lib/**/*.js"
13
+ "lib/**/*.js",
14
+ "shorthand.macro.js"
14
15
  ],
15
16
  "sideEffects": false,
16
17
  "dependencies": {
@@ -0,0 +1,224 @@
1
+ const t = require('@babel/types');
2
+ const { expression } = require('@babel/template');
3
+ const isObject = require('iter-tools-es/methods/is-object');
4
+ const isUndefined = require('iter-tools-es/methods/is-undefined');
5
+ const isNull = require('iter-tools-es/methods/is-null');
6
+ const isString = require('iter-tools-es/methods/is-string');
7
+ const concat = require('iter-tools-es/methods/concat');
8
+ const { createMacro } = require('babel-plugin-macros');
9
+ const { TemplateParser } = require('./lib/miniparser.js');
10
+ const i = require('./lib/languages/instruction.js');
11
+ const re = require('./lib/languages/regex.js');
12
+ const spam = require('./lib/languages/spamex.js');
13
+ const str = require('./lib/languages/string.js');
14
+ const cstml = require('./lib/languages/cstml.js');
15
+ const { addNamespace, addNamed } = require('@babel/helper-module-imports');
16
+ const { PathResolver } = require('@bablr/boot-helpers/path');
17
+
18
+ const { hasOwn } = Object;
19
+ const { isArray } = Array;
20
+ const isNumber = (v) => typeof v === 'number';
21
+ const isBoolean = (v) => typeof v === 'boolean';
22
+
23
+ const isPlainObject = (v) => isObject(v) && !isArray(v);
24
+
25
+ const set = (obj, path, value, pathIsArray) => {
26
+ if (pathIsArray) {
27
+ if (!obj[path]) {
28
+ obj[path] = [];
29
+ }
30
+
31
+ if (!isArray(obj[path])) throw new Error('bad array value');
32
+
33
+ obj[path].push(value);
34
+ } else {
35
+ if (hasOwn(obj, path)) {
36
+ throw new Error('duplicate child name');
37
+ }
38
+ obj[path] = value;
39
+ }
40
+ };
41
+
42
+ const getASTValue = (v, exprs, bindings) => {
43
+ return isNull(v)
44
+ ? t.nullLiteral()
45
+ : isUndefined(v)
46
+ ? t.identifier('undefined')
47
+ : isString(v)
48
+ ? t.stringLiteral(v)
49
+ : isNumber(v)
50
+ ? t.numericLiteral(v)
51
+ : isBoolean(v)
52
+ ? t.booleanLiteral(v)
53
+ : isArray(v)
54
+ ? t.arrayExpression(v.map((v) => getASTValue(v, exprs, bindings)))
55
+ : isPlainObject(v) && !v.language
56
+ ? t.objectExpression(
57
+ Object.entries(v).map(([k, v]) =>
58
+ t.objectProperty(t.identifier(k), getASTValue(v, exprs, bindings)),
59
+ ),
60
+ )
61
+ : generateNode(v, exprs, bindings);
62
+ };
63
+
64
+ const generateNodeChild = (child, bindings) => {
65
+ if (child.type === 'Reference') {
66
+ return expression(`%%t%%.ref\`${child.value}\``)({ t: bindings.t });
67
+ } else if (child.type === 'Literal') {
68
+ return expression(`%%t%%.lit\`${child.value.replace(/\\/g, '\\\\')}\``)({ t: bindings.t });
69
+ } else if (child.type === 'Trivia') {
70
+ return expression(`%%t%%.trivia\` \``)({ t: bindings.t });
71
+ } else if (child.type === 'Escape') {
72
+ return expression(`%%t%%.esc(%%cooked%%, %%raw%%)`)({
73
+ t: bindings.t,
74
+ cooked: child.cooked,
75
+ raw: child.raw,
76
+ });
77
+ } else {
78
+ throw new Error(`Unknown child type ${child.type}`);
79
+ }
80
+ };
81
+
82
+ const generateNode = (node, exprs, bindings) => {
83
+ const resolver = new PathResolver(node);
84
+ const { children, type, language, attributes, properties } = node;
85
+ const properties_ = {};
86
+ const children_ = [];
87
+
88
+ if (!children) {
89
+ throw new Error();
90
+ }
91
+
92
+ for (const child of children) {
93
+ children_.push(generateNodeChild(child, bindings));
94
+
95
+ if (child.type === 'Reference') {
96
+ const path = child.value;
97
+ const resolved = resolver.get(path);
98
+ const pathIsArray = isArray(properties[path]);
99
+
100
+ let embedded = resolved;
101
+ if (resolved) {
102
+ embedded = generateNode(resolved, exprs, bindings);
103
+ } else {
104
+ embedded = exprs.pop();
105
+ const { interpolateArray, interpolateString } = bindings;
106
+
107
+ if (pathIsArray) {
108
+ embedded = expression('[...%%interpolateArray%%(%%embedded%%)]')({
109
+ interpolateArray,
110
+ embedded,
111
+ }).elements[0];
112
+ } else if (language === 'String' && type === 'String') {
113
+ embedded = expression('%%interpolateString%%(%%embedded%%)')({
114
+ interpolateString,
115
+ embedded,
116
+ });
117
+ }
118
+ }
119
+
120
+ set(properties_, path, embedded, pathIsArray);
121
+ }
122
+ }
123
+
124
+ return expression(
125
+ `%%t%%.node(%%language%%, %%type%%, %%children%%, %%properties%%, %%attributes%%)`,
126
+ )({
127
+ t: bindings.t,
128
+ language: t.stringLiteral(language),
129
+ type: t.stringLiteral(type),
130
+ children: t.arrayExpression(children_),
131
+ properties: t.objectExpression(
132
+ Object.entries(properties_).map(([key, value]) =>
133
+ t.objectProperty(t.identifier(key), isArray(value) ? t.arrayExpression(value) : value),
134
+ ),
135
+ ),
136
+ attributes: t.objectExpression(
137
+ Object.entries(attributes).map(([key, value]) =>
138
+ t.objectProperty(t.identifier(key), getASTValue(value, exprs, bindings)),
139
+ ),
140
+ ),
141
+ });
142
+ };
143
+
144
+ const languages = {
145
+ i,
146
+ re,
147
+ spam,
148
+ str,
149
+ cst: cstml,
150
+ };
151
+
152
+ const topTypes = {
153
+ i: 'Call',
154
+ re: 'Pattern',
155
+ spam: 'Matcher',
156
+ str: 'String',
157
+ cst: 'Node',
158
+ };
159
+
160
+ const getTopScope = (scope) => {
161
+ let top = scope;
162
+ while (top.parent) top = top.parent;
163
+ return top;
164
+ };
165
+
166
+ const shorthandMacro = ({ references }) => {
167
+ const bindings = {};
168
+
169
+ // decorator references
170
+
171
+ for (const ref of concat(
172
+ references.i,
173
+ references.spam,
174
+ references.re,
175
+ references.str,
176
+ references.cst,
177
+ )) {
178
+ if (!bindings.t) {
179
+ bindings.t = addNamespace(getTopScope(ref.scope).path, '@bablr/boot-helpers/types', {
180
+ nameHint: 't',
181
+ });
182
+ }
183
+
184
+ if (!bindings.interpolateArray) {
185
+ bindings.interpolateArray = addNamed(
186
+ getTopScope(ref.scope).path,
187
+ 'interpolateArray',
188
+ '@bablr/boot-helpers/template',
189
+ );
190
+ }
191
+
192
+ if (!bindings.interpolateString) {
193
+ bindings.interpolateString = addNamed(
194
+ getTopScope(ref.scope).path,
195
+ 'interpolateString',
196
+ '@bablr/boot-helpers/template',
197
+ );
198
+ }
199
+
200
+ const taggedTemplate =
201
+ ref.parentPath.type === 'MemberExpression' ? ref.parentPath.parentPath : ref.parentPath;
202
+
203
+ const { quasis, expressions } = taggedTemplate.node.quasi;
204
+
205
+ const tagName = ref.node.name;
206
+ const language = languages[tagName];
207
+ const type =
208
+ ref.parentPath.type === 'MemberExpression'
209
+ ? ref.parentPath.node.property.name
210
+ : topTypes[tagName];
211
+
212
+ if (!language) throw new Error();
213
+
214
+ const ast = new TemplateParser(
215
+ language,
216
+ quasis.map((q) => q.value.raw),
217
+ expressions.map(() => null),
218
+ ).eval({ language: language.name, type });
219
+
220
+ taggedTemplate.replaceWith(generateNode(ast, expressions.reverse(), bindings));
221
+ }
222
+ };
223
+
224
+ module.exports = createMacro(shorthandMacro);