@bablr/helpers 0.1.1 → 0.1.3

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/grammar.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import every from 'iter-tools-es/methods/every';
2
2
  import isString from 'iter-tools-es/methods/is-string';
3
- import { objectEntries } from './object.js';
3
+ import { objectEntries, getPrototypeOf } from './object.js';
4
4
 
5
5
  const { isArray } = Array;
6
6
  const isSymbol = (value) => typeof value === 'symbol';
@@ -38,3 +38,70 @@ export const buildCovers = (rawAliases) => {
38
38
 
39
39
  return new Map(aliases);
40
40
  };
41
+
42
+ export const getProduction = (grammar, type) => {
43
+ return getPrototypeOf(grammar)[type];
44
+ };
45
+
46
+ export class Coroutine {
47
+ static from(grammar, type, props) {
48
+ const production = grammar.get(type);
49
+
50
+ if (!production) throw new Error(`Unknown production of {type: ${type}}`);
51
+
52
+ return new Coroutine(production.match(props));
53
+ }
54
+
55
+ constructor(generator) {
56
+ this.generator = generator;
57
+ }
58
+
59
+ get value() {
60
+ return this.current.value;
61
+ }
62
+
63
+ get done() {
64
+ return this.current?.done;
65
+ }
66
+
67
+ advance(value) {
68
+ if (this.done) {
69
+ throw new Error('Cannot advance a coroutine that is done');
70
+ }
71
+ this.current = this.generator.next(value);
72
+ return this;
73
+ }
74
+
75
+ return(value) {
76
+ if (!this.done) {
77
+ this.current = this.generator.return(value);
78
+ } else {
79
+ return this.current;
80
+ }
81
+ }
82
+
83
+ throw(value) {
84
+ if (!this.done) {
85
+ this.current = { value: undefined, done: true };
86
+
87
+ let caught = false;
88
+ try {
89
+ this.generator.throw(value);
90
+ } catch (e) {
91
+ caught = true;
92
+ }
93
+ if (!caught) {
94
+ throw new Error('Generator attempted to yield a command after failing');
95
+ }
96
+ } else {
97
+ throw value;
98
+ }
99
+ }
100
+
101
+ finalize() {
102
+ // ensures failures can be logged!
103
+ if (!this.done) {
104
+ this.throw('failure');
105
+ }
106
+ }
107
+ }
package/lib/object.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import map from 'iter-tools-es/methods/map';
2
2
 
3
- export const { hasOwn, getOwnPropertySymbols, fromEntries } = Object;
3
+ export const { hasOwn, getOwnPropertySymbols, getPrototypeOf, fromEntries } = Object;
4
4
 
5
5
  export const isObject = (obj) => obj !== null && typeof obj === 'object';
6
6
  export const { isArray } = Array;
