eyelang 1.3.0 → 1.3.1

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/docs/guide.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  This guide introduces Eyelang, a small Horn-clause language and engine whose source syntax is Prolog-like but deliberately its own compact language for rules, goals, answers, and proofs. Eyelang works over ordinary terms, lists, arithmetic, strings, and finite search. Run it with the `eyelang` CLI, or use `node bin/eyelang.js` when working directly from a source checkout.
4
4
 
5
- Programs write relations directly, for example `ancestor(pat, emma)` or `status(case1, accepted)`. Eyelang output is ordinary Eyelang syntax: by default, the CLI materializes selected answer facts and prints those facts only. Pass `--proof` (or `-p`) when you also want each answer followed by a `why/2` explanation fact that records the proof. Programs may add `materialize/2` declarations such as `materialize(answer, 2).` to focus output on selected predicates.
5
+ Programs write relations directly, for example `ancestor(pat, emma)` or `status(case1, accepted)`. Absolute IRI atoms can be written explicitly with angle brackets, for example `<https://schema.org/name>`, when a program needs web identifiers without prefix declarations. Eyelang output is ordinary Eyelang syntax: by default, the CLI materializes selected answer facts and prints those facts only. Pass `--proof` (or `-p`) when you also want each answer followed by a `why/2` explanation fact that records the proof. Programs may add `materialize/2` declarations such as `materialize(answer, 2).` to focus output on selected predicates.
6
6
 
7
7
 
8
8
  For the normative language definition, including lexical syntax, terms, clauses, goals, built-ins, `table/2`, `materialize/2`, and conformance boundaries, read the [Eyelang language reference](language-reference.md).
@@ -138,7 +138,7 @@ Each `?_` anonymous variable occurrence is fresh. A bare `_` is not a variable i
138
138
 
139
139
  ### 3.5 Atom constants
140
140
 
141
- A plain atom constant starts with a lowercase ASCII letter and is followed by zero or more ASCII letters, digits, or underscores. A dot is not part of a plain atom; dotted web spaces such as `'be.ugent'` or `'org.schema'` MUST be quoted if they are meant as one atom constant. Names such as `a-b` or `http://example` MUST also be quoted if they are meant as one atom constant:
141
+ A plain atom constant starts with a lowercase ASCII letter and is followed by zero or more ASCII letters, digits, or underscores. A dot is not part of a plain atom; dotted web spaces such as `'be.ugent'` or `'org.schema'` MUST be quoted if they are meant as one atom constant. Names such as `a-b` MUST also be quoted if they are meant as one atom constant:
142
142
 
143
143
  ```eyelang
144
144
  pat
@@ -148,9 +148,18 @@ case_123
148
148
  'org.schema'
149
149
  'eyereasoner.github'
150
150
  'a-b'
151
- 'http://example'
152
151
  ```
153
152
 
153
+ Absolute IRI atom constants MAY also be written between angle brackets. The content must be an absolute IRI-like lexical value with a scheme such as `https:` or `urn:`. Eyelang stores the content as the atom value and prints absolute IRI atoms with angle brackets on read-back:
154
+
155
+ ```eyelang
156
+ <https://example.org/alice>
157
+ <urn:example:bob>
158
+ triple(<https://example.org/alice>, <https://schema.org/name>, "Alice").
159
+ ```
160
+
161
+ Quoted absolute IRI atoms remain valid input, but read-back normalizes them to angle-bracket syntax. For example, `'https://example.org/alice'` prints as `<https://example.org/alice>`.
162
+
154
163
  A quoted atom constant is enclosed in single quotes. A single quote inside a quoted atom constant is represented by doubling it:
155
164
 
156
165
  ```eyelang
@@ -165,6 +174,8 @@ A graphic atom constant is one or more graphic characters from this set:
165
174
  #$&*+-/<=>?@^~\
166
175
  ```
167
176
 
177
+ Angle-bracket IRI syntax is recognized only for absolute IRI-like contents. Graphic atoms such as `<=>`, `<`, and `>=` remain graphic atoms.
178
+
168
179
  ### 3.6 Strings
169
180
 
170
181
  A string is enclosed in double quotes. The implementation supports common escapes such as `\n`, `\t`, `\"`, and `\\`.
@@ -659,7 +670,7 @@ eyelang source is intended to be familiar to Prolog readers, but eyelang is not
659
670
  - no variables in functor or predicate position;
660
671
  - no occurs check in unification.
661
672
 
662
- Programs intended to be portable to eyelang SHOULD use `?` variables, avoid ISO-specific syntax, and keep terms explicit. Atom names that are not plain lowercase-starting names or graphic atom tokens SHOULD be written as quoted atoms, for example `'a-b'`.
673
+ Programs intended to be portable to eyelang SHOULD use `?` variables, avoid ISO-specific syntax, and keep terms explicit. Atom names that are not plain lowercase-starting names, graphic atom tokens, or angle-bracket absolute IRI atoms SHOULD be written as quoted atoms, for example `'a-b'`.
663
674
 
664
675
  ## 16. Examples
