@bablr/helpers 0.24.0 → 0.25.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/lib/trivia.js CHANGED
@@ -1,345 +1,210 @@
1
1
  /* global WeakSet */
2
- import { spam as m } from '@bablr/boot';
3
2
  import { Coroutine } from '@bablr/coroutine';
4
- import { eat, eatMatch, mapOwnProductions, mapProductions, o } from './grammar.js';
3
+ import { eat, eatMatch, mapProductions, o, r } from './grammar.js';
4
+ import { Matcher, Regex } from './symbols.js';
5
+ import { get, getCooked, getFlagsWithGap, nodeFlags } from '@bablr/agast-helpers/tree';
6
+ import {
7
+ buildTreeNodeMatcher,
8
+ buildIdentifier,
9
+ buildNodeFlags,
10
+ buildTreeNodeMatcherOpen,
11
+ buildPropertyMatcher,
12
+ buildString,
13
+ buildBoundNodeMatcher,
14
+ } from './builders.js';
15
+ import { spam as m } from '@bablr/boot';
5
16
  import {
6
- Matcher,
7
- Regex,
8
- Node,
9
- OpenNodeTag,
10
- CloseNodeTag,
11
- ReferenceTag,
12
- Property,
13
- } from './symbols.js';
14
- import { buildIdentifier, buildString } from './builders.js';
15
- import { buildCall, buildEmbeddedInstruction } from '@bablr/agast-vm-helpers/builders';
16
- import { getEmbeddedInstruction } from '@bablr/agast-vm-helpers/deembed';
17
+ buildCall,
18
+ buildEmbeddedMatcher,
19
+ buildEmbeddedNode,
20
+ buildEmbeddedObject,
21
+ buildEmbeddedRegex,
22
+ } from '@bablr/agast-vm-helpers/builders';
17
23
  import { reifyExpression } from '@bablr/agast-vm-helpers';
18
- import { get, getCooked } from '@bablr/agast-helpers/tree';
19
- import { buildPathSegment } from '@bablr/agast-helpers/path';
20
-
21
- const lookbehind = (context, s) => {
22
- let token = s.resultPath;
23
- while (token && [OpenNodeTag, CloseNodeTag, ReferenceTag].includes(token.type)) {
24
- const prevToken = context.getPreviousTagPath(token);
25
- if (!prevToken) break;
26
- token = prevToken;
27
- }
28
- return token;
29
- };
30
-
31
- const matchedResults = new WeakSet();
32
-
33
- export const basicTriviaEnhancer = ({ triviaIsAllowed, triviaMatcher }, grammar) => {
34
- let Wrapper_ = 'Wrapper';
35
- let Literal_ = 'Literal';
36
-
37
- while (grammar.prototype[Wrapper_]) Wrapper_ += '_';
38
- while (grammar.prototype[Literal_]) Literal_ += '_';
39
-
40
- const resultGrammar = mapOwnProductions((production) => {
41
- return function* (props) {
42
- const co = new Coroutine(production(props));
43
- const { s, ctx, flags, isCover, isCovered } = props;
44
-
45
- co.advance();
46
-
47
- try {
48
- while (!co.done) {
49
- const instr = co.value;
50
- const { verb, arguments: args = [] } = instr;
51
- let returnValue = undefined;
52
-
53
- switch (verb) {
54
- case 'eat':
55
- case 'eatMatch':
56
- case 'match':
57
- case 'guard': {
58
- const { 0: matcher } = args;
59
-
60
- if (
61
- matcher &&
62
- !isCovered &&
63
- matcher.type === Matcher &&
64
- getCooked(get(['refMatcher', 'type', 'value'], matcher.value)) !== '#'
65
- ) {
66
- const previous = lookbehind(ctx, s);
67
- if (triviaIsAllowed(s) && (!previous || !matchedResults.has(previous))) {
68
- matchedResults.add(previous);
69
- yield eatMatch(triviaMatcher);
70
- matchedResults.add(s.resultPath);
71
- }
72
- }
73
-
74
- returnValue = returnValue || (yield instr);
75
- break;
76
- }
77
-
78
- default:
79
- returnValue = yield instr;
80
- break;
81
- }
82
-
83
- co.advance(returnValue);
84
- }
85
-
86
- if (co.value) {
87
- let realMatcher = reifyExpression(get('nodeMatcher', co.value.arguments[0].value));
88
- let { flags: matcherFlags } = realMatcher;
89
-
90
- let isNode = matcherFlags && !matcherFlags.fragment;
91
-
92
- if (
93
- !flags.token &&
94
- isNode &&
95
- !isCover &&
96
- (!s.depths.path || (co.value && ['shift', 'shiftMatch'].includes(co.value.verb)))
97
- ) {
98
- if (triviaIsAllowed(s)) {
99
- yield eatMatch(triviaMatcher);
100
- }
101
- }
102
-
103
- return co.value;
104
- } else {
105
- if (!s.depths.path && !flags.token && !isCovered) {
106
- if (triviaIsAllowed(s)) {
107
- yield eatMatch(triviaMatcher);
108
- }
109
- }
110
- }
111
- } catch (e) {
112
- co.throw(e);
113
- throw e;
114
- }
115
- };
116
- }, grammar);
117
-
118
- return class extends resultGrammar {
119
- *[Wrapper_]({ value: wrapped }) {
120
- for (const instr of wrapped) {
121
- yield getEmbeddedInstruction(instr);
122
- }
123
- }
124
-
125
- *[Literal_]({ value: matcher }) {
126
- yield eat(matcher);
127
- }
128
- };
129
- };
130
24
 
