Skip to main content

larvae_worm/
node.rs

1//! The guest side of the node API, so a rule reads an ordinary type and not offsets
2
3use crate::abi;
4
5#[link(wasm_import_module = "larvae")]
6unsafe extern "C" {
7    #[link_name = "node_kind"]
8    safe fn host_node_kind(epoch: u64, id: u32) -> i64;
9    #[link_name = "node_text"]
10    safe fn host_node_text(epoch: u64, id: u32) -> i64;
11    #[link_name = "node_span_start"]
12    safe fn host_span_start(epoch: u64, id: u32) -> i64;
13    #[link_name = "node_span_end"]
14    safe fn host_span_end(epoch: u64, id: u32) -> i64;
15    #[link_name = "node_parent"]
16    safe fn host_parent(epoch: u64, id: u32) -> i64;
17    #[link_name = "node_child_count"]
18    safe fn host_child_count(epoch: u64, id: u32) -> i64;
19    #[link_name = "node_child"]
20    safe fn host_child(epoch: u64, id: u32, index: u32) -> i64;
21    #[link_name = "take_str"]
22    safe fn host_take_str(ptr: u32, len: u32) -> i64;
23    #[link_name = "replace"]
24    safe fn host_replace(epoch: u64, id: u32, ptr: u32, len: u32) -> i64;
25    #[link_name = "remove"]
26    safe fn host_remove(epoch: u64, id: u32) -> i64;
27}
28
29/// A handle to one node of the larvae AST, valid only for the file it came from
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct Node {
32    epoch: u64,
33    id: u32,
34}
35
36impl Node {
37    /// Rebuild a handle that the host named. Only the generated exports call this.
38    #[doc(hidden)]
39    pub fn from_raw(epoch: u64, id: u32) -> Self {
40        Self { epoch, id }
41    }
42
43    /// The kind of this node, for example `"CallExpr"`
44    pub fn kind(&self) -> String {
45        pull(host_node_kind(self.epoch, self.id))
46    }
47
48    /// The source text that this node covers
49    pub fn text(&self) -> String {
50        pull(host_node_text(self.epoch, self.id))
51    }
52
53    /// Byte offsets into the original source, as a half open range
54    pub fn span(&self) -> (u32, u32) {
55        let start = host_span_start(self.epoch, self.id).max(0) as u32;
56        let end = host_span_end(self.epoch, self.id).max(0) as u32;
57
58        (start, end)
59    }
60
61    /// The node that contains this one. Only the root has none.
62    pub fn parent(&self) -> Option<Node> {
63        match host_parent(self.epoch, self.id) {
64            id if id < 0 => None,
65
66            id => Some(Node::from_raw(self.epoch, id as u32)),
67        }
68    }
69
70    /// The direct children, in source order
71    pub fn children(&self) -> Vec<Node> {
72        let count = host_child_count(self.epoch, self.id).max(0) as u32;
73
74        (0..count)
75            .filter_map(|i| match host_child(self.epoch, self.id, i) {
76                id if id < 0 => None,
77
78                id => Some(Node::from_raw(self.epoch, id as u32)),
79            })
80            .collect()
81    }
82
83    /// Queue a replacement of the bytes of this node
84    pub fn replace(&self, text: &str) -> bool {
85        host_replace(self.epoch, self.id, text.as_ptr() as u32, text.len() as u32) >= 0
86    }
87
88    /// Queue a removal. larvae keeps the newlines, so the line counts hold.
89    pub fn remove(&self) -> bool {
90        host_remove(self.epoch, self.id) >= 0
91    }
92}
93
94/*
95An accessor stages its text on the host side and returns a length, because a
96wasm function returns one number. The guest allocates that many bytes and asks
97for the copy. Thus the host needs no allocator on the guest side of the
98boundary.
99*/
100fn pull(len: i64) -> String {
101    if len <= 0 {
102        return String::new();
103    }
104
105    let len = len as u32;
106    let buf = abi::alloc(len);
107    let written = host_take_str(buf as u32, len);
108
109    if written < 0 {
110        // SAFETY: buf came from alloc with exactly len bytes, and no code uses it
111        unsafe { abi::dealloc(buf, len) };
112
113        return String::new();
114    }
115
116    // SAFETY: the host wrote `written` bytes of a &str that it held, so this is utf-8
117    unsafe {
118        let bytes = std::slice::from_raw_parts(buf, written as usize).to_vec();
119        abi::dealloc(buf, len);
120
121        String::from_utf8_unchecked(bytes)
122    }
123}