eyelang 1.7.2 → 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 +8 -1
- package/docs/language-reference.md +1 -1
- package/package.json +3 -2
- package/test/conformance/README.md +27 -6
- package/test/conformance/cases/atoms/graphic_angle_atom_stays_graphic.eye +4 -0
- package/test/conformance/cases/atoms/graphic_less_than_atom.eye +4 -0
- package/test/conformance/cases/atoms/iri_atoms_in_lists.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_quoted_absolute_readback.eye +5 -0
- package/test/conformance/cases/atoms/iri_quoted_and_angle_unify.eye +3 -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/atoms/zero_arity_atom_roundtrip.eye +5 -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/memoize_is_ordinary_fact.eye +4 -0
- package/test/conformance/cases/declarations/mode_determinism_are_facts.eye +8 -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/negation/stratified_negation_answers.eye +6 -0
- package/test/conformance/cases/syntax/parenthesized_query_style_body.eye +5 -0
- package/test/conformance/cases/table/table_does_not_change_answers.eye +7 -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_anonymous_not_reused.eye +6 -0
- package/test/conformance/cases/variables/question_underscore_named_reuse.eye +5 -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/atoms/zero_arity_compound_rejected.eye +1 -0
- package/test/conformance/errors/syntax/colon_name_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/unclosed_quoted_atom_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/bare_underscore_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/errors/variables/underscore_named_rejected.eye +1 -0
- package/test/conformance/errors/variables/uppercase_variable_rejected.eye +1 -0
- package/test/conformance/expected/atoms/graphic_angle_atom_stays_graphic.eye +1 -0
- package/test/conformance/expected/atoms/graphic_less_than_atom.eye +1 -0
- package/test/conformance/expected/atoms/iri_atoms_in_lists.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_quoted_absolute_readback.eye +2 -0
- package/test/conformance/expected/atoms/iri_quoted_and_angle_unify.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/atoms/zero_arity_atom_roundtrip.eye +3 -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/memoize_is_ordinary_fact.eye +1 -0
- package/test/conformance/expected/declarations/mode_determinism_are_facts.eye +3 -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/negation/stratified_negation_answers.eye +1 -0
- package/test/conformance/expected/syntax/parenthesized_query_style_body.eye +1 -0
- package/test/conformance/expected/table/table_does_not_change_answers.eye +3 -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_anonymous_not_reused.eye +4 -0
- package/test/conformance/expected/variables/question_underscore_named_reuse.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/atoms/zero_arity_compound_rejected.txt +1 -0
- package/test/conformance/expected-errors/syntax/colon_name_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/unclosed_quoted_atom_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/bare_underscore_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-errors/variables/underscore_named_rejected.txt +1 -0
- package/test/conformance/expected-errors/variables/uppercase_variable_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/stratified_quiet.eye +1 -0
- package/test/conformance/expected-warnings/negation/stratified_quiet.txt +0 -0
- package/test/conformance/expected-warnings/negation/unstratified_mutual.eye +0 -0
- package/test/conformance/expected-warnings/negation/unstratified_mutual.txt +3 -0
- package/test/conformance/expected-warnings/negation/unstratified_self.eye +0 -0
- package/test/conformance/expected-warnings/negation/unstratified_self.txt +2 -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/stratified_quiet.eye +4 -0
- package/test/conformance/warnings/negation/unstratified_mutual.eye +5 -0
- package/test/conformance/warnings/negation/unstratified_self.eye +4 -0
- package/test/conformance/warnings/negation/unstratified_three_step.eye +6 -0
- package/test/run-conformance-report.mjs +108 -0
- package/test/run-conformance.mjs +91 -9
- package/test/run-regression.mjs +13 -0
package/README.md
CHANGED
package/docs/guide.md
CHANGED
|
@@ -497,7 +497,13 @@ node test/run-regression.mjs
|
|
|
497
497
|
node test/run-examples.mjs
|
|
498
498
|
```
|
|
499
499
|
|
|
500
|
-
|
|
500
|
+
Summarize the conformance corpus by category:
|
|
501
|
+
|
|
502
|
+
```sh
|
|
503
|
+
npm run conformance:report
|
|
504
|
+
```
|
|
505
|
+
|
|
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
|
|
503
509
|
|
|
@@ -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
|
|
@@ -702,7 +702,7 @@ A conforming eyelang implementation supports the standard language described abo
|
|
|
702
702
|
|
|
703
703
|
Browser execution, package layout, CLI URL loading, and any implementation-specific built-ins described in host documentation are outside this conformance surface unless separately standardized.
|
|
704
704
|
|
|
705
|
-
Conformance cases live in the repository under `test/conformance/`. They are run by `npm test` before the example suite, and can be run alone with `node test/run-conformance.mjs`.
|
|
705
|
+
Conformance cases live in the repository under `test/conformance/`. They are run by `npm test` before the example suite, and can be run alone with `node test/run-conformance.mjs`. Positive cases have input programs under `conformance/cases/` and exact expected standard-output files under `conformance/expected/`; both use `.eye` so expected output remains eyelang-readable. Expected-error cases live under `conformance/errors/` with exact messages under `conformance/expected-errors/`. Expected-warning cases live under `conformance/warnings/` with exact `--warnings` stdout and stderr files under `conformance/expected-warnings/`.
|
|
706
706
|
|
|
707
707
|
## 15. Relationship to ISO Prolog
|
|
708
708
|
|
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
|
}
|
|
@@ -2,12 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
This directory contains the executable conformance cases for the Eyelang language and reference engine. The normative language description is in the [Eyelang language reference](../../../docs/language-reference.md).
|
|
4
4
|
|
|
5
|
-
The suite is intentionally file-based so another implementation can run the same programs and compare exact standard output.
|
|
5
|
+
The suite is intentionally file-based so another implementation can run the same programs and compare exact standard output, expected errors, and expected warnings. The conformance corpus is part of the public language contract, not just an implementation smoke test.
|
|
6
|
+
|
|
7
|
+
A normal positive case consists of:
|
|
6
8
|
|
|
7
9
|
- `conformance/cases/<name>.eye` — input program;
|
|
8
10
|
- `conformance/expected/<name>.eye` — exact expected standard output, stored as eyelang-readable facts.
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
Expected-error cases consist of:
|
|
13
|
+
|
|
14
|
+
- `conformance/errors/<name>.eye` — input program that must fail during parsing or execution;
|
|
15
|
+
- `conformance/expected-errors/<name>.txt` — exact expected error message followed by a newline.
|
|
16
|
+
|
|
17
|
+
Expected-warning cases consist of:
|
|
18
|
+
|
|
19
|
+
- `conformance/warnings/<name>.eye` — input program run through the CLI with `--warnings`;
|
|
20
|
+
- `conformance/expected-warnings/<name>.eye` — exact expected standard output;
|
|
21
|
+
- `conformance/expected-warnings/<name>.txt` — exact expected standard error.
|
|
22
|
+
|
|
23
|
+
Case names may be nested in category directories such as `atoms/`, `variables/`, `negation/`, or `syntax/`. Expected files mirror the same relative path.
|
|
11
24
|
|
|
12
25
|
## Running the suite
|
|
13
26
|
|
|
@@ -23,21 +36,29 @@ Run only the conformance suite:
|
|
|
23
36
|
node test/run-conformance.mjs
|
|
24
37
|
```
|
|
25
38
|
|
|
26
|
-
|
|
39
|
+
Summarize conformance coverage by category:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
npm run conformance:report
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Run matching conformance cases by passing a filename or directory fragment:
|
|
27
46
|
|
|
28
47
|
```sh
|
|
29
48
|
node test/run-conformance.mjs reusable
|
|
30
49
|
node test/run-conformance.mjs 092_scalar_string_conversions
|
|
50
|
+
node test/run-conformance.mjs variables/
|
|
51
|
+
node test/run-conformance.mjs error/variables
|
|
31
52
|
```
|
|
32
53
|
|
|
33
|
-
The runner executes materialized programs in-process through the public JavaScript API so small conformance cases avoid measuring Node startup overhead.
|
|
54
|
+
The runner executes normal materialized programs in-process through the public JavaScript API so small conformance cases avoid measuring Node startup overhead. Warning cases intentionally use the CLI because warning output is a host-interface contract.
|
|
34
55
|
|
|
35
56
|
## Scope
|
|
36
57
|
|
|
37
|
-
The conformance corpus is a single eyelang suite. It covers the standard language described by the language reference: lexical syntax, facts, definite clauses, first-order terms, lists, conjunction, structured unification, left-to-right goal-directed proof search, materialized output, read-back printing, standard built-ins, declarations, and standard host behavior.
|
|
58
|
+
The conformance corpus is a single eyelang suite. It covers the standard language described by the language reference: lexical syntax, facts, definite clauses, first-order terms, lists, conjunction, structured unification, left-to-right goal-directed proof search, materialized output, read-back printing, standard built-ins, declarations, warnings, errors, and standard host behavior.
|
|
38
59
|
|
|
39
60
|
The suite deliberately does not separate `core` and `extension` profiles. Reusable built-ins such as arithmetic, strings, lists, aggregation, context terms, term inspection, and search control are part of the standard eyelang conformance surface. Implementation-specific built-ins may still exist in downstream hosts, but they should have their own tests outside this corpus unless they are standardized.
|
|
40
61
|
|
|
41
62
|
## Updating expected output
|
|
42
63
|
|
|
43
|
-
There is no committed auto-accept mode. To update an expected file, run the matching case with the conformance runner, inspect the result, and replace the corresponding file under `conformance/expected/` deliberately.
|
|
64
|
+
There is no committed auto-accept mode. To update an expected file, run the matching case with the conformance runner, inspect the result, and replace the corresponding file under `conformance/expected/`, `conformance/expected-errors/`, or `conformance/expected-warnings/` deliberately.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
% Arity-zero data is represented as atoms, never zero-arity compounds.
|
|
2
|
+
materialize(answer, 2).
|
|
3
|
+
answer(name, ?name) :- compound_name_arguments(nil, ?name, ?args).
|
|
4
|
+
answer(args, ?args) :- compound_name_arguments(nil, ?name, ?args).
|
|
5
|
+
answer(term, ?term) :- compound_name_arguments(?term, nil, []).
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
% Advisory declarations are also ordinary facts that programs can inspect.
|
|
2
|
+
mode(path, 2, [in, out]).
|
|
3
|
+
semidet(edge, 2).
|
|
4
|
+
det(root, 1).
|
|
5
|
+
materialize(answer, 2).
|
|
6
|
+
answer(mode, ?modes) :- mode(path, 2, ?modes).
|
|
7
|
+
answer(semidet, edge) :- semidet(edge, 2).
|
|
8
|
+
answer(det, root) :- det(root, 1).
|
|
@@ -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
|
+
p(nil()).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ex:alice.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(ok)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer([a, b).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
p('unterminated).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Answer(ok).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(f(nil())).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
p(_).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(?1).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(?.).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
p(_name).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
p(X).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(<=>).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(<).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer([<urn:example:a>, <urn:example:b>, <urn:example:c>]).
|
|
@@ -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(ok).
|
|
@@ -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(path, 2).
|
|
@@ -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
|
+
open(a).
|
|
@@ -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(a).
|
|
@@ -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 1: zero-arity compound syntax is not supported; use atom "nil" for arity zero data
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
colon names are not supported; use name or prefix_name
|
|
@@ -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: unterminated quoted term
|
|
@@ -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: bad character "_"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
parse line 1: expected ), got 1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
parse line 1: expected ), got .
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
parse line 1: bad character "_"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
parse line 1: bad character "X"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(ok).
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(ok).
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(ok).
|
|
File without changes
|
|
File without changes
|
|
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-conformance.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Conformance test runner.
|
|
3
|
-
// It executes cases in-process so the conformance corpus measures engine behavior instead of Node process startup.
|
|
3
|
+
// It executes normal cases in-process so the conformance corpus measures engine behavior instead of Node process startup.
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { spawnSync } from 'node:child_process';
|
|
@@ -9,29 +9,67 @@ import { fileURLToPath } from 'node:url';
|
|
|
9
9
|
import { TestReporter, isMainModule } from './test-style.mjs';
|
|
10
10
|
|
|
11
11
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
|
|
12
|
+
const packageRoot = path.resolve(root, '..');
|
|
13
|
+
const cliBin = path.join(packageRoot, 'src', 'bin.js');
|
|
12
14
|
const filterArg = process.argv[2] ?? null;
|
|
13
15
|
|
|
14
16
|
export function runConformance(reporter = new TestReporter(), requestedFilter = null) {
|
|
15
17
|
const filter = requestedFilter ?? filterArg;
|
|
16
18
|
const label = filter == null ? 'eyelang' : `eyelang ${filter}`;
|
|
17
19
|
reporter.section(`Conformance ${label}`);
|
|
18
|
-
for (const file of listCaseFiles(filter)) runCaseFile(reporter, file);
|
|
20
|
+
for (const file of listCaseFiles('cases', filter)) runCaseFile(reporter, file);
|
|
21
|
+
for (const file of listCaseFiles('errors', filter)) runErrorFile(reporter, file);
|
|
22
|
+
for (const file of listCaseFiles('warnings', filter)) runWarningFile(reporter, file);
|
|
19
23
|
reporter.sectionTotal(`conformance ${label}`);
|
|
20
24
|
}
|
|
21
25
|
|
|
22
|
-
function listCaseFiles(filter = null) {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
.filter((name) =>
|
|
26
|
+
function listCaseFiles(kind, filter = null) {
|
|
27
|
+
const base = path.join(root, 'conformance', kind);
|
|
28
|
+
if (!fs.existsSync(base)) return [];
|
|
29
|
+
return listEyeFiles(base)
|
|
30
|
+
.filter((name) => matchesFilter(kind, name, filter))
|
|
27
31
|
.sort();
|
|
28
32
|
}
|
|
29
33
|
|
|
34
|
+
function matchesFilter(kind, name, filter) {
|
|
35
|
+
if (filter == null) return true;
|
|
36
|
+
const stem = name.slice(0, -4);
|
|
37
|
+
const label = kind === 'errors' ? 'error' : kind === 'warnings' ? 'warning' : kind;
|
|
38
|
+
return name.includes(filter)
|
|
39
|
+
|| stem === filter
|
|
40
|
+
|| stem.includes(filter)
|
|
41
|
+
|| `${kind}/${stem}`.includes(filter)
|
|
42
|
+
|| `${label}/${stem}`.includes(filter);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function listEyeFiles(base, dir = base) {
|
|
46
|
+
const files = [];
|
|
47
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
48
|
+
const full = path.join(dir, entry.name);
|
|
49
|
+
if (entry.isDirectory()) {
|
|
50
|
+
files.push(...listEyeFiles(base, full));
|
|
51
|
+
} else if (entry.isFile() && entry.name.endsWith('.eye')) {
|
|
52
|
+
files.push(path.relative(base, full).split(path.sep).join('/'));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return files;
|
|
56
|
+
}
|
|
57
|
+
|
|
30
58
|
function runCaseFile(reporter, file) {
|
|
31
59
|
const name = file.slice(0, -4);
|
|
32
60
|
reporter.test(name, () => runCase(name, file));
|
|
33
61
|
}
|
|
34
62
|
|
|
63
|
+
function runErrorFile(reporter, file) {
|
|
64
|
+
const name = file.slice(0, -4);
|
|
65
|
+
reporter.test(`error/${name}`, () => runErrorCase(name, file));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function runWarningFile(reporter, file) {
|
|
69
|
+
const name = file.slice(0, -4);
|
|
70
|
+
reporter.test(`warning/${name}`, () => runWarningCase(name, file));
|
|
71
|
+
}
|
|
72
|
+
|
|
35
73
|
function runCase(name, file) {
|
|
36
74
|
const casesDir = path.join(root, 'conformance', 'cases');
|
|
37
75
|
const expectedDir = path.join(root, 'conformance', 'expected');
|
|
@@ -41,13 +79,57 @@ function runCase(name, file) {
|
|
|
41
79
|
const program = Program.parseSources([{ text, filename: file }], { sourceMetadata: false, markRecursive: false });
|
|
42
80
|
const actual = run(program).stdout;
|
|
43
81
|
|
|
82
|
+
compareExpectedFile(expected, actual, name, 'output');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function runErrorCase(name, file) {
|
|
86
|
+
const casesDir = path.join(root, 'conformance', 'errors');
|
|
87
|
+
const expectedDir = path.join(root, 'conformance', 'expected-errors');
|
|
88
|
+
const programFile = path.join(casesDir, file);
|
|
89
|
+
const expected = path.join(expectedDir, `${name}.txt`);
|
|
90
|
+
const text = fs.readFileSync(programFile, 'utf8');
|
|
91
|
+
let actual = null;
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const program = Program.parseSources([{ text, filename: file }], { sourceMetadata: false, markRecursive: false });
|
|
95
|
+
run(program);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
actual = `${error?.message ?? String(error)}\n`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (actual == null) throw new Error(`expected error for ${name}, but program succeeded`);
|
|
101
|
+
compareExpectedFile(expected, actual, name, 'error');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function runWarningCase(name, file) {
|
|
105
|
+
const warningsDir = path.join(root, 'conformance', 'warnings');
|
|
106
|
+
const expectedDir = path.join(root, 'conformance', 'expected-warnings');
|
|
107
|
+
const programFile = path.join(warningsDir, file);
|
|
108
|
+
const expectedStdout = path.join(expectedDir, `${name}.eye`);
|
|
109
|
+
const expectedStderr = path.join(expectedDir, `${name}.txt`);
|
|
110
|
+
const text = fs.readFileSync(programFile, 'utf8');
|
|
111
|
+
const result = spawnSync(process.execPath, [cliBin, '--warnings', '-'], {
|
|
112
|
+
cwd: packageRoot,
|
|
113
|
+
input: text,
|
|
114
|
+
encoding: 'utf8',
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (result.status !== 0) {
|
|
118
|
+
throw new Error(`warning case ${name} exited with ${result.status}\n${result.stderr}`.trimEnd());
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
compareExpectedFile(expectedStdout, result.stdout, name, 'warning stdout');
|
|
122
|
+
compareExpectedFile(expectedStderr, result.stderr, name, 'warning stderr');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function compareExpectedFile(expected, actual, name, kind) {
|
|
44
126
|
if (!fs.existsSync(expected)) {
|
|
45
|
-
throw new Error(`missing expected file: ${path.relative(root, expected)}`);
|
|
127
|
+
throw new Error(`missing expected ${kind} file: ${path.relative(root, expected)}`);
|
|
46
128
|
}
|
|
47
129
|
|
|
48
130
|
const expectedText = fs.readFileSync(expected, 'utf8');
|
|
49
131
|
if (expectedText !== actual) {
|
|
50
|
-
throw new Error(
|
|
132
|
+
throw new Error(`${kind} mismatch for ${name}\n${diffText(expected, actual)}`.trimEnd());
|
|
51
133
|
}
|
|
52
134
|
}
|
|
53
135
|
|
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: () => {
|