eyeprolog 1.3.78 → 1.3.79

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 CHANGED
@@ -12,28 +12,22 @@ EyeProlog turns portable ISO Prolog programs into answers and inspectable proofs
12
12
  <strong>Click the cover to read <em>The Art of EyeProlog</em>.</strong>
13
13
  </p>
14
14
 
15
- **[Why EyeProlog?](https://eyereasoner.github.io/eyeprolog/why-eyeprolog)** — Discover its purpose and design.
16
-
17
- **[Playground](https://eyereasoner.github.io/eyeprolog/playground)** — Run EyeProlog in your browser.
18
-
19
- The book is the reference for the language, command line, JavaScript API,
20
- examples, proofs, conformance, and implementation.
15
+ The single implementation reference is [*The Art of EyeProlog*](the-art-of-eyeprolog.md).
16
+ It documents the language, built-ins, libraries, command line, JavaScript API,
17
+ examples, proofs, conformance profile, and implementation.
21
18
 
22
19
  ## Quick start
23
20
 
24
- EyeProlog requires Node.js 18 or newer. Check the active runtime before
25
- installing:
21
+ EyeProlog requires Node.js 18 or newer:
26
22
 
27
23
  ```sh
28
24
  node --version
29
25
  ```
30
26
 
31
- If it reports an older release, upgrade through a Node version manager or the
32
- [official Node.js download](https://nodejs.org/en/download) and check again.
33
- Distribution packages can provide an older Node.js even on a current operating
34
- system.
27
+ If necessary, upgrade through a Node version manager or the
28
+ [official Node.js download](https://nodejs.org/en/download).
35
29
 
36
- Run EyeProlog without a global installation:
30
+ Run EyeProlog without installing it globally:
37
31
 
38
32
  ```sh
39
33
  npx --yes eyeprolog
@@ -45,8 +39,7 @@ npx --yes eyeprolog
45
39
  ?- halt.
46
40
  ```
47
41
 
48
- For a persistent `eyeprolog` command without administrator access, install it
49
- under a user-owned prefix and put that prefix's `bin` directory on `PATH`:
42
+ For a persistent command, use a user-owned npm prefix:
50
43
 
51
44
  ```sh
52
45
  npm install --global --prefix "$HOME/.local" eyeprolog
@@ -54,335 +47,26 @@ export PATH="$HOME/.local/bin:$PATH"
54
47
  eyeprolog
55
48
  ```
56
49
 
57
- Add the `PATH` export to your shell startup file to keep it across sessions.
58
- Do not use `sudo npm install`; npm's
59
- [EACCES guidance](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally/)
60
- also recommends a Node version manager or a user-owned npm prefix.
50
+ Add the `PATH` export to your shell startup file. Do not use `sudo npm install`;
51
+ npm's [EACCES guidance](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally/)
52
+ also recommends a Node version manager or a user-owned prefix.
61
53
 
62
- For a non-interactive run:
54
+ Run a program non-interactively:
63
55
 
64
56
  ```sh
65
57
  printf 'human(socrates).\nmortal(X) :- human(X).\n' |
66
58
  npx --yes eyeprolog --proof --goal 'mortal(socrates)' -
67
59
  ```
68
60
 
69
- Programs may declare their default queries with `%% goal:` comments.
70
- Double-quoted text follows the ISO `double_quotes` flag and defaults to a
71
- proper list of one-character atoms (`chars`), matching Trealla and Scryer.
72
-
73
- Portable unit tests can be embedded as quads—a query followed by its expected
74
- top-level answer—and run with `eyeprolog --quads program.pl`:
75
-
76
- ```prolog
77
- member_test ?- member(X, [prolog, logic]).
78
- X = prolog
79
- ; X = logic.
80
- ```
81
-
82
- A label is simply the first argument of the ordinary `(?-)/2` term, so it may
83
- use any normal Prolog term syntax; EyeProlog only requires it to be ground when
84
- the quad is checked. A non-ground label is a quad failure, not a source syntax
85
- error, and later quads still run. When a query has multiple indented answer
86
- descriptions, each description is checked and counted independently, so one
87
- failed expectation does not prevent the later ones from running. Following the
88
- Trealla quad convention, `sto` declares that the query is subject to occurs-check.
89
- EyeProlog now uses an occurs-check event observed during the query's ordinary
90
- execution as positive STO evidence, and rejects `sto` when a finite execution
91
- completes without such an event (for example `?- true. sto.`). If execution is
92
- cut short by a search/resource boundary, the STO claim remains conservatively
93
- unverified rather than being guessed.
94
- For nontermination expectations, `loops` is kept distinct from resource
95
- exhaustion: structural loop evidence may satisfy `loops`, while a bounded
96
- search that cannot establish the requested answer sequence is reported as
97
- `UNDECIDED`. In CLI quad mode, failures use exit status `1`; if there are no
98
- failures but at least one undecided quad, the exit status is `2`.
99
-
100
- ## Tabling and well-founded negation
101
-
102
- In normal mode, EyeProlog automatically tables eligible positive recursive
103
- predicates. For large finite, function-free Datalog dependency cones it may use
104
- one shared relation-wide table, so an open query such as `tc(X, Y)` computes a
105
- finite closure once and bound recursive calls can reuse indexed answers. The
106
- choice of when to use relation-wide tabling is an implementation optimization,
107
- not a language-level threshold or directive.
108
-
109
- Recursive nonterminals reached through `phrase/2-3` use a dedicated table
110
- scope keyed by the complete invocation rather than the caller's general memo.
111
- When a new input is seen, recursive DCGs that static analysis proves consume a
112
- list tail on every recursive step run directly without automatic tabling; this
113
- avoids building and retaining a family of suffix tables that cannot help the
114
- next distinct input. If the same `phrase/2-3` invocation repeats, normal tabling
115
- is enabled again and its small completed table can be reused. The retained
116
- phrase table is also bounded, with active fixed points never evicted. This
117
- keeps both long-running filters such as `\+ phrase((..., Pattern), Sequence)`
118
- and repeated same-input probes bounded without turning memory safety into a
119
- per-call cache-eviction cost.
120
-
121
- Finite-tree unification also accepts an internal proven-nonoccurrence hint: when
122
- the solver can prove that a variable cannot occur in the value it is about to
123
- receive, that binding skips an otherwise redundant occurs traversal. The main
124
- source-level case is conservative first use in a freshly renamed clause, inspired
125
- by the local-variable optimization used by WAM-family systems; native construction
126
- paths such as relational `length/2` reuse the same unifier mechanism. Repeated
127
- variables, variables seen earlier in the clause, and ordinary public unification
128
- remain fully occurs-checked. This is a proof local to one binding, not a WAM-style
129
- local/global variable stack. `phrase/2` likewise supplies its fixed `[]`
130
- remainder directly to the grammar; `phrase/3` retains its delayed final output
131
- unification for steadfastness.
132
-
133
- ### Delayed disequality with `dif/2`
134
-
135
- The normal EyeProlog runtime provides `dif/2` as a conforming extension;
136
- `--iso-strict` remains limited to ISO/IEC 13211-1 and does not expose it.
137
- Unlike `\=/2`, which only tests whether two terms are non-unifiable *now*,
138
- `dif/2` can succeed with a residual disequality constraint and reject a later
139
- binding that would make the terms identical. For example,
140
- `dif(X, Y), X = Y` fails, while `dif(X-Y, 1-2), X = Y, Y = 1` succeeds once
141
- the structural constraint becomes provably satisfied.
142
-
143
- The implementation uses backtracking-safe annotated variables in `Env`. A
144
- pending constraint is indexed by every currently unbound variable it mentions;
145
- variable aliasing and later bindings reindex the annotation, entailed constraints
146
- are removed, and violated constraints make unification fail. The interactive
147
- top level includes pending `dif/2` goals in residual answers. See
148
- [`examples/dif-constraints.pl`](examples/dif-constraints.pl).
149
-
150
- ### Attributed variables and CLP(Z)
151
-
152
- `library(atts)` exposes Scryer-style attributed-variable operations on the same
153
- persistent `Env` machinery used by `dif/2`: `put_atts/2`, `get_atts/2`,
154
- `put_attr/3`, `get_attr/3`, `del_attr/2`, `term_attributed_variables/2`, and
155
- `call_residue_vars/2`. The latter reports variables whose residual attributes or
156
- constraints were created or modified by a goal, including native `dif/2` residues.
157
- `:- attribute ...` declarations are accepted, `verify_attributes/3` runs before
158
- an attributed binding is committed, and the hook's returned goals run after
159
- that binding. Attribute state follows variable representatives and ordinary
160
- Prolog backtracking, and the REPL projects module `attribute_goals//1` hooks as
161
- residual goals. See [`examples/attributed-variables.pl`](examples/attributed-variables.pl).
162
-
163
- `library(clpz)` is the MIT-licensed Scryer Prolog implementation of CLP(Z),
164
- bundled as `src/lib/clpz.pl`. It provides relational integer arithmetic,
165
- finite-domain constraints, reification, labeling, global constraints such as
166
- `all_distinct/1`, `global_cardinality/2,3`, `cumulative/1,2`, `circuit/1`,
167
- `disjoint2/1`, and `automaton/3,8`, plus domain reflection predicates. The
168
- bundled source is synchronized with Scryer commit
169
- `e3df91e25f8a09ee942c04e8baef553bba5c6110` (Git blob
170
- `806445c11e14c8b2515f3de7f309e0ac04d9ad04`).
171
-
172
- EyeProlog's runtime supplies the generic facilities used by that Prolog source:
173
- attributed variables, user `term_expansion/2` and `goal_expansion/2`, DCG
174
- expansion through `expand_term/2`, module-aware meta-calls, a backtrackable
175
- blackboard, and the support modules `assoc`, `pairs`, `between`, `dcgs`, `terms`,
176
- `error`, `si`, `freeze`, `arithmetic`, `debug`, and `format`.
177
-
178
- Recursion through negation is explicit. EyeProlog provides `tnot/1` for
179
- well-founded negation over finite, range-restricted, function-free Datalog
180
- components. Ordinary `\+/1` remains negation-as-failure and is not silently
181
- reinterpreted as WFS.
182
-
183
- ```prolog
184
- move(1, 2).
185
- move(2, 1).
186
- win(X) :- move(X, Y), tnot(win(Y)).
187
-
188
- %% goal: win(X)
189
- ```
190
-
191
- A cycle through `tnot/1` can leave atoms *undefined* rather than true or false.
192
- EyeProlog exposes those as conditional successes so they can participate in
193
- collectors such as `findall/3`; it does not currently expose a residual-program
194
- API. Direct `tnot/1` calls must be ground. Variables used in an eligible WFS
195
- rule are range-restricted by positive body literals before they are negated.
196
-
197
- The normal-mode statistics interface includes `wfs_fixpoint_rounds` and
198
- `wfs_undefined_answers`:
199
-
200
- ```prolog
201
- statistics(wfs_fixpoint_rounds, Rounds).
202
- statistics(wfs_undefined_answers, UndefinedObservations).
203
- ```
204
-
205
- Both automatic tabling and `tnot/1` are EyeProlog extensions. Strict ISO mode
206
- disables automatic tabling and does not provide `tnot/1`.
207
-
208
- ## Cleanup-aware control
209
-
210
- Normal mode provides `call_cleanup/2` and `setup_call_cleanup/3` for resource
211
- lifetimes that follow Prolog search. Cleanup runs exactly once when the protected
212
- goal completes deterministically, is exhausted, is cut or otherwise pruned, the
213
- top level stops answer enumeration, or an exception unwinds the search.
214
- `setup_call_cleanup/3` runs Setup once and installs Cleanup only after Setup
215
- succeeds. On ordinary pruning Cleanup sees the current goal bindings; during
216
- exception unwinding bindings made by the protected goal have already been
217
- unwound. Cleanup failure is ignored, and an exception already being propagated
218
- takes precedence over a cleanup exception. Nested cleanups run inside-out.
219
-
220
- These predicates are EyeProlog normal-mode extensions and are absent from
221
- `--iso-strict`. Their implementation is lifecycle-aware: the interactive top
222
- level can report a remaining choicepoint without speculatively requesting the
223
- next solution, while abandoning that choicepoint still closes protected
224
- resources.
225
-
226
- ## OpenRuleBench portable profile
227
-
228
- The `openrulebench/` directory contains a deterministic four-engine adaptation
229
- for EyeProlog, Trealla, Scryer, and SWI-Prolog. Its default `portable` profile
230
- keeps the characteristic joins, recursive closures, and WFS cases while
231
- avoiding benchmark sizes that require multi-gigabyte collectors on some
232
- engines. Run EyeProlog's complete profile with:
233
-
234
- ```sh
235
- ./openrulebench/run-eyeprolog.mjs
236
- ```
237
-
238
- The benchmark README records the expected answer counts. Timing values are
239
- machine-dependent; use the same generated profile when comparing engines.
240
-
241
- ## Strict ISO/IEC 13211-1 core
242
-
243
- For portability and conformance work, run the Part 1 core with Technical
244
- Corrigenda 1–3 in strict mode:
245
-
246
- ```sh
247
- eyeprolog --iso-strict
248
- eyeprolog --iso-strict --goal 'p(X)' program.pl
249
- ```
250
-
251
- The equivalent JavaScript option is `isoStrict: true`. Strict mode keeps
252
- EyeProlog's documented implementation-defined Unicode scalar processor
253
- character set, while excluding normal-profile language extensions such as Part
254
- 2 module compatibility forms, Part 3 DCG expansion, quads, extra libraries,
255
- automatic tabling, and non-standard flags/control facilities. Normal mode is
256
- unchanged.
257
-
258
- This README intentionally stays at the project-overview level. The detailed
259
- implementation reference is [*The Art of EyeProlog*](the-art-of-eyeprolog.md).
260
- The current Part 1 audit status and closure criteria are recorded in
261
- [`test/conformance/ISO-COMPLIANCE.md`](test/conformance/ISO-COMPLIANCE.md);
262
- implementation-defined decisions are indexed in
263
- [`ISO-IMPLEMENTATION-DEFINED.md`](test/conformance/ISO-IMPLEMENTATION-DEFINED.md). The complete
264
- vendored WG17 syntax corpus and the strict ISO regression suite run as release
265
- gates.
266
-
267
- The release-facing Part 1 audit ledger has explicit dispositions for its tracked requirements. This implementation evidence is not an independent ISO certification.
268
-
269
- ## Module and definite clause grammar compatibility profiles
270
-
271
- Normal EyeProlog supports the widely used `module/2`, `use_module/1-2`,
272
- `meta_predicate/1`, and `Module:Goal` interface reflected in later WG17 module
273
- amendment work. This is a practical module compatibility profile; EyeProlog
274
- does **not** currently claim a clause-by-clause implementation or certification
275
- of the complete ISO/IEC 13211-2:2000 module model.
276
-
277
- Definite clause grammar support follows the ISO/IEC TS 13211-3 grammar-rule and
278
- `phrase/2-3` model and is exercised by the conformance corpus. As with Part 1,
279
- that implementation evidence is not an independent certification of every
280
- Part 3 requirement. For example:
281
-
282
- ```prolog
283
- sentence --> [hello], noun.
284
- noun --> [world] | [prolog].
285
-
286
- %% goal: phrase(sentence, Words)
287
- ```
288
-
289
- For a larger bidirectional example, see
290
- [`examples/dcg-expression-language.pl`](examples/dcg-expression-language.pl).
291
- It parses precedence-sensitive arithmetic into an AST, evaluates expressions
292
- with variables, generates tokens back from an AST with only the necessary
293
- parentheses, round-trips the generated form, and demonstrates `phrase/3`
294
- remainder handling. The example uses accumulator nonterminals for
295
- left-associative operators, so state is handed repeatedly from one nonterminal
296
- to the next rather than hidden in host code.
297
-
298
- Deep finite DCG traversal is kept relational but does not have to consume one
299
- general solver frame per token. In particular, the interoperable `... //0`
300
- helper from `library(iso_ext)` can scan a finite compact list iteratively, and a
301
- following grammar that is statically known to leave the DCG state unchanged can
302
- be continued without rebuilding a full clause-resolution frame for every
303
- suffix. Open and remainder-producing uses still retain the ordinary DCG
304
- solutions and backtracking behavior.
61
+ ## Links
305
62
 
306
- EyeProlog also adds 135 public library predicate indicators to its 129-entry ISO
307
- profile. **89 are implemented entirely as ordinary Prolog clauses** in focused
308
- modules under `src/lib/`; the remaining control predicates, attributed-variable
309
- primitives, and the transitional finite-domain `library(clpz)` kernel use
310
- backtrackable host support.
311
- They are ordinary Prolog modules using EyeProlog's documented module compatibility surface, loaded explicitly by purpose, such as
312
- `library(lists)`, `library(lambda)`, `library(strings)`, `library(aggregate)`,
313
- `library(atts)`, or `library(clpz)`.
314
- Portable text
315
- predicates use ISO atoms or character lists. The old catch-all
316
- `library(eyeprolog)` module is no longer needed.
317
-
318
- ## Trealla and Scryer interoperability
319
-
320
- EyeProlog keeps a conservative source-level interoperability profile separate
321
- from the larger EyeProlog library surface. `library(lists)` follows the common
322
- Trealla/Scryer organization for predicates such as `member/2`, `memberchk/2`,
323
- `append/2-3`, `nth0/3-4`, `nth1/3-4`, `length/2`, `maplist/2-8`, and
324
- `foldl/4-6`. Its `length/2` remains relational: with both arguments variable,
325
- `length(Xs, N)` enumerates lists of increasing length together with `N = 0, 1,
326
- 2, ...`. Open-ended generation uses the normal memory guard with recovery
327
- headroom, so an exhausted finite heap is reported as a catchable
328
- `resource_error(memory)` instead of degenerating into quadratic list checks.
329
-
330
- `library(iso_ext)` is also accepted as a common interop module name.
331
- EyeProlog exports `call_nth/2`, `time/1`, and the DCG helper `... //0` there.
332
- The latter describes an arbitrary number of input elements and supports the
333
- nonterminal hand-off benchmark used by the interoperability tests. These common predicates
334
- may be imported explicitly, while source/CLI/API dependency loading can resolve
335
- their unqualified forms conservatively. For Trealla-style interactive timing,
336
- `time/1` is also available directly in the normal EyeProlog runtime; strict ISO
337
- mode does not expose it.
338
- `library(lists)`, `library(iso_ext)`, and `library(freeze)` follow the focused
339
- module organization used by Trealla and Scryer. EyeProlog's legacy
340
- `library(prologue)` is now a compatibility facade over these canonical modules
341
- and `library(between)`, so it can be imported before or after the focused
342
- libraries without duplicate-import errors.
343
-
344
- `library(lambda)` follows Scryer's higher-order lambda notation, adapted from
345
- Ulrich Neumerkel's permissively licensed implementation. Importing it installs
346
- the `+\` operator and enables closures such as `\X^Goal` and
347
- `Free+\X^Goal`. Parameters are supplied by `call/N`; variables not listed in
348
- `Free` are copied afresh for each invocation, while explicitly free variables
349
- remain shared. For example:
350
-
351
- ```prolog
352
- :- use_module(library(lambda)).
353
- :- use_module(library(lists)).
354
-
355
- all_positive(Xs) :- maplist(\X^(X > 0), Xs).
356
- all_equal(Y, Xs) :- maplist(Y+\X^(X = Y), Xs).
357
- ```
358
-
359
- The explicit import is intentional: unlike ordinary predicate autoloading, the
360
- lambda syntax also changes the active operator table.
361
-
362
- Outside `--iso-strict`, an otherwise undefined unqualified call may autoload a
363
- predicate only when the interop profile has one canonical EyeProlog provider.
364
- For example, `member/2` autoloads from `library(lists)`, `call_nth/2` from
365
- `library(iso_ext)`, `call_residue_vars/2` from `library(atts)`, and `between/3`
366
- from `library(between)`. Use `--no-autoload` to disable this
367
- convenience. Strict ISO mode never autoloads library predicates.
368
-
369
- Use `-w` / `--warnings` to diagnose dependencies outside the interop profile,
370
- or `--portable` to make such diagnostics fail the run. This catches both
371
- implementation-specific library names and EyeProlog-only predicates such as
372
- `set_nth0/4` even when they live in an otherwise common module.
373
-
374
- Cross-engine smoke tests live in `test/run-interop.mjs`. With Trealla (`tpl`)
375
- and Scryer (`scryer-prolog`) installed, run:
376
-
377
- ```sh
378
- npm run test:interop
379
- ```
380
-
381
- This optional check runs the same portable Towers of Hanoi source under
382
- EyeProlog, Trealla, and Scryer. The default `npm test` does not require external
383
- Prolog implementations; it does validate all four generated OpenRuleBench
384
- source trees and their engine-specific tabling and WFS adaptations. Run those
385
- fast structural checks separately with `npm run test:openrulebench`.
63
+ - [The Art of EyeProlog](https://eyereasoner.github.io/eyeprolog/the-art-of-eyeprolog) — complete reference
64
+ - [Why EyeProlog?](https://eyereasoner.github.io/eyeprolog/why-eyeprolog) — project scope and design
65
+ - [Playground](https://eyereasoner.github.io/eyeprolog/playground) — run EyeProlog in a browser
66
+ - [Examples](examples) — runnable programs and checked output
67
+ - [ISO conformance audit](test/conformance/ISO-COMPLIANCE.md) — supported Part 1 profile
68
+ - [Conformance report](conformance-report.md) — generated public corpus summary
69
+ - [OpenRuleBench](openrulebench/README.md) — portable benchmark profile
386
70
 
387
71
  ## Development
388
72
 
@@ -393,14 +77,4 @@ npm install
393
77
  npm test
394
78
  ```
395
79
 
396
- The GitHub test workflow runs the complete suite and an npm package dry-run on
397
- both the minimum supported Node.js 18 release line and Node.js 24. Publishing
398
- repeats those release checks before uploading the package.
399
-
400
- The runtime JavaScript modules stay flat under `src/`; the existing `src/lib/`
401
- directory contains the portable Prolog library modules. See
402
- [`src/ARCHITECTURE.md`](src/ARCHITECTURE.md) for the source-layer boundaries,
403
- facade modules, dependency rule, and the requirement that architectural cleanup
404
- must preserve the existing solver hot paths and benchmark performance.
405
-
406
80
  EyeProlog is released under the [MIT License](LICENSE.md).
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.3.78",
6
+ "version": "1.3.79",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
@@ -69,25 +69,44 @@ export const eyePrologNativeLibraryIndicators = Object.freeze([
69
69
  'in/2', 'ins/2', 'all_different/1', 'all_distinct/1', 'nvalue/2', 'sum/3',
70
70
  'scalar_product/4', 'tuples_in/2', 'labeling/2', 'label/1',
71
71
  'indomain/1', 'lex_chain/1', 'serialized/2', 'global_cardinality/2',
72
- 'global_cardinality/3', 'circuit/1', 'chain/2', 'element/3', 'zcompare/3',
72
+ 'global_cardinality/3', 'circuit/1', 'cumulative/1', 'cumulative/2',
73
+ 'disjoint2/1', 'automaton/3', 'automaton/8', 'chain/2', 'element/3', 'zcompare/3',
73
74
  'fd_var/1', 'fd_inf/2', 'fd_sup/2', 'fd_size/2', 'fd_dom/2',
75
+ 'clpz_t/2', '#=/3', '#</3',
74
76
  ]);
75
77
  export const eyePrologPortableLibraryIndicators = Object.freeze([
76
78
  'sumall/3', 'aggregate_min/5', 'aggregate_max/5',
77
- 'lt/2', 'gt/2', 'le/2', 'ge/2', 'difference/3',
78
- 'maplist/2', 'maplist/3', 'maplist/4', 'maplist/5',
79
- 'maplist/6', 'maplist/7', 'maplist/8', 'between/3',
80
- 'smallest_divisor_from/3', 'random/3', 'matches/3', 'split/3',
81
- 'replace/4', 'lowercase/2', 'uppercase/2', 'trim/2', 'number_string/2',
82
- 'atom_string/2', 'term_string/2', 'append/2', 'append/3', 'string_concat/3', 'contains/2',
83
- 'matches/2', 'join/3', 'substring/4', 'member/2', 'memberchk/2', 'select/3', 'last/2', 'same_length/2',
84
- 'nth0/3', 'nth0/4', 'nth1/3', 'nth1/4', 'set_nth0/4', 'take/3', 'drop/3', 'slice/4', 'reverse/2',
85
- 'length/2', 'sum_list/2', 'min_list/2', 'max_list/2', 'list_to_set/2',
86
- 'succ/2', 'foldl/4', 'foldl/5', 'foldl/6',
87
- 'forall/2', 'cfor/3', 'findall/4', 'variant/2', '.../2', 'uuid/3',
79
+ 'lsb/2', 'msb/2', 'popcount/2',
80
+ 'empty_assoc/1', 'assoc_to_list/2', 'get_assoc/3', 'put_assoc/4',
81
+ 'between/3', 'gen_int/1', 'gen_nat/1', 'numlist/2', 'numlist/3', 'repeat/1',
82
+ 'lt/2', 'gt/2', 'le/2', 'ge/2',
83
+ 'difference/3',
84
+ 'seq/3', 'seqq/3',
85
+ 'debug/1', 'debug/3', 'nodebug/1', 'bb_get/2', 'bb_b_put/2',
86
+ 'must_be/2', 'can_be/2', 'instantiation_error/0', 'instantiation_error/1',
87
+ 'domain_error/2', 'domain_error/3', 'type_error/2', 'type_error/3',
88
+ 'representation_error/1', 'resource_error/1', 'call_with_error_context/2',
89
+ 'format/2',
90
+ 'forall/2', 'succ/2', 'cfor/3', 'findall/4', 'variant/2', '.../2',
88
91
  '^/3', '^/4', '^/5', '^/6', '^/7', '^/8', '^/9', '^/10',
89
92
  '\\/1', '\\/2', '\\/3', '\\/4', '\\/5', '\\/6', '\\/7', '\\/8',
90
93
  '+\\/2', '+\\/3', '+\\/4', '+\\/5', '+\\/6', '+\\/7', '+\\/8', '+\\/9',
94
+ 'member/2', 'memberchk/2', 'select/3', 'append/2', 'append/3', 'last/2', 'same_length/2',
95
+ 'nth0/3', 'nth0/4', 'nth1/3', 'nth1/4', 'set_nth0/4', 'take/3', 'drop/3', 'slice/4', 'reverse/2',
96
+ 'length/2', 'maplist/2', 'maplist/3', 'maplist/4', 'maplist/5',
97
+ 'maplist/6', 'maplist/7', 'maplist/8', 'foldl/4', 'foldl/5', 'foldl/6',
98
+ 'sum_list/2', 'min_list/2', 'max_list/2', 'list_to_set/2',
99
+ 'pairs_keys_values/3', 'pairs_keys/2', 'pairs_values/2',
100
+ 'group_pairs_by_key/2', 'map_list_to_pairs/3',
101
+ 'smallest_divisor_from/3',
102
+ 'random/3',
103
+ 'atom_si/1', 'integer_si/1', 'atomic_si/1', 'list_si/1', 'character_si/1',
104
+ 'term_si/1', 'chars_si/1', 'dif_si/2', 'not_si/1', 'when_si/2',
105
+ 'matches/3', 'split/3', 'replace/4', 'lowercase/2', 'uppercase/2', 'trim/2',
106
+ 'number_string/2', 'atom_string/2', 'term_string/2', 'string_concat/3',
107
+ 'contains/2', 'matches/2', 'join/3', 'substring/4',
108
+ 'numbervars/3', 'copy_term_nat/2',
109
+ 'uuid/3',
91
110
  ]);
92
111
  export const eyePrologLibraryIndicators = Object.freeze([
93
112
  ...eyePrologPortableLibraryIndicators,
@@ -3987,7 +3987,15 @@ function documentationSyncCases() {
3987
3987
  },
3988
3988
  {
3989
3989
  name: 'book EyeProlog library matches runtime registry',
3990
- run: () => assertArrayEqual(bookEyePrologLibraryNames(), registeredEyePrologLibraryNames(), 'EyeProlog library predicates'),
3990
+ run: () => {
3991
+ assertArrayEqual(bookEyePrologLibraryNames(), registeredEyePrologLibraryNames(), 'EyeProlog library predicates');
3992
+ assertArrayEqual(bookLibraryModuleIssues(), [], 'library module export catalog');
3993
+ const book = fs.readFileSync(path.join(packageRoot, 'the-art-of-eyeprolog.md'), 'utf8');
3994
+ assertIncludes(book, `**${eyePrologLibraryIndicators.length} distinct non-ISO library and normal-extension predicate`, 'library predicate count');
3995
+ assertIncludes(book, `**${eyePrologPortableLibraryIndicators.length} are defined entirely as ordinary Prolog clauses**`, 'portable library count');
3996
+ assertIncludes(book, `**${eyePrologNativeLibraryIndicators.length} use host support**`, 'host-supported library count');
3997
+ assertIncludes(book, `**${createDefaultRegistry().defs.size + eyePrologLibraryIndicators.length} distinct predicate indicators**`, 'combined catalog count');
3998
+ },
3991
3999
  },
3992
4000
  {
3993
4001
  name: 'README cover links to the book and the book documents runtime boundaries',
@@ -3999,11 +4007,10 @@ function documentationSyncCases() {
3999
4007
  '<a href="https://eyereasoner.github.io/eyeprolog/the-art-of-eyeprolog">\n <img src="book-assets/title-page.svg" alt="Read The Art of EyeProlog"',
4000
4008
  'README cover links to the book',
4001
4009
  );
4002
- for (const filename of ['src/iso.js', 'src/dcg.js', 'src/atts.js', 'src/standard-library.js',
4003
- 'src/lib/aggregate.pl', 'src/lib/atts.pl', 'src/lib/comparison.pl', 'src/lib/dates.pl',
4004
- 'src/lib/iso_ext.pl', 'src/lib/lists.pl', 'src/lib/primes.pl', 'src/lib/prologue.pl',
4005
- 'src/lib/random.pl', 'src/lib/strings.pl', 'src/lib/uuid.pl',
4006
- 'src/playground-worker.js']) {
4010
+ const documentedSources = ['src/iso.js', 'src/dcg.js', 'src/atts.js', 'src/standard-library.js',
4011
+ ...[...standardLibrarySources.values()].map((entry) => entry.filename),
4012
+ 'src/playground-worker.js'];
4013
+ for (const filename of documentedSources) {
4007
4014
  assertEqual(fs.existsSync(path.join(packageRoot, filename)), true, `${filename} exists`);
4008
4015
  assertIncludes(book, filename, `book documents ${filename}`);
4009
4016
  }
@@ -4161,6 +4168,11 @@ function documentationSyncCases() {
4161
4168
  ]) assertIncludes(book, name, `book ${name}`);
4162
4169
  assertIncludes(readme, 'implementation reference is [*The Art of EyeProlog*]', 'README book hand-off');
4163
4170
  assertIncludes(readme, 'test/conformance/ISO-COMPLIANCE.md', 'README concise audit link');
4171
+ assertEqual(readme.split('\n').length <= 100, true, 'README remains a compact entry point');
4172
+ for (const heading of ['## Tabling', '## Cleanup-aware control', '## Strict ISO',
4173
+ '## Module and definite clause grammar', '## Trealla and Scryer interoperability']) {
4174
+ assertNotIncludes(readme, heading, `README delegates ${heading} to the book`);
4175
+ }
4164
4176
  assertEqual(readme.includes('2026-08-23 draft items #73-#76'), false, 'README omits audit-history detail');
4165
4177
  assertIncludes(profile, 'Part 1 processor, syntax, semantic, built-in, and arithmetic', 'Why EyeProlog audit state');
4166
4178
  },
@@ -5119,8 +5131,8 @@ answer(ok) :-
5119
5131
  assertEqual(Boolean(library.get('call_cleanup', 2)), true, 'call_cleanup/2 is an EyeProlog cleanup control');
5120
5132
  assertEqual(registry.get('setup_call_cleanup', 3), null, 'setup_call_cleanup/3 is absent from the ISO registry');
5121
5133
  assertEqual(Boolean(library.get('setup_call_cleanup', 3)), true, 'setup_call_cleanup/3 is an EyeProlog cleanup control');
5122
- assertEqual(registeredNativeEyePrologLibraryNames().length, 49, 'public native EyeProlog builtin count');
5123
- assertEqual(eyePrologPortableLibraryIndicators.length, 87, 'portable Prolog library count');
5134
+ assertEqual(registeredNativeEyePrologLibraryNames().length, 57, 'public host-supported EyeProlog library count');
5135
+ assertEqual(eyePrologPortableLibraryIndicators.length, 135, 'portable Prolog library count');
5124
5136
  assertEqual(eyePrologInteropLibraryIndicators.length, 30, 'cross-implementation interop profile count');
5125
5137
  assertEqual(eyePrologInteropLibraryModules.join(','), 'lists,iso_ext,lambda,atts,freeze', 'common explicit library module profile');
5126
5138
  assertEqual(eyePrologInteropAutoload['member/2'], 'lists', 'member/2 canonical autoload');
@@ -5130,9 +5142,9 @@ answer(ok) :-
5130
5142
  assertEqual(eyePrologInteropAutoload['time/1'], 'iso_ext', 'time/1 canonical interop autoload');
5131
5143
  assertEqual(eyePrologInteropAutoload['.../2'], 'iso_ext', '.../2 canonical interop autoload');
5132
5144
  assertEqual(eyePrologInteropAutoload['set_nth0/4'] ?? null, null, 'EyeProlog-only set_nth0/4 is not autoloadable');
5133
- assertEqual(eyePrologNativeLibraryIndicators.length, 49, 'native host library count');
5145
+ assertEqual(eyePrologNativeLibraryIndicators.length, 57, 'host-supported library count');
5134
5146
  assertEqual(eyePrologNativeLibraryIndicators.slice(0, 3).join(','), 'call_nth/2,freeze/2,dif/2', 'control and constraint predicates requiring host support');
5135
- assertEqual(eyePrologLibraryIndicators.length, 136, 'complete EyeProlog library surface');
5147
+ assertEqual(eyePrologLibraryIndicators.length, 192, 'complete non-ISO EyeProlog library surface');
5136
5148
  assertEqual(registry.get('eyeprolog__call_nth', 2), null, 'private call_nth adapter is absent from ISO registry');
5137
5149
  assertEqual(Boolean(library.get('eyeprolog__call_nth', 2)), true, 'private call_nth adapter is registered for EyeProlog');
5138
5150
  assertEqual(Boolean(library.get('eyeprolog__call_residue_vars', 2)), true, 'private call_residue_vars adapter is registered for EyeProlog');
@@ -6292,7 +6304,45 @@ function bookBuiltinNames() {
6292
6304
 
6293
6305
  function bookEyePrologLibraryNames() {
6294
6306
  const book = fs.readFileSync(path.join(packageRoot, 'the-art-of-eyeprolog.md'), 'utf8');
6295
- return documentedBuiltinNames(between(book, '<!-- eyeprolog-library-catalog:start -->', '<!-- eyeprolog-library-catalog:end -->'), 2);
6307
+ const iso = new Set(registeredBuiltinNames());
6308
+ return documentedBuiltinNames(
6309
+ between(book, '<!-- eyeprolog-library-catalog:start -->', '<!-- eyeprolog-library-catalog:end -->'),
6310
+ 2,
6311
+ ).filter((indicator) => !iso.has(indicator));
6312
+ }
6313
+
6314
+ function bookLibraryModuleIssues() {
6315
+ const book = fs.readFileSync(path.join(packageRoot, 'the-art-of-eyeprolog.md'), 'utf8');
6316
+ const section = between(book, '<!-- eyeprolog-library-catalog:start -->', '<!-- eyeprolog-library-catalog:end -->');
6317
+ const documented = new Map();
6318
+ for (const line of section.split('\n')) {
6319
+ const match = line.match(/^\|\s*`library\(([^)`]+)\)`\s*\|/);
6320
+ if (match == null) continue;
6321
+ documented.set(match[1], documentedBuiltinNames(line, 2));
6322
+ }
6323
+
6324
+ const issues = [];
6325
+ for (const [moduleName, entry] of standardLibrarySources) {
6326
+ const parsed = Program.parse(entry.source);
6327
+ const definition = parsed.modules.get(moduleName);
6328
+ if (definition == null) {
6329
+ issues.push(`missing module declaration for ${moduleName}`);
6330
+ continue;
6331
+ }
6332
+ const actual = [...definition.exports.keys()].sort();
6333
+ const expected = documented.get(moduleName);
6334
+ if (expected == null) {
6335
+ issues.push(`book catalog omits library(${moduleName})`);
6336
+ continue;
6337
+ }
6338
+ if (JSON.stringify(expected) !== JSON.stringify(actual)) {
6339
+ issues.push(`library(${moduleName}) exports: expected ${actual.join(', ')}, documented ${expected.join(', ')}`);
6340
+ }
6341
+ }
6342
+ for (const moduleName of documented.keys()) {
6343
+ if (!standardLibrarySources.has(moduleName)) issues.push(`book catalogs unknown library(${moduleName})`);
6344
+ }
6345
+ return issues.sort();
6296
6346
  }
6297
6347
 
6298
6348
  function bookBuiltinSummary() {
@@ -6211,16 +6211,18 @@ so side effects occur in Prolog execution order.
6211
6211
 
6212
6212
  ### The EyeProlog library
6213
6213
 
6214
- EyeProlog exposes **135 library predicate indicators** in addition to the 129
6215
- indicators in its isolated ISO profile. **89 are defined entirely as ordinary
6216
- Prolog clauses** in focused modules under `src/lib/`. The remaining public
6217
- relations use small private host adapters where control, constraints, or host
6218
- observability cannot be expressed by ordinary clauses alone; `time/1` is one
6219
- such relation. The resulting normal EyeProlog language surface is therefore
6220
- **264 public predicate indicators**. Most library relations remain module source
6221
- clauses over private adapters. `time/1` is additionally registered directly in
6222
- the normal EyeProlog runtime so Trealla-style timing works at the interactive
6223
- top level without an import; it is absent from the strict ISO registry.
6214
+ EyeProlog exposes **192 distinct non-ISO library and normal-extension predicate
6215
+ indicators** in addition to the 129 indicators in its isolated ISO profile.
6216
+ **135 are defined entirely as ordinary Prolog clauses** in focused modules under
6217
+ `src/lib/`; **57 use host support** for control, attributed variables,
6218
+ constraints, or observability. The ISO and library catalogs therefore cover
6219
+ **321 distinct predicate indicators**. Normal-mode controls and observability
6220
+ relations that do not belong to a library, such as `call_cleanup/2`,
6221
+ `setup_call_cleanup/3`, `statistics/0,2`, and `tnot/1`, are documented in their
6222
+ own sections rather than counted as library predicates. `time/1` is additionally
6223
+ registered directly in the normal runtime so Trealla-style timing works at the
6224
+ interactive top level without an import; it is absent from the strict ISO
6225
+ registry.
6224
6226
 
6225
6227
  The sources are `src/lib/aggregate.pl`, `src/lib/arithmetic.pl`,
6226
6228
  `src/lib/assoc.pl`, `src/lib/atts.pl`, `src/lib/between.pl`,
@@ -6256,22 +6258,33 @@ between solution branches.
6256
6258
 
6257
6259
  <!-- eyeprolog-library-catalog:start -->
6258
6260
 
6259
- | Module | Exported predicate indicators |
6260
- | --- | --- |
6261
- | `library(aggregate)` | `sumall/3`, `aggregate_min/5`, `aggregate_max/5` |
6262
- | `library(atts)` | `put_atts/2`, `get_atts/2`, `put_attr/3`, `get_attr/3`, `del_attr/2`, `term_attributed_variables/2`, `call_residue_vars/2` |
6263
- | `library(clpz)` | `#>/2`, `#</2`, `#>=/2`, `#=</2`, `#=/2`, `#\=/2`, `#\/1`, `#<==>/2`, `#==>/2`, `#<==/2`, `#\//2`, `#\/2`, `#/\/2`, `in/2`, `ins/2`, `all_different/1`, `all_distinct/1`, `nvalue/2`, `sum/3`, `scalar_product/4`, `tuples_in/2`, `labeling/2`, `label/1`, `indomain/1`, `lex_chain/1`, `serialized/2`, `global_cardinality/2`, `global_cardinality/3`, `circuit/1`, `chain/2`, `element/3`, `zcompare/3`, `fd_var/1`, `fd_inf/2`, `fd_sup/2`, `fd_size/2`, `fd_dom/2` |
6264
- | `library(comparison)` | `lt/2`, `gt/2`, `le/2`, `ge/2` |
6265
- | `library(dates)` | `difference/3` |
6266
- | `library(iso_ext)` | `call_nth/2`, `countall/2`, `forall/2`, `succ/2`, `cfor/3`, `findall/4`, `variant/2`, `time/1`, `.../2` |
6267
- | Normal runtime extension | `dif/2` |
6268
- | `library(lambda)` | `^/3`, `^/4`, `^/5`, `^/6`, `^/7`, `^/8`, `^/9`, `^/10`, `\/1`, `\/2`, `\/3`, `\/4`, `\/5`, `\/6`, `\/7`, `\/8`, `+\/2`, `+\/3`, `+\/4`, `+\/5`, `+\/6`, `+\/7`, `+\/8`, `+\/9` |
6269
- | `library(lists)` | `member/2`, `memberchk/2`, `select/3`, `append/2`, `append/3`, `last/2`, `same_length/2`, `nth0/3`, `nth0/4`, `nth1/3`, `nth1/4`, `reverse/2`, `length/2`, `maplist/2`, `maplist/3`, `maplist/4`, `maplist/5`, `maplist/6`, `maplist/7`, `maplist/8`, `foldl/4`, `foldl/5`, `foldl/6`, `sum_list/2`, `min_list/2`, `max_list/2`, `list_to_set/2`, `set_nth0/4`, `take/3`, `drop/3`, `slice/4` |
6270
- | `library(primes)` | `smallest_divisor_from/3` |
6271
- | `library(prologue)` | `member/2`, `append/3`, `length/2`, `between/3`, `select/3`, `succ/2`, `maplist/2`, `maplist/3`, `maplist/4`, `maplist/5`, `maplist/6`, `maplist/7`, `maplist/8`, `nth0/3`, `nth0/4`, `nth1/3`, `nth1/4`, `call_nth/2`, `freeze/2`, `foldl/4`, `foldl/5`, `foldl/6`, `countall/2` |
6272
- | `library(random)` | `random/3` |
6273
- | `library(strings)` | `matches/3`, `split/3`, `replace/4`, `lowercase/2`, `uppercase/2`, `trim/2`, `number_string/2`, `atom_string/2`, `term_string/2`, `string_concat/3`, `contains/2`, `matches/2`, `join/3`, `substring/4` |
6274
- | `library(uuid)` | `uuid/3` |
6261
+ | Module | Exported predicate indicators | Primary role |
6262
+ | --- | --- | --- |
6263
+ | `library(aggregate)` | `sumall/3`, `aggregate_min/5`, `aggregate_max/5` | Aggregation |
6264
+ | `library(arithmetic)` | `lsb/2`, `msb/2`, `popcount/2` | CLP(Z) arithmetic support |
6265
+ | `library(assoc)` | `empty_assoc/1`, `assoc_to_list/2`, `get_assoc/3`, `put_assoc/4` | Association lists used by CLP(Z) |
6266
+ | `library(atts)` | `put_atts/2`, `get_atts/2`, `put_attr/3`, `get_attr/3`, `del_attr/2`, `term_attributed_variables/2`, `call_residue_vars/2` | Attributed variables |
6267
+ | `library(between)` | `between/3`, `gen_int/1`, `gen_nat/1`, `numlist/2`, `numlist/3`, `repeat/1` | Integer generation |
6268
+ | `library(clpz)` | `#>/2`, `#</2`, `#>=/2`, `#=</2`, `#=/2`, `#\=/2`, `#\/1`, `#<==>/2`, `#==>/2`, `#<==/2`, `#\//2`, `#\/2`, `#/\/2`, `in/2`, `ins/2`, `all_different/1`, `all_distinct/1`, `nvalue/2`, `sum/3`, `scalar_product/4`, `tuples_in/2`, `labeling/2`, `label/1`, `indomain/1`, `lex_chain/1`, `serialized/2`, `global_cardinality/2`, `global_cardinality/3`, `circuit/1`, `cumulative/1`, `cumulative/2`, `disjoint2/1`, `element/3`, `automaton/3`, `automaton/8`, `zcompare/3`, `chain/2`, `fd_var/1`, `fd_inf/2`, `fd_sup/2`, `fd_size/2`, `fd_dom/2`, `clpz_t/2`, `#=/3`, `#</3` | Constraint logic programming over integers; the final three indicators support reified compatibility libraries |
6269
+ | `library(comparison)` | `lt/2`, `gt/2`, `le/2`, `ge/2` | Generic comparison |
6270
+ | `library(dates)` | `difference/3` | ISO duration differences |
6271
+ | `library(dcgs)` | `phrase/2`, `phrase/3`, `seq/3`, `seqq/3` | DCG compatibility; `seq/3` and `seqq/3` are declared as `seq //1` and `seqq //1` |
6272
+ | `library(debug)` | `debug/1`, `debug/3`, `nodebug/1`, `bb_get/2`, `bb_b_put/2` | Scryer compatibility and backtrackable-blackboard support |
6273
+ | `library(error)` | `must_be/2`, `can_be/2`, `instantiation_error/0`, `instantiation_error/1`, `domain_error/2`, `domain_error/3`, `type_error/2`, `type_error/3`, `representation_error/1`, `resource_error/1`, `call_with_error_context/2` | Error checking and construction |
6274
+ | `library(format)` | `format/2` | Formatted output used by bundled libraries |
6275
+ | `library(freeze)` | `freeze/2` | Delayed goals |
6276
+ | `library(iso_ext)` | `call_nth/2`, `countall/2`, `forall/2`, `succ/2`, `cfor/3`, `findall/4`, `variant/2`, `time/1`, `.../2` | Common ISO extensions |
6277
+ | Normal runtime extension | `dif/2` | Delayed disequality; available without a library import |
6278
+ | `library(lambda)` | `^/3`, `^/4`, `^/5`, `^/6`, `^/7`, `^/8`, `^/9`, `^/10`, `\/1`, `\/2`, `\/3`, `\/4`, `\/5`, `\/6`, `\/7`, `\/8`, `+\/2`, `+\/3`, `+\/4`, `+\/5`, `+\/6`, `+\/7`, `+\/8`, `+\/9` | Higher-order lambda notation |
6279
+ | `library(lists)` | `member/2`, `memberchk/2`, `select/3`, `append/2`, `append/3`, `last/2`, `same_length/2`, `nth0/3`, `nth0/4`, `nth1/3`, `nth1/4`, `reverse/2`, `length/2`, `maplist/2`, `maplist/3`, `maplist/4`, `maplist/5`, `maplist/6`, `maplist/7`, `maplist/8`, `foldl/4`, `foldl/5`, `foldl/6`, `sum_list/2`, `min_list/2`, `max_list/2`, `list_to_set/2`, `set_nth0/4`, `take/3`, `drop/3`, `slice/4` | List relations |
6280
+ | `library(pairs)` | `pairs_keys_values/3`, `pairs_keys/2`, `pairs_values/2`, `group_pairs_by_key/2`, `map_list_to_pairs/3` | Key-value pair support |
6281
+ | `library(primes)` | `smallest_divisor_from/3` | Prime factor support |
6282
+ | `library(prologue)` | `member/2`, `append/3`, `length/2`, `between/3`, `select/3`, `succ/2`, `maplist/2`, `maplist/3`, `maplist/4`, `maplist/5`, `maplist/6`, `maplist/7`, `maplist/8`, `nth0/3`, `nth0/4`, `nth1/3`, `nth1/4`, `call_nth/2`, `freeze/2`, `foldl/4`, `foldl/5`, `foldl/6`, `countall/2` | Legacy facade over canonical focused modules |
6283
+ | `library(random)` | `random/3` | Pure state-threaded random generation |
6284
+ | `library(si)` | `atom_si/1`, `integer_si/1`, `atomic_si/1`, `list_si/1`, `character_si/1`, `term_si/1`, `chars_si/1`, `dif_si/2`, `not_si/1`, `when_si/2` | Sufficient-instantiation checks used by CLP(Z) |
6285
+ | `library(strings)` | `matches/3`, `split/3`, `replace/4`, `lowercase/2`, `uppercase/2`, `trim/2`, `number_string/2`, `atom_string/2`, `term_string/2`, `string_concat/3`, `contains/2`, `matches/2`, `join/3`, `substring/4` | Text relations |
6286
+ | `library(terms)` | `numbervars/3`, `copy_term_nat/2` | Term operations used by bundled libraries |
6287
+ | `library(uuid)` | `uuid/3` | Pure state-threaded UUID generation |
6275
6288
 
6276
6289
  <!-- eyeprolog-library-catalog:end -->
6277
6290