quilt-python 0.1.0__tar.gz → 0.2.0__tar.gz
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.
- {quilt_python-0.1.0 → quilt_python-0.2.0}/Cargo.lock +13 -5
- {quilt_python-0.1.0 → quilt_python-0.2.0}/Cargo.toml +3 -3
- {quilt_python-0.1.0 → quilt_python-0.2.0}/PKG-INFO +1 -1
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/lang.rs +31 -1
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/bash/lang.rs +6 -5
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/html/lang.rs +10 -7
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/python/lang.rs +9 -8
- quilt_python-0.2.0/quilt/src/langs/rust/lang.rs +152 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/typescript/lang.rs +8 -7
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/wgsl/lang.rs +8 -6
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/zsh/lang.rs +6 -5
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/multi.rs +4 -5
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/treesitter.rs +20 -4
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/tests/inner_kinds.rs +8 -4
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/tests/kinds.rs +51 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/Cargo.toml +1 -1
- quilt_python-0.1.0/quilt/src/langs/rust/lang.rs +0 -90
- {quilt_python-0.1.0 → quilt_python-0.2.0}/pyproject.toml +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/Cargo.toml +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/__init__.py +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/bin.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/bash/mod.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/bootstrap/lang.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/bootstrap/meta.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/bootstrap/mk_meta.rs.quilt +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/bootstrap/mod.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/bootstrap/strlift.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/html/mod.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/mod.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/omni.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/python/meta.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/python/mod.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/python/ops.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/rust/meta.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/rust/mod.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/rust/ops.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/text/lang.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/text/meta.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/text/mod.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/typescript/meta.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/typescript/mod.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/typescript/ops.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/wgsl/mod.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/zsh/mod.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/lib.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/lift.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/meta.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/node.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/prelude.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/qmatch.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/qterm.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/strcmd.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/term.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/util.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/validate.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/zipper.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/tests/bootstrap.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/tests/expand.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/tests/expand_html.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/tests/expand_python.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/tests/expand_rust.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/tests/expand_typescript.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/tests/expand_wgsl.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/tests/multiline.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/tests/strlift.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/tests/tests.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt-python/.gitignore +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt-python/Cargo.toml +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt-python/src/lib.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt-python/tests/demo.py +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt-python/tests/test_main.py +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/LICENSE-APACHE +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/LICENSE-MIT +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/bindings/rust/build.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/bindings/rust/lib.rs +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/grammar.js +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/queries/highlights.scm +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/src/grammar.json +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/src/node-types.json +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/src/parser.c +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/src/tree_sitter/alloc.h +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/src/tree_sitter/array.h +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/src/tree_sitter/parser.h +0 -0
- {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/tree-sitter.json +0 -0
|
@@ -977,9 +977,17 @@ dependencies = [
|
|
|
977
977
|
"syn",
|
|
978
978
|
]
|
|
979
979
|
|
|
980
|
+
[[package]]
|
|
981
|
+
name = "quilt-expand-wasm"
|
|
982
|
+
version = "0.2.0"
|
|
983
|
+
dependencies = [
|
|
984
|
+
"miette",
|
|
985
|
+
"quiltlang",
|
|
986
|
+
]
|
|
987
|
+
|
|
980
988
|
[[package]]
|
|
981
989
|
name = "quilt-lsp"
|
|
982
|
-
version = "0.
|
|
990
|
+
version = "0.2.0"
|
|
983
991
|
dependencies = [
|
|
984
992
|
"anyhow",
|
|
985
993
|
"dashmap 6.2.1",
|
|
@@ -1002,7 +1010,7 @@ dependencies = [
|
|
|
1002
1010
|
|
|
1003
1011
|
[[package]]
|
|
1004
1012
|
name = "quilt-wasm"
|
|
1005
|
-
version = "0.
|
|
1013
|
+
version = "0.2.0"
|
|
1006
1014
|
dependencies = [
|
|
1007
1015
|
"quiltlang",
|
|
1008
1016
|
"wasm-bindgen",
|
|
@@ -1010,7 +1018,7 @@ dependencies = [
|
|
|
1010
1018
|
|
|
1011
1019
|
[[package]]
|
|
1012
1020
|
name = "quilt_python"
|
|
1013
|
-
version = "0.
|
|
1021
|
+
version = "0.2.0"
|
|
1014
1022
|
dependencies = [
|
|
1015
1023
|
"postcard",
|
|
1016
1024
|
"pyo3",
|
|
@@ -1019,7 +1027,7 @@ dependencies = [
|
|
|
1019
1027
|
|
|
1020
1028
|
[[package]]
|
|
1021
1029
|
name = "quiltlang"
|
|
1022
|
-
version = "0.
|
|
1030
|
+
version = "0.2.0"
|
|
1023
1031
|
dependencies = [
|
|
1024
1032
|
"clap",
|
|
1025
1033
|
"indoc",
|
|
@@ -1602,7 +1610,7 @@ dependencies = [
|
|
|
1602
1610
|
|
|
1603
1611
|
[[package]]
|
|
1604
1612
|
name = "tree-sitter-quilt"
|
|
1605
|
-
version = "0.
|
|
1613
|
+
version = "0.2.0"
|
|
1606
1614
|
dependencies = [
|
|
1607
1615
|
"cc",
|
|
1608
1616
|
"tree-sitter",
|
|
@@ -4,7 +4,7 @@ members = ["quilt", "quilt-python", "tree-sitter-quilt"]
|
|
|
4
4
|
|
|
5
5
|
[workspace.package]
|
|
6
6
|
edition = "2021"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
license = "MIT OR Apache-2.0"
|
|
9
9
|
authors = ["Alexander Varga <varga.alex@gmail.com>"]
|
|
10
10
|
repository = "https://github.com/QuiltLang/quilt"
|
|
@@ -12,10 +12,10 @@ repository = "https://github.com/QuiltLang/quilt"
|
|
|
12
12
|
[workspace.dependencies]
|
|
13
13
|
# Internal crates. The core package is published as `quiltlang` (issue #33);
|
|
14
14
|
# `package =` keeps the `quilt` dependency key so `use quilt::...` is unchanged.
|
|
15
|
-
quilt = {version = "0.
|
|
15
|
+
quilt = {version = "0.2.0", path = "quilt", package = "quiltlang", default-features = false}
|
|
16
16
|
# `version` is required alongside `path` so dependents (quiltlang, quilt-lsp)
|
|
17
17
|
# can be `cargo publish`ed: the registry copy drops `path` and keeps `version`.
|
|
18
|
-
tree-sitter-quilt = {version = "0.
|
|
18
|
+
tree-sitter-quilt = {version = "0.2.0", path = "tree-sitter-quilt"}
|
|
19
19
|
# Grammar crates pulled from their QuiltLang forks (previously git submodules).
|
|
20
20
|
# Each is pinned to an explicit `rev` so the build is reproducible even for
|
|
21
21
|
# library consumers and `cargo install --git … quiltlang` runs that ignore our
|
|
@@ -6,16 +6,46 @@ use std::sync::Arc;
|
|
|
6
6
|
/**************************************************************/
|
|
7
7
|
|
|
8
8
|
/// Kinds of terms. These are sorts of messages for communicating between parsers.
|
|
9
|
+
///
|
|
10
|
+
/// The variants name the grammatical roles a fragment can play. Languages map
|
|
11
|
+
/// their tree-sitter node tags onto these via [`Language::typ`] /
|
|
12
|
+
/// [`Language::classify_term`], and holes record the kind their position
|
|
13
|
+
/// demands via [`TSProvider::hole_kind`](crate::treesitter::TSProvider::hole_kind).
|
|
9
14
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
|
10
15
|
pub enum InnerKind {
|
|
16
|
+
/// An expression — produces a value (e.g. `1 + 2`, a function call).
|
|
11
17
|
Expr,
|
|
18
|
+
/// A statement — runs for its effect and lives among siblings in a block
|
|
19
|
+
/// (e.g. `let x = 1;`, an expression statement).
|
|
12
20
|
Stmt,
|
|
21
|
+
/// An item / top-level declaration (e.g. `fn`, `struct`, `impl`, `use`).
|
|
22
|
+
/// Like [`Stmt`](InnerKind::Stmt) it sits among siblings rather than
|
|
23
|
+
/// producing a value, but it is a definition rather than a runtime
|
|
24
|
+
/// statement. [`InnerKind::is_stmt_like`] groups the two.
|
|
25
|
+
Item,
|
|
26
|
+
/// A brace-delimited block *body* — the `{ … }` of a function, loop, or
|
|
27
|
+
/// branch. Distinct from a block used as a value (which is an
|
|
28
|
+
/// [`Expr`](InnerKind::Expr)); see
|
|
29
|
+
/// [`TSProvider::hole_kind`](crate::treesitter::TSProvider::hole_kind),
|
|
30
|
+
/// which reads the surrounding tree to tell the two apart.
|
|
31
|
+
Block,
|
|
13
32
|
#[default]
|
|
14
|
-
|
|
33
|
+
/// A whole file / module — a sequence of top-level items.
|
|
15
34
|
File,
|
|
16
35
|
// TODO: add more, language specific types, number, function, etc.
|
|
17
36
|
}
|
|
18
37
|
|
|
38
|
+
impl InnerKind {
|
|
39
|
+
/// Whether a fragment of this kind lives in a "variadic" sibling position
|
|
40
|
+
/// (a statement or an item) rather than producing a value. The expander's
|
|
41
|
+
/// emit/splice heuristics treat statements and items alike, so they ask
|
|
42
|
+
/// this instead of comparing against a single variant.
|
|
43
|
+
#[must_use]
|
|
44
|
+
pub fn is_stmt_like(self) -> bool {
|
|
45
|
+
matches!(self, InnerKind::Stmt | InnerKind::Item)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
19
49
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
|
20
50
|
pub enum Arity {
|
|
21
51
|
#[default]
|
|
@@ -10,6 +10,7 @@ use crate::{
|
|
|
10
10
|
qterm::QTerm,
|
|
11
11
|
treesitter::{DynTSLanguage, TSLanguage, TSProvider},
|
|
12
12
|
};
|
|
13
|
+
use miette::Result;
|
|
13
14
|
use tree_sitter::Parser;
|
|
14
15
|
|
|
15
16
|
/**************************************************************/
|
|
@@ -43,21 +44,21 @@ impl TSProvider for BashProvider {
|
|
|
43
44
|
/// Squash the `program` wrapper around a single quoted fragment so the
|
|
44
45
|
/// term is the fragment itself (command / statement). A multi-statement
|
|
45
46
|
/// fragment (a whole script) stays a `program`.
|
|
46
|
-
fn unwrap(&self, qterm: QTerm, _ikind: Option<InnerKind>) -> (QTerm, InnerKind) {
|
|
47
|
+
fn unwrap(&self, qterm: QTerm, _ikind: Option<InnerKind>) -> Result<(QTerm, InnerKind)> {
|
|
47
48
|
let QTerm::Tuple { tag, terms, .. } = &qterm else {
|
|
48
|
-
return (qterm, InnerKind::default());
|
|
49
|
+
return Ok((qterm, InnerKind::default()));
|
|
49
50
|
};
|
|
50
51
|
if &**tag != "program" {
|
|
51
|
-
return (qterm, InnerKind::default());
|
|
52
|
+
return Ok((qterm, InnerKind::default()));
|
|
52
53
|
}
|
|
53
54
|
if terms.len() != 1 {
|
|
54
|
-
return (qterm, InnerKind::File);
|
|
55
|
+
return Ok((qterm, InnerKind::File));
|
|
55
56
|
}
|
|
56
57
|
let kind = match &*terms[0] {
|
|
57
58
|
QTerm::Tuple { tag, .. } if is_expr_tag(tag) => InnerKind::Expr,
|
|
58
59
|
_ => InnerKind::Stmt,
|
|
59
60
|
};
|
|
60
|
-
(qterm.squash(), kind)
|
|
61
|
+
Ok((qterm.squash(), kind))
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
fn arity(&self, tag: &str) -> Arity {
|
|
@@ -14,6 +14,7 @@ use crate::{
|
|
|
14
14
|
qterm::QTerm,
|
|
15
15
|
treesitter::{DynTSLanguage, TSLanguage, TSProvider},
|
|
16
16
|
};
|
|
17
|
+
use miette::Result;
|
|
17
18
|
use tree_sitter::Parser;
|
|
18
19
|
|
|
19
20
|
/**************************************************************/
|
|
@@ -44,24 +45,26 @@ impl TSProvider for HtmlProvider {
|
|
|
44
45
|
/// term is the fragment itself (element / text / …). A multi-node fragment
|
|
45
46
|
/// (a whole page) stays a `document`.
|
|
46
47
|
///
|
|
47
|
-
/// The returned `InnerKind` is advisory only (`parse_pre` discards it);
|
|
48
|
-
///
|
|
49
|
-
|
|
48
|
+
/// The returned `InnerKind` is advisory only (`parse_pre` discards it); like
|
|
49
|
+
/// the WGSL provider, we accept any shape here rather than rejecting
|
|
50
|
+
/// unrecognised ones (only the Rust provider errors on shapes it can't
|
|
51
|
+
/// place).
|
|
52
|
+
fn unwrap(&self, qterm: QTerm, _ikind: Option<InnerKind>) -> Result<(QTerm, InnerKind)> {
|
|
50
53
|
let QTerm::Tuple { tag, terms, .. } = &qterm else {
|
|
51
|
-
return (qterm, InnerKind::default());
|
|
54
|
+
return Ok((qterm, InnerKind::default()));
|
|
52
55
|
};
|
|
53
56
|
if &**tag != "document" {
|
|
54
|
-
return (qterm, InnerKind::default());
|
|
57
|
+
return Ok((qterm, InnerKind::default()));
|
|
55
58
|
}
|
|
56
59
|
if terms.len() != 1 {
|
|
57
60
|
// empty file, or several top-level nodes (a whole page)
|
|
58
|
-
return (qterm, InnerKind::File);
|
|
61
|
+
return Ok((qterm, InnerKind::File));
|
|
59
62
|
}
|
|
60
63
|
let kind = match &*terms[0] {
|
|
61
64
|
QTerm::Tuple { tag, .. } if is_expr_tag(tag) => InnerKind::Expr,
|
|
62
65
|
_ => InnerKind::Stmt,
|
|
63
66
|
};
|
|
64
|
-
(qterm.squash(), kind)
|
|
67
|
+
Ok((qterm.squash(), kind))
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
fn arity(&self, tag: &str) -> Arity {
|
|
@@ -4,6 +4,7 @@ use crate::{
|
|
|
4
4
|
term::Term,
|
|
5
5
|
treesitter::{DynTSLanguage, TSLanguage, TSProvider},
|
|
6
6
|
};
|
|
7
|
+
use miette::Result;
|
|
7
8
|
use tree_sitter::Parser;
|
|
8
9
|
|
|
9
10
|
/**************************************************************/
|
|
@@ -50,9 +51,9 @@ impl TSProvider for PythonProvider {
|
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
fn unwrap(&self, qterm: QTerm, ikind: Option<InnerKind>) -> (QTerm, InnerKind) {
|
|
54
|
+
fn unwrap(&self, qterm: QTerm, ikind: Option<InnerKind>) -> Result<(QTerm, InnerKind)> {
|
|
54
55
|
if qterm.len() != 1 {
|
|
55
|
-
return (qterm, InnerKind::File);
|
|
56
|
+
return Ok((qterm, InnerKind::File));
|
|
56
57
|
}
|
|
57
58
|
let qterm = qterm.squash();
|
|
58
59
|
if qterm.tag() == QTermTag::tuple("expression_statement") {
|
|
@@ -61,28 +62,28 @@ impl TSProvider for PythonProvider {
|
|
|
61
62
|
// the statement whole; bare tuples render without delimiters, so
|
|
62
63
|
// the fragment splices flat into expression position.
|
|
63
64
|
if qterm.len() != 1 {
|
|
64
|
-
return (qterm, InnerKind::Expr);
|
|
65
|
+
return Ok((qterm, InnerKind::Expr));
|
|
65
66
|
}
|
|
66
67
|
let inner = qterm.squash();
|
|
67
68
|
if inner.tag() == QTermTag::tuple("assignment") {
|
|
68
69
|
// An assignment is always a statement, regardless of position.
|
|
69
|
-
return (inner, InnerKind::Stmt);
|
|
70
|
+
return Ok((inner, InnerKind::Stmt));
|
|
70
71
|
}
|
|
71
72
|
// A non-assignment expression statement like `foo()`. When the
|
|
72
73
|
// caller explicitly placed the hole in statement position, honour
|
|
73
74
|
// that: keep the `expression_statement` wrapper and report Stmt.
|
|
74
75
|
// Otherwise treat it as a bare expression and squash to the inner.
|
|
75
76
|
if ikind == Some(InnerKind::Stmt) {
|
|
76
|
-
return (qterm, InnerKind::Stmt);
|
|
77
|
+
return Ok((qterm, InnerKind::Stmt));
|
|
77
78
|
}
|
|
78
|
-
return (inner, InnerKind::Expr);
|
|
79
|
+
return Ok((inner, InnerKind::Expr));
|
|
79
80
|
}
|
|
80
81
|
// If the caller explicitly expected an expression (e.g. the hole was
|
|
81
82
|
// in expression position), trust that over the default Stmt guess.
|
|
82
83
|
if ikind == Some(InnerKind::Expr) {
|
|
83
|
-
return (qterm, InnerKind::Expr);
|
|
84
|
+
return Ok((qterm, InnerKind::Expr));
|
|
84
85
|
}
|
|
85
|
-
(qterm, InnerKind::Stmt)
|
|
86
|
+
Ok((qterm, InnerKind::Stmt))
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
89
|
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
use crate::{
|
|
2
|
+
lang::{Arity, InnerKind},
|
|
3
|
+
qterm::QTerm,
|
|
4
|
+
treesitter::{DynTSLanguage, TSLanguage, TSProvider},
|
|
5
|
+
};
|
|
6
|
+
use miette::{miette, Result};
|
|
7
|
+
use tree_sitter::Parser;
|
|
8
|
+
|
|
9
|
+
/**************************************************************/
|
|
10
|
+
|
|
11
|
+
pub struct RustProvider(tree_sitter::Parser);
|
|
12
|
+
|
|
13
|
+
impl Default for RustProvider {
|
|
14
|
+
fn default() -> Self {
|
|
15
|
+
let mut parser = Parser::new();
|
|
16
|
+
parser
|
|
17
|
+
.set_language(&tree_sitter_rust::LANGUAGE.into())
|
|
18
|
+
.expect("Error loading Rust parser");
|
|
19
|
+
Self(parser)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
impl TSProvider for RustProvider {
|
|
24
|
+
fn parser(&mut self) -> &mut tree_sitter::Parser {
|
|
25
|
+
&mut self.0
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
fn hole_str(&self) -> &'static str {
|
|
29
|
+
"{}"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fn unwrap(&self, qterm: QTerm, ikind: Option<InnerKind>) -> Result<(QTerm, InnerKind)> {
|
|
33
|
+
// dbg!(&qterm);
|
|
34
|
+
#[allow(clippy::single_match)]
|
|
35
|
+
Ok(match &qterm {
|
|
36
|
+
QTerm::Tuple { tag, terms, .. } => match &**tag {
|
|
37
|
+
"source_file" => {
|
|
38
|
+
if terms.len() == 1 {
|
|
39
|
+
let q0 = &terms[0];
|
|
40
|
+
match &**q0 {
|
|
41
|
+
QTerm::Tuple { tag, .. } => {
|
|
42
|
+
// The hole's position (when known) settles
|
|
43
|
+
// ambiguous bodies like a bare `{ }`; otherwise
|
|
44
|
+
// guess from the tag.
|
|
45
|
+
let kind = match ikind {
|
|
46
|
+
Some(k) if k != InnerKind::File => k,
|
|
47
|
+
_ if **tag == *"{}" => InnerKind::Stmt,
|
|
48
|
+
_ => self.typ(tag),
|
|
49
|
+
};
|
|
50
|
+
(qterm.squash(), kind)
|
|
51
|
+
}
|
|
52
|
+
_ => return Err(unsupported_shape(&qterm)),
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
(qterm, InnerKind::File)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
"{}" => (qterm, InnerKind::Expr),
|
|
59
|
+
_ => return Err(unsupported_shape(&qterm)),
|
|
60
|
+
},
|
|
61
|
+
_ => return Err(unsupported_shape(&qterm)),
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fn arity(&self, tag: &str) -> Arity {
|
|
66
|
+
match tag {
|
|
67
|
+
"block" | "source_file" => Arity::Variadic,
|
|
68
|
+
_ => Arity::Unknown,
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fn typ(&self, tag: &str) -> InnerKind {
|
|
73
|
+
match tag {
|
|
74
|
+
"source_file" => InnerKind::File,
|
|
75
|
+
// `let_declaration` ends with "declaration" but is a statement, not
|
|
76
|
+
// an item — match it before the item rule below.
|
|
77
|
+
"let_declaration" => InnerKind::Stmt,
|
|
78
|
+
_ if tag.ends_with("item") || tag.ends_with("declaration") => InnerKind::Item,
|
|
79
|
+
_ if tag.ends_with("statement") => InnerKind::Stmt,
|
|
80
|
+
_ => InnerKind::Expr,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fn hole_kind(&self, node: tree_sitter::Node) -> InnerKind {
|
|
85
|
+
// A `block` is a value (expression) by tag, but in body/branch
|
|
86
|
+
// position it is a block body. Read the parent to tell the two apart:
|
|
87
|
+
// `fn f() {…}` / `loop {…}` use the `body` field, `if c {…}` uses
|
|
88
|
+
// `consequence`, and the `else {…}` block hangs off an `else_clause`.
|
|
89
|
+
// (A `block` in `value` position — `let x = {…}` — stays `Expr`.)
|
|
90
|
+
if node.kind() == "block" {
|
|
91
|
+
if let Some(parent) = node.parent() {
|
|
92
|
+
let is_body = parent.kind() == "else_clause"
|
|
93
|
+
|| ["body", "consequence"]
|
|
94
|
+
.iter()
|
|
95
|
+
.any(|field| parent.child_by_field_name(field) == Some(node));
|
|
96
|
+
if is_body {
|
|
97
|
+
return InnerKind::Block;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
self.typ(node.kind())
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
fn hashbang(&self) -> Option<&'static str> {
|
|
105
|
+
Some("#!/usr/bin/env rust-script")
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Diagnostic for a Rust fragment whose tree-sitter parse shape the provider
|
|
110
|
+
/// doesn't know how to unwrap. Replaces the `unimplemented!` panics that used
|
|
111
|
+
/// to crash the expander on unusual-but-valid Rust (issue #11); the parse's
|
|
112
|
+
/// s-expression is included so the gap can be reported and reproduced.
|
|
113
|
+
fn unsupported_shape(qterm: &QTerm) -> miette::Report {
|
|
114
|
+
miette!(
|
|
115
|
+
"Quilt can't place this Rust fragment — unsupported parse shape: {}.\n\
|
|
116
|
+
This is a gap in Quilt's Rust support; please report it along with the \
|
|
117
|
+
fragment that triggered it.",
|
|
118
|
+
qterm.sexp()
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
pub type RustLanguage = TSLanguage<RustProvider>;
|
|
123
|
+
pub type DynRustLanguage = DynTSLanguage<RustProvider>;
|
|
124
|
+
|
|
125
|
+
#[cfg(test)]
|
|
126
|
+
mod tests {
|
|
127
|
+
use super::*;
|
|
128
|
+
use crate::qterm::tb;
|
|
129
|
+
|
|
130
|
+
/// A parse shape the provider can't place (here, a root tag that is neither
|
|
131
|
+
/// `source_file` nor the `{}` hole) now surfaces a diagnostic instead of
|
|
132
|
+
/// panicking via `unimplemented!` (issue #11).
|
|
133
|
+
#[test]
|
|
134
|
+
fn unsupported_shape_returns_err_not_panic() {
|
|
135
|
+
let provider = RustProvider::default();
|
|
136
|
+
let err = provider
|
|
137
|
+
.unwrap(tb("not_a_real_node_kind").build(), None)
|
|
138
|
+
.expect_err("an unrecognised parse shape should be an error");
|
|
139
|
+
assert!(
|
|
140
|
+
err.to_string().contains("unsupported parse shape"),
|
|
141
|
+
"diagnostic should name the unsupported shape, got: {err}"
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/// A well-formed single-node `source_file` still unwraps successfully.
|
|
146
|
+
#[test]
|
|
147
|
+
fn source_file_single_node_unwraps_ok() {
|
|
148
|
+
let provider = RustProvider::default();
|
|
149
|
+
let qterm = tb("source_file").c(&tb("expression_statement").b()).build();
|
|
150
|
+
assert!(provider.unwrap(qterm, None).is_ok());
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -4,6 +4,7 @@ use crate::{
|
|
|
4
4
|
term::Term,
|
|
5
5
|
treesitter::{DynTSLanguage, TSLanguage, TSProvider},
|
|
6
6
|
};
|
|
7
|
+
use miette::Result;
|
|
7
8
|
use tree_sitter::Parser;
|
|
8
9
|
|
|
9
10
|
/**************************************************************/
|
|
@@ -63,10 +64,10 @@ impl TSProvider for TypeScriptProvider {
|
|
|
63
64
|
/// Python provider. A single top-level `expression_statement` squashes to
|
|
64
65
|
/// its inner expression (so `ts↖foo()↗` splices in expression position);
|
|
65
66
|
/// the hole's known position (`ikind`) settles the ambiguous cases.
|
|
66
|
-
fn unwrap(&self, qterm: QTerm, ikind: Option<InnerKind>) -> (QTerm, InnerKind) {
|
|
67
|
+
fn unwrap(&self, qterm: QTerm, ikind: Option<InnerKind>) -> Result<(QTerm, InnerKind)> {
|
|
67
68
|
// empty file, or several top-level nodes — keep the `program` whole.
|
|
68
69
|
if qterm.len() != 1 {
|
|
69
|
-
return (qterm, InnerKind::File);
|
|
70
|
+
return Ok((qterm, InnerKind::File));
|
|
70
71
|
}
|
|
71
72
|
let qterm = qterm.squash();
|
|
72
73
|
if qterm.tag() == QTermTag::tuple("expression_statement") {
|
|
@@ -74,27 +75,27 @@ impl TSProvider for TypeScriptProvider {
|
|
|
74
75
|
// sequence: keep the statement, but it splices flat as an
|
|
75
76
|
// expression.
|
|
76
77
|
if qterm.len() != 1 {
|
|
77
|
-
return (qterm, InnerKind::Expr);
|
|
78
|
+
return Ok((qterm, InnerKind::Expr));
|
|
78
79
|
}
|
|
79
80
|
let inner = qterm.squash();
|
|
80
81
|
// When the caller explicitly placed the hole in statement position,
|
|
81
82
|
// honour that (keep the `expression_statement`); otherwise treat a
|
|
82
83
|
// bare expression statement as an expression.
|
|
83
84
|
if ikind == Some(InnerKind::Stmt) {
|
|
84
|
-
return (qterm, InnerKind::Stmt);
|
|
85
|
+
return Ok((qterm, InnerKind::Stmt));
|
|
85
86
|
}
|
|
86
|
-
return (inner, InnerKind::Expr);
|
|
87
|
+
return Ok((inner, InnerKind::Expr));
|
|
87
88
|
}
|
|
88
89
|
// A declaration / control-flow statement / other top-level node. Trust
|
|
89
90
|
// an explicit expression expectation over the default Stmt guess.
|
|
90
91
|
if ikind == Some(InnerKind::Expr) {
|
|
91
|
-
return (qterm, InnerKind::Expr);
|
|
92
|
+
return Ok((qterm, InnerKind::Expr));
|
|
92
93
|
}
|
|
93
94
|
let tag = match &qterm {
|
|
94
95
|
QTerm::Tuple { tag, .. } => self.typ(tag),
|
|
95
96
|
_ => InnerKind::Stmt,
|
|
96
97
|
};
|
|
97
|
-
(qterm, tag)
|
|
98
|
+
Ok((qterm, tag))
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
101
|
|
|
@@ -10,6 +10,7 @@ use crate::{
|
|
|
10
10
|
qterm::QTerm,
|
|
11
11
|
treesitter::{DynTSLanguage, TSLanguage, TSProvider},
|
|
12
12
|
};
|
|
13
|
+
use miette::Result;
|
|
13
14
|
use tree_sitter::Parser;
|
|
14
15
|
|
|
15
16
|
/**************************************************************/
|
|
@@ -42,25 +43,26 @@ impl TSProvider for WgslProvider {
|
|
|
42
43
|
///
|
|
43
44
|
/// The returned `InnerKind` is advisory only (`parse_pre` discards it; the
|
|
44
45
|
/// emit heuristic re-derives the kind from the term via `classify_term`).
|
|
45
|
-
/// We
|
|
46
|
-
|
|
46
|
+
/// We accept any shape here rather than rejecting unrecognised ones, unlike
|
|
47
|
+
/// the Rust provider (which errors on shapes it can't place).
|
|
48
|
+
fn unwrap(&self, qterm: QTerm, _ikind: Option<InnerKind>) -> Result<(QTerm, InnerKind)> {
|
|
47
49
|
let QTerm::Tuple { tag, terms, .. } = &qterm else {
|
|
48
|
-
return (qterm, InnerKind::default());
|
|
50
|
+
return Ok((qterm, InnerKind::default()));
|
|
49
51
|
};
|
|
50
52
|
if &**tag != "source_file" {
|
|
51
|
-
return (qterm, InnerKind::default());
|
|
53
|
+
return Ok((qterm, InnerKind::default()));
|
|
52
54
|
}
|
|
53
55
|
if terms.len() != 1 {
|
|
54
56
|
// empty file, or several top-level declarations (a whole shader),
|
|
55
57
|
// or a statement plus its trailing `;` — all kept whole. The
|
|
56
58
|
// statement/`;` shape is recognised by `classify_term` below.
|
|
57
|
-
return (qterm, InnerKind::File);
|
|
59
|
+
return Ok((qterm, InnerKind::File));
|
|
58
60
|
}
|
|
59
61
|
let kind = match &*terms[0] {
|
|
60
62
|
QTerm::Tuple { tag, .. } if is_expr_tag(tag) => InnerKind::Expr,
|
|
61
63
|
_ => InnerKind::Stmt,
|
|
62
64
|
};
|
|
63
|
-
(qterm.squash(), kind)
|
|
65
|
+
Ok((qterm.squash(), kind))
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
fn arity(&self, tag: &str) -> Arity {
|
|
@@ -10,6 +10,7 @@ use crate::{
|
|
|
10
10
|
qterm::QTerm,
|
|
11
11
|
treesitter::{DynTSLanguage, TSLanguage, TSProvider},
|
|
12
12
|
};
|
|
13
|
+
use miette::Result;
|
|
13
14
|
use tree_sitter::Parser;
|
|
14
15
|
|
|
15
16
|
/**************************************************************/
|
|
@@ -43,21 +44,21 @@ impl TSProvider for ZshProvider {
|
|
|
43
44
|
/// Squash the `program` wrapper around a single quoted fragment so the
|
|
44
45
|
/// term is the fragment itself (command / statement). A multi-statement
|
|
45
46
|
/// fragment (a whole script) stays a `program`.
|
|
46
|
-
fn unwrap(&self, qterm: QTerm, _ikind: Option<InnerKind>) -> (QTerm, InnerKind) {
|
|
47
|
+
fn unwrap(&self, qterm: QTerm, _ikind: Option<InnerKind>) -> Result<(QTerm, InnerKind)> {
|
|
47
48
|
let QTerm::Tuple { tag, terms, .. } = &qterm else {
|
|
48
|
-
return (qterm, InnerKind::default());
|
|
49
|
+
return Ok((qterm, InnerKind::default()));
|
|
49
50
|
};
|
|
50
51
|
if &**tag != "program" {
|
|
51
|
-
return (qterm, InnerKind::default());
|
|
52
|
+
return Ok((qterm, InnerKind::default()));
|
|
52
53
|
}
|
|
53
54
|
if terms.len() != 1 {
|
|
54
|
-
return (qterm, InnerKind::File);
|
|
55
|
+
return Ok((qterm, InnerKind::File));
|
|
55
56
|
}
|
|
56
57
|
let kind = match &*terms[0] {
|
|
57
58
|
QTerm::Tuple { tag, .. } if is_expr_tag(tag) => InnerKind::Expr,
|
|
58
59
|
_ => InnerKind::Stmt,
|
|
59
60
|
};
|
|
60
|
-
(qterm.squash(), kind)
|
|
61
|
+
Ok((qterm.squash(), kind))
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
fn arity(&self, tag: &str) -> Arity {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
use crate::lang::{Arity,
|
|
1
|
+
use crate::lang::{Arity, Language, LanguagePost};
|
|
2
2
|
#[cfg(feature = "parse")]
|
|
3
3
|
use crate::lang::{FlatNode, Hole};
|
|
4
4
|
use crate::meta::{MetaLanguage, OuterKind};
|
|
@@ -476,9 +476,8 @@ impl<M: MetaLanguage + ?Sized, LS: Languages> Expander<'_, LS, M> {
|
|
|
476
476
|
..
|
|
477
477
|
} = &**term
|
|
478
478
|
{
|
|
479
|
-
if self.langs.get(self.lang)?.typ(qtag)
|
|
480
|
-
&& self.langs.get(lang2)?.classify_term(body)
|
|
481
|
-
== InnerKind::Stmt
|
|
479
|
+
if self.langs.get(self.lang)?.typ(qtag).is_stmt_like()
|
|
480
|
+
&& self.langs.get(lang2)?.classify_term(body).is_stmt_like()
|
|
482
481
|
{
|
|
483
482
|
okind = OuterKind::Emit;
|
|
484
483
|
}
|
|
@@ -550,7 +549,7 @@ impl<M: MetaLanguage + ?Sized, LS: Languages> Expander<'_, LS, M> {
|
|
|
550
549
|
{
|
|
551
550
|
if index == d {
|
|
552
551
|
if let QTerm::Tuple { tag, .. } = &**child {
|
|
553
|
-
if self.langs.get(lang1)?.typ(tag)
|
|
552
|
+
if self.langs.get(lang1)?.typ(tag).is_stmt_like() {
|
|
554
553
|
okind = OuterKind::Splice;
|
|
555
554
|
}
|
|
556
555
|
}
|
|
@@ -46,8 +46,8 @@ pub trait TSProvider {
|
|
|
46
46
|
/// A string representing a hole where another language is dropped in.
|
|
47
47
|
/// Must not contain new-lines.
|
|
48
48
|
fn hole_str(&self) -> &'static str;
|
|
49
|
-
fn unwrap(&self, qterm: QTerm, _ikind: Option<InnerKind>) -> (QTerm, InnerKind) {
|
|
50
|
-
(qterm, Default::default())
|
|
49
|
+
fn unwrap(&self, qterm: QTerm, _ikind: Option<InnerKind>) -> Result<(QTerm, InnerKind)> {
|
|
50
|
+
Ok((qterm, Default::default()))
|
|
51
51
|
}
|
|
52
52
|
fn arity(&self, _tag: &str) -> Arity {
|
|
53
53
|
Default::default()
|
|
@@ -58,6 +58,19 @@ pub trait TSProvider {
|
|
|
58
58
|
Default::default()
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
/// The [`InnerKind`] a hole at this tree-sitter node's position demands.
|
|
62
|
+
///
|
|
63
|
+
/// Unlike [`typ`](TSProvider::typ), which only sees a node's *kind* (its
|
|
64
|
+
/// tag), this is handed the node in its parse tree, so it can read context
|
|
65
|
+
/// the tag alone can't express. The motivating case is [`InnerKind::Block`]:
|
|
66
|
+
/// a Rust `block` is an expression by tag (`let x = { … }`), but in body
|
|
67
|
+
/// position (`fn f() { … }`, `loop { … }`, `if c { … }`) it denotes a block
|
|
68
|
+
/// body. The default ignores the extra context and falls back to
|
|
69
|
+
/// `typ(node.kind())`.
|
|
70
|
+
fn hole_kind(&self, node: tree_sitter::Node) -> InnerKind {
|
|
71
|
+
self.typ(node.kind())
|
|
72
|
+
}
|
|
73
|
+
|
|
61
74
|
/// Classify a fully-parsed term to determine its kind. Unlike [`typ`],
|
|
62
75
|
/// which only sees the root tag, this can inspect the full term tree.
|
|
63
76
|
/// The default falls back to `typ` on the root tag; override for languages
|
|
@@ -122,7 +135,10 @@ impl<P: TSProvider> Language for TSLanguage<P> {
|
|
|
122
135
|
hole_points.next();
|
|
123
136
|
holes.push(Hole {
|
|
124
137
|
otag: node.kind().into(),
|
|
125
|
-
|
|
138
|
+
// `hole_kind` (not `typ`) so the surrounding tree can refine
|
|
139
|
+
// the kind — e.g. a body-position `block` becomes `Block`
|
|
140
|
+
// rather than the `Expr` its tag alone implies.
|
|
141
|
+
ikind: Some(provider.hole_kind(node)),
|
|
126
142
|
prefix: prefix.clone().into(),
|
|
127
143
|
});
|
|
128
144
|
return qsym(hole_str);
|
|
@@ -262,7 +278,7 @@ impl<P: TSProvider> Language for TSLanguage<P> {
|
|
|
262
278
|
&mut prefix,
|
|
263
279
|
true,
|
|
264
280
|
);
|
|
265
|
-
let (qterm, _ikind) = self.provider.unwrap(qterm, ikind)
|
|
281
|
+
let (qterm, _ikind) = self.provider.unwrap(qterm, ikind)?;
|
|
266
282
|
let holes = holes.into();
|
|
267
283
|
let hole_str = self.provider.hole_str();
|
|
268
284
|
|
|
@@ -14,10 +14,14 @@
|
|
|
14
14
|
//! 3. Python `unwrap` respects `ikind` — when the caller passes an explicit
|
|
15
15
|
//! `InnerKind` hint, Python's `unwrap` uses it instead of guessing.
|
|
16
16
|
//!
|
|
17
|
-
//! (Issue #25
|
|
18
|
-
//!
|
|
19
|
-
//!
|
|
20
|
-
//!
|
|
17
|
+
//! (Issue #25 item #2 — `typ` for target languages — is still deferred: it
|
|
18
|
+
//! needs the emit/splice heuristic to classify a child by its *own* language
|
|
19
|
+
//! rather than the enclosing quote's before it can be added without breaking
|
|
20
|
+
//! the existing splice behaviour. Item #4 — `InnerKind::Block` — has since
|
|
21
|
+
//! landed via issue #10: rather than mapping the `{}` placeholder's tag (which
|
|
22
|
+
//! would poison every Rust expression hole), `TSProvider::hole_kind` reads the
|
|
23
|
+
//! hole's parent in the parse tree, so only body-position blocks become
|
|
24
|
+
//! `Block`. See `rs_block_hole_ikind` in tests/kinds.rs.)
|
|
21
25
|
|
|
22
26
|
use indoc::indoc;
|
|
23
27
|
use quilt::{
|
|
@@ -71,6 +71,57 @@ fn rs_hole_ikinds() -> Result<()> {
|
|
|
71
71
|
Ok(())
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/// `typ` distinguishes items (definitions) from statements and expressions.
|
|
75
|
+
#[test]
|
|
76
|
+
fn rs_typ_kinds() {
|
|
77
|
+
use quilt::lang::Language as _;
|
|
78
|
+
let lang = RustLanguage::default();
|
|
79
|
+
assert_eq!(lang.typ("source_file"), InnerKind::File);
|
|
80
|
+
assert_eq!(lang.typ("function_item"), InnerKind::Item);
|
|
81
|
+
assert_eq!(lang.typ("struct_item"), InnerKind::Item);
|
|
82
|
+
assert_eq!(lang.typ("use_declaration"), InnerKind::Item);
|
|
83
|
+
// `let` is a statement, not an item, despite the "declaration" suffix.
|
|
84
|
+
assert_eq!(lang.typ("let_declaration"), InnerKind::Stmt);
|
|
85
|
+
assert_eq!(lang.typ("expression_statement"), InnerKind::Stmt);
|
|
86
|
+
assert_eq!(lang.typ("binary_expression"), InnerKind::Expr);
|
|
87
|
+
// A `block`'s tag alone is an expression (a block expression).
|
|
88
|
+
assert_eq!(lang.typ("block"), InnerKind::Expr);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// A hole in block-body position is typed `Block`, while a hole in value
|
|
92
|
+
/// position stays `Expr` — even though both are spelled with the same `{}`
|
|
93
|
+
/// placeholder. The distinction comes from `hole_kind` reading the parent.
|
|
94
|
+
#[test]
|
|
95
|
+
fn rs_block_hole_ikind() -> Result<()> {
|
|
96
|
+
use quilt::lang::{FlatNode, LanguagePost as _};
|
|
97
|
+
let mut lang = RustLanguage::default();
|
|
98
|
+
|
|
99
|
+
// `fn f() <hole>` — the hole is the function body block.
|
|
100
|
+
let body = [FlatNode::Str("fn f() "), FlatNode::Hole];
|
|
101
|
+
let kinds = lang
|
|
102
|
+
.parse_pre(None, &body)?
|
|
103
|
+
.holes()
|
|
104
|
+
.iter()
|
|
105
|
+
.map(|h| h.ikind)
|
|
106
|
+
.collect::<Vec<_>>();
|
|
107
|
+
assert_eq!(kinds, [Some(InnerKind::Block)]);
|
|
108
|
+
|
|
109
|
+
// `let x = <hole>;` — the hole is the let value, an expression.
|
|
110
|
+
let value = [
|
|
111
|
+
FlatNode::Str("let x = "),
|
|
112
|
+
FlatNode::Hole,
|
|
113
|
+
FlatNode::Str(";"),
|
|
114
|
+
];
|
|
115
|
+
let kinds = lang
|
|
116
|
+
.parse_pre(None, &value)?
|
|
117
|
+
.holes()
|
|
118
|
+
.iter()
|
|
119
|
+
.map(|h| h.ikind)
|
|
120
|
+
.collect::<Vec<_>>();
|
|
121
|
+
assert_eq!(kinds, [Some(InnerKind::Expr)]);
|
|
122
|
+
Ok(())
|
|
123
|
+
}
|
|
124
|
+
|
|
74
125
|
#[test]
|
|
75
126
|
fn rs_auto_stmt() -> Result<()> {
|
|
76
127
|
rs_helper(None, "let x = 1 + 2;", &QTermTag::tuple("let_declaration"))
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "tree-sitter-quilt"
|
|
3
3
|
description = "Quilt grammar for tree-sitter"
|
|
4
|
-
version = "0.
|
|
4
|
+
version = "0.2.0"
|
|
5
5
|
authors = ["Alexander Varga <varga.alex@gmail.com>"]
|
|
6
6
|
license.workspace = true
|
|
7
7
|
keywords = ["incremental", "parsing", "tree-sitter", "quilt"]
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
use crate::{
|
|
2
|
-
lang::{Arity, InnerKind},
|
|
3
|
-
qterm::QTerm,
|
|
4
|
-
treesitter::{DynTSLanguage, TSLanguage, TSProvider},
|
|
5
|
-
};
|
|
6
|
-
use tree_sitter::Parser;
|
|
7
|
-
|
|
8
|
-
/**************************************************************/
|
|
9
|
-
|
|
10
|
-
pub struct RustProvider(tree_sitter::Parser);
|
|
11
|
-
|
|
12
|
-
impl Default for RustProvider {
|
|
13
|
-
fn default() -> Self {
|
|
14
|
-
let mut parser = Parser::new();
|
|
15
|
-
parser
|
|
16
|
-
.set_language(&tree_sitter_rust::LANGUAGE.into())
|
|
17
|
-
.expect("Error loading Rust parser");
|
|
18
|
-
Self(parser)
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
impl TSProvider for RustProvider {
|
|
23
|
-
fn parser(&mut self) -> &mut tree_sitter::Parser {
|
|
24
|
-
&mut self.0
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
fn hole_str(&self) -> &'static str {
|
|
28
|
-
"{}"
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
fn unwrap(&self, qterm: QTerm, ikind: Option<InnerKind>) -> (QTerm, InnerKind) {
|
|
32
|
-
// dbg!(&qterm);
|
|
33
|
-
#[allow(clippy::single_match)]
|
|
34
|
-
match &qterm {
|
|
35
|
-
QTerm::Tuple { tag, terms, .. } => match &**tag {
|
|
36
|
-
"source_file" => {
|
|
37
|
-
if terms.len() == 1 {
|
|
38
|
-
let q0 = &terms[0];
|
|
39
|
-
match &**q0 {
|
|
40
|
-
QTerm::Tuple { tag, .. } => {
|
|
41
|
-
// The hole's position (when known) settles
|
|
42
|
-
// ambiguous bodies like a bare `{ }`; otherwise
|
|
43
|
-
// guess from the tag.
|
|
44
|
-
let kind = match ikind {
|
|
45
|
-
Some(k) if k != InnerKind::File => k,
|
|
46
|
-
_ if **tag == *"{}" => InnerKind::Stmt,
|
|
47
|
-
_ => self.typ(tag),
|
|
48
|
-
};
|
|
49
|
-
(qterm.squash(), kind)
|
|
50
|
-
}
|
|
51
|
-
_ => unimplemented!("{}", qterm.sexp()),
|
|
52
|
-
}
|
|
53
|
-
} else {
|
|
54
|
-
(qterm, InnerKind::File)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
"{}" => (qterm, InnerKind::Expr),
|
|
58
|
-
_ => unimplemented!("{}", qterm.sexp()),
|
|
59
|
-
},
|
|
60
|
-
_ => unimplemented!("{}", qterm.sexp()),
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
fn arity(&self, tag: &str) -> Arity {
|
|
65
|
-
match tag {
|
|
66
|
-
"block" | "source_file" => Arity::Variadic,
|
|
67
|
-
_ => Arity::Unknown,
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
fn typ(&self, tag: &str) -> InnerKind {
|
|
72
|
-
if tag == "source_file" {
|
|
73
|
-
InnerKind::File
|
|
74
|
-
} else if tag.ends_with("statement")
|
|
75
|
-
|| tag.ends_with("item")
|
|
76
|
-
|| tag.ends_with("declaration")
|
|
77
|
-
{
|
|
78
|
-
InnerKind::Stmt
|
|
79
|
-
} else {
|
|
80
|
-
InnerKind::Expr
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
fn hashbang(&self) -> Option<&'static str> {
|
|
85
|
-
Some("#!/usr/bin/env rust-script")
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
pub type RustLanguage = TSLanguage<RustProvider>;
|
|
90
|
-
pub type DynRustLanguage = DynTSLanguage<RustProvider>;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|