eyeprolog 0.5.14 → 0.5.16

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.
Files changed (96) hide show
  1. package/README.md +2 -0
  2. package/conformance-report.md +2 -2
  3. package/examples/output/aliases-and-namespaces.pl +1 -1
  4. package/examples/output/dijkstra-findall-sort.pl +1 -1
  5. package/examples/output/dijkstra.pl +16 -16
  6. package/examples/output/drone-corridor-planner.pl +17 -17
  7. package/examples/output/iso-atomic-conversion.pl +1 -1
  8. package/examples/output/list-collection.pl +2 -2
  9. package/examples/proof/aliases-and-namespaces.pl +5 -5
  10. package/examples/proof/graph-reachability.pl +22 -22
  11. package/examples/proof/list-collection.pl +13 -13
  12. package/examples/proof/term-tools.pl +1 -1
  13. package/index.d.ts +5 -2
  14. package/package.json +1 -1
  15. package/src/cli.js +17 -9
  16. package/src/index.js +15 -5
  17. package/src/iso.js +1 -0
  18. package/src/parser.js +57 -12
  19. package/src/program.js +8 -5
  20. package/src/solver.js +11 -3
  21. package/src/term.js +41 -10
  22. package/src/write.js +6 -0
  23. package/test/conformance/README.md +1 -1
  24. package/test/conformance/cases/arithmetic/082_comparison_semantics.pl +2 -2
  25. package/test/conformance/cases/arithmetic/extra_le_equal_string.pl +1 -1
  26. package/test/conformance/cases/context/extra_context_ignores_string_parts.pl +1 -1
  27. package/test/conformance/cases/iso/double_quoted_lists.pl +27 -0
  28. package/test/conformance/cases/strings/023_quoted_escapes_readback.pl +2 -3
  29. package/test/conformance/cases/terms/003_terms_and_readback.pl +1 -1
  30. package/test/conformance/cases/terms/073_term_introspection_edges.pl +1 -1
  31. package/test/conformance/cases/terms/096_functor_scalar_edges.pl +2 -2
  32. package/test/conformance/cases/terms/extra_functor_string_scalar.pl +1 -1
  33. package/test/conformance/cases/terms/functor_number_and_string.pl +1 -1
  34. package/test/conformance/cases/unification/extra_eq_scalar_string_number.pl +1 -1
  35. package/test/conformance/cases/unification/scalar_cross_type_same_lexical_value.pl +2 -2
  36. package/test/conformance/expected/aggregation/044_findall_user_goal.pl +1 -1
  37. package/test/conformance/expected/arithmetic/082_comparison_semantics.pl +1 -1
  38. package/test/conformance/expected/arithmetic/extra_le_equal_string.pl +1 -1
  39. package/test/conformance/expected/context/extra_context_atom_parts.pl +1 -1
  40. package/test/conformance/expected/context/extra_context_ignores_string_parts.pl +1 -0
  41. package/test/conformance/expected/context/extra_context_list_parts.pl +1 -1
  42. package/test/conformance/expected/iso/atomic_term_processing.pl +1 -1
  43. package/test/conformance/expected/iso/double_quoted_lists.pl +8 -0
  44. package/test/conformance/expected/iso/exceptions_and_flags.pl +1 -1
  45. package/test/conformance/expected/iso/grouped_solutions_and_clauses.pl +1 -1
  46. package/test/conformance/expected/iso/logtalk_atom_chars.pl +1 -1
  47. package/test/conformance/expected/iso/logtalk_number_chars.pl +1 -1
  48. package/test/conformance/expected/iso/logtalk_prolog_flags.pl +1 -1
  49. package/test/conformance/expected/iso/logtalk_univ.pl +1 -1
  50. package/test/conformance/expected/iso/scryer_lexical_terms.pl +1 -1
  51. package/test/conformance/expected/lists/005_list_deconstruction.pl +1 -1
  52. package/test/conformance/expected/lists/031_lists_aggregation_ordering.pl +4 -4
  53. package/test/conformance/expected/lists/035_list_relations.pl +3 -3
  54. package/test/conformance/expected/lists/036_append_splits.pl +3 -3
  55. package/test/conformance/expected/lists/045_sort_deduplicates_atoms.pl +1 -1
  56. package/test/conformance/expected/lists/046_append_bound_prefix_suffix.pl +2 -2
  57. package/test/conformance/expected/lists/048_set_nth0_edges.pl +2 -2
  58. package/test/conformance/expected/lists/049_select_duplicate_occurrences.pl +2 -2
  59. package/test/conformance/expected/lists/067_list_edges.pl +1 -1
  60. package/test/conformance/expected/lists/068_list_generation_order.pl +6 -6
  61. package/test/conformance/expected/lists/084_append_and_select_composition.pl +9 -9
  62. package/test/conformance/expected/lists/085_nth_and_update_edges.pl +1 -1
  63. package/test/conformance/expected/lists/087_sort_reverse_length.pl +2 -2
  64. package/test/conformance/expected/lists/extra_append_all_splits_three.pl +4 -4
  65. package/test/conformance/expected/lists/extra_list_to_set_stable_order.pl +1 -1
  66. package/test/conformance/expected/lists/extra_reverse_nested_terms.pl +1 -1
  67. package/test/conformance/expected/lists/extra_slice_end.pl +1 -1
  68. package/test/conformance/expected/lists/extra_take_all.pl +1 -1
  69. package/test/conformance/expected/lists/list_pattern_last.pl +1 -1
  70. package/test/conformance/expected/lists/select_all_occurrences.pl +3 -3
  71. package/test/conformance/expected/lists/set_nth0_middle.pl +1 -1
  72. package/test/conformance/expected/rules/077_recursive_path_with_lists.pl +3 -3
  73. package/test/conformance/expected/strings/023_quoted_escapes_readback.pl +1 -1
  74. package/test/conformance/expected/strings/071_string_edges.pl +1 -1
  75. package/test/conformance/expected/strings/extra_split_empty_separator.pl +1 -1
  76. package/test/conformance/expected/terms/003_terms_and_readback.pl +3 -3
  77. package/test/conformance/expected/terms/065_reusable_term_control_builtins.pl +1 -1
  78. package/test/conformance/expected/terms/073_term_introspection_edges.pl +2 -2
  79. package/test/conformance/expected/terms/095_term_introspection_roundtrip.pl +1 -1
  80. package/test/conformance/expected/terms/096_functor_scalar_edges.pl +1 -1
  81. package/test/conformance/expected/terms/extra_compound_name_arguments_decompose_list.pl +1 -1
  82. package/test/conformance/expected/terms/extra_functor_string_scalar.pl +1 -1
  83. package/test/conformance/expected/terms/functor_number_and_string.pl +1 -1
  84. package/test/conformance/expected/unification/012_nested_compound_unification.pl +1 -1
  85. package/test/conformance/expected/unification/034_equality_and_inequality.pl +1 -1
  86. package/test/conformance/expected/unification/extra_eq_list_tail_binding.pl +1 -1
  87. package/test/conformance/expected/unification/list_tail_binding.pl +1 -1
  88. package/test/conformance/expected-proofs/proofs/bindings_nested_term.pl +5 -5
  89. package/test/conformance/expected-proofs/proofs/extra_compound_name_arguments.pl +4 -4
  90. package/test/conformance/expected-proofs/proofs/extra_findall_builtin.pl +5 -5
  91. package/test/conformance/expected-proofs/proofs/extra_nested_list_binding.pl +5 -5
  92. package/test/conformance/expected-proofs/proofs/extra_once_member.pl +2 -2
  93. package/test/conformance/expected-proofs/proofs/list_builtin.pl +1 -1
  94. package/test/run-playground.mjs +2 -2
  95. package/test/run-regression.mjs +70 -12
  96. package/the-art-of-eyeprolog.md +305 -61
