eyelang 1.5.5 → 1.5.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyelang",
3
- "version": "1.5.5",
3
+ "version": "1.5.6",
4
4
  "type": "module",
5
5
  "description": "A small rule engine for Prolog-style Horn clauses",
6
6
  "keywords": [
package/src/index.js CHANGED
@@ -24,8 +24,8 @@ export function run(source, options = {}) {
24
24
  const facts = program.sourceFactLines(materializedKeys);
25
25
  const seen = new Set();
26
26
  for (const goal of goals) {
27
- const localSolver = new Solver(program, runOptions);
28
- for (const env of localSolver.solve([goal], new Env(), 0)) {
27
+ solver.solutionsSeen = 0;
28
+ for (const env of solver.solve([goal], new Env(), 0)) {
29
29
  const resolved = copyResolved(goal, env);
30
30
  if (!termIsGround(resolved)) continue;
31
31
  const line = `${termToString(resolved, new Env(), true)}.\n`;
package/src/parser.js CHANGED
@@ -287,12 +287,14 @@ function isSimpleName(text) {
287
287
 
288
288
  const SIMPLE_NUMBER = /^-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?$/;
289
289
  const SIMPLE_ARG_FORBIDDEN = /[\s()[\]|"']/;
290
+ const FAST_BINARY_FACT = /^([a-z][A-Za-z0-9_]*)\(\s*([^,\s()[\]|"']+)\s*,\s*([^,\s()[\]|"']+)\s*\)\.$/;
291
+ const FAST_BINARY_RULE = /^([a-z][A-Za-z0-9_]*)\(\s*([^,\s()[\]|"']+)\s*,\s*([^,\s()[\]|"']+)\s*\)\s*:-\s*([a-z][A-Za-z0-9_]*)\(\s*([^,\s()[\]|"']+)\s*,\s*([^,\s()[\]|"']+)\s*\)\.$/;
290
292
 
291
293
  function parseClausesFastNoSource(source) {
292
294
  source = String(source ?? '');
293
- const atomCache = new Map();
294
295
  const numberCache = new Map();
295
296
  const stringCache = new Map();
297
+ const variableCache = new Map();
296
298
  const clauses = [];
297
299
  let anonymous = 0;
298
300
  let chunk = '';
@@ -304,22 +306,23 @@ function parseClausesFastNoSource(source) {
304
306
  cache.set(key, value);
305
307
  return value;
306
308
  };
307
- const scalarOrVariable = (text, variables) => {
308
- text = text.trim();
309
+ const scalarOrVariableFast = (text) => {
309
310
  if (!text) throw new Error('empty simple term');
311
+ const first = text.charCodeAt(0);
310
312
  if (text === '_') return variable(`__anon${anonymous++}`);
311
- if (isVariableStart(text)) {
312
- const existing = variables.get(text);
313
+ if (first === 95 || (first >= 65 && first <= 90)) {
314
+ const existing = variableCache.get(text);
313
315
  if (existing) return existing;
314
316
  const value = variable(text);
315
- variables.set(text, value);
317
+ variableCache.set(text, value);
316
318
  return value;
317
319
  }
318
- if (SIMPLE_NUMBER.test(text)) return cached(numberCache, text, numberTerm);
319
- if (text[0] === '"' && text.endsWith('"')) return cached(stringCache, text.slice(1, -1), stringTerm);
320
- return cached(atomCache, text, atom);
320
+ if ((first === 45 || isDigitCode(first)) && SIMPLE_NUMBER.test(text)) return cached(numberCache, text, numberTerm);
321
+ if (first === 34 && text.endsWith('"')) return cached(stringCache, text.slice(1, -1), stringTerm);
322
+ return atom(text);
321
323
  };
322
- const parseBinaryCompound = (text, variables) => {
324
+ const scalarOrVariable = (text) => scalarOrVariableFast(text.trim());
325
+ const parseBinaryCompound = (text) => {
323
326
  text = text.trim();
324
327
  const open = text.indexOf('(');
325
328
  if (open <= 0 || text[text.length - 1] !== ')') return null;
@@ -332,19 +335,37 @@ function parseClausesFastNoSource(source) {
332
335
  const left = inner.slice(0, comma).trim();
333
336
  const right = inner.slice(comma + 1).trim();
334
337
  if (!left || !right || SIMPLE_ARG_FORBIDDEN.test(left) || SIMPLE_ARG_FORBIDDEN.test(right)) return null;
335
- return compound(name, [scalarOrVariable(left, variables), scalarOrVariable(right, variables)]);
338
+ return compound(name, [scalarOrVariable(left), scalarOrVariable(right)]);
339
+ };
340
+ const parseFastBinaryMatch = (match) => {
341
+ return compound(match[1], [scalarOrVariableFast(match[2]), scalarOrVariableFast(match[3])]);
342
+ };
343
+ const parseFastBinaryRuleMatch = (match) => {
344
+ return {
345
+ head: compound(match[1], [scalarOrVariableFast(match[2]), scalarOrVariableFast(match[3])]),
346
+ body: [compound(match[4], [scalarOrVariableFast(match[5]), scalarOrVariableFast(match[6])])],
347
+ };
348
+ };
349
+ const parseFastLine = (text) => {
350
+ if (!text.endsWith('.')) return null;
351
+ const ruleMatch = FAST_BINARY_RULE.exec(text);
352
+ if (ruleMatch) return parseFastBinaryRuleMatch(ruleMatch);
353
+ const factMatch = FAST_BINARY_FACT.exec(text);
354
+ if (factMatch) return { head: parseFastBinaryMatch(factMatch), body: [] };
355
+ return null;
336
356
  };
337
357
  const parseSimple = (text) => {
338
358
  if (!text.endsWith('.') || text.includes('\n')) return null;
359
+ const fast = parseFastLine(text);
360
+ if (fast) return fast;
339
361
  text = text.slice(0, -1);
340
- const variables = new Map();
341
362
  const rule = text.indexOf(':-');
342
363
  if (rule < 0) {
343
- const head = parseBinaryCompound(text, variables);
364
+ const head = parseBinaryCompound(text);
344
365
  return head ? { head, body: [] } : null;
345
366
  }
346
- const head = parseBinaryCompound(text.slice(0, rule), variables);
347
- const bodyGoal = parseBinaryCompound(text.slice(rule + 2), variables);
367
+ const head = parseBinaryCompound(text.slice(0, rule));
368
+ const bodyGoal = parseBinaryCompound(text.slice(rule + 2));
348
369
  return head && bodyGoal ? { head, body: [bodyGoal] } : null;
349
370
  };
350
371
 
@@ -375,7 +396,7 @@ function parseClausesFastNoSource(source) {
375
396
  const trimmed = line.trim();
376
397
  if (trimmed && !trimmed.startsWith('%')) {
377
398
  if (!chunk && trimmed.endsWith('.')) {
378
- const simple = parseSimple(trimmed);
399
+ const simple = parseFastLine(trimmed) ?? parseSimple(trimmed);
379
400
  if (simple) clauses.push(simple);
380
401
  else {
381
402
  chunk = line + '\n';