131
25
  export const triviaEnhancer = (
132
26
  { triviaIsAllowed, triviaIsRequired = () => false, triviaMatcher },
133
27
  grammar,
134
28
  ) => {
135
- if (!triviaMatcher) throw new Error();
136
-
137
- let Wrapper_ = 'Wrapper';
29
+ let Trivia_ = 'Trivia';
138
30
  let Literal_ = 'Literal';
139
31
 
140
- while (grammar.prototype[Wrapper_]) Wrapper_ += '_';
32
+ while (grammar.prototype[Trivia_]) Trivia_ += '_';
141
33
  while (grammar.prototype[Literal_]) Literal_ += '_';
142
34
 
143
- const resultGrammar = mapProductions((production) => {
144
- return function* (args) {
145
- const co = new Coroutine(production(args));
146
- const { ctx, s, isCovered, isCover, isCoverBoundary, flags, mergedReference } = args;
35
+ let tmi = eatMatch(triviaMatcher);
36
+ let literalMatcher = get(['nodeMatcher', 'open', 'literalValue'], triviaMatcher.value);
147
37
 
148
- co.advance();
38
+ let resultGrammar = mapProductions((production) => {
39
+ return function* (props) {
40
+ let co = new Coroutine(production(props));
41
+ let outerProps = props;
42
+ let { type, getState, s, flags, isCover, isCoverBoundary: thisIsCoverBoundary } = props;
43
+ let isRootFragment = type === Symbol.for('__') && !s().depths.path;
149
44
 
150
45
  try {
151
- while (!co.done) {
152
- const instr = co.value;
153
- const { verb, arguments: args = [] } = instr;
154
- let returnValue = undefined;
46
+ let returnValue = undefined;
47
+ do {
48
+ co.advance(returnValue);
49
+
50
+ let instr = co.done ? co.value?.shift : co.value;
51
+
52
+ if (co.done && !instr) break;
53
+
54
+ let { verb, arguments: args = [] } = instr;
155
55
 
156
56
  switch (verb) {
157
57
  case 'eat':
158
58
  case 'eatMatch':
59
+ case 'shift':
60
+ case 'shiftMatch':
159
61
  case 'match':
160
62
  case 'guard': {
161
- const { 0: matcher, 1: props, 2: options } = args;
162
-
163
- if (
164
- matcher.type === Matcher &&
165
- ['ArrayNodeMatcher', 'NullNodeMatcher'].includes(
166
- get('nodeMatcher', matcher.value).type.description,
167
- )
168
- ) {
169
- returnValue = yield instr;
170
- break;
171
- }
172
-
173
- if (
174
- matcher.type === Regex ||
175
- typeof matcher === 'string' ||
176
- (matcher.type === Node && matcher.value.flags.token)
177
- ) {
63
+ let { 0: matcher, 1: props, 2: options } = args;
64
+ let s = getState();
65
+
66
+ let isCoverBoundary = co.done
67
+ ? thisIsCoverBoundary
68
+ : isRootFragment ||
69
+ (reifyExpression(
70
+ get(['valueMatcher', 'nodeMatcher', 'open', 'type'], matcher.value),
71
+ ) !== '__' &&
72
+ !isCover);
73
+
74
+ if (matcher.type === Regex || typeof matcher === 'string') {
178
75
  if (triviaIsAllowed(s) && !flags.token) {
76
+ if (literalMatcher) {
77
+ let literalResult = yield buildCall(
78
+ 'match',
79
+ buildEmbeddedRegex(literalMatcher),
80
+ );
81
+ if (!literalResult) {
82
+ returnValue = yield instr;
83
+ break;
84
+ }
85
+ }
86
+
179
87
  let isString = typeof matcher === 'string';
180
- let wrappedMatcher = m`#: <*Literal ${
88
+ let wrappedMatcher = m`#: <*${buildIdentifier(Literal_)} ${
181
89
  isString ? buildString(matcher) : matcher.value
182
90
  } />`;
183
91
 
184
- let tmi = buildEmbeddedInstruction(eatMatch(triviaMatcher));
185
-
186
- let result = yield buildCall(verb, m`<__${buildIdentifier(Wrapper_)} />`, [
187
- tmi,
188
- buildEmbeddedInstruction(eat(wrappedMatcher, props, options)),
189
- ]);
190
-
191
- let prop = null;
192
-
193
- if (result) {
194
- for (let child of result.tagsInner) {
195
- if (child.type === Property) prop = child.value;
196
- }
197
- }
92
+ let result = yield buildCall(
93
+ verb,
94
+ m`<_${buildIdentifier(Trivia_)} />`,
95
+ o({
96
+ matcher: wrappedMatcher,
97
+ props,
98
+ options: o({ ...(options?.value ?? {}), allowEmpty: true }),
99
+ matchTrailing: false,
100
+ }),
101
+ );
198
102
 
199
- returnValue = prop?.node;
200
- } else {
201
- returnValue = yield instr;
103
+ returnValue = result?.value;
104
+ break;
202
105
  }
203
- break;
204
106
  }
205
107
 
206
- let { type: refType } = reifyExpression(get('refMatcher', matcher.value)) || {};
207
- let realMatcher = reifyExpression(get('nodeMatcher', matcher.value));
208
- let { flags: matcherFlags } = realMatcher;
209
-
210
- let isNode = matcherFlags && !matcherFlags.fragment;
211
- let isCoverBoundary = matcherFlags && (matcherFlags.cover || (isNode && !isCovered));
212
108
  if (
213
109
  matcher &&
214
- (matcher.type !== Matcher || refType !== '#') &&
215
- !s.holding &&
216
- !flags.token &&
217
- isCoverBoundary
110
+ !s.node.value.flags.token &&
111
+ isCoverBoundary &&
112
+ (matcher.type !== Matcher ||
113
+ getCooked(get(['refMatcher', 'type'], matcher.value)) !== '#')
218
114
  ) {
219
- if (triviaIsAllowed(s)) {
220
- let { nodeMatcher } = reifyExpression(matcher.value);
221
-
222
- let tmi = buildEmbeddedInstruction(
223
- triviaIsRequired(s, nodeMatcher) ? eat(triviaMatcher) : eatMatch(triviaMatcher),
224
- );
225
-
226
- let result = yield buildCall(
227
- verb,
228
- m`<__${buildIdentifier(Wrapper_)} />`,
229
- [tmi, buildEmbeddedInstruction(eat(matcher, props, options))],
230
- o({ bind: options?.value.bind ?? false }),
231
- );
232
- let trivialResult = result;
233
-
234
- if (result && !result.isNull) {
235
- if (isNode || isCoverBoundary) {
236
- let refMatcher = get('refMatcher', matcher.value);
237
-
238
- let name, isArray;
239
-
240
- if (!refMatcher) {
241
- ({ name, isArray } = mergedReference);
115
+ if (
116
+ triviaIsAllowed(s) &&
117
+ !(getState().holding || co.done) &&
118
+ get(['valueMatcher', 'nodeMatcher'], matcher.value).value.name?.description ===
119
+ 'TreeNodeMatcher'
120
+ ) {
121
+ // TODO this is a problem. What if there's trivia before?
122
+ if (literalMatcher) {
123
+ let literalResult = yield buildCall(
124
+ 'match',
125
+ buildEmbeddedRegex(literalMatcher),
126
+ );
127
+ if (!literalResult) {
128
+ if (co.done) {
129
+ return r(instr, co.value.value);
242
130
  } else {
243
- let name_ = get('name', refMatcher);
244
- let openIndexToken = get('openIndexToken', refMatcher);
245
- name = name_ && ctx.sourceTextFor(name_);
246
- isArray = !!openIndexToken;
247
- }
248
-
249
- if (name) {
250
- let pathSpec = name;
251
-
252
- if (isArray) {
253
- pathSpec = buildPathSegment(name, -1);
254
- }
255
-
256
- result = result.get([pathSpec]);
131
+ returnValue = yield instr;
257
132
  }
133
+ break;
258
134
  }
259
-
260
- result = result && result.merge(trivialResult);
261
135
  }
262
- returnValue = result;
263
- } else {
264
- returnValue = yield instr;
136
+
137
+ instr = buildCall(
138
+ verb,
139
+ buildEmbeddedMatcher(
140
+ buildPropertyMatcher(
141
+ get('refMatcher', matcher.value),
142
+ buildBoundNodeMatcher(
143
+ [],
144
+ buildTreeNodeMatcher(
145
+ buildTreeNodeMatcherOpen(
146
+ buildNodeFlags(getFlagsWithGap(nodeFlags, flags.hasGap)),
147
+ '_',
148
+ Trivia_,
149
+ ),
150
+ ),
151
+ ),
152
+ ),
153
+ ),
154
+ o({
155
+ matcher,
156
+ props,
157
+ matchTrailing: isRootFragment,
158
+ }),
159
+ ...(options ? [options] : []),
160
+ );
265
161
  }
162
+ }
163
+
164
+ // if (co.done) {
165
+ // // account for language shift due to binding
166
+ // let outerMatcher = reifyExpression(outerProps.matcher);
167
+ // if (outerMatcher.bindingMatchers.length) {
168
+ // instr = buildCall(
169
+ // verb,
170
+ // buildEmbeddedMatcher(
171
+ // buildPropertyMatcher(
172
+ // get('refMatcher', matcher.value),
173
+ // buildBoundNodeMatcher(
174
+ // buildBindingMatchers(outerMatcher.bindingMatchers),
175
+ // get('nodeMatcher', matcher.value),
176
+ // ),
177
+ // ),
178
+ // ),
179
+ // props,
180
+ // options,
181
+ // );
182
+ // }
183
+
184
+ // return r(instr, co.value.value);
185
+ // } else {
186
+ if (co.done) {
187
+ return r(instr, co.value.value);
266
188
  } else {
267
189
  returnValue = yield instr;
268
190
  }
269
191
  break;
192
+ // }
270
193
  }
271
194
 
272
195
  default:
273
- returnValue = yield instr;
196
+ if (co.done) {
197
+ return r(instr, co.value.value);
198
+ } else {
199
+ returnValue = yield instr;
200
+ }
274
201
  break;
275
202
  }
203
+ } while (!co.done);
276
204
 
277
- co.advance(returnValue);
278
- }
279
-
280
- if (!flags.token && !(isCovered || isCover)) {
281
- if (triviaIsAllowed(s)) {
282
- yield eatMatch(triviaMatcher);
283
- }
284
- } else if (co.value) {
285
- let matcher = co.value.arguments[0];
286
- let realMatcher = reifyExpression(get('nodeMatcher', matcher.value));
287
- let { flags: matcherFlags } = realMatcher;
288
-
289
- let isNode = matcherFlags && !matcherFlags.fragment;
290
-
291
- if (
292
- !flags.token &&
293
- isNode &&
294
- !isCover &&
295
- co.value &&
296
- ['shift', 'shiftMatch'].includes(co.value.verb)
297
- ) {
298
- if (triviaIsAllowed(s)) {
299
- let tmi = buildEmbeddedInstruction(
300
- triviaIsRequired() ? eat(triviaMatcher) : eatMatch(triviaMatcher),
301
- );
302
- let result = yield buildCall(co.value.verb, m`<__${buildIdentifier(Wrapper_)} />`, [
303
- tmi,
304
- buildEmbeddedInstruction(eat(co.value.arguments[0], co.value.arguments[1])),
305
- ]);
306
-
307
- let trivialResult = result;
308
-
309
- if (result && !result.isNull) {
310
- if (isNode || isCoverBoundary) {
311
- let refMatcher = get('refMatcher', matcher.value);
312
-
313
- let name, isArray;
314
-
315
- if (!refMatcher) {
316
- ({ name, isArray } = mergedReference);
317
- } else {
318
- let name = get('name', refMatcher);
319
- let openIndexToken = get('openIndexToken', refMatcher);
320
- name = name && ctx.sourceTextFor(name);
321
- isArray = !!openIndexToken;
322
- }
323
-
324
- if (name) {
325
- let pathSpec = name;
326
-
327
- if (isArray) {
328
- pathSpec = buildPathSegment(name, -1);
329
- }
330
-
331
- result = result.get([pathSpec]);
332
- }
333
- }
334
-
335
- result = result && result.merge(trivialResult);
336
- }
337
- return result;
338
- }
339
- }
205
+ if (co.value) {
206
+ throw new Error();
340
207
  }
341
-
342
- return co.value;
343
208
  } catch (e) {
344
209
  co.throw(e);
345
210
  throw e;
@@ -348,34 +213,33 @@ export const triviaEnhancer = (
348
213
  }, grammar);
349
214
 
350
215
  return class extends resultGrammar {
351
- constructor() {
352
- super();
353
-
354
- if (!this.emptyables) {
355
- this.emptyables = new Set();
356
- }
357
-
358
- if (!this.covers) {
359
- this.covers = new Map();
360
- }
361
-
362
- if (!this.covers.get(Symbol.for('@bablr/node'))) {
363
- this.covers.set(Symbol.for('@bablr/node'), new Set());
364
- }
365
-
366
- this.covers.get(Symbol.for('@bablr/node')).add(Literal_);
367
-
368
- this.emptyables.add(Wrapper_);
216
+ static get atrivial() {
217
+ return grammar;
369
218
  }
370
219
 
371
- *[Wrapper_]({ props: { value: wrapped } }) {
372
- for (const instr of wrapped) {
373
- yield getEmbeddedInstruction(instr);
220
+ *[Trivia_]({ props: { matchTrailing, matcher, props, options } }) {
221
+ yield tmi;
222
+ let returnValue = yield eat(
223
+ // TODO fixme binding tag
224
+ matcher.value.value.name === Symbol.for('PropertyMatcher')
225
+ ? buildEmbeddedMatcher(
226
+ buildPropertyMatcher(
227
+ null,
228
+ buildBoundNodeMatcher([], get(['valueMatcher', 'nodeMatcher'], matcher.value)),
229
+ ),
230
+ )
231
+ : matcher,
232
+ props,
233
+ buildEmbeddedObject({ ...(options?.value || {}), shift: false }),
234
+ );
235
+ if (matchTrailing || returnValue.value?.shift) {
236
+ yield tmi;
374
237
  }
238
+ return returnValue.value;
375
239
  }
376
240
 
377
241
  *[Literal_]({ literalValue }) {
378
- yield eat(literalValue);
242
+ return r(null, yield eat(buildEmbeddedNode(literalValue)));
379
243
  }
380
244
  };
381
245
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bablr/helpers",
3
3
  "description": "Command helpers for use in writing BABLR grammars",
4
- "version": "0.24.0",
4
+ "version": "0.25.1",
5
5
  "author": "Conrad Buck<conartist6@gmail.com>",
6
6
  "type": "module",
7
7
  "files": [
@@ -23,32 +23,29 @@
23
23
  ".": "./lib/index.js"
24
24
  },
25
25
  "sideEffects": false,
26
- "scripts": {
27
- "build": "macrome build",
28
- "watch": "macrome watch",
29
- "clean": "macrome clean"
30
- },
31
26
  "dependencies": {
32
- "@bablr/language_enhancer-debug-log": "0.11.1",
33
- "@bablr/strategy_enhancer-debug-log": "0.10.0",
34
- "@bablr/agast-helpers": "0.9.0",
35
- "@bablr/agast-vm-helpers": "0.9.0",
36
- "@bablr/boot": "0.10.0",
27
+ "@bablr/language_enhancer-debug-log": "0.12.2",
28
+ "@bablr/strategy_enhancer-debug-log": "0.11.1",
29
+ "@bablr/stream-iterator": "2.0.0",
30
+ "@bablr/agast-helpers": "0.10.2",
31
+ "@bablr/agast-vm-helpers": "0.10.2",
32
+ "@bablr/boot": "0.11.0",
37
33
  "@bablr/coroutine": "0.1.0",
38
34
  "@iter-tools/imm-stack": "1.2.0",
39
35
  "iter-tools-es": "^7.5.3"
40
36
  },
41
37
  "devDependencies": {
42
38
  "@bablr/eslint-config-base": "github:bablr-lang/eslint-config-base#c97bfa4b3663f8378e9b3e42bb5a41e685406cf9",
43
- "@bablr/macrome": "0.1.3",
44
- "@bablr/macrome-generator-bablr": "0.3.2",
45
39
  "enhanced-resolve": "^5.12.0",
46
40
  "eslint": "^7.32.0",
47
41
  "eslint-import-resolver-enhanced-resolve": "^1.0.5",
48
42
  "eslint-plugin-import": "^2.27.5",
49
43
  "prettier": "^2.6.2"
50
44
  },
51
- "repository": "github:bablr-lang/helpers",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/bablr-lang/helpers.git"
48
+ },
52
49
  "homepage": "https://github.com/bablr-lang/helpers",
53
50
  "license": "MIT"
54
51
  }