eyelang 1.7.3 → 1.7.4
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/README.md +1 -0
- package/docs/guide.md +7 -0
- package/package.json +3 -2
- package/test/conformance/README.md +6 -0
- package/test/conformance/cases/atoms/graphic_less_than_atom.eye +4 -0
- package/test/conformance/cases/atoms/iri_mailto_readback.eye +4 -0
- package/test/conformance/cases/atoms/iri_query_fragment_readback.eye +4 -0
- package/test/conformance/cases/atoms/iri_urn_readback.eye +4 -0
- package/test/conformance/cases/atoms/quoted_urn_iri_readback.eye +4 -0
- package/test/conformance/cases/builtins/atom_string_iri_atom.eye +3 -0
- package/test/conformance/cases/builtins/eq_unifies_iri_atoms.eye +3 -0
- package/test/conformance/cases/declarations/mode_ignored_when_arity_mismatch.eye +5 -0
- package/test/conformance/cases/declarations/table_is_also_fact.eye +4 -0
- package/test/conformance/cases/lists/empty_list_term.eye +4 -0
- package/test/conformance/cases/lists/improper_list_readback.eye +4 -0
- package/test/conformance/cases/lists/list_with_iri_atoms.eye +4 -0
- package/test/conformance/cases/materialize/source_facts_suppressed.eye +5 -0
- package/test/conformance/cases/negation/not_known_goal_fails.eye +4 -0
- package/test/conformance/cases/negation/not_unknown_goal_succeeds.eye +3 -0
- package/test/conformance/cases/syntax/parenthesized_query_style_body.eye +5 -0
- package/test/conformance/cases/terms/compound_name_arguments_atom_zero_args.eye +3 -0
- package/test/conformance/cases/terms/compound_name_arguments_builds_atom_from_empty_args.eye +3 -0
- package/test/conformance/cases/terms/functor_atom_arity_zero.eye +3 -0
- package/test/conformance/cases/variables/anonymous_in_two_goals.eye +4 -0
- package/test/conformance/cases/variables/question_uppercase_variable.eye +4 -0
- package/test/conformance/cases/variables/question_variable_digits_and_underscore.eye +4 -0
- package/test/conformance/cases/variables/variable_scope_per_clause.eye +6 -0
- package/test/conformance/errors/atoms/relative_angle_name_rejected.eye +1 -0
- package/test/conformance/errors/atoms/space_in_angle_iri_rejected.eye +1 -0
- package/test/conformance/errors/atoms/unclosed_angle_iri_rejected.eye +1 -0
- package/test/conformance/errors/syntax/missing_final_dot_rejected.eye +1 -0
- package/test/conformance/errors/syntax/unclosed_list_rejected.eye +1 -0
- package/test/conformance/errors/syntax/uppercase_predicate_rejected.eye +1 -0
- package/test/conformance/errors/terms/zero_arity_compound_nested_rejected.eye +1 -0
- package/test/conformance/errors/variables/question_digit_rejected.eye +1 -0
- package/test/conformance/errors/variables/question_dot_rejected.eye +1 -0
- package/test/conformance/expected/atoms/graphic_less_than_atom.eye +1 -0
- package/test/conformance/expected/atoms/iri_mailto_readback.eye +1 -0
- package/test/conformance/expected/atoms/iri_query_fragment_readback.eye +1 -0
- package/test/conformance/expected/atoms/iri_urn_readback.eye +1 -0
- package/test/conformance/expected/atoms/quoted_urn_iri_readback.eye +1 -0
- package/test/conformance/expected/builtins/atom_string_iri_atom.eye +1 -0
- package/test/conformance/expected/builtins/eq_unifies_iri_atoms.eye +1 -0
- package/test/conformance/expected/declarations/mode_ignored_when_arity_mismatch.eye +1 -0
- package/test/conformance/expected/declarations/table_is_also_fact.eye +1 -0
- package/test/conformance/expected/lists/empty_list_term.eye +1 -0
- package/test/conformance/expected/lists/improper_list_readback.eye +1 -0
- package/test/conformance/expected/lists/list_with_iri_atoms.eye +1 -0
- package/test/conformance/expected/materialize/source_facts_suppressed.eye +1 -0
- package/test/conformance/expected/negation/not_known_goal_fails.eye +0 -0
- package/test/conformance/expected/negation/not_unknown_goal_succeeds.eye +1 -0
- package/test/conformance/expected/syntax/parenthesized_query_style_body.eye +1 -0
- package/test/conformance/expected/terms/compound_name_arguments_atom_zero_args.eye +1 -0
- package/test/conformance/expected/terms/compound_name_arguments_builds_atom_from_empty_args.eye +1 -0
- package/test/conformance/expected/terms/functor_atom_arity_zero.eye +1 -0
- package/test/conformance/expected/variables/anonymous_in_two_goals.eye +1 -0
- package/test/conformance/expected/variables/question_uppercase_variable.eye +1 -0
- package/test/conformance/expected/variables/question_variable_digits_and_underscore.eye +1 -0
- package/test/conformance/expected/variables/variable_scope_per_clause.eye +2 -0
- package/test/conformance/expected-errors/atoms/relative_angle_name_rejected.txt +1 -0
- package/test/conformance/expected-errors/atoms/space_in_angle_iri_rejected.txt +1 -0
- package/test/conformance/expected-errors/atoms/unclosed_angle_iri_rejected.txt +1 -0
- package/test/conformance/expected-errors/syntax/missing_final_dot_rejected.txt +1 -0
- package/test/conformance/expected-errors/syntax/unclosed_list_rejected.txt +1 -0
- package/test/conformance/expected-errors/syntax/uppercase_predicate_rejected.txt +1 -0
- package/test/conformance/expected-errors/terms/zero_arity_compound_nested_rejected.txt +1 -0
- package/test/conformance/expected-errors/variables/question_digit_rejected.txt +1 -0
- package/test/conformance/expected-errors/variables/question_dot_rejected.txt +1 -0
- package/test/conformance/expected-warnings/negation/negative_unknown_group_quiet.eye +1 -0
- package/test/conformance/expected-warnings/negation/negative_unknown_group_quiet.txt +0 -0
- package/test/conformance/expected-warnings/negation/no_negation_quiet.eye +1 -0
- package/test/conformance/expected-warnings/negation/no_negation_quiet.txt +0 -0
- package/test/conformance/expected-warnings/negation/unstratified_three_step.eye +1 -0
- package/test/conformance/expected-warnings/negation/unstratified_three_step.txt +2 -0
- package/test/conformance/warnings/negation/negative_unknown_group_quiet.eye +2 -0
- package/test/conformance/warnings/negation/no_negation_quiet.eye +3 -0
- package/test/conformance/warnings/negation/unstratified_three_step.eye +6 -0
- package/test/run-conformance-report.mjs +108 -0
- package/test/run-regression.mjs +13 -0
package/README.md
CHANGED
package/docs/guide.md
CHANGED
|
@@ -497,6 +497,12 @@ node test/run-regression.mjs
|
|
|
497
497
|
node test/run-examples.mjs
|
|
498
498
|
```
|
|
499
499
|
|
|
500
|
+
Summarize the conformance corpus by category:
|
|
501
|
+
|
|
502
|
+
```sh
|
|
503
|
+
npm run conformance:report
|
|
504
|
+
```
|
|
505
|
+
|
|
500
506
|
The conformance suite lives in [`test/conformance/`](../test/conformance/) as a file-based eyelang corpus. Positive cases pair `cases/<name>.eye` with exact expected stdout under `expected/<name>.eye`; negative cases pair `errors/<name>.eye` with exact expected error text under `expected-errors/<name>.txt`; warning cases pair `warnings/<name>.eye` with exact `--warnings` stdout and stderr files under `expected-warnings/`. Cases may be grouped in category directories such as `atoms/`, `variables/`, `negation/`, and `syntax/`, so another implementation can reuse the same corpus as an executable language contract. The suite covers the standard language surface from the language reference, including reusable built-ins, standard errors, and standard warnings. The regression suite lives in [`test/run-regression.mjs`](../test/run-regression.mjs) and covers CLI regressions, the public JavaScript API, and white-box invariants for parser, unification, and indexing behavior.
|
|
501
507
|
|
|
502
508
|
## Development and release
|
|
@@ -506,6 +512,7 @@ Common commands:
|
|
|
506
512
|
```sh
|
|
507
513
|
npm run test:eyelang # alias for npm test
|
|
508
514
|
npm test # full conformance, regression/API/white-box, examples, and proof examples
|
|
515
|
+
npm run conformance:report # conformance coverage summary by category
|
|
509
516
|
node test/run-conformance.mjs
|
|
510
517
|
node test/run-regression.mjs
|
|
511
518
|
node test/run-examples.mjs
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eyelang",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.4",
|
|
4
4
|
"description": "A small Prolog-like logic programming language for rules, goals, answers, and proofs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"test:examples": "node test/run-examples.mjs",
|
|
45
45
|
"test:regression": "node test/run-regression.mjs",
|
|
46
46
|
"preversion": "npm test",
|
|
47
|
-
"postversion": "git push origin HEAD --follow-tags"
|
|
47
|
+
"postversion": "git push origin HEAD --follow-tags",
|
|
48
|
+
"conformance:report": "node test/run-conformance-report.mjs"
|
|
48
49
|
}
|
|
49
50
|
}
|
|
@@ -36,6 +36,12 @@ Run only the conformance suite:
|
|
|
36
36
|
node test/run-conformance.mjs
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
Summarize conformance coverage by category:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
npm run conformance:report
|
|
43
|
+
```
|
|
44
|
+
|
|
39
45
|
Run matching conformance cases by passing a filename or directory fragment:
|
|
40
46
|
|
|
41
47
|
```sh
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(<relative>).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(<urn:example:a b>).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(<urn:example:open).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(ok)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer([a, b).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Answer(ok).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(f(nil())).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(?1).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(?.).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(<).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(<mailto:alice@example.org>).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(<https://example.org/path?x=1#frag>).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(<urn:example:alpha>).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(<urn:example:quoted>).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer("urn:example:a").
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(ok).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(ok).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(path, 2).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer([]).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer([a, b | tail]).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer([<urn:example:a>, <urn:example:b>]).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(a).
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(ok).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(ok).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(nil, []).
|
package/test/conformance/expected/terms/compound_name_arguments_builds_atom_from_empty_args.eye
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(nil).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(nil, 0).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(ok).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(ok).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(a, b).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
parse line 1: expected ), got relative
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
parse line 1: expected ), got urn
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
parse line 1: expected ), got urn
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
parse line 2: expected ., got
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
parse line 1: expected ], got )
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
parse line 1: bad character "A"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
parse line 1: zero-arity compound syntax is not supported; use atom "nil" for arity zero data
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
parse line 1: expected ), got 1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
parse line 1: expected ), got .
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(ok).
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(ok).
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(ok).
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Static conformance corpus report.
|
|
3
|
+
// This complements the executable runner with a category summary that makes
|
|
4
|
+
// coverage growth visible without running every case.
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
|
|
9
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
|
|
10
|
+
const conformanceRoot = path.join(root, 'conformance');
|
|
11
|
+
|
|
12
|
+
const KINDS = [
|
|
13
|
+
{ kind: 'cases', expectedKind: 'expected', expectedExt: '.eye', column: 'positive' },
|
|
14
|
+
{ kind: 'errors', expectedKind: 'expected-errors', expectedExt: '.txt', column: 'errors' },
|
|
15
|
+
{ kind: 'warnings', expectedKind: 'expected-warnings', expectedExt: '.eye', column: 'warnings' },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export function buildConformanceReport() {
|
|
19
|
+
const categories = new Map();
|
|
20
|
+
const issues = [];
|
|
21
|
+
|
|
22
|
+
for (const { kind, expectedKind, expectedExt, column } of KINDS) {
|
|
23
|
+
const base = path.join(conformanceRoot, kind);
|
|
24
|
+
if (!fs.existsSync(base)) continue;
|
|
25
|
+
for (const file of listEyeFiles(base)) {
|
|
26
|
+
const category = categoryOf(file);
|
|
27
|
+
const counts = ensureCategory(categories, category);
|
|
28
|
+
counts[column]++;
|
|
29
|
+
counts.total++;
|
|
30
|
+
|
|
31
|
+
const stem = file.slice(0, -4);
|
|
32
|
+
const expected = path.join(conformanceRoot, expectedKind, `${stem}${expectedExt}`);
|
|
33
|
+
if (!fs.existsSync(expected)) issues.push(`missing ${expectedKind}/${stem}${expectedExt}`);
|
|
34
|
+
if (kind === 'warnings') {
|
|
35
|
+
const expectedStderr = path.join(conformanceRoot, expectedKind, `${stem}.txt`);
|
|
36
|
+
if (!fs.existsSync(expectedStderr)) issues.push(`missing ${expectedKind}/${stem}.txt`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const rows = [...categories.entries()]
|
|
42
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
43
|
+
.map(([category, counts]) => ({ category, ...counts }));
|
|
44
|
+
const total = rows.reduce((acc, row) => ({
|
|
45
|
+
positive: acc.positive + row.positive,
|
|
46
|
+
errors: acc.errors + row.errors,
|
|
47
|
+
warnings: acc.warnings + row.warnings,
|
|
48
|
+
total: acc.total + row.total,
|
|
49
|
+
}), { positive: 0, errors: 0, warnings: 0, total: 0 });
|
|
50
|
+
|
|
51
|
+
return { rows, total, issues: issues.sort() };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function formatConformanceReport(report = buildConformanceReport()) {
|
|
55
|
+
const lines = [
|
|
56
|
+
'# Conformance Eyelang report',
|
|
57
|
+
'',
|
|
58
|
+
'This report summarizes the file-based conformance corpus under `test/conformance/`.',
|
|
59
|
+
'',
|
|
60
|
+
'| Category | Positive | Errors | Warnings | Total |',
|
|
61
|
+
'|---|---:|---:|---:|---:|',
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
for (const row of report.rows) {
|
|
65
|
+
lines.push(`| ${row.category} | ${row.positive} | ${row.errors} | ${row.warnings} | ${row.total} |`);
|
|
66
|
+
}
|
|
67
|
+
lines.push(`| **Total** | **${report.total.positive}** | **${report.total.errors}** | **${report.total.warnings}** | **${report.total.total}** |`);
|
|
68
|
+
|
|
69
|
+
if (report.issues.length > 0) {
|
|
70
|
+
lines.push('', '## Corpus issues', '');
|
|
71
|
+
for (const issue of report.issues) lines.push(`- ${issue}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return `${lines.join('\n')}\n`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function listEyeFiles(base, dir = base) {
|
|
78
|
+
const files = [];
|
|
79
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
80
|
+
const full = path.join(dir, entry.name);
|
|
81
|
+
if (entry.isDirectory()) {
|
|
82
|
+
files.push(...listEyeFiles(base, full));
|
|
83
|
+
} else if (entry.isFile() && entry.name.endsWith('.eye')) {
|
|
84
|
+
files.push(path.relative(base, full).split(path.sep).join('/'));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return files.sort();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function categoryOf(file) {
|
|
91
|
+
const parts = file.split('/');
|
|
92
|
+
return parts.length > 1 ? parts[0] : 'legacy-numbered';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function ensureCategory(categories, category) {
|
|
96
|
+
let counts = categories.get(category);
|
|
97
|
+
if (!counts) {
|
|
98
|
+
counts = { positive: 0, errors: 0, warnings: 0, total: 0 };
|
|
99
|
+
categories.set(category, counts);
|
|
100
|
+
}
|
|
101
|
+
return counts;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (process.argv[1] != null && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
105
|
+
const report = buildConformanceReport();
|
|
106
|
+
process.stdout.write(formatConformanceReport(report));
|
|
107
|
+
if (report.issues.length > 0) process.exit(1);
|
|
108
|
+
}
|
package/test/run-regression.mjs
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
import { parseGoalText } from '../src/parser.js';
|
|
36
36
|
import { selectClauseCandidates } from '../src/program.js';
|
|
37
37
|
import { TestReporter, isMainModule } from './test-style.mjs';
|
|
38
|
+
import { buildConformanceReport, formatConformanceReport } from './run-conformance-report.mjs';
|
|
38
39
|
import { hashHex } from '../src/hash.js';
|
|
39
40
|
|
|
40
41
|
const testRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
|
|
@@ -365,6 +366,18 @@ function documentationSyncCases() {
|
|
|
365
366
|
name: 'documented npm scripts exist in package.json',
|
|
366
367
|
run: () => assertArrayEqual(missingDocumentedPackageScripts(), [], 'missing documented npm scripts'),
|
|
367
368
|
},
|
|
369
|
+
{
|
|
370
|
+
name: 'conformance report summarizes public corpus',
|
|
371
|
+
run: () => {
|
|
372
|
+
const report = buildConformanceReport();
|
|
373
|
+
assertArrayEqual(report.issues, [], 'conformance report issues');
|
|
374
|
+
assertEqual(report.total.total >= 150, true, 'conformance case count');
|
|
375
|
+
assertEqual(report.total.positive + report.total.errors + report.total.warnings, report.total.total, 'conformance total');
|
|
376
|
+
const text = formatConformanceReport(report);
|
|
377
|
+
assertIncludes(text, '| variables |', 'report');
|
|
378
|
+
assertIncludes(text, '| **Total** |', 'report');
|
|
379
|
+
},
|
|
380
|
+
},
|
|
368
381
|
{
|
|
369
382
|
name: 'source-checkout setup docs match package bin',
|
|
370
383
|
run: () => {
|