tree-sitter-swi-prolog 0.1.0 → 0.1.2

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
@@ -70,7 +70,7 @@ node scripts/check-corpus.mjs path/to/prolog/sources
70
70
 
71
71
  Releases are made from [changesets](https://github.com/changesets/changesets).
72
72
 
73
- 1. A pull request with a user-visible change adds a changeset: run `npx changeset`, pick the bump and describe the change for users.
73
+ 1. A pull request with a user-visible change adds a changeset: run `npx changeset`, pick the bump and describe the change for users. CI fails a pull request without a changeset; for a change users don't see, add an empty one with `npx changeset --empty`.
74
74
  2. When it is merged, the **Release** workflow opens (or updates) a **Version packages** pull request that applies all pending changesets: it bumps the version in every manifest and writes their text into `CHANGELOG.md`.
75
75
  3. Merging that pull request releases the new version: the `vX.Y.Z` tag and GitHub release (with the changelog section as notes and the Wasm parser attached), npm, crates.io and PyPI.
76
76
 
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @file Node types of the generated SWI-Prolog parser
3
+ * @license MIT
4
+ */
5
+
6
+ // Sets of node types read from src/node-types.json, which `tree-sitter
7
+ // generate` writes from grammar.js. They are derived, never restated, so a
8
+ // grammar change that adds or removes a node type changes them too. The
9
+ // Kibi proofs in test/proof/ range over them.
10
+
11
+ import { readFileSync } from 'node:fs';
12
+
13
+ /** The node types of the generated parser, as tree-sitter describes them. */
14
+ export const NODE_TYPES = JSON.parse(
15
+ readFileSync(new URL('../src/node-types.json', import.meta.url), 'utf8'),
16
+ );
17
+
18
+ /** Every named node type. */
19
+ export const NAMED_NODE_TYPES = NODE_TYPES.filter(t => t.named).map(t => t.type);
20
+
21
+ const sourceFile = NODE_TYPES.find(t => t.type === 'source_file');
22
+
23
+ /**
24
+ * The node types a top-level term can have: the children of `source_file`,
25
+ * without `shebang`, which is the `#!` line before the first term rather
26
+ * than a term.
27
+ */
28
+ export const TOP_LEVEL_TERM_TYPES = sourceFile.children.types
29
+ .map(t => t.type)
30
+ .filter(type => type !== 'shebang');
31
+
32
+ /**
33
+ * The token types: named node types with no fields and no children, such as
34
+ * `atom`, `variable`, `string` and `comment`.
35
+ */
36
+ export const TOKEN_TYPES = NODE_TYPES
37
+ .filter(t => t.named && !t.subtypes && !t.children
38
+ && Object.keys(t.fields ?? {}).length === 0)
39
+ .map(t => t.type);
@@ -0,0 +1,93 @@
1
+ /**
2
+ * @file Operator tables of the SWI-Prolog grammar
3
+ * @license MIT
4
+ */
5
+
6
+ // Default SWI-Prolog operator table (see `current_op/3` in a fresh `swipl`).
7
+ // Prolog priorities run the other way from tree-sitter precedences, so each
8
+ // rule uses `1200 - priority`.
9
+ //
10
+ // Operators at priority 1200 only appear as the neck of a clause, in a
11
+ // directive, or inside parentheses; see `clause`, `directive` and `_term_1200`.
12
+ export const INFIX_HIGH = [
13
+ // [priority, type, operators]
14
+ // library(chr)
15
+ [1190, 'xfx', ['==>', '<=>']],
16
+ [1180, 'xfx', ['\\']],
17
+ [1150, 'xfx', ['pragma']],
18
+ [1105, 'xfy', ['|']],
19
+ [1100, 'xfy', [';']],
20
+ [1050, 'xfy', ['->', '*->']],
21
+ [1000, 'xfy', [',']],
22
+ ];
23
+
24
+ export const INFIX_LOW = [
25
+ [990, 'xfx', [':=']],
26
+ // library(clpfd)
27
+ [760, 'yfx', ['#<==>']],
28
+ [750, 'xfy', ['#==>']],
29
+ [750, 'yfx', ['#<==']],
30
+ [740, 'yfx', ['#\\/']],
31
+ [730, 'yfx', ['#\\']],
32
+ [720, 'yfx', ['#/\\']],
33
+ [700, 'xfx', ['#>', '#<', '#>=', '#=<', '#=', '#\\=', 'in', 'ins', 'in_set']],
34
+ [450, 'xfx', ['..']],
35
+ [700, 'xfx', [
36
+ '=', '\\=', '==', '\\==', '@<', '@>', '@=<', '@>=', '=..', 'is', '=:=',
37
+ '=\\=', '<', '>', '=<', '>=', '>:<', ':<', 'as', '=@=', '\\=@=', '?=',
38
+ ]],
39
+ [600, 'xfy', [':']],
40
+ [500, 'yfx', ['+', '-', '/\\', '\\/', 'xor']],
41
+ [400, 'yfx', ['*', '/', '//', 'rem', 'mod', 'div', '<<', '>>', 'divmod', 'rdiv']],
42
+ [200, 'xfx', ['**']],
43
+ [200, 'xfy', ['^']],
44
+ // library(semweb/rdf_db) literals: Value^^Type, Text@Lang
45
+ [110, 'xfx', ['^^']],
46
+ ];
47
+
48
+ export const PREFIX_HIGH = [
49
+ [1150, 'fx', [
50
+ 'dynamic', 'discontiguous', 'initialization', 'meta_predicate',
51
+ 'module_transparent', 'multifile', 'public', 'thread_local',
52
+ 'thread_initialization', 'volatile', 'table',
53
+ // declarations from common libraries
54
+ 'rdf_meta', 'persistent', 'record', 'html_meta', 'chr_constraint', 'chr_type', 'chr_option',
55
+ ]],
56
+ ];
57
+
58
+ export const PREFIX_LOW = [
59
+ [900, 'fy', ['\\+']],
60
+ [710, 'fy', ['#\\']],
61
+ [500, 'fx', ['?']],
62
+ [200, 'fy', ['-', '+', '\\']],
63
+ [100, 'fx', ['@']],
64
+ // library(wasm)
65
+ [50, 'fx', ['#']],
66
+ [1, 'fx', ['$']],
67
+ ];
68
+
69
+ export const NECKS = [':-', '-->', '=>'];
70
+
71
+ // Every operator name. Each one is also a plain atom wherever an operand is
72
+ // not expected, e.g. `foo(-, +)` or `X = (=)`.
73
+ export const OPERATOR_NAMES = [...new Set([
74
+ ...NECKS, '?-',
75
+ ...[...INFIX_HIGH, ...INFIX_LOW, ...PREFIX_HIGH, ...PREFIX_LOW]
76
+ .flatMap(([, , ops]) => ops),
77
+ ])].filter(op => op !== ',' && op !== '|');
78
+
79
+
80
+ // Operators declared with op/3 are not known to the parser. Between two
81
+ // operands, a token of one of these kinds is read as an infix operator at
82
+ // UNKNOWN_INFIX_PRIORITY instead: two adjacent operands are never valid
83
+ // Prolog otherwise. Keys name the kind, values the grammar rule for it.
84
+ export const UNKNOWN_INFIX_RULES = {
85
+ symbolic: '_graphic',
86
+ alphanumeric: '_name',
87
+ };
88
+
89
+ /** The kinds of unknown infix operator the parser reads. */
90
+ export const UNKNOWN_INFIX_KINDS = Object.keys(UNKNOWN_INFIX_RULES);
91
+
92
+ /** The priority an unknown infix operator is read at. */
93
+ export const UNKNOWN_INFIX_PRIORITY = 700;
package/grammar.js CHANGED
@@ -6,78 +6,10 @@
6
6
  /// <reference types="tree-sitter-cli/dsl" />
7
7
  // @ts-check
8
8
 
9
- // Default SWI-Prolog operator table (see `current_op/3` in a fresh `swipl`).
10
- // Prolog priorities run the other way from tree-sitter precedences, so each
11
- // rule uses `1200 - priority`.
12
- //
13
- // Operators at priority 1200 only appear as the neck of a clause, in a
14
- // directive, or inside parentheses; see `clause`, `directive` and `_term_1200`.
15
- const INFIX_HIGH = [
16
- // [priority, type, operators]
17
- // library(chr)
18
- [1190, 'xfx', ['==>', '<=>']],
19
- [1180, 'xfx', ['\\']],
20
- [1150, 'xfx', ['pragma']],
21
- [1105, 'xfy', ['|']],
22
- [1100, 'xfy', [';']],
23
- [1050, 'xfy', ['->', '*->']],
24
- [1000, 'xfy', [',']],
25
- ];
26
-
27
- const INFIX_LOW = [
28
- [990, 'xfx', [':=']],
29
- // library(clpfd)
30
- [760, 'yfx', ['#<==>']],
31
- [750, 'xfy', ['#==>']],
32
- [750, 'yfx', ['#<==']],
33
- [740, 'yfx', ['#\\/']],
34
- [730, 'yfx', ['#\\']],
35
- [720, 'yfx', ['#/\\']],
36
- [700, 'xfx', ['#>', '#<', '#>=', '#=<', '#=', '#\\=', 'in', 'ins', 'in_set']],
37
- [450, 'xfx', ['..']],
38
- [700, 'xfx', [
39
- '=', '\\=', '==', '\\==', '@<', '@>', '@=<', '@>=', '=..', 'is', '=:=',
40
- '=\\=', '<', '>', '=<', '>=', '>:<', ':<', 'as', '=@=', '\\=@=', '?=',
41
- ]],
42
- [600, 'xfy', [':']],
43
- [500, 'yfx', ['+', '-', '/\\', '\\/', 'xor']],
44
- [400, 'yfx', ['*', '/', '//', 'rem', 'mod', 'div', '<<', '>>', 'divmod', 'rdiv']],
45
- [200, 'xfx', ['**']],
46
- [200, 'xfy', ['^']],
47
- // library(semweb/rdf_db) literals: Value^^Type, Text@Lang
48
- [110, 'xfx', ['^^']],
49
- ];
50
-
51
- const PREFIX_HIGH = [
52
- [1150, 'fx', [
53
- 'dynamic', 'discontiguous', 'initialization', 'meta_predicate',
54
- 'module_transparent', 'multifile', 'public', 'thread_local',
55
- 'thread_initialization', 'volatile', 'table',
56
- // declarations from common libraries
57
- 'rdf_meta', 'persistent', 'record', 'html_meta', 'chr_constraint', 'chr_type', 'chr_option',
58
- ]],
59
- ];
60
-
61
- const PREFIX_LOW = [
62
- [900, 'fy', ['\\+']],
63
- [710, 'fy', ['#\\']],
64
- [500, 'fx', ['?']],
65
- [200, 'fy', ['-', '+', '\\']],
66
- [100, 'fx', ['@']],
67
- // library(wasm)
68
- [50, 'fx', ['#']],
69
- [1, 'fx', ['$']],
70
- ];
71
-
72
- const NECKS = [':-', '-->', '=>'];
73
-
74
- // Every operator name. Each one is also a plain atom wherever an operand is
75
- // not expected, e.g. `foo(-, +)` or `X = (=)`.
76
- const OPERATOR_NAMES = [...new Set([
77
- ...NECKS, '?-',
78
- ...[...INFIX_HIGH, ...INFIX_LOW, ...PREFIX_HIGH, ...PREFIX_LOW]
79
- .flatMap(([, , ops]) => ops),
80
- ])].filter(op => op !== ',' && op !== '|');
9
+ import {
10
+ INFIX_HIGH, INFIX_LOW, PREFIX_HIGH, PREFIX_LOW, NECKS, OPERATOR_NAMES,
11
+ UNKNOWN_INFIX_PRIORITY, UNKNOWN_INFIX_RULES,
12
+ } from './grammar/operators.js';
81
13
 
82
14
  const precOf = priority => 1200 - priority;
83
15
 
@@ -104,7 +36,7 @@ export default grammar({
104
36
 
105
37
  extras: $ => [/\s/, $.comment],
106
38
 
107
- externals: $ => [$.end, $._error_sentinel],
39
+ externals: $ => [$.end, $._error_sentinel, $._end_guard],
108
40
 
109
41
  word: $ => $._name,
110
42
 
@@ -160,9 +92,12 @@ export default grammar({
160
92
  // Operators declared with op/3 are not known to the parser. A symbol
161
93
  // atom or a plain name between two operands is read as an infix operator
162
94
  // at 700: two adjacent operands are never valid Prolog otherwise.
163
- prec.left(precOf(700), seq(
95
+ prec.left(precOf(UNKNOWN_INFIX_PRIORITY), seq(
164
96
  field('left', $._arg),
165
- field('operator', alias(choice($._graphic, $._name), $.operator)),
97
+ field('operator', alias(
98
+ choice(...Object.values(UNKNOWN_INFIX_RULES).map(rule => $[rule])),
99
+ $.operator,
100
+ )),
166
101
  field('right', $._arg),
167
102
  )),
168
103
  ),
@@ -190,6 +125,11 @@ export default grammar({
190
125
  ),
191
126
 
192
127
  _primary: $ => choice(
128
+ // Never produced. It makes the scanner run wherever an operand is
129
+ // expected, so a clause terminator there is lexed as `end`, a syntax
130
+ // error, instead of as a `.` atom that swallows the next clauses. The
131
+ // atom after it keeps every field that holds an operand required.
132
+ seq($._end_guard, $.atom),
193
133
  $.atom,
194
134
  $.variable,
195
135
  $.integer,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tree-sitter-swi-prolog",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "SWI-Prolog grammar for tree-sitter",
5
5
  "type": "module",
6
6
  "repository": {
@@ -11,8 +11,8 @@
11
11
  "author": {
12
12
  "name": "Piotr Franczyk"
13
13
  },
14
- "main": "bindings/node",
15
- "types": "bindings/node",
14
+ "main": "bindings/node/index.js",
15
+ "types": "bindings/node/index.d.ts",
16
16
  "keywords": [
17
17
  "incremental",
18
18
  "parsing",
@@ -22,6 +22,7 @@
22
22
  ],
23
23
  "files": [
24
24
  "grammar.js",
25
+ "grammar/*.js",
25
26
  "tree-sitter.json",
26
27
  "binding.gyp",
27
28
  "prebuilds/**",
@@ -36,9 +37,11 @@
36
37
  },
37
38
  "devDependencies": {
38
39
  "@changesets/cli": "^2.31.1",
39
- "kibi-cli": "^2.38.0",
40
- "kibi-core": "^0.27.0",
41
- "kibi-mcp": "^3.22.0",
40
+ "kibi-cli": "^2.40.0",
41
+ "kibi-core": "^0.29.0",
42
+ "kibi-lang-go": "^1.0.0",
43
+ "kibi-mcp": "^3.24.0",
44
+ "kibi-plugin-treesitter": "^3.0.0",
42
45
  "prebuildify": "^6.0.1",
43
46
  "tree-sitter": "^0.25.0",
44
47
  "tree-sitter-cli": "^0.26.7",
@@ -60,5 +63,20 @@
60
63
  "test:corpus": "node scripts/check-corpus.mjs",
61
64
  "version-packages": "node scripts/version-packages.mjs",
62
65
  "changeset": "changeset"
66
+ },
67
+ "kibi": {
68
+ "plugins": [
69
+ {
70
+ "package": "kibi-plugin-treesitter",
71
+ "capabilities": {
72
+ "kibi.reference-extractor.v1": {
73
+ "mode": "augment"
74
+ },
75
+ "kibi.symbol-extractor.v2": {
76
+ "mode": "augment"
77
+ }
78
+ }
79
+ }
80
+ ]
63
81
  }
64
82
  }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @file Tag kinds of queries/tags.scm
3
+ * @license MIT
4
+ */
5
+
6
+ // The kinds of tag queries/tags.scm produces (`definition.function`,
7
+ // `reference.call`, ...), read from the query file itself so they cannot
8
+ // drift from it. The Kibi proof in test/proof/queries.test.js ranges over
9
+ // them.
10
+
11
+ import { readFileSync } from 'node:fs';
12
+
13
+ const source = readFileSync(new URL('./tags.scm', import.meta.url), 'utf8')
14
+ .replace(/;.*$/gm, '');
15
+
16
+ /** Every `@definition.*` and `@reference.*` capture in tags.scm, once each. */
17
+ export const TAG_KINDS = [...new Set(
18
+ [...source.matchAll(/@((?:definition|reference)\.[\w.]+)/g)].map(m => m[1]),
19
+ )];
package/src/grammar.json CHANGED
@@ -2240,6 +2240,19 @@
2240
2240
  "_primary": {
2241
2241
  "type": "CHOICE",
2242
2242
  "members": [
2243
+ {
2244
+ "type": "SEQ",
2245
+ "members": [
2246
+ {
2247
+ "type": "SYMBOL",
2248
+ "name": "_end_guard"
2249
+ },
2250
+ {
2251
+ "type": "SYMBOL",
2252
+ "name": "atom"
2253
+ }
2254
+ ]
2255
+ },
2243
2256
  {
2244
2257
  "type": "SYMBOL",
2245
2258
  "name": "atom"
@@ -3507,6 +3520,10 @@
3507
3520
  {
3508
3521
  "type": "SYMBOL",
3509
3522
  "name": "_error_sentinel"
3523
+ },
3524
+ {
3525
+ "type": "SYMBOL",
3526
+ "name": "_end_guard"
3510
3527
  }
3511
3528
  ],
3512
3529
  "inline": [],