665
676
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyelang",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
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",
package/src/parser.js CHANGED
@@ -7,6 +7,22 @@ const TOK = {
7
7
  LPAREN: '(', RPAREN: ')', LBRACKET: '[', RBRACKET: ']', COMMA: ',', BAR: '|', DOT: '.', IF: ':-'
8
8
  };
9
9
 
10
+ function isAbsoluteIriText(text) {
11
+ return /^[A-Za-z][A-Za-z0-9+.-]*:[^\s<>"'{}|\\^`]*$/.test(text);
12
+ }
13
+
14
+ function hasAngleIriToken(source, pos) {
15
+ if (source[pos] !== '<') return false;
16
+ let text = '';
17
+ for (let i = pos + 1; i < source.length; i++) {
18
+ const ch = source[i];
19
+ if (ch === '>') return text.length > 0 && isAbsoluteIriText(text);
20
+ if (ch === '\n' || ch === '\r' || /\s/.test(ch) || ch === '<') return false;
21
+ text += ch;
22
+ }
23
+ return false;
24
+ }
25
+
10
26
  function isWhitespaceCode(code) {
11
27
  return code === 32 || code === 9 || code === 10 || code === 13 || code === 12 || code === 11;
12
28
  }
@@ -97,6 +113,18 @@ class Parser {
97
113
  }
98
114
  if (ch === ':') throw new Error('colon names are not supported; use name or prefix_name');
99
115
 
116
+ if (hasAngleIriToken(this.source, this.pos)) {
117
+ this.take(); // <
118
+ let text = '';
119
+ while (true) {
120
+ if (!this.peek()) throw new Error(`parse line ${line}: unterminated IRI`);
121
+ const value = this.take();
122
+ if (value === '>') break;
123
+ text += value;
124
+ }
125
+ return { type: TOK.ATOM, text, line };
126
+ }
127
+
100
128
  if (ch === '"' || ch === "'") {
101
129
  const quote = this.take();
102
130
  let text = '';
package/src/term.js CHANGED
@@ -135,6 +135,14 @@ export function termIsGround(term, env = new Env()) {
135
135
 
136
136
  const graphicAtomChars = new Set('#$&*+-/<=>?@^~\\'.split(''));
137
137
 
138
+ function isAbsoluteIriText(text) {
139
+ return /^[A-Za-z][A-Za-z0-9+.-]*:[^\s<>"'{}|\\^`]*$/.test(text);
140
+ }
141
+
142
+ function writeAngleIri(name) {
143
+ return `<${name}>`;
144
+ }
145
+
138
146
  function atomNeedsQuotes(name) {
139
147
  if (!name) return true;
140
148
  if (name === '[]') return false;
@@ -156,6 +164,7 @@ function quoteAtom(name) {
156
164
  }
157
165
 
158
166
  function writeAtom(name) {
167
+ if (isAbsoluteIriText(name)) return writeAngleIri(name);
159
168
  return atomNeedsQuotes(name) ? quoteAtom(name) : name;
160
169
  }
161
170
 
@@ -0,0 +1,7 @@
1
+ % Reference 3.5: absolute IRIs may be written as angle-bracket atom constants.
2
+ materialize(iri_subject, 1).
3
+ materialize(iri_object, 1).
4
+ triple(<https://example.org/alice>, <https://schema.org/name>, "Alice").
5
+ triple(<urn:example:bob>, <https://schema.org/knows>, <https://example.org/alice>).
6
+ iri_subject(?iri) :- triple(?iri, ?_, ?_).
7
+ iri_object(?iri) :- triple(?_, <https://schema.org/knows>, ?iri).
@@ -0,0 +1,3 @@
1
+ iri_subject(<https://example.org/alice>).
2
+ iri_subject(<urn:example:bob>).
3
+ iri_object(<https://example.org/alice>).
@@ -529,6 +529,27 @@ function whiteBoxCases() {
529
529
  assertEqual(termToString(clauses[0].head, new Env(), true), "p(web('be.ugent', josd), 'org.schema')", 'head');
530
530
  },
531
531
  },
532
+ {
533
+ name: 'parser accepts angle-bracket absolute IRI atoms',
534
+ run: () => {
535
+ const clauses = parseProgramText('p(<https://example.org/alice>, <urn:example:bob>).\n');
536
+ assertEqual(termToString(clauses[0].head, new Env(), true), 'p(<https://example.org/alice>, <urn:example:bob>)', 'head');
537
+ },
538
+ },
539
+ {
540
+ name: 'readback prints absolute IRI atoms with angle brackets',
541
+ run: () => {
542
+ const clauses = parseProgramText("p('https://example.org/alice').\n");
543
+ assertEqual(termToString(clauses[0].head, new Env(), true), 'p(<https://example.org/alice>)', 'head');
544
+ },
545
+ },
546
+ {
547
+ name: 'angle IRI syntax does not steal graphic atom syntax',
548
+ run: () => {
549
+ const clauses = parseProgramText('p(<=>).\n');
550
+ assertEqual(termToString(clauses[0].head, new Env(), true), 'p(<=>)', 'head');
551
+ },
552
+ },
532
553
  {
533
554
  name: 'list construction round-trips through properListItems',
534
555
  run: () => {