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.
Files changed (84) hide show
  1. {quilt_python-0.1.0 → quilt_python-0.2.0}/Cargo.lock +13 -5
  2. {quilt_python-0.1.0 → quilt_python-0.2.0}/Cargo.toml +3 -3
  3. {quilt_python-0.1.0 → quilt_python-0.2.0}/PKG-INFO +1 -1
  4. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/lang.rs +31 -1
  5. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/bash/lang.rs +6 -5
  6. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/html/lang.rs +10 -7
  7. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/python/lang.rs +9 -8
  8. quilt_python-0.2.0/quilt/src/langs/rust/lang.rs +152 -0
  9. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/typescript/lang.rs +8 -7
  10. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/wgsl/lang.rs +8 -6
  11. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/zsh/lang.rs +6 -5
  12. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/multi.rs +4 -5
  13. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/treesitter.rs +20 -4
  14. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/tests/inner_kinds.rs +8 -4
  15. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/tests/kinds.rs +51 -0
  16. {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/Cargo.toml +1 -1
  17. quilt_python-0.1.0/quilt/src/langs/rust/lang.rs +0 -90
  18. {quilt_python-0.1.0 → quilt_python-0.2.0}/pyproject.toml +0 -0
  19. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/Cargo.toml +0 -0
  20. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/__init__.py +0 -0
  21. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/bin.rs +0 -0
  22. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/bash/mod.rs +0 -0
  23. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/bootstrap/lang.rs +0 -0
  24. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/bootstrap/meta.rs +0 -0
  25. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/bootstrap/mk_meta.rs.quilt +0 -0
  26. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/bootstrap/mod.rs +0 -0
  27. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/bootstrap/strlift.rs +0 -0
  28. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/html/mod.rs +0 -0
  29. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/mod.rs +0 -0
  30. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/omni.rs +0 -0
  31. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/python/meta.rs +0 -0
  32. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/python/mod.rs +0 -0
  33. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/python/ops.rs +0 -0
  34. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/rust/meta.rs +0 -0
  35. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/rust/mod.rs +0 -0
  36. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/rust/ops.rs +0 -0
  37. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/text/lang.rs +0 -0
  38. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/text/meta.rs +0 -0
  39. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/text/mod.rs +0 -0
  40. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/typescript/meta.rs +0 -0
  41. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/typescript/mod.rs +0 -0
  42. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/typescript/ops.rs +0 -0
  43. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/wgsl/mod.rs +0 -0
  44. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/langs/zsh/mod.rs +0 -0
  45. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/lib.rs +0 -0
  46. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/lift.rs +0 -0
  47. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/meta.rs +0 -0
  48. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/node.rs +0 -0
  49. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/prelude.rs +0 -0
  50. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/qmatch.rs +0 -0
  51. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/qterm.rs +0 -0
  52. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/strcmd.rs +0 -0
  53. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/term.rs +0 -0
  54. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/util.rs +0 -0
  55. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/validate.rs +0 -0
  56. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/src/zipper.rs +0 -0
  57. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/tests/bootstrap.rs +0 -0
  58. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/tests/expand.rs +0 -0
  59. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/tests/expand_html.rs +0 -0
  60. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/tests/expand_python.rs +0 -0
  61. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/tests/expand_rust.rs +0 -0
  62. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/tests/expand_typescript.rs +0 -0
  63. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/tests/expand_wgsl.rs +0 -0
  64. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/tests/multiline.rs +0 -0
  65. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/tests/strlift.rs +0 -0
  66. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt/tests/tests.rs +0 -0
  67. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt-python/.gitignore +0 -0
  68. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt-python/Cargo.toml +0 -0
  69. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt-python/src/lib.rs +0 -0
  70. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt-python/tests/demo.py +0 -0
  71. {quilt_python-0.1.0 → quilt_python-0.2.0}/quilt-python/tests/test_main.py +0 -0
  72. {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/LICENSE-APACHE +0 -0
  73. {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/LICENSE-MIT +0 -0
  74. {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/bindings/rust/build.rs +0 -0
  75. {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/bindings/rust/lib.rs +0 -0
  76. {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/grammar.js +0 -0
  77. {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/queries/highlights.scm +0 -0
  78. {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/src/grammar.json +0 -0
  79. {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/src/node-types.json +0 -0
  80. {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/src/parser.c +0 -0
  81. {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/src/tree_sitter/alloc.h +0 -0
  82. {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/src/tree_sitter/array.h +0 -0
  83. {quilt_python-0.1.0 → quilt_python-0.2.0}/tree-sitter-quilt/src/tree_sitter/parser.h +0 -0
  84. {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.1.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.1.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.1.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.1.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.1.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.1.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.1.0", path = "quilt", package = "quiltlang", default-features = false}
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.1.0", path = "tree-sitter-quilt"}
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quilt-python
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
@@ -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
- // TODO: rename or also add `Block`
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); we
48
- /// never panic on an unexpected shape, like the WGSL provider.
49
- fn unwrap(&self, qterm: QTerm, _ikind: Option<InnerKind>) -> (QTerm, InnerKind) {
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 never panic on an unexpected shape, unlike the Rust provider.
46
- fn unwrap(&self, qterm: QTerm, _ikind: Option<InnerKind>) -> (QTerm, InnerKind) {
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, InnerKind, Language, LanguagePost};
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) == InnerKind::Stmt
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) == InnerKind::Stmt {
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
- ikind: Some(provider.typ(node.kind())),
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 items #2 `typ` for target languages and #4 `InnerKind::Block`
18
- //! are deferred: both need the emit/splice heuristic to classify a child by its
19
- //! *own* language rather than the enclosing quote's before they can be added
20
- //! without breaking the existing splice behaviour.)
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.1.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>;