package/README.md CHANGED
@@ -28,6 +28,8 @@ eyeprolog --goal 'type(socrates, mortal)' examples/socrates.pl
28
28
  ```
29
29
 
30
30
  Programs may declare their default queries with `%% goal:` comments.
31
+ Double-quoted text follows the ISO `double_quotes` flag and defaults to a
32
+ proper list of one-character atoms (`chars`), matching Trealla and Scryer.
31
33
 
32
34
  ## Portable library
33
35
 
@@ -11,7 +11,7 @@ This report summarizes the file-based conformance corpus under `test/conformance
11
11
  | builtins | 11 | 0 | 0 | 0 | 11 |
12
12
  | context | 11 | 0 | 0 | 0 | 11 |
13
13
  | control | 15 | 0 | 0 | 0 | 15 |
14
- | iso | 120 | 159 | 0 | 0 | 279 |
14
+ | iso | 121 | 159 | 0 | 0 | 280 |
15
15
  | lists | 52 | 3 | 0 | 0 | 55 |
16
16
  | negation | 8 | 0 | 19 | 0 | 27 |
17
17
  | proofs | 0 | 0 | 0 | 21 | 21 |
@@ -22,4 +22,4 @@ This report summarizes the file-based conformance corpus under `test/conformance
22
22
  | terms | 26 | 3 | 0 | 0 | 29 |
23
23
  | unification | 18 | 0 | 0 | 0 | 18 |
24
24
  | variables | 16 | 9 | 0 | 0 | 25 |
25
- | **Total** | **433** | **212** | **19** | **21** | **685** |
25
+ | **Total** | **434** | **212** | **19** | **21** | **686** |
@@ -1,5 +1,5 @@
1
1
  value(nativeMath, 1.0).
2
2
  ok(nativeCompare, true).
3
3
  ok(nativeString, true).
4
- tail(nativeList, [b, c]).
4
+ tail(nativeList, "bc").
5
5
  label(vocabularyExample, "vocabulary names are ordinary predicate names").
@@ -1,2 +1,2 @@
1
- shortestPath(dijkstra_findall_sort, [a, c, b, d, e, f]).
1
+ shortestPath(dijkstra_findall_sort, "acbdef").
2
2
  cost(dijkstra_findall_sort, 13).
@@ -1,16 +1,16 @@
1
- edge([b, a], 4).
2
- edge([c, a], 2).
3
- edge([c, b], 1).
4
- edge([d, b], 5).
5
- edge([d, c], 8).
6
- edge([e, c], 10).
7
- edge([e, d], 2).
8
- edge([f, d], 6).
9
- edge([f, e], 3).
10
- path([a, f], [[a, b, d, e, f], 14]).
11
- path([a, f], [[a, b, d, f], 15]).
12
- path([a, f], [[a, c, d, e, f], 15]).
13
- path([a, f], [[a, c, d, f], 16]).
14
- path([a, f], [[a, c, e, f], 15]).
15
- path([a, f], [[a, c, b, d, e, f], 13]).
16
- path([a, f], [[a, c, b, d, f], 14]).
1
+ edge("ba", 4).
2
+ edge("ca", 2).
3
+ edge("cb", 1).
4
+ edge("db", 5).
5
+ edge("dc", 8).
6
+ edge("ec", 10).
7
+ edge("ed", 2).
8
+ edge("fd", 6).
9
+ edge("fe", 3).
10
+ path("af", ["abdef", 14]).
11
+ path("af", ["abdf", 15]).
12
+ path("af", ["acdef", 15]).
13
+ path("af", ["acdf", 16]).
14
+ path("af", ["acef", 15]).
15
+ path("af", ["acbdef", 13]).
16
+ path("af", ["acbdf", 14]).
@@ -1,17 +1,17 @@
1
- gps_plan(d1, plan([fly_gent_brugge, public_coastline_brugge_oostende], 2800.0, 0.012, 0.96029999999999993, 0.95039999999999991, low, none, [t, t, t, t, t])).
2
- gps_plan(d1, plan([fly_gent_brugge, buy_permit_brugge, public_coastline_brugge_oostende], 3250.0, 0.014, 0.94109399999999999, 0.95039999999999991, low, yes, [t, t, t, t])).
3
- gps_plan(d1, plan([fly_gent_brugge, buy_permit_brugge, topup_brugge, cross_corridor_brugge_oostende], 3250.0, 0.015000000000000001, 0.94984520399999994, 0.97019999999999995, mid, yes, [t, t, t])).
4
- gps_plan(d1, plan([fly_gent_brugge, buy_permit_brugge, topup_brugge, public_coastline_brugge_oostende], 3550.0, 0.017000000000000001, 0.94499905500000003, 0.931392, mid, yes, [t, t, t])).
5
- gps_plan(d1, plan([fly_gent_brugge, topup_brugge, public_coastline_brugge_oostende], 3100.0, 0.015000000000000001, 0.96428475000000002, 0.931392, mid, none, [t, t, t, t])).
6
- gps_plan(d1, plan([fly_gent_brugge, topup_brugge, buy_permit_brugge, cross_corridor_brugge_oostende], 3250.0, 0.015000000000000001, 0.94984520399999994, 0.97019999999999995, mid, yes, [t, t, t])).
7
- gps_plan(d1, plan([fly_gent_brugge, topup_brugge, buy_permit_brugge, public_coastline_brugge_oostende], 3550.0, 0.017000000000000001, 0.94499905500000003, 0.931392, mid, yes, [t, t, t])).
8
- gps_plan(d1, plan([train_gent_brugge, public_coastline_brugge_oostende], 2900.0, 0.018000000000000002, 0.97402500000000003, 0.95519999999999994, mid, none, [t, t, t, t, t])).
9
- gps_plan(d1, plan([train_gent_brugge, buy_permit_brugge, cross_corridor_brugge_oostende], 3050.0, 0.018000000000000002, 0.95943959999999995, 0.995, mid, yes, [t, t, t, t])).
10
- gps_plan(d1, plan([train_gent_brugge, buy_permit_brugge, public_coastline_brugge_oostende], 3350.0, 0.02, 0.95454450000000002, 0.95519999999999994, mid, yes, [t, t, t, t])).
11
- gps_plan(d1, plan([fly_gent_kortrijk, fly_kortrijk_brugge, buy_permit_brugge, quick_charge_brugge, cross_corridor_brugge_oostende], 5150.0, 0.024, 0.94034675195999995, 0.9506969999999999, mid, yes, [t, t])).
12
- gps_plan(d1, plan([fly_gent_kortrijk, fly_kortrijk_brugge, quick_charge_brugge, public_coastline_brugge_oostende], 5000.0, 0.024, 0.95464190250000003, 0.91266911999999989, mid, none, [t, t, t])).
13
- gps_plan(d1, plan([fly_gent_kortrijk, fly_kortrijk_brugge, quick_charge_brugge, buy_permit_brugge, cross_corridor_brugge_oostende], 5150.0, 0.024, 0.94034675195999995, 0.9506969999999999, mid, yes, [t, t])).
14
- gps_plan(d1, plan([fly_gent_kortrijk, get_zone_permit_kortrijk, fly_kortrijk_brugge, quick_charge_brugge, cross_corridor_brugge_oostende], 5000.0, 0.023, 0.95857796449800003, 0.9506969999999999, mid, yes, [t, t])).
15
- gps_plan(d1, plan([fly_gent_kortrijk, get_zone_permit_kortrijk, fly_kortrijk_brugge, quick_charge_brugge, public_coastline_brugge_oostende], 5300.0, 0.025000000000000001, 0.95368726059749998, 0.91266911999999989, mid, yes, [t, t])).
16
- gps_plan(d1, plan([fly_gent_kortrijk, get_zone_permit_kortrijk, emergency_charge_kortrijk, direct_corridor_kortrijk_oostende], 3500.0, 0.02, 0.94356004544999994, 0.86526000000000003, mid, yes, [t, t, t])).
17
- gps_plan(d1, plan([fly_gent_kortrijk, emergency_charge_kortrijk, get_zone_permit_kortrijk, direct_corridor_kortrijk_oostende], 3500.0, 0.019999999999999997, 0.94356004544999994, 0.86526000000000003, mid, yes, [t, t, t])).
1
+ gps_plan(d1, plan([fly_gent_brugge, public_coastline_brugge_oostende], 2800.0, 0.012, 0.96029999999999993, 0.95039999999999991, low, none, "ttttt")).
2
+ gps_plan(d1, plan([fly_gent_brugge, buy_permit_brugge, public_coastline_brugge_oostende], 3250.0, 0.014, 0.94109399999999999, 0.95039999999999991, low, yes, "tttt")).
3
+ gps_plan(d1, plan([fly_gent_brugge, buy_permit_brugge, topup_brugge, cross_corridor_brugge_oostende], 3250.0, 0.015000000000000001, 0.94984520399999994, 0.97019999999999995, mid, yes, "ttt")).
4
+ gps_plan(d1, plan([fly_gent_brugge, buy_permit_brugge, topup_brugge, public_coastline_brugge_oostende], 3550.0, 0.017000000000000001, 0.94499905500000003, 0.931392, mid, yes, "ttt")).
5
+ gps_plan(d1, plan([fly_gent_brugge, topup_brugge, public_coastline_brugge_oostende], 3100.0, 0.015000000000000001, 0.96428475000000002, 0.931392, mid, none, "tttt")).
6
+ gps_plan(d1, plan([fly_gent_brugge, topup_brugge, buy_permit_brugge, cross_corridor_brugge_oostende], 3250.0, 0.015000000000000001, 0.94984520399999994, 0.97019999999999995, mid, yes, "ttt")).
7
+ gps_plan(d1, plan([fly_gent_brugge, topup_brugge, buy_permit_brugge, public_coastline_brugge_oostende], 3550.0, 0.017000000000000001, 0.94499905500000003, 0.931392, mid, yes, "ttt")).
8
+ gps_plan(d1, plan([train_gent_brugge, public_coastline_brugge_oostende], 2900.0, 0.018000000000000002, 0.97402500000000003, 0.95519999999999994, mid, none, "ttttt")).
9
+ gps_plan(d1, plan([train_gent_brugge, buy_permit_brugge, cross_corridor_brugge_oostende], 3050.0, 0.018000000000000002, 0.95943959999999995, 0.995, mid, yes, "tttt")).
10
+ gps_plan(d1, plan([train_gent_brugge, buy_permit_brugge, public_coastline_brugge_oostende], 3350.0, 0.02, 0.95454450000000002, 0.95519999999999994, mid, yes, "tttt")).
11
+ gps_plan(d1, plan([fly_gent_kortrijk, fly_kortrijk_brugge, buy_permit_brugge, quick_charge_brugge, cross_corridor_brugge_oostende], 5150.0, 0.024, 0.94034675195999995, 0.9506969999999999, mid, yes, "tt")).
12
+ gps_plan(d1, plan([fly_gent_kortrijk, fly_kortrijk_brugge, quick_charge_brugge, public_coastline_brugge_oostende], 5000.0, 0.024, 0.95464190250000003, 0.91266911999999989, mid, none, "ttt")).
13
+ gps_plan(d1, plan([fly_gent_kortrijk, fly_kortrijk_brugge, quick_charge_brugge, buy_permit_brugge, cross_corridor_brugge_oostende], 5150.0, 0.024, 0.94034675195999995, 0.9506969999999999, mid, yes, "tt")).
14
+ gps_plan(d1, plan([fly_gent_kortrijk, get_zone_permit_kortrijk, fly_kortrijk_brugge, quick_charge_brugge, cross_corridor_brugge_oostende], 5000.0, 0.023, 0.95857796449800003, 0.9506969999999999, mid, yes, "tt")).
15
+ gps_plan(d1, plan([fly_gent_kortrijk, get_zone_permit_kortrijk, fly_kortrijk_brugge, quick_charge_brugge, public_coastline_brugge_oostende], 5300.0, 0.025000000000000001, 0.95368726059749998, 0.91266911999999989, mid, yes, "tt")).
16
+ gps_plan(d1, plan([fly_gent_kortrijk, get_zone_permit_kortrijk, emergency_charge_kortrijk, direct_corridor_kortrijk_oostende], 3500.0, 0.02, 0.94356004544999994, 0.86526000000000003, mid, yes, "ttt")).
17
+ gps_plan(d1, plan([fly_gent_kortrijk, emergency_charge_kortrijk, get_zone_permit_kortrijk, direct_corridor_kortrijk_oostende], 3500.0, 0.019999999999999997, 0.94356004544999994, 0.86526000000000003, mid, yes, "ttt")).
@@ -1,6 +1,6 @@
1
1
  report(joined, eyeprolog).
2
2
  report(prefix, eye).
3
- report(characters, [e, y, e]).
3
+ report(characters, "eye").
4
4
  report(codes, [65, 90]).
5
5
  report(decoded_character, 'λ').
6
6
  report(number, 42).
@@ -2,6 +2,6 @@ collectionLength(numbers, 3).
2
2
  collectionMember(numbers, 1).
3
3
  collectionMember(numbers, 2).
4
4
  collectionMember(numbers, 3).
5
- collectionAppend(letters, [a, b, c]).
5
+ collectionAppend(letters, "abc").
6
6
  head(letters, a).
7
- tail(letters, [b]).
7
+ tail(letters, "b").
@@ -44,16 +44,16 @@ why(
44
44
  )
45
45
  ).
46
46
 
47
- tail(nativeList, [b, c]).
47
+ tail(nativeList, "bc").
48
48
  why(
49
- tail(nativeList, [b, c]),
49
+ tail(nativeList, "bc"),
50
50
  proof(
51
- goal(tail(nativeList, [b, c])),
51
+ goal(tail(nativeList, "bc")),
52
52
  by(rule("aliases-and-namespaces.pl", clause(4))),
53
- bindings([binding("Tail", [b, c]), binding("_head", a)]),
53
+ bindings([binding("Tail", "bc"), binding("_head", a)]),
54
54
  uses([
55
55
  proof(
56
- goal(=([a, b, c], [a, b, c])),
56
+ goal(=("abc", "abc")),
57
57
  by(builtin(=, 2))
58
58
  )
59
59
  ])
@@ -11,48 +11,48 @@ why(
11
11
  bindings([binding("Start", a), binding("Goal", f)]),
12
12
  uses([
13
13
  proof(
14
- goal(reachable(a, f, [a])),
14
+ goal(reachable(a, f, "a")),
15
15
  by(rule("graph-reachability.pl", clause(9))),
16
- bindings([binding("Start", a), binding("Goal", f), binding("Visited", [a]), binding("Next", b)]),
16
+ bindings([binding("Start", a), binding("Goal", f), binding("Visited", "a"), binding("Next", b)]),
17
17
  uses([
18
18
  proof(
19
19
  goal(edge(a, b)),
20
20
  by(fact("graph-reachability.pl", clause(1)))
21
21
  ),
22
22
  proof(
23
- goal('\\+'(member(b, [a]))),
23
+ goal('\\+'(member(b, "a"))),
24
24
  by(builtin('\\+', 1))
25
25
  ),
26
26
  proof(
27
- goal(reachable(b, f, [b, a])),
27
+ goal(reachable(b, f, "ba")),
28
28
  by(rule("graph-reachability.pl", clause(9))),
29
- bindings([binding("Start", b), binding("Goal", f), binding("Visited", [b, a]), binding("Next", d)]),
29
+ bindings([binding("Start", b), binding("Goal", f), binding("Visited", "ba"), binding("Next", d)]),
30
30
  uses([
31
31
  proof(
32
32
  goal(edge(b, d)),
33
33
  by(fact("graph-reachability.pl", clause(3)))
34
34
  ),
35
35
  proof(
36
- goal('\\+'(member(d, [b, a]))),
36
+ goal('\\+'(member(d, "ba"))),
37
37
  by(builtin('\\+', 1))
38
38
  ),
39
39
  proof(
40
- goal(reachable(d, f, [d, b, a])),
40
+ goal(reachable(d, f, "dba")),
41
41
  by(rule("graph-reachability.pl", clause(9))),
42
- bindings([binding("Start", d), binding("Goal", f), binding("Visited", [d, b, a]), binding("Next", f)]),
42
+ bindings([binding("Start", d), binding("Goal", f), binding("Visited", "dba"), binding("Next", f)]),
43
43
  uses([
44
44
  proof(
45
45
  goal(edge(d, f)),
46
46
  by(fact("graph-reachability.pl", clause(5)))
47
47
  ),
48
48
  proof(
49
- goal('\\+'(member(f, [d, b, a]))),
49
+ goal('\\+'(member(f, "dba"))),
50
50
  by(builtin('\\+', 1))
51
51
  ),
52
52
  proof(
53
- goal(reachable(f, f, [f, d, b, a])),
53
+ goal(reachable(f, f, "fdba")),
54
54
  by(fact("graph-reachability.pl", clause(8))),
55
- bindings([binding("Node", f), binding("_visited", [f, d, b, a])])
55
+ bindings([binding("Node", f), binding("_visited", "fdba")])
56
56
  )
57
57
  ])
58
58
  )
@@ -79,48 +79,48 @@ why(
79
79
  bindings([binding("Start", c), binding("Goal", g)]),
80
80
  uses([
81
81
  proof(
82
- goal(reachable(c, g, [c])),
82
+ goal(reachable(c, g, "c")),
83
83
  by(rule("graph-reachability.pl", clause(9))),
84
- bindings([binding("Start", c), binding("Goal", g), binding("Visited", [c]), binding("Next", e)]),
84
+ bindings([binding("Start", c), binding("Goal", g), binding("Visited", "c"), binding("Next", e)]),
85
85
  uses([
86
86
  proof(
87
87
  goal(edge(c, e)),
88
88
  by(fact("graph-reachability.pl", clause(4)))
89
89
  ),
90
90
  proof(
91
- goal('\\+'(member(e, [c]))),
91
+ goal('\\+'(member(e, "c"))),
92
92
  by(builtin('\\+', 1))
93
93
  ),
94
94
  proof(
95
- goal(reachable(e, g, [e, c])),
95
+ goal(reachable(e, g, "ec")),
96
96
  by(rule("graph-reachability.pl", clause(9))),
97
- bindings([binding("Start", e), binding("Goal", g), binding("Visited", [e, c]), binding("Next", f)]),
97
+ bindings([binding("Start", e), binding("Goal", g), binding("Visited", "ec"), binding("Next", f)]),
98
98
  uses([
99
99
  proof(
100
100
  goal(edge(e, f)),
101
101
  by(fact("graph-reachability.pl", clause(6)))
102
102
  ),
103
103
  proof(
104
- goal('\\+'(member(f, [e, c]))),
104
+ goal('\\+'(member(f, "ec"))),
105
105
  by(builtin('\\+', 1))
106
106
  ),
107
107
  proof(
108
- goal(reachable(f, g, [f, e, c])),
108
+ goal(reachable(f, g, "fec")),
109
109
  by(rule("graph-reachability.pl", clause(9))),
110
- bindings([binding("Start", f), binding("Goal", g), binding("Visited", [f, e, c]), binding("Next", g)]),
110
+ bindings([binding("Start", f), binding("Goal", g), binding("Visited", "fec"), binding("Next", g)]),
111
111
  uses([
112
112
  proof(
113
113
  goal(edge(f, g)),
114
114
  by(fact("graph-reachability.pl", clause(7)))
115
115
  ),
116
116
  proof(
117
- goal('\\+'(member(g, [f, e, c]))),
117
+ goal('\\+'(member(g, "fec"))),
118
118
  by(builtin('\\+', 1))
119
119
  ),
120
120
  proof(
121
- goal(reachable(g, g, [g, f, e, c])),
121
+ goal(reachable(g, g, "gfec")),
122
122
  by(fact("graph-reachability.pl", clause(8))),
123
- bindings([binding("Node", g), binding("_visited", [g, f, e, c])])
123
+ bindings([binding("Node", g), binding("_visited", "gfec")])
124
124
  )
125
125
  ])
126
126
  )
@@ -78,20 +78,20 @@ why(
78
78
  )
79
79
  ).
80
80
 
81
- collectionAppend(letters, [a, b, c]).
81
+ collectionAppend(letters, "abc").
82
82
  why(
83
- collectionAppend(letters, [a, b, c]),
83
+ collectionAppend(letters, "abc"),
84
84
  proof(
85
- goal(collectionAppend(letters, [a, b, c])),
85
+ goal(collectionAppend(letters, "abc")),
86
86
  by(rule("list-collection.pl", clause(5))),
87
- bindings([binding("Extended", [a, b, c]), binding("List", [a, b])]),
87
+ bindings([binding("Extended", "abc"), binding("List", "ab")]),
88
88
  uses([
89
89
  proof(
90
- goal(collection(letters, [a, b])),
90
+ goal(collection(letters, "ab")),
91
91
  by(fact("list-collection.pl", clause(2)))
92
92
  ),
93
93
  proof(
94
- goal(append([a, b], [c], [a, b, c])),
94
+ goal(append("ab", "c", "abc")),
95
95
  by(library(append, 3))
96
96
  )
97
97
  ])
@@ -104,26 +104,26 @@ why(
104
104
  proof(
105
105
  goal(head(letters, a)),
106
106
  by(rule("list-collection.pl", clause(6))),
107
- bindings([binding("Head", a), binding("_tail", [b])]),
107
+ bindings([binding("Head", a), binding("_tail", "b")]),
108
108
  uses([
109
109
  proof(
110
- goal(collection(letters, [a, b])),
110
+ goal(collection(letters, "ab")),
111
111
  by(fact("list-collection.pl", clause(2)))
112
112
  )
113
113
  ])
114
114
  )
115
115
  ).
116
116
 
117
- tail(letters, [b]).
117
+ tail(letters, "b").
118
118
  why(
119
- tail(letters, [b]),
119
+ tail(letters, "b"),
120
120
  proof(
121
- goal(tail(letters, [b])),
121
+ goal(tail(letters, "b")),
122
122
  by(rule("list-collection.pl", clause(7))),
123
- bindings([binding("Tail", [b]), binding("_head", a)]),
123
+ bindings([binding("Tail", "b"), binding("_head", a)]),
124
124
  uses([
125
125
  proof(
126
- goal(collection(letters, [a, b])),
126
+ goal(collection(letters, "ab")),
127
127
  by(fact("list-collection.pl", clause(2)))
128
128
  )
129
129
  ])
@@ -71,7 +71,7 @@ why(
71
71
  bindings([binding("Text", 'edge(a, [b, c])')]),
72
72
  uses([
73
73
  proof(
74
- goal(term_string(edge(a, [b, c]), 'edge(a, [b, c])')),
74
+ goal(term_string(edge(a, "bc"), 'edge(a, [b, c])')),
75
75
  by(library(term_string, 2))
76
76
  )
77
77
  ])
package/index.d.ts CHANGED
@@ -16,6 +16,8 @@ export interface EyePrologRunOptions {
16
16
  sourceMetadata?: boolean;
17
17
  strictNegation?: boolean;
18
18
  analyzeNegation?: boolean;
19
+ /** Initial ISO interpretation of double-quoted list notation. Defaults to chars. */
20
+ doubleQuotes?: 'chars' | 'codes' | 'atom';
19
21
  ioOptions?: {
20
22
  input?: string;
21
23
  write?: (text: string) => void;
@@ -88,6 +90,7 @@ export class Program {
88
90
  constructor(clauses?: EyePrologClause[], options?: EyePrologRunOptions);
89
91
  clauses: EyePrologClause[];
90
92
  groups: Map<string, EyePrologPredicateGroup>;
93
+ doubleQuotes: 'chars' | 'codes' | 'atom';
91
94
  negationDependencies: Array<{ from: string; to: string; negative: boolean }>;
92
95
  negationStratificationErrors: Array<{ from: string; to: string }>;
93
96
  stratifiedNegation: boolean;
@@ -101,7 +104,7 @@ export class Program {
101
104
  assertStratifiedNegation(): true;
102
105
  isStratifiedNegation(): boolean;
103
106
  groupHasRule(group: EyePrologPredicateGroup): boolean;
104
- sourceFactLines(predicateKeys?: Set<string> | null): Set<string>;
107
+ sourceFactLines(predicateKeys?: Set<string> | null, options?: { doubleQuotes?: 'chars' | 'codes' | 'atom' }): Set<string>;
105
108
  }
106
109
 
107
110
  export interface BuiltinDefinition {
@@ -181,7 +184,7 @@ export function compareNumberText(left: string, right: string): number;
181
184
  export function makeProgram(source: string, options?: EyePrologRunOptions): Program;
182
185
  export function parseClauses(source: string, options?: EyePrologRunOptions): EyePrologClause[];
183
186
  export function parseProgramText(source: string, options?: EyePrologRunOptions): EyePrologClause[];
184
- export function parseGoalText(source: string): EyePrologTerm;
187
+ export function parseGoalText(source: string, options?: EyePrologRunOptions): EyePrologTerm;
185
188
  export function createDefaultRegistry(): BuiltinRegistry;
186
189
  export function createEyePrologRegistry(): BuiltinRegistry;
187
190
  export function getDefaultRegistry(): BuiltinRegistry;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.5.14",
6
+ "version": "0.5.16",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/src/cli.js CHANGED
@@ -128,22 +128,27 @@ async function loadExplanation() {
128
128
  }
129
129
 
130
130
  async function runDefault(engine, program, options) {
131
+ const registry = engine.getEyePrologRegistry();
132
+ const solver = new engine.Solver(program, {
133
+ registry,
134
+ ioOptions: { write: (text) => process.stdout.write(String(text)) },
135
+ });
136
+ program = solver.program;
131
137
  const goals = options.goals.map((text) => {
132
- const goal = engine.parseGoalText(text);
138
+ const goal = engine.parseGoalText(text, {
139
+ doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
140
+ });
133
141
  if (goal.type === 'var') throw new engine.PrologError('instantiation_error');
134
142
  if (goal.type !== 'atom' && goal.type !== 'compound') throw new engine.PrologError('type_error(callable)', goal);
135
143
  return goal;
136
144
  });
137
145
  const queriedKeys = new Set(goals.map((goal) => `${goal.name}/${goal.arity}`));
138
- const facts = program.sourceFactLines(queriedKeys);
146
+ const writeOptions = {
147
+ doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
148
+ };
149
+ const facts = program.sourceFactLines(queriedKeys, writeOptions);
139
150
  const lines = new Set();
140
- const registry = engine.getEyePrologRegistry();
141
151
  const explanation = options.proof ? await loadExplanation() : null;
142
- const solver = new engine.Solver(program, {
143
- registry,
144
- ioOptions: { write: (text) => process.stdout.write(String(text)) },
145
- });
146
- program = solver.program;
147
152
  try {
148
153
  solver.runInitializations();
149
154
  for (const goal of goals) {
@@ -151,7 +156,10 @@ async function runDefault(engine, program, options) {
151
156
  for (const env of solver.solve([goal], new engine.Env(), 0)) {
152
157
  if (!engine.termIsGround(goal, env)) continue;
153
158
 
154
- const line = `${engine.termToString(goal, env, true)}.\n`;
159
+ const currentWriteOptions = {
160
+ doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
161
+ };
162
+ const line = `${engine.termToString(goal, env, true, currentWriteOptions)}.\n`;
155
163
  if (facts.has(line) || lines.has(line)) continue;
156
164
 
157
165
  lines.add(line);
package/src/index.js CHANGED
@@ -47,9 +47,12 @@ export function run(source, options = {}) {
47
47
  },
48
48
  });
49
49
  program = solver.program;
50
- const goals = normalizeGoals(options);
50
+ const goals = normalizeGoals(options, solver);
51
+ const writeOptions = {
52
+ doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
53
+ };
51
54
  const queriedKeys = new Set(goals.map((goal) => `${goal.name}/${goal.arity}`));
52
- const facts = program.sourceFactLines(queriedKeys);
55
+ const facts = program.sourceFactLines(queriedKeys, writeOptions);
53
56
  const seen = new Set();
54
57
  let haltCode = null;
55
58
  try {
@@ -59,7 +62,10 @@ export function run(source, options = {}) {
59
62
  for (const env of solver.solve([goal], new Env(), 0)) {
60
63
  const resolved = copyResolved(goal, env);
61
64
  if (!termIsGround(resolved)) continue;
62
- const line = `${termToString(resolved, new Env(), true)}.\n`;
65
+ const currentWriteOptions = {
66
+ doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
67
+ };
68
+ const line = `${termToString(resolved, new Env(), true, currentWriteOptions)}.\n`;
63
69
  if (facts.has(line) || seen.has(line)) continue;
64
70
  seen.add(line);
65
71
  output.push(line);
@@ -73,10 +79,14 @@ export function run(source, options = {}) {
73
79
  return { stdout: output.join(''), stats: solver.stats, haltCode };
74
80
  }
75
81
 
76
- function normalizeGoals(options) {
82
+ function normalizeGoals(options, solver) {
77
83
  const requested = options.goals ?? (options.goal == null ? [] : [options.goal]);
78
84
  return requested.map((requestedGoal) => {
79
- const goal = typeof requestedGoal === 'string' ? parseGoalText(requestedGoal) : requestedGoal;
85
+ const goal = typeof requestedGoal === 'string'
86
+ ? parseGoalText(requestedGoal, {
87
+ doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
88
+ })
89
+ : requestedGoal;
80
90
  if (goal.type === VAR) throw new PrologError('instantiation_error');
81
91
  if (goal.type !== ATOM && goal.type !== COMPOUND) throw new PrologError('type_error(callable)', goal);
82
92
  return goal;
package/src/iso.js CHANGED
@@ -920,6 +920,7 @@ function readTermFromStream(stream, solver) {
920
920
  const clauses = parseClauses(convertedTermText(candidate.text, solver), {
921
921
  sourceMetadata: false,
922
922
  operatorState,
923
+ doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
923
924
  });
924
925
  if (clauses.length !== 1 || clauses[0].body.length) throw new Error('bad term');
925
926
  stream.position = candidate.end;
package/src/parser.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // Tokenizer and recursive-descent parser for the EyeProlog source language.
2
2
  // It preserves the compact Prolog-like syntax while producing Term objects for the solver.
3
- import { atom, compound, cons, emptyList, numberTerm, stringTerm, variable } from './term.js';
3
+ import { atom, compound, cons, emptyList, numberTerm, variable } from './term.js';
4
4
 
5
5
  const TOK = {
6
6
  EOF: 'eof', ATOM: 'atom', VAR: 'var', STRING: 'string', NUMBER: 'number',
@@ -143,6 +143,12 @@ class Parser {
143
143
  this.line = 1;
144
144
  this.anonymous = 0;
145
145
  this.sourceMetadata = options.sourceMetadata !== false;
146
+ this.parserFlagState = options.parserFlagState ?? {
147
+ doubleQuotes: options.doubleQuotes ?? 'chars',
148
+ };
149
+ if (!['chars', 'codes', 'atom'].includes(this.parserFlagState.doubleQuotes)) {
150
+ throw new Error(`invalid double_quotes parser flag: ${this.parserFlagState.doubleQuotes}`);
151
+ }
146
152
  const operatorState = options.operatorState ?? createParserOperatorState(
147
153
  options.operatorDefinitions ?? [],
148
154
  options.includeDefaultOperators !== false,
@@ -174,6 +180,14 @@ class Parser {
174
180
  for (const name of names) this.defineOperator(priority, specifierTerm.name, name);
175
181
  return true;
176
182
  }
183
+ applyParserFlagDirective(directive) {
184
+ if (directive.type !== 'compound' || directive.name !== 'set_prolog_flag' || directive.arity !== 2) return;
185
+ const [flag, value] = directive.args;
186
+ if (flag.type === 'atom' && flag.name === 'double_quotes' &&
187
+ value.type === 'atom' && ['chars', 'codes', 'atom'].includes(value.name)) {
188
+ this.parserFlagState.doubleQuotes = value.name;
189
+ }
190
+ }
177
191
  peek(offset = 0) {
178
192
  return this.source[this.pos + offset] ?? '';
179
193
  }
@@ -494,7 +508,31 @@ class Parser {
494
508
  if (this.token.type === TOK.STRING) {
495
509
  const value = this.token.text;
496
510
  this.advance();
497
- return stringTerm(value);
511
+ if (this.parserFlagState.doubleQuotes === 'atom') {
512
+ if (this.token.type === TOK.LPAREN) {
513
+ this.advance();
514
+ const args = [];
515
+ if (this.token.type === TOK.RPAREN) {
516
+ throw new Error(`parse line ${this.token.line}: zero-arity compound syntax is not supported; use atom ${JSON.stringify(value)} for arity zero data`);
517
+ }
518
+ while (true) {
519
+ args.push(this.parseTerm(0, false));
520
+ if (this.token.type !== TOK.COMMA) break;
521
+ this.advance();
522
+ }
523
+ this.expect(TOK.RPAREN, ')');
524
+ this.advance();
525
+ return compound(value, args);
526
+ }
527
+ return atom(value);
528
+ }
529
+ const items = Array.from(value, (character) =>
530
+ this.parserFlagState.doubleQuotes === 'chars'
531
+ ? atom(character)
532
+ : numberTerm(character.codePointAt(0)));
533
+ let list = emptyList();
534
+ for (let i = items.length - 1; i >= 0; i--) list = cons(items[i], list);
535
+ return list;
498
536
  }
499
537
  if (this.token.type === TOK.NUMBER) {
500
538
  const value = this.token.text;
@@ -544,6 +582,7 @@ class Parser {
544
582
  throw new Error(`parse line ${line}: bad term`);
545
583
  }
546
584
  this.expect(TOK.DOT, '.');
585
+ this.applyParserFlagDirective(directive);
547
586
  this.advance();
548
587
  const clause = { head: compound(':-', [directive]), body: [] };
549
588
  clauseNumber++;
@@ -588,11 +627,17 @@ function listAtomNames(term) {
588
627
 
589
628
 
590
629
  export function parseClauses(source, options = {}) {
630
+ const ownsParserFlagState = options.parserFlagState == null;
631
+ const initialDoubleQuotes = options.doubleQuotes ?? 'chars';
632
+ const parserOptions = ownsParserFlagState
633
+ ? { ...options, parserFlagState: { doubleQuotes: initialDoubleQuotes } }
634
+ : options;
591
635
  if (options.sourceMetadata === false) {
592
- const clauses = parseClausesFastNoSource(source);
636
+ const clauses = parseClausesFastNoSource(source, null, null, parserOptions);
593
637
  if (clauses) return clauses;
638
+ if (ownsParserFlagState) parserOptions.parserFlagState.doubleQuotes = initialDoubleQuotes;
594
639
  }
595
- return new Parser(source, options).parseProgram();
640
+ return new Parser(source, parserOptions).parseProgram();
596
641
  }
597
642
 
598
643
  // Streaming parser entry points used by ProgramBuilder. The ordinary public
@@ -602,8 +647,8 @@ export function parseClausesInto(source, options = {}, emit) {
602
647
  new Parser(source, options).parseProgram(emit);
603
648
  }
604
649
 
605
- export function tryParseClausesFastInto(source, emit, emitBinary = null) {
606
- return parseClausesFastNoSource(source, emit, emitBinary) !== null;
650
+ export function tryParseClausesFastInto(source, emit, emitBinary = null, options = {}) {
651
+ return parseClausesFastNoSource(source, emit, emitBinary, options) !== null;
607
652
  }
608
653
 
609
654
  function isSimpleName(text) {
@@ -624,7 +669,7 @@ const SIMPLE_VARIABLE = /^(?:_|[A-Z_][A-Za-z0-9_]*)$/;
624
669
  const SIMPLE_ATOM = /^[a-z][A-Za-z0-9_]*$/;
625
670
  const GRAPHIC_ATOM = /^[#$&*+\-\/<=>@^~\\]+$/;
626
671
 
627
- function parseClausesFastNoSource(source, emit = null, emitBinary = null) {
672
+ function parseClausesFastNoSource(source, emit = null, emitBinary = null, options = {}) {
628
673
  source = String(source ?? '');
629
674
  const numberCache = new Map();
630
675
  const stringCache = new Map();
@@ -896,7 +941,7 @@ function parseClausesFastNoSource(source, emit = null, emitBinary = null) {
896
941
  return true;
897
942
  }
898
943
  try {
899
- const parsed = new Parser(text, { sourceMetadata: false }).parseProgram();
944
+ const parsed = new Parser(text, { ...options, sourceMetadata: false }).parseProgram();
900
945
  for (const clause of parsed) accept(clause);
901
946
  return true;
902
947
  } catch (_) {
@@ -938,12 +983,12 @@ function parseClausesFastNoSource(source, emit = null, emitBinary = null) {
938
983
  return clauses ?? true;
939
984
  }
940
985
 
941
- export function parseProgramText(source) {
942
- return parseClauses(source);
986
+ export function parseProgramText(source, options = {}) {
987
+ return parseClauses(source, options);
943
988
  }
944
989
 
945
- export function parseGoalText(text) {
946
- const clauses = parseClauses(`zz_goal((${text})).`);
990
+ export function parseGoalText(text, options = {}) {
991
+ const clauses = parseClauses(`zz_goal((${text})).`, options);
947
992
  const head = clauses[0]?.head;
948
993
  if (clauses.length !== 1 || head?.type !== 'compound' ||
949
994
  head.name !== 'zz_goal' || head.arity !== 1 || clauses[0].body.length !== 0) {