tree-sitter-swi-prolog 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +80 -2
- package/binding.gyp +35 -0
- package/bindings/node/binding.cc +19 -0
- package/bindings/node/binding_test.js +11 -0
- package/bindings/node/index.d.ts +60 -0
- package/bindings/node/index.js +37 -0
- package/grammar.js +346 -0
- package/package.json +62 -4
- package/prebuilds/darwin-arm64/tree-sitter-swi-prolog.node +0 -0
- package/prebuilds/darwin-x64/tree-sitter-swi-prolog.node +0 -0
- package/prebuilds/linux-arm64/tree-sitter-swi-prolog.node +0 -0
- package/prebuilds/linux-x64/tree-sitter-swi-prolog.node +0 -0
- package/prebuilds/win32-x64/tree-sitter-swi-prolog.node +0 -0
- package/queries/highlights.scm +107 -0
- package/queries/locals.scm +7 -0
- package/queries/tags.scm +47 -0
- package/src/grammar.json +3515 -0
- package/src/node-types.json +1789 -0
- package/src/parser.c +50987 -0
- package/src/scanner.c +45 -0
- package/src/tree_sitter/alloc.h +54 -0
- package/src/tree_sitter/array.h +330 -0
- package/src/tree_sitter/parser.h +286 -0
- package/tree-sitter-swi_prolog.wasm +0 -0
- package/tree-sitter.json +21 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Piotr Franczyk
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,81 @@
|
|
|
1
|
-
#
|
|
1
|
+
# tree-sitter-swi-prolog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://looted.github.io/tree-sitter-swi-prolog/kibi-report/)
|
|
4
|
+
|
|
5
|
+
[SWI-Prolog](https://www.swi-prolog.org) grammar for [tree-sitter](https://tree-sitter.github.io/tree-sitter/).
|
|
6
|
+
|
|
7
|
+
It parses the Prolog that real SWI-Prolog projects are written in, not only ISO core:
|
|
8
|
+
|
|
9
|
+
- the default SWI-Prolog operator table, including `:` module qualification, `=>` single-sided unification, `=@=`, `>:<`, `:<`, `as`, `$`, and declaration prefixes such as `dynamic`, `discontiguous`, `meta_predicate`, `multifile` and `table`
|
|
10
|
+
- operators from common libraries: `library(clpfd)` (`#=`, `in`, `..`, ...), `library(chr)` (`<=>`, `==>`, `\`), `library(semweb/rdf_db)` (`^^`), `library(wasm)` (`#Value`, `Expr[Index]`) and the `rdf_meta`, `persistent`, `record` and `html_meta` declarations
|
|
11
|
+
- dicts (`_{name: "x"}`, `Tag{...}`) and functional notation on dicts (`D.key`, `D.put(k, V)`)
|
|
12
|
+
- strings, back-quoted strings, `0'c` character codes, radix integers (`16'FF`), digit groups (`1 000 000`), rationals (`1r3`) and special floats (`1.0Inf`)
|
|
13
|
+
- Unicode atoms and variables, multi-line quoted text, `%` and `/* */` comments, and a leading `#!` line
|
|
14
|
+
|
|
15
|
+
Clauses are structured for tools: every top-level term is a `clause` (with `head`, `neck` and `body` fields), a `directive` or a `query`.
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
max(X, Y, X) :- X >= Y, !.
|
|
19
|
+
|
|
20
|
+
(clause
|
|
21
|
+
head: (compound_term functor: (atom) arguments: (arguments (variable) (variable) (variable)))
|
|
22
|
+
neck: (operator)
|
|
23
|
+
body: (binary_operation left: (binary_operation ...) operator: (operator) right: (atom))
|
|
24
|
+
(end))
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Coverage
|
|
28
|
+
|
|
29
|
+
CI parses SWI-Prolog's own `boot/` and `library/` sources and fails if fewer than 99.5% of top-level terms parse without errors. On SWI-Prolog's current `boot/` and `library/` 99.88% of terms parse cleanly, and on the full SWI-Prolog 10.0.2 library with packages (413 files, about 240,000 lines) 99.91%; the rest use prefix operators their files declare themselves.
|
|
30
|
+
|
|
31
|
+
## Limits
|
|
32
|
+
|
|
33
|
+
A tree-sitter grammar has a fixed operator table, so operators declared at load time with `op/3` are not known while parsing:
|
|
34
|
+
|
|
35
|
+
- an unknown infix operator between two operands, symbolic or alphanumeric (for example `X ===> Y` or `X cis Y`), is read as an infix operator at priority 700, so the clause still parses, with the grouping that priority gives
|
|
36
|
+
- unknown prefix and postfix operators produce an `ERROR` node for that term only; the parser recovers at the next clause
|
|
37
|
+
- the parser does not lower operator priorities the way SWI-Prolog does in some argument positions, so `foo(:- a)` or `foo(a|b)` are errors
|
|
38
|
+
|
|
39
|
+
A negative number literal is parsed as the prefix operator `-` applied to the number.
|
|
40
|
+
|
|
41
|
+
## Queries
|
|
42
|
+
|
|
43
|
+
- `queries/highlights.scm`: syntax highlighting
|
|
44
|
+
- `queries/tags.scm`: predicate definitions (clause heads, including module-qualified heads and single-sided-unification heads with guards), module declarations, and calls. `@name` is the predicate name without arity; count the head's arguments for the arity, and add two for DCG rules (neck `-->`)
|
|
45
|
+
- `queries/locals.scm`: variables are local to their clause, directive or query
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
Node, Rust, Python, Go, Swift and C bindings are generated by `tree-sitter init`. For the browser or other WebAssembly hosts, build the parser with:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
npx tree-sitter build --wasm
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
and load `tree-sitter-swi_prolog.wasm` with [`web-tree-sitter`](https://www.npmjs.com/package/web-tree-sitter).
|
|
56
|
+
|
|
57
|
+
## Development
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
npm install
|
|
61
|
+
npx tree-sitter generate
|
|
62
|
+
npx tree-sitter test
|
|
63
|
+
npx tree-sitter build --wasm
|
|
64
|
+
node scripts/check-corpus.mjs path/to/prolog/sources
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`grammar.js` lists the operator tables at the top. To support another library's operators, add them to the matching table with their SWI-Prolog priority and type.
|
|
68
|
+
|
|
69
|
+
## Releasing
|
|
70
|
+
|
|
71
|
+
Releases are made from [changesets](https://github.com/changesets/changesets).
|
|
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.
|
|
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
|
+
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
|
+
|
|
77
|
+
The release step publishes only what is missing for the current version, so if a registry fails, run the **Release** workflow again from the Actions tab. Don't create tags or releases by hand.
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
MIT
|
package/binding.gyp
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"targets": [
|
|
3
|
+
{
|
|
4
|
+
"target_name": "tree_sitter_swi_prolog_binding",
|
|
5
|
+
"dependencies": [
|
|
6
|
+
"<!(node -p \"require('node-addon-api').targets\"):node_addon_api_except",
|
|
7
|
+
],
|
|
8
|
+
"include_dirs": [
|
|
9
|
+
"src",
|
|
10
|
+
],
|
|
11
|
+
"sources": [
|
|
12
|
+
"bindings/node/binding.cc",
|
|
13
|
+
"src/parser.c",
|
|
14
|
+
],
|
|
15
|
+
"variables": {
|
|
16
|
+
"has_scanner": "<!(node -p \"fs.existsSync('src/scanner.c')\")"
|
|
17
|
+
},
|
|
18
|
+
"conditions": [
|
|
19
|
+
["has_scanner=='true'", {
|
|
20
|
+
"sources+": ["src/scanner.c"],
|
|
21
|
+
}],
|
|
22
|
+
["OS!='win'", {
|
|
23
|
+
"cflags_c": [
|
|
24
|
+
"-std=c11",
|
|
25
|
+
],
|
|
26
|
+
}, { # OS == "win"
|
|
27
|
+
"cflags_c": [
|
|
28
|
+
"/std:c11",
|
|
29
|
+
"/utf-8",
|
|
30
|
+
],
|
|
31
|
+
}],
|
|
32
|
+
],
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#include <napi.h>
|
|
2
|
+
|
|
3
|
+
typedef struct TSLanguage TSLanguage;
|
|
4
|
+
|
|
5
|
+
extern "C" TSLanguage *tree_sitter_swi_prolog();
|
|
6
|
+
|
|
7
|
+
// "tree-sitter", "language" hashed with BLAKE2
|
|
8
|
+
const napi_type_tag LANGUAGE_TYPE_TAG = {
|
|
9
|
+
0x8AF2E5212AD58ABF, 0xD5006CAD83ABBA16
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
13
|
+
auto language = Napi::External<TSLanguage>::New(env, tree_sitter_swi_prolog());
|
|
14
|
+
language.TypeTag(&LANGUAGE_TYPE_TAG);
|
|
15
|
+
exports["language"] = language;
|
|
16
|
+
return exports;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
NODE_API_MODULE(tree_sitter_swi_prolog_binding, Init)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
import Parser from "tree-sitter";
|
|
4
|
+
|
|
5
|
+
test("can load grammar", () => {
|
|
6
|
+
const parser = new Parser();
|
|
7
|
+
assert.doesNotReject(async () => {
|
|
8
|
+
const { default: language } = await import("./index.js");
|
|
9
|
+
parser.setLanguage(language);
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
type BaseNode = {
|
|
2
|
+
type: string;
|
|
3
|
+
named: boolean;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
type ChildNode = {
|
|
7
|
+
multiple: boolean;
|
|
8
|
+
required: boolean;
|
|
9
|
+
types: BaseNode[];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type NodeInfo =
|
|
13
|
+
| (BaseNode & {
|
|
14
|
+
subtypes: BaseNode[];
|
|
15
|
+
})
|
|
16
|
+
| (BaseNode & {
|
|
17
|
+
fields: { [name: string]: ChildNode };
|
|
18
|
+
children: ChildNode[];
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The tree-sitter language object for this grammar.
|
|
23
|
+
*
|
|
24
|
+
* @see {@linkcode https://tree-sitter.github.io/node-tree-sitter/interfaces/Parser.Language.html Parser.Language}
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* import Parser from "tree-sitter";
|
|
28
|
+
* import SwiProlog from "tree-sitter-swi-prolog";
|
|
29
|
+
*
|
|
30
|
+
* const parser = new Parser();
|
|
31
|
+
* parser.setLanguage(SwiProlog);
|
|
32
|
+
*/
|
|
33
|
+
declare const binding: {
|
|
34
|
+
/**
|
|
35
|
+
* The inner language object.
|
|
36
|
+
* @private
|
|
37
|
+
*/
|
|
38
|
+
language: unknown;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The content of the `node-types.json` file for this grammar.
|
|
42
|
+
*
|
|
43
|
+
* @see {@linkplain https://tree-sitter.github.io/tree-sitter/using-parsers/6-static-node-types Static Node Types}
|
|
44
|
+
*/
|
|
45
|
+
nodeTypeInfo: NodeInfo[];
|
|
46
|
+
|
|
47
|
+
/** The syntax highlighting query for this grammar. */
|
|
48
|
+
HIGHLIGHTS_QUERY?: string;
|
|
49
|
+
|
|
50
|
+
/** The language injection query for this grammar. */
|
|
51
|
+
INJECTIONS_QUERY?: string;
|
|
52
|
+
|
|
53
|
+
/** The local variable query for this grammar. */
|
|
54
|
+
LOCALS_QUERY?: string;
|
|
55
|
+
|
|
56
|
+
/** The symbol tagging query for this grammar. */
|
|
57
|
+
TAGS_QUERY?: string;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default binding;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
|
|
4
|
+
const root = fileURLToPath(new URL("../..", import.meta.url));
|
|
5
|
+
|
|
6
|
+
const binding = typeof process.versions.bun === "string"
|
|
7
|
+
// Support `bun build --compile` by being statically analyzable enough to find the .node file at build-time
|
|
8
|
+
? await import(`${root}/prebuilds/${process.platform}-${process.arch}/tree-sitter-swi-prolog.node`)
|
|
9
|
+
: (await import("node-gyp-build")).default(root);
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const nodeTypes = await import(`${root}/src/node-types.json`, { with: { type: "json" } });
|
|
13
|
+
binding.nodeTypeInfo = nodeTypes.default;
|
|
14
|
+
} catch { }
|
|
15
|
+
|
|
16
|
+
const queries = [
|
|
17
|
+
["HIGHLIGHTS_QUERY", `${root}/queries/highlights.scm`],
|
|
18
|
+
["INJECTIONS_QUERY", `${root}/queries/injections.scm`],
|
|
19
|
+
["LOCALS_QUERY", `${root}/queries/locals.scm`],
|
|
20
|
+
["TAGS_QUERY", `${root}/queries/tags.scm`],
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
for (const [prop, path] of queries) {
|
|
24
|
+
Object.defineProperty(binding, prop, {
|
|
25
|
+
configurable: true,
|
|
26
|
+
enumerable: true,
|
|
27
|
+
get() {
|
|
28
|
+
delete binding[prop];
|
|
29
|
+
try {
|
|
30
|
+
binding[prop] = readFileSync(path, "utf8");
|
|
31
|
+
} catch { }
|
|
32
|
+
return binding[prop];
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default binding;
|
package/grammar.js
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file SWI-Prolog grammar for tree-sitter
|
|
3
|
+
* @license MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/// <reference types="tree-sitter-cli/dsl" />
|
|
7
|
+
// @ts-check
|
|
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 !== '|');
|
|
81
|
+
|
|
82
|
+
const precOf = priority => 1200 - priority;
|
|
83
|
+
|
|
84
|
+
function infix($, [priority, type, ops], operand) {
|
|
85
|
+
const op = field('operator', choice(...ops.map(o => alias(o, $.operator))));
|
|
86
|
+
const body = seq(field('left', operand), op, field('right', operand));
|
|
87
|
+
const p = precOf(priority);
|
|
88
|
+
return type === 'xfy' ? prec.right(p, body) : prec.left(p, body);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function prefix($, [priority, type, ops], operand) {
|
|
92
|
+
const op = field('operator', choice(...ops.map(o => alias(o, $.operator))));
|
|
93
|
+
const body = seq(op, field('argument', operand));
|
|
94
|
+
const p = precOf(priority);
|
|
95
|
+
return type === 'fy' ? prec.right(p, body) : prec.left(p, body);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const LOWER = /[\p{Ll}]/u.source;
|
|
99
|
+
const UPPER = /[\p{Lu}_]/u.source;
|
|
100
|
+
const ALNUM = /[\p{L}\p{N}_]/u.source;
|
|
101
|
+
|
|
102
|
+
export default grammar({
|
|
103
|
+
name: 'swi_prolog',
|
|
104
|
+
|
|
105
|
+
extras: $ => [/\s/, $.comment],
|
|
106
|
+
|
|
107
|
+
externals: $ => [$.end, $._error_sentinel],
|
|
108
|
+
|
|
109
|
+
word: $ => $._name,
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
rules: {
|
|
113
|
+
source_file: $ => seq(optional($.shebang), repeat(choice(
|
|
114
|
+
$.directive,
|
|
115
|
+
$.query,
|
|
116
|
+
$.clause,
|
|
117
|
+
))),
|
|
118
|
+
|
|
119
|
+
shebang: _ => token(prec(2, /#![^\n]*/)),
|
|
120
|
+
|
|
121
|
+
directive: $ => seq(':-', field('body', $._term), $.end),
|
|
122
|
+
|
|
123
|
+
query: $ => seq('?-', field('body', $._term), $.end),
|
|
124
|
+
|
|
125
|
+
// A fact, rule, DCG rule or single-sided-unification rule. The head of a
|
|
126
|
+
// rule is any term below 1200, which covers `Head, Guard => Body`.
|
|
127
|
+
clause: $ => seq(
|
|
128
|
+
field('head', $._term),
|
|
129
|
+
optional(seq(
|
|
130
|
+
field('neck', choice(...NECKS.map(n => alias(n, $.operator)))),
|
|
131
|
+
field('body', $._term),
|
|
132
|
+
)),
|
|
133
|
+
$.end,
|
|
134
|
+
),
|
|
135
|
+
|
|
136
|
+
// Priority 1199 and below.
|
|
137
|
+
_term: $ => choice(
|
|
138
|
+
$._arg,
|
|
139
|
+
alias($._binary_operation_high, $.binary_operation),
|
|
140
|
+
alias($._prefix_operation_high, $.prefix_operation),
|
|
141
|
+
),
|
|
142
|
+
|
|
143
|
+
_binary_operation_high: $ => choice(
|
|
144
|
+
...INFIX_HIGH.map(op => infix($, op, $._term)),
|
|
145
|
+
),
|
|
146
|
+
|
|
147
|
+
_prefix_operation_high: $ => choice(
|
|
148
|
+
...PREFIX_HIGH.map(op => prefix($, op, $._term)),
|
|
149
|
+
),
|
|
150
|
+
|
|
151
|
+
// Priority 999 and below: list elements and arguments.
|
|
152
|
+
_arg: $ => choice(
|
|
153
|
+
$._primary,
|
|
154
|
+
$.binary_operation,
|
|
155
|
+
$.prefix_operation,
|
|
156
|
+
),
|
|
157
|
+
|
|
158
|
+
binary_operation: $ => choice(
|
|
159
|
+
...INFIX_LOW.map(op => infix($, op, $._arg)),
|
|
160
|
+
// Operators declared with op/3 are not known to the parser. A symbol
|
|
161
|
+
// atom or a plain name between two operands is read as an infix operator
|
|
162
|
+
// at 700: two adjacent operands are never valid Prolog otherwise.
|
|
163
|
+
prec.left(precOf(700), seq(
|
|
164
|
+
field('left', $._arg),
|
|
165
|
+
field('operator', alias(choice($._graphic, $._name), $.operator)),
|
|
166
|
+
field('right', $._arg),
|
|
167
|
+
)),
|
|
168
|
+
),
|
|
169
|
+
|
|
170
|
+
prefix_operation: $ => choice(
|
|
171
|
+
...PREFIX_LOW.map(op => prefix($, op, $._arg)),
|
|
172
|
+
),
|
|
173
|
+
|
|
174
|
+
// Full 1200 term, only reachable inside brackets.
|
|
175
|
+
_term_1200: $ => choice(
|
|
176
|
+
$._term,
|
|
177
|
+
alias($._neck_operation, $.binary_operation),
|
|
178
|
+
alias($._prefix_neck_operation, $.prefix_operation),
|
|
179
|
+
),
|
|
180
|
+
|
|
181
|
+
_neck_operation: $ => prec.left(0, seq(
|
|
182
|
+
field('left', $._term),
|
|
183
|
+
field('operator', choice(...NECKS.map(n => alias(n, $.operator)))),
|
|
184
|
+
field('right', $._term),
|
|
185
|
+
)),
|
|
186
|
+
|
|
187
|
+
_prefix_neck_operation: $ => seq(
|
|
188
|
+
field('operator', alias(choice(':-', '?-'), $.operator)),
|
|
189
|
+
field('argument', $._term),
|
|
190
|
+
),
|
|
191
|
+
|
|
192
|
+
_primary: $ => choice(
|
|
193
|
+
$.atom,
|
|
194
|
+
$.variable,
|
|
195
|
+
$.integer,
|
|
196
|
+
$.float,
|
|
197
|
+
$.rational,
|
|
198
|
+
$.char_code,
|
|
199
|
+
$.string,
|
|
200
|
+
$.back_quoted_string,
|
|
201
|
+
$.compound_term,
|
|
202
|
+
$.list,
|
|
203
|
+
$.curly_term,
|
|
204
|
+
$.dict,
|
|
205
|
+
$.dict_call,
|
|
206
|
+
$.index_term,
|
|
207
|
+
$.parenthesized_term,
|
|
208
|
+
),
|
|
209
|
+
|
|
210
|
+
// `Expr[Index]`, the postfix `[]` operator of library(wasm).
|
|
211
|
+
index_term: $ => prec.left(precOf(40), seq(
|
|
212
|
+
field('object', choice($.variable, $.dict_call, $.index_term)),
|
|
213
|
+
token.immediate('['),
|
|
214
|
+
field('index', $._arg),
|
|
215
|
+
']',
|
|
216
|
+
)),
|
|
217
|
+
|
|
218
|
+
parenthesized_term: $ => seq('(', $._term_1200, ')'),
|
|
219
|
+
|
|
220
|
+
// `foo(...)`: the functor must touch the opening bracket.
|
|
221
|
+
compound_term: $ => seq(
|
|
222
|
+
field('functor', $.atom),
|
|
223
|
+
token.immediate('('),
|
|
224
|
+
field('arguments', $.arguments),
|
|
225
|
+
')',
|
|
226
|
+
),
|
|
227
|
+
|
|
228
|
+
arguments: $ => seq($._arg, repeat(seq(',', $._arg))),
|
|
229
|
+
|
|
230
|
+
list: $ => seq(
|
|
231
|
+
'[',
|
|
232
|
+
$._arg,
|
|
233
|
+
repeat(seq(',', $._arg)),
|
|
234
|
+
optional(seq('|', field('tail', $._arg))),
|
|
235
|
+
']',
|
|
236
|
+
),
|
|
237
|
+
|
|
238
|
+
curly_term: $ => seq('{', $._term_1200, '}'),
|
|
239
|
+
|
|
240
|
+
// `Tag{Key: Value, ...}`
|
|
241
|
+
dict: $ => seq(
|
|
242
|
+
field('tag', choice($.atom, $.variable)),
|
|
243
|
+
token.immediate('{'),
|
|
244
|
+
optional(seq(
|
|
245
|
+
$.dict_pair,
|
|
246
|
+
repeat(seq(',', $.dict_pair)),
|
|
247
|
+
)),
|
|
248
|
+
'}',
|
|
249
|
+
),
|
|
250
|
+
|
|
251
|
+
dict_pair: $ => seq(
|
|
252
|
+
field('key', choice($.atom, $.integer)),
|
|
253
|
+
':',
|
|
254
|
+
field('value', $._arg),
|
|
255
|
+
),
|
|
256
|
+
|
|
257
|
+
// `Dict.key` and `Dict.put(...)`: the dot touches both sides.
|
|
258
|
+
dict_call: $ => prec.left(precOf(100), seq(
|
|
259
|
+
field('dict', choice($.variable, $.atom, $.dict, $.dict_call, $.index_term)),
|
|
260
|
+
token.immediate('.'),
|
|
261
|
+
field('function', choice(
|
|
262
|
+
alias($._immediate_name, $.atom),
|
|
263
|
+
alias($._immediate_variable, $.variable),
|
|
264
|
+
alias($._immediate_quoted_atom, $.atom),
|
|
265
|
+
alias($._immediate_compound, $.compound_term),
|
|
266
|
+
alias($._immediate_integer, $.integer),
|
|
267
|
+
)),
|
|
268
|
+
)),
|
|
269
|
+
|
|
270
|
+
_immediate_name: _ => token.immediate(new RegExp(`${LOWER}${ALNUM}*`, 'u')),
|
|
271
|
+
_immediate_variable: _ => token.immediate(new RegExp(`${UPPER}${ALNUM}*`, 'u')),
|
|
272
|
+
_immediate_quoted_atom: _ => token.immediate(seq(
|
|
273
|
+
'\'',
|
|
274
|
+
repeat(choice(/[^'\\]/, '\'\'', /\\(.|\n)/)),
|
|
275
|
+
'\'',
|
|
276
|
+
)),
|
|
277
|
+
_immediate_integer: _ => token.immediate(/\d+/),
|
|
278
|
+
_immediate_compound: $ => seq(
|
|
279
|
+
field('functor', alias($._immediate_name, $.atom)),
|
|
280
|
+
token.immediate('('),
|
|
281
|
+
field('arguments', $.arguments),
|
|
282
|
+
')',
|
|
283
|
+
),
|
|
284
|
+
|
|
285
|
+
atom: $ => choice(
|
|
286
|
+
$._name,
|
|
287
|
+
$._graphic,
|
|
288
|
+
$._quoted_atom,
|
|
289
|
+
seq('[', ']'),
|
|
290
|
+
seq('{', '}'),
|
|
291
|
+
'!',
|
|
292
|
+
';',
|
|
293
|
+
prec(-10, choice(...OPERATOR_NAMES)),
|
|
294
|
+
),
|
|
295
|
+
|
|
296
|
+
_name: _ => new RegExp(`${LOWER}${ALNUM}*`, 'u'),
|
|
297
|
+
|
|
298
|
+
// Symbol-character atoms that are not in the default operator table.
|
|
299
|
+
// A dot directly before layout is handled by the external `end` token.
|
|
300
|
+
_graphic: _ => token(/[#$&*+\-./:<=>?@^~\\]+/),
|
|
301
|
+
|
|
302
|
+
_quoted_atom: _ => token(seq(
|
|
303
|
+
'\'',
|
|
304
|
+
repeat(choice(/[^'\\]/, '\'\'', /\\(.|\n)/, /\\x[0-9a-fA-F]+\\?/, /\\[0-7]+\\?/)),
|
|
305
|
+
'\'',
|
|
306
|
+
)),
|
|
307
|
+
|
|
308
|
+
variable: _ => new RegExp(`${UPPER}${ALNUM}*`, 'u'),
|
|
309
|
+
|
|
310
|
+
integer: _ => token(choice(
|
|
311
|
+
/\d(_?\d)*/,
|
|
312
|
+
/0x[0-9a-fA-F](_?[0-9a-fA-F])*/,
|
|
313
|
+
/0o[0-7](_?[0-7])*/,
|
|
314
|
+
/0b[01](_?[01])*/,
|
|
315
|
+
/[1-9]\d*'[0-9a-zA-Z]+/,
|
|
316
|
+
// SWI digit groups: 1 000 000
|
|
317
|
+
/\d{1,3}( \d{3})+/,
|
|
318
|
+
)),
|
|
319
|
+
|
|
320
|
+
float: _ => token(choice(
|
|
321
|
+
/\d(_?\d)*\.\d+([eE][+-]?\d+)?(Inf|NaN)?/,
|
|
322
|
+
/\d(_?\d)*[eE][+-]?\d+/,
|
|
323
|
+
)),
|
|
324
|
+
|
|
325
|
+
rational: _ => token(/\d+r\d+/),
|
|
326
|
+
|
|
327
|
+
char_code: _ => token(seq('0\'', choice(/\\(.|x[0-9a-fA-F]+\\?|[0-7]+\\?)/, '\'\'', '\'', /[^\\']/))),
|
|
328
|
+
|
|
329
|
+
string: _ => token(seq(
|
|
330
|
+
'"',
|
|
331
|
+
repeat(choice(/[^"\\]/, '""', /\\(.|\n)/)),
|
|
332
|
+
'"',
|
|
333
|
+
)),
|
|
334
|
+
|
|
335
|
+
back_quoted_string: _ => token(seq(
|
|
336
|
+
'`',
|
|
337
|
+
repeat(choice(/[^`\\]/, '``', /\\(.|\n)/)),
|
|
338
|
+
'`',
|
|
339
|
+
)),
|
|
340
|
+
|
|
341
|
+
comment: _ => token(prec(1, choice(
|
|
342
|
+
seq('%', /.*/),
|
|
343
|
+
seq('/*', /[^*]*\*+([^/*][^*]*\*+)*/, '/'),
|
|
344
|
+
))),
|
|
345
|
+
},
|
|
346
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,64 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tree-sitter-swi-prolog",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "SWI-Prolog grammar for tree-sitter",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/Looted/tree-sitter-swi-prolog.git"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"author": {
|
|
12
|
+
"name": "Piotr Franczyk"
|
|
13
|
+
},
|
|
14
|
+
"main": "bindings/node",
|
|
15
|
+
"types": "bindings/node",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"incremental",
|
|
18
|
+
"parsing",
|
|
19
|
+
"tree-sitter",
|
|
20
|
+
"prolog",
|
|
21
|
+
"swi-prolog"
|
|
22
|
+
],
|
|
23
|
+
"files": [
|
|
24
|
+
"grammar.js",
|
|
25
|
+
"tree-sitter.json",
|
|
26
|
+
"binding.gyp",
|
|
27
|
+
"prebuilds/**",
|
|
28
|
+
"bindings/node/*",
|
|
29
|
+
"queries/*",
|
|
30
|
+
"src/**",
|
|
31
|
+
"*.wasm"
|
|
32
|
+
],
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"node-addon-api": "^8.5.0",
|
|
35
|
+
"node-gyp-build": "^4.8.4"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@changesets/cli": "^2.31.1",
|
|
39
|
+
"kibi-cli": "^2.38.0",
|
|
40
|
+
"kibi-core": "^0.27.0",
|
|
41
|
+
"kibi-mcp": "^3.22.0",
|
|
42
|
+
"prebuildify": "^6.0.1",
|
|
43
|
+
"tree-sitter": "^0.25.0",
|
|
44
|
+
"tree-sitter-cli": "^0.26.7",
|
|
45
|
+
"web-tree-sitter": "^0.27.0"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"tree-sitter": "^0.25.0"
|
|
49
|
+
},
|
|
50
|
+
"peerDependenciesMeta": {
|
|
51
|
+
"tree-sitter": {
|
|
52
|
+
"optional": true
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"install": "node-gyp-build",
|
|
57
|
+
"prestart": "tree-sitter build --wasm",
|
|
58
|
+
"start": "tree-sitter playground",
|
|
59
|
+
"test": "node --test bindings/node/*_test.js",
|
|
60
|
+
"test:corpus": "node scripts/check-corpus.mjs",
|
|
61
|
+
"version-packages": "node scripts/version-packages.mjs",
|
|
62
|
+
"changeset": "changeset"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
Binary file
|