@@ -13,7 +13,7 @@ export function* List({
13
13
  for (;;) {
14
14
  it = yield _t.node("Instruction", "Call", [_t.ref`verb`, _t.ref`arguments`], {
15
15
  verb: _t.node("Instruction", "Identifier", [_t.lit`eatMatch`], {}, {}),
16
- arguments: _t.node("Instruction", "Tuple", [_t.ref`open`, _t.ref`values`, _t.ref`close`], {
16
+ arguments: _t.node("Instruction", "Tuple", [_t.ref`open`, _t.ref`values[]`, _t.ref`close`], {
17
17
  open: _t.node("Instruction", "Punctuator", [_t.lit`(`], {}, {}),
18
18
  values: [..._interpolateArray(element)],
19
19
  close: _t.node("Instruction", "Punctuator", [_t.lit`)`], {}, {})
@@ -22,7 +22,7 @@ export function* List({
22
22
  if (it || allowTrailingSeparator) {
23
23
  sep = yield _t.node("Instruction", "Call", [_t.ref`verb`, _t.ref`arguments`], {
24
24
  verb: _t.node("Instruction", "Identifier", [_t.lit`eatMatch`], {}, {}),
25
- arguments: _t.node("Instruction", "Tuple", [_t.ref`open`, _t.ref`values`, _t.ref`close`], {
25
+ arguments: _t.node("Instruction", "Tuple", [_t.ref`open`, _t.ref`values[]`, _t.ref`close`], {
26
26
  open: _t.node("Instruction", "Punctuator", [_t.lit`(`], {}, {}),
27
27
  values: [..._interpolateArray(separator)],
28
28
  close: _t.node("Instruction", "Punctuator", [_t.lit`)`], {}, {})
@@ -40,7 +40,7 @@ export function* Any({
40
40
  for (const matcher of matchers) {
41
41
  if (yield _t.node("Instruction", "Call", [_t.ref`verb`, _t.ref`arguments`], {
42
42
  verb: _t.node("Instruction", "Identifier", [_t.lit`eatMatch`], {}, {}),
43
- arguments: _t.node("Instruction", "Tuple", [_t.ref`open`, _t.ref`values`, _t.ref`close`], {
43
+ arguments: _t.node("Instruction", "Tuple", [_t.ref`open`, _t.ref`values[]`, _t.ref`close`], {
44
44
  open: _t.node("Instruction", "Punctuator", [_t.lit`(`], {}, {}),
45
45
  values: [..._interpolateArray(matcher)],
46
46
  close: _t.node("Instruction", "Punctuator", [_t.lit`)`], {}, {})
@@ -56,7 +56,7 @@ export function* All({
56
56
  for (const matcher of matchers) {
57
57
  yield _t.node("Instruction", "Call", [_t.ref`verb`, _t.ref`arguments`], {
58
58
  verb: _t.node("Instruction", "Identifier", [_t.lit`eat`], {}, {}),
59
- arguments: _t.node("Instruction", "Tuple", [_t.ref`open`, _t.ref`values`, _t.ref`close`], {
59
+ arguments: _t.node("Instruction", "Tuple", [_t.ref`open`, _t.ref`values[]`, _t.ref`close`], {
60
60
  open: _t.node("Instruction", "Punctuator", [_t.lit`(`], {}, {}),
61
61
  values: [..._interpolateArray(matcher)],
62
62
  close: _t.node("Instruction", "Punctuator", [_t.lit`)`], {}, {})
@@ -74,7 +74,7 @@ export function* Optional({
74
74
  }
75
75
  yield _t.node("Instruction", "Call", [_t.ref`verb`, _t.ref`arguments`], {
76
76
  verb: _t.node("Instruction", "Identifier", [_t.lit`eatMatch`], {}, {}),
77
- arguments: _t.node("Instruction", "Tuple", [_t.ref`open`, _t.ref`values`, _t.ref`close`], {
77
+ arguments: _t.node("Instruction", "Tuple", [_t.ref`open`, _t.ref`values[]`, _t.ref`close`], {
78
78
  open: _t.node("Instruction", "Punctuator", [_t.lit`(`], {}, {}),
79
79
  values: [..._interpolateArray(matchers[0])],
80
80
  close: _t.node("Instruction", "Punctuator", [_t.lit`)`], {}, {})
package/lib/trivia.js ADDED
@@ -0,0 +1,84 @@
1
+ import { getCooked } from './token.js';
2
+ import { mapProductions } from './enhancers.js';
3
+ import { Coroutine } from './grammar.js';
4
+
5
+ const lookbehind = (context, s) => {
6
+ let token = s.result;
7
+ while (['OpenNode', 'CloseNode', 'OpenFragmentNode', 'Reference'].includes(token.type)) {
8
+ const prevToken = context.getPreviousTerminal(token);
9
+ if (!prevToken) break;
10
+ token = prevToken;
11
+ }
12
+ return token;
13
+ };
14
+
15
+ // eslint-disable-next-line no-undef
16
+ const matchedResults = new WeakSet();
17
+
18
+ export const triviaEnhancer = ({ spaceIsAllowed, eatMatchTrivia }, grammar) => {
19
+ return mapProductions((production) => {
20
+ return function* (props, s, ctx, ...args) {
21
+ const co = new Coroutine(production(props, s, ctx, ...args));
22
+
23
+ co.advance();
24
+
25
+ try {
26
+ while (!co.done) {
27
+ const instr = co.value;
28
+ let returnValue = undefined;
29
+
30
+ const {
31
+ verb: verbToken,
32
+ verbSuffix: verbSuffixToken,
33
+ arguments: {
34
+ properties: { values: { 0: matcher } = [] },
35
+ },
36
+ } = instr.properties;
37
+ const verb = getCooked(verbToken);
38
+ const verbSuffix = verbSuffixToken && getCooked(verbSuffixToken);
39
+
40
+ switch (verb) {
41
+ case 'eat':
42
+ case 'eatMatch':
43
+ case 'match':
44
+ case 'guard': {
45
+ if (
46
+ ((['String', 'Pattern'].includes(matcher.type) && !s.isTerminal) ||
47
+ matcher.type === 'TerminalMatcher') &&
48
+ verbSuffix !== '#'
49
+ ) {
50
+ const previous = lookbehind(ctx, s);
51
+ if (spaceIsAllowed(s) && !matchedResults.has(previous)) {
52
+ matchedResults.add(previous);
53
+ yield eatMatchTrivia;
54
+ matchedResults.add(s.result);
55
+ }
56
+ }
57
+
58
+ returnValue = returnValue || (yield instr);
59
+ break;
60
+ }
61
+
62
+ default:
63
+ returnValue = yield instr;
64
+ break;
65
+ }
66
+
67
+ co.advance(returnValue);
68
+ }
69
+
70
+ if (!s.isTerminal) {
71
+ const previous = lookbehind(ctx, s);
72
+ if (spaceIsAllowed(s) && !matchedResults.has(previous)) {
73
+ matchedResults.add(previous);
74
+ yield eatMatchTrivia;
75
+ matchedResults.add(s.result);
76
+ }
77
+ }
78
+ } catch (e) {
79
+ co.throw(e);
80
+ throw e;
81
+ }
82
+ };
83
+ }, grammar);
84
+ };
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.1.1",
4
+ "version": "0.1.3",
5
5
  "author": "Conrad Buck<conartist6@gmail.com>",
6
6
  "type": "module",
7
7
  "files": [
@@ -16,7 +16,8 @@
16
16
  "./productions": "./lib/productions.generated.js",
17
17
  "./shorthand": "./lib/shorthand.js",
18
18
  "./symbols": "./lib/symbols.js",
19
- "./token": "./lib/token.js"
19
+ "./token": "./lib/token.js",
20
+ "./trivia": "./lib/trivia.js"
20
21
  },
21
22
  "sideEffects": false,
22
23
  "scripts": {
@@ -24,8 +25,8 @@
24
25
  "clean": "rm lib/productions.generated.js"
25
26
  },
26
27
  "dependencies": {
27
- "@bablr/boot": "0.1.1",
28
- "@bablr/boot-helpers": "0.1.1",
28
+ "@bablr/boot": "0.1.6",
29
+ "@bablr/boot-helpers": "0.1.3",
29
30
  "iter-tools-es": "^7.5.3"
30
31
  },
31
32
  "devDependencies": {