@suss/recognize 0.12.0 → 0.13.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/dist/index.cjs CHANGED
@@ -22,17 +22,20 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  compile: () => compile,
24
24
  constructedFrom: () => constructedFrom,
25
+ declarationsIn: () => declarationsIn,
25
26
  declaredBy: () => declaredBy,
26
27
  examplesMissing: () => examplesMissing,
27
28
  opsIn: () => opsIn,
28
29
  pack: () => pack,
29
30
  runExamples: () => runExamples,
31
+ sqlStatements: () => sqlStatements,
30
32
  storageCalls: () => storageCalls
31
33
  });
32
34
  module.exports = __toCommonJS(index_exports);
33
35
 
34
36
  // src/compile.ts
35
37
  var import_behavioral_ir = require("@suss/behavioral-ir");
38
+ var import_sql = require("@suss/sql");
36
39
 
37
40
  // src/ops.ts
38
41
  function declaredBy(...importedFrom) {
@@ -47,104 +50,297 @@ function opsIn(ctx) {
47
50
  }
48
51
 
49
52
  // src/compile.ts
53
+ var MAX_RECEIVER_HOPS = 8;
50
54
  function compile(chain, recognition) {
51
55
  return (_call, ctx) => {
52
56
  const ops = opsIn(ctx);
53
- if (ops === null) {
57
+ const methods = methodsIn(chain);
58
+ if (ops === null || methods === null) {
54
59
  return null;
55
60
  }
56
- const method = ops.method();
57
- if (method === null) {
58
- return null;
59
- }
60
- const meaning = meaningOf(chain, method);
61
- if (meaning === null || !startsHere(chain, ops)) {
62
- return null;
61
+ for (const subject of subjectsOf(chain, ops)) {
62
+ const method = operationOf(subject);
63
+ const meaning = method === null ? null : listed(methods, method);
64
+ if (method === null || meaning === null || !startsHere(chain, subject)) {
65
+ continue;
66
+ }
67
+ return YIELD[chain.ending.yields]({
68
+ ops,
69
+ subject,
70
+ method,
71
+ meaning,
72
+ chain,
73
+ recognition
74
+ });
63
75
  }
64
- return YIELD[chain.ending.yields]({
65
- ops,
66
- method,
67
- meaning,
68
- chain,
69
- recognition
70
- });
76
+ return null;
71
77
  };
72
78
  }
73
- function meaningOf(chain, method) {
74
- const link = chain.links.find(
75
- (candidate) => candidate.asks === "methods"
76
- );
77
- if (link === void 0) {
78
- return null;
79
- }
79
+ function linkIn(chain, asks) {
80
+ const link = chain.links.find((candidate) => candidate.asks === asks);
81
+ return link ?? null;
82
+ }
83
+ function methodsIn(chain) {
84
+ return linkIn(chain, "methods");
85
+ }
86
+ function operationOf(ops) {
87
+ return ops.method() ?? ops.calleeText();
88
+ }
89
+ function listed(link, method) {
80
90
  if (!link.ignoringCase) {
81
91
  return link.table[method] ?? null;
82
92
  }
83
93
  return link.table[method.toLowerCase()] ?? null;
84
94
  }
95
+ function subjectsOf(chain, ops) {
96
+ const link = linkIn(chain, "subject");
97
+ return link === null ? [ops] : walk([ops], link.of);
98
+ }
99
+ var STEP = {
100
+ receiver: (step, ops) => receiversOf(ops, step.method),
101
+ argument: (step, ops) => argumentsOf(ops, step)
102
+ };
103
+ function walk(from, steps) {
104
+ let reached = [...from];
105
+ for (const step of steps) {
106
+ reached = reached.flatMap((ops) => STEP[step.to](step, ops));
107
+ }
108
+ return reached;
109
+ }
110
+ function receiversOf(ops, method) {
111
+ let step = ops.receiver();
112
+ for (let hops = 0; step !== null && hops < MAX_RECEIVER_HOPS; hops += 1) {
113
+ if (method === void 0 || step.method() === method) {
114
+ return [step];
115
+ }
116
+ step = step.receiver();
117
+ }
118
+ return [];
119
+ }
120
+ function argumentsOf(ops, step) {
121
+ const found = [];
122
+ for (const index of positions(ops, step.at)) {
123
+ const argument = ops.argument(index);
124
+ if (argument !== null && cameFrom(argument, step.origin)) {
125
+ found.push(argument);
126
+ }
127
+ }
128
+ return found;
129
+ }
130
+ function cameFrom(argument, origin) {
131
+ return origin === void 0 || argument.isFrom(origin);
132
+ }
133
+ function positions(ops, pick) {
134
+ if (typeof pick === "number") {
135
+ return [pick];
136
+ }
137
+ const first = "at" in pick ? pick.at : pick.from;
138
+ const last = "at" in pick ? pick.at + 1 : ops.argumentCount();
139
+ const found = [];
140
+ for (let index = first; index < last; index += 1) {
141
+ found.push(index);
142
+ }
143
+ return found;
144
+ }
85
145
  var START = {
86
146
  receiver: (start, ops) => ops.receiverIsFrom(start.origin)
87
147
  };
88
- function startsHere(chain, ops) {
148
+ function startsHere(chain, subject) {
89
149
  return chain.links.every(
90
- (link) => link.asks === "start" ? START[link.at.starts](link.at, ops) : true
150
+ (link) => link.asks === "start" ? START[link.at.starts](link.at, subject) : true
91
151
  );
92
152
  }
93
153
  var YIELD = {
94
- storageAccess
154
+ storageAccess,
155
+ sqlAccess
95
156
  };
96
157
  function storageAccess(matched) {
97
- const { ops, method, meaning, chain, recognition } = matched;
158
+ const { subject, chain } = matched;
159
+ const meaning = matched.meaning;
160
+ const unsettled = chain.ending.unsettledName;
161
+ const kind = kindOf(meaning.kind, subject, unsettled);
162
+ const link = linkIn(chain, "input");
163
+ const input = link === null ? null : statedValue(subject, link.at);
164
+ if (kind === null || link !== null && input === null) {
165
+ return null;
166
+ }
167
+ return reachedBy(chain, subject, unsettled).map(
168
+ (reached) => accessEffect(matched, { input, kind, reached, unsettled })
169
+ );
170
+ }
171
+ function accessEffect(matched, access) {
172
+ const { ops, subject, method, chain, recognition } = matched;
173
+ const meaning = matched.meaning;
98
174
  const ending = chain.ending;
99
- const selector = namesAt(ops, meaning.selector, ending.unsettledName);
100
- const fields = namesAt(ops, meaning.fields, ending.unsettledName);
101
- const container = containerOf(chain, selector, ops);
102
- return [
103
- {
104
- type: "interaction",
105
- binding: (0, import_behavioral_ir.storageBinding)({
106
- recognition,
107
- storageSystem: ending.system,
108
- ...ending.transport === void 0 ? {} : { transport: ending.transport },
109
- scope: ending.scope,
110
- container,
111
- accessPath: null
112
- }),
113
- callee: ops.calleeText(),
114
- interaction: {
115
- class: "storage-access",
116
- kind: meaning.kind,
117
- fields,
118
- operation: method,
119
- ...selector.length > 0 ? { selector } : {}
120
- }
175
+ const { input, kind, reached, unsettled } = access;
176
+ const stated = { input, entry: reached.entry, kind };
177
+ const selector = namesFor(meaning.selector, subject, stated, unsettled);
178
+ const fields = namesFor(meaning.fields, subject, stated, unsettled);
179
+ return {
180
+ type: "interaction",
181
+ binding: (0, import_behavioral_ir.storageBinding)({
182
+ recognition,
183
+ storageSystem: ending.system,
184
+ ...ending.transport === void 0 ? {} : { transport: ending.transport },
185
+ scope: ending.scope,
186
+ container: reached.entry === null ? containerOf(chain, selector, subject, unsettled) : reached.container,
187
+ accessPath: accessPathOf(chain, subject, unsettled)
188
+ }),
189
+ callee: ops.calleeText(),
190
+ interaction: {
191
+ class: "storage-access",
192
+ kind,
193
+ fields,
194
+ operation: namesAt(subject, meaning.operation, unsettled)[0] ?? method,
195
+ ...selector.length > 0 ? { selector } : {}
121
196
  }
122
- ];
197
+ };
123
198
  }
124
- function containerOf(chain, selector, ops) {
125
- const link = chain.links.find(
126
- (candidate) => candidate.asks === "container"
127
- );
128
- if (link === void 0) {
199
+ function reachedBy(chain, subject, unsettled) {
200
+ const link = linkIn(chain, "containers");
201
+ const map = link === null ? null : statedValue(subject, link.in);
202
+ if (map === null) {
203
+ return [{ container: null, entry: null }];
204
+ }
205
+ return map.entries(unsettled).map((entry) => ({ container: entry.key, entry: entry.value }));
206
+ }
207
+ function kindOf(kind, subject, unsettled) {
208
+ if (typeof kind === "string") {
209
+ return kind;
210
+ }
211
+ const asked = kind;
212
+ const [answer] = namesAt(subject, asked.asks, unsettled);
213
+ const said = answer === void 0 ? void 0 : asked.means[answer];
214
+ return said ?? asked.otherwise ?? null;
215
+ }
216
+ function containerOf(chain, selector, subject, unsettled) {
217
+ const link = linkIn(chain, "container");
218
+ if (link === null) {
129
219
  return selector[0] ?? null;
130
220
  }
131
- return link.from(selector, ops);
221
+ if ("from" in link) {
222
+ return link.from(selector, subject);
223
+ }
224
+ return namesAt(subject, link.argument, unsettled)[0] ?? null;
225
+ }
226
+ function accessPathOf(chain, subject, unsettled) {
227
+ const link = linkIn(chain, "accessPath");
228
+ return link === null ? null : namesAt(subject, link.argument, unsettled)[0] ?? null;
229
+ }
230
+ function statedValue(subject, pick) {
231
+ for (const ops of walk([subject], pick.of ?? [])) {
232
+ const stated = ops.valueAt(pick.at);
233
+ if (stated === null) {
234
+ continue;
235
+ }
236
+ if (pick.property === void 0) {
237
+ return stated;
238
+ }
239
+ for (const property of pick.property) {
240
+ const inside = stated.property(property);
241
+ if (inside !== null) {
242
+ return inside;
243
+ }
244
+ }
245
+ }
246
+ return null;
132
247
  }
133
- function namesAt(ops, pick, unsettled) {
248
+ var NOTHING_STATED = {
249
+ text: () => null,
250
+ flag: () => null,
251
+ entries: () => [],
252
+ items: () => [],
253
+ property: () => null,
254
+ parts: () => null,
255
+ holes: () => []
256
+ };
257
+ function namesFor(says, subject, stated, unsettled) {
258
+ if (says === void 0) {
259
+ return [];
260
+ }
261
+ if (Array.isArray(says)) {
262
+ return [...says];
263
+ }
264
+ if (typeof says === "function") {
265
+ const input2 = stated.input;
266
+ return input2 === null ? [] : [...says({ ...stated, input: input2 })];
267
+ }
268
+ const pointed = says;
269
+ if (!("by" in pointed)) {
270
+ return namesAt(subject, pointed, unsettled);
271
+ }
272
+ const input = statedValue(subject, pointed.of) ?? NOTHING_STATED;
273
+ return [...pointed.by({ ...stated, input })];
274
+ }
275
+ function namesAt(subject, pick, unsettled) {
134
276
  if (pick === void 0) {
135
277
  return [];
136
278
  }
137
- const last = "at" in pick ? pick.at + 1 : ops.argumentCount();
138
- const first = "at" in pick ? pick.at : pick.from;
139
279
  const names = [];
140
- for (let index = first; index < last; index += 1) {
141
- const name = ops.nameAt(index, unsettled);
142
- if (name !== null) {
143
- names.push(name);
280
+ for (const ops of walk([subject], pick.of ?? [])) {
281
+ for (const index of positions(ops, pick)) {
282
+ const name = nameOf(ops, pick, index, unsettled);
283
+ if (name !== null) {
284
+ names.push(name);
285
+ }
144
286
  }
145
287
  }
146
288
  return names;
147
289
  }
290
+ function nameOf(ops, pick, index, unsettled) {
291
+ if (pick.property === void 0) {
292
+ return ops.nameAt(index, unsettled);
293
+ }
294
+ for (const property of pick.property) {
295
+ const name = ops.propertyAt(index, property, unsettled);
296
+ if (name !== null) {
297
+ return name;
298
+ }
299
+ }
300
+ return null;
301
+ }
302
+ function sqlAccess(matched) {
303
+ const { ops, subject, method, chain, recognition } = matched;
304
+ const ending = chain.ending;
305
+ const stated = statedValue(subject, matched.meaning.statement);
306
+ const parts = stated?.parts() ?? null;
307
+ if (stated === null || parts === null) {
308
+ return null;
309
+ }
310
+ const statement = (0, import_sql.sqlFromParts)(parts, namesInHoles(chain, stated));
311
+ const accesses = (0, import_sql.readSqlAccess)(statement, { dialect: ending.dialect });
312
+ return accesses.length === 0 ? null : accesses.map((access) => ({
313
+ type: "interaction",
314
+ binding: (0, import_behavioral_ir.storageBinding)({
315
+ recognition,
316
+ storageSystem: ending.system,
317
+ ...ending.transport === void 0 ? {} : { transport: ending.transport },
318
+ scope: ending.scope,
319
+ container: access.table
320
+ }),
321
+ callee: ops.calleeText(),
322
+ interaction: {
323
+ class: "storage-access",
324
+ kind: access.kind,
325
+ fields: access.fields,
326
+ ...access.selector.length > 0 ? { selector: access.selector } : {},
327
+ operation: method
328
+ }
329
+ }));
330
+ }
331
+ function namesInHoles(chain, stated) {
332
+ const link = linkIn(chain, "interpolates");
333
+ if (link === null) {
334
+ return [];
335
+ }
336
+ return stated.holes().map((hole) => nameInHole(hole, link));
337
+ }
338
+ function nameInHole(hole, link) {
339
+ if (hole === null || link.from !== void 0 && !hole.isFrom(link.from)) {
340
+ return null;
341
+ }
342
+ return namesAt(hole, link.named, "nothing")[0] ?? null;
343
+ }
148
344
 
149
345
  // src/example.ts
150
346
  function runExamples(pack2, run) {
@@ -167,24 +363,35 @@ function examplesMissing(pack2) {
167
363
  }
168
364
 
169
365
  // src/pack.ts
366
+ var DISPATCHED_ON = {
367
+ storageAccess: "invocation",
368
+ sqlAccess: "access"
369
+ };
370
+ function walkedBy(chains, walk2, recognizedAs) {
371
+ return chains.filter((chain) => DISPATCHED_ON[chain.ending.yields] === walk2).map((chain) => compile(chain, recognizedAs));
372
+ }
170
373
  function pack(name, matches, spec) {
171
374
  const chains = matches.map((match) => match.declared);
172
- const recognizers = chains.map(
173
- (chain) => compile(chain, spec.recognizedAs)
174
- );
375
+ const access = walkedBy(chains, "access", spec.recognizedAs);
175
376
  return {
176
377
  name,
177
378
  languages: spec.languages,
178
379
  ...spec.version === void 0 ? {} : { version: spec.version },
179
- protocol: protocolOf(chains),
380
+ protocol: spec.protocol ?? protocolOf(chains),
180
381
  discovery: [],
181
382
  terminals: [],
182
383
  inputMapping: { type: "positionalParams", params: [] },
183
- requiresImport: gateOf(chains),
184
- invocationRecognizers: recognizers,
185
- declarations: { declarations: chains.map(describe) }
384
+ requiresImport: [
385
+ .../* @__PURE__ */ new Set([...gateOf(chains), ...spec.requiresImport ?? []])
386
+ ],
387
+ invocationRecognizers: walkedBy(chains, "invocation", spec.recognizedAs),
388
+ ...access.length === 0 ? {} : { accessRecognizers: access },
389
+ declarations: declarationsIn(matches)
186
390
  };
187
391
  }
392
+ function declarationsIn(matches) {
393
+ return { declarations: matches.map((match) => describe(match.declared)) };
394
+ }
188
395
  function protocolOf(chains) {
189
396
  const wires = new Set(
190
397
  chains.map((chain) => chain.ending.transport ?? chain.ending.system)
@@ -200,42 +407,97 @@ function protocolOf(chains) {
200
407
  function gateOf(chains) {
201
408
  const modules = /* @__PURE__ */ new Set();
202
409
  for (const chain of chains) {
203
- for (const link of chain.links) {
204
- if (link.asks !== "start" || link.at.starts !== "receiver") {
205
- continue;
206
- }
207
- for (const module2 of link.at.origin.importedFrom) {
410
+ for (const origin of originsIn(chain)) {
411
+ for (const module2 of origin.importedFrom) {
208
412
  modules.add(module2);
209
413
  }
210
414
  }
211
415
  }
212
416
  return [...modules];
213
417
  }
418
+ function originsIn(chain) {
419
+ const found = [];
420
+ for (const link of chain.links) {
421
+ if (link.asks === "start" && link.at.starts === "receiver") {
422
+ found.push(link.at.origin);
423
+ }
424
+ if (link.asks === "interpolates" && link.from !== void 0) {
425
+ found.push(link.from);
426
+ }
427
+ if (link.asks !== "subject") {
428
+ continue;
429
+ }
430
+ for (const step of link.of) {
431
+ if (step.to === "argument" && step.origin !== void 0) {
432
+ found.push(step.origin);
433
+ }
434
+ }
435
+ }
436
+ return found;
437
+ }
214
438
  function describe(chain) {
215
- const functions = chain.links.filter(isFunctionLink);
439
+ const links = chain.links.filter(isFunctionLink);
440
+ const written = [
441
+ ...links.map((link) => ({
442
+ asks: link.asks,
443
+ reachesAst: link.from.reachesAst === true
444
+ })),
445
+ ...rulesIn(chain)
446
+ ];
216
447
  return {
217
448
  name: chain.ending.system,
218
- dataLinks: chain.links.length - functions.length,
219
- functionLinks: functions.map((link) => link.asks),
220
- astLinks: functions.filter((link) => link.from.reachesAst === true).map((link) => link.asks),
449
+ // A method table with a rule inside it is still a table, so that
450
+ // link stays counted as data and the rule is priced beside it.
451
+ dataLinks: chain.links.length - links.length,
452
+ functionLinks: written.map((rule) => rule.asks),
453
+ astLinks: written.filter((rule) => rule.reachesAst).map((rule) => rule.asks),
221
454
  example: chain.example
222
455
  };
223
456
  }
224
457
  function isFunctionLink(link) {
225
- return link.asks === "container";
458
+ return link.asks === "container" && "from" in link;
459
+ }
460
+ function rulesIn(chain) {
461
+ const link = chain.links.find(
462
+ (candidate) => candidate.asks === "methods"
463
+ );
464
+ const written = /* @__PURE__ */ new Map();
465
+ for (const method of Object.values(link?.table ?? {})) {
466
+ for (const asks of ["selector", "fields"]) {
467
+ const rule = ruleFor(method[asks]);
468
+ if (rule !== null) {
469
+ written.set(asks, rule);
470
+ }
471
+ }
472
+ }
473
+ return [...written].map(([asks, from]) => ({
474
+ asks,
475
+ reachesAst: from.reachesAst === true
476
+ }));
477
+ }
478
+ function ruleFor(says) {
479
+ if (typeof says === "function") {
480
+ return says;
481
+ }
482
+ if (says === void 0 || Array.isArray(says)) {
483
+ return null;
484
+ }
485
+ const pointed = says;
486
+ return "by" in pointed ? pointed.by : null;
226
487
  }
227
488
 
228
- // src/storage.ts
229
- function storageCalls(spec) {
489
+ // src/sqlStatements.ts
490
+ function sqlStatements(spec) {
230
491
  const ending = {
231
- yields: "storageAccess",
492
+ yields: "sqlAccess",
232
493
  system: spec.system,
233
494
  ...spec.transport === void 0 ? {} : { transport: spec.transport },
234
495
  scope: spec.scope ?? "default",
235
- unsettledName: spec.unsettledName ?? "reference"
496
+ dialect: spec.dialect
236
497
  };
498
+ const client = spec.client;
237
499
  return chainFrom({
238
- links: [{ asks: "start", at: { starts: "receiver", origin: spec.client } }],
500
+ links: client === void 0 ? [] : [{ asks: "start", at: { starts: "receiver", origin: client } }],
239
501
  ending,
240
502
  example: null
241
503
  });
@@ -249,18 +511,56 @@ function chainFrom(declared) {
249
511
  table,
250
512
  ignoringCase: options?.ignoringCase ?? false
251
513
  }),
252
- container: (from) => adding({ asks: "container", from }),
514
+ interpolating: (says) => adding({ asks: "interpolates", ...says }),
253
515
  example: (code) => chainFrom({ ...declared, example: code })
254
516
  };
255
517
  }
518
+
519
+ // src/storage.ts
520
+ function storageCalls(spec) {
521
+ const ending = {
522
+ yields: "storageAccess",
523
+ system: spec.system,
524
+ ...spec.transport === void 0 ? {} : { transport: spec.transport },
525
+ scope: spec.scope ?? "default",
526
+ unsettledName: spec.unsettledName ?? "reference"
527
+ };
528
+ const client = spec.client;
529
+ return chainFrom2({
530
+ links: client === void 0 ? [] : [{ asks: "start", at: { starts: "receiver", origin: client } }],
531
+ ending,
532
+ example: null
533
+ });
534
+ }
535
+ function chainFrom2(declared) {
536
+ const adding = (link) => chainFrom2({ ...declared, links: [...declared.links, link] });
537
+ return {
538
+ declared,
539
+ about: (...of) => adding({ asks: "subject", of }),
540
+ methods: (table, options) => adding({
541
+ asks: "methods",
542
+ table,
543
+ ignoringCase: options?.ignoringCase ?? false
544
+ }),
545
+ container: (says) => adding(
546
+ typeof says === "function" ? { asks: "container", from: says } : { asks: "container", argument: says }
547
+ ),
548
+ accessPath: (says) => adding({ asks: "accessPath", argument: says }),
549
+ containersIn: (says) => adding({ asks: "containers", in: says }),
550
+ input: (says) => adding({ asks: "input", at: says }),
551
+ example: (code) => chainFrom2({ ...declared, example: code })
552
+ };
553
+ }
256
554
  // Annotate the CommonJS export names for ESM import in node:
257
555
  0 && (module.exports = {
258
556
  compile,
259
557
  constructedFrom,
558
+ declarationsIn,
260
559
  declaredBy,
261
560
  examplesMissing,
262
561
  opsIn,
263
562
  pack,
264
563
  runExamples,
564
+ sqlStatements,
265
565
  storageCalls
266
566
  });