1
use std::collections::BTreeMap;
2
use std::sync::{Mutex, OnceLock};
3

            
4
use tree_sitter::{Node, Parser, Tree};
5
use tree_sitter_essence::LANGUAGE;
6

            
7
use super::traversal::WalkDFS;
8
use crate::diagnostics::diagnostics_api::SymbolKind;
9
use crate::diagnostics::source_map::{HoverInfo, SourceMap, SpanId, span_with_hover};
10
use crate::errors::RecoverableParseError;
11
use conjure_cp_core::ast::{Name, SymbolTablePtr};
12

            
13
/// Context for parsing, containing shared state passed through parser functions.
14
pub struct ParseContext<'a> {
15
    pub source_code: &'a str,
16
    pub root: &'a Node<'a>,
17
    pub symbols: Option<SymbolTablePtr>,
18
    pub errors: &'a mut Vec<RecoverableParseError>,
19
    pub source_map: &'a mut SourceMap,
20
    pub decl_spans: &'a mut BTreeMap<Name, SpanId>,
21
    /// What type the current expression/literal itself should be
22
    pub typechecking_context: TypecheckingContext,
23
    /// What type the elements within a collection should be
24
    pub inner_typechecking_context: TypecheckingContext,
25
}
26

            
27
impl<'a> ParseContext<'a> {
28
1141297
    pub fn new(
29
1141297
        source_code: &'a str,
30
1141297
        root: &'a Node<'a>,
31
1141297
        symbols: Option<SymbolTablePtr>,
32
1141297
        errors: &'a mut Vec<RecoverableParseError>,
33
1141297
        source_map: &'a mut SourceMap,
34
1141297
        decl_spans: &'a mut BTreeMap<Name, SpanId>,
35
1141297
    ) -> Self {
36
1141297
        Self {
37
1141297
            source_code,
38
1141297
            root,
39
1141297
            symbols,
40
1141297
            errors,
41
1141297
            source_map,
42
1141297
            decl_spans,
43
1141297
            typechecking_context: TypecheckingContext::Unknown,
44
1141297
            inner_typechecking_context: TypecheckingContext::Unknown,
45
1141297
        }
46
1141297
    }
47

            
48
1369
    pub fn record_error(&mut self, error: RecoverableParseError) {
49
1369
        self.errors.push(error);
50
1369
    }
51

            
52
    /// Create a new ParseContext with different symbols but sharing source_code, root, errors, and source_map.
53
367173
    pub fn with_new_symbols(&mut self, symbols: Option<SymbolTablePtr>) -> ParseContext<'_> {
54
367173
        ParseContext {
55
367173
            source_code: self.source_code,
56
367173
            root: self.root,
57
367173
            symbols,
58
367173
            errors: self.errors,
59
367173
            source_map: self.source_map,
60
367173
            decl_spans: self.decl_spans,
61
367173
            typechecking_context: self.typechecking_context,
62
367173
            inner_typechecking_context: self.inner_typechecking_context,
63
367173
        }
64
367173
    }
65

            
66
2863141
    pub fn save_decl_span(&mut self, name: Name, span_id: SpanId) {
67
2863141
        self.decl_spans.insert(name, span_id);
68
2863141
    }
69

            
70
5266007
    pub fn lookup_decl_span(&self, name: &Name) -> Option<SpanId> {
71
5266007
        self.decl_spans.get(name).copied()
72
5266007
    }
73

            
74
120
    pub fn lookup_decl_line(&self, name: &Name) -> Option<u32> {
75
120
        let span_id = self.lookup_decl_span(name)?;
76
120
        let span = self.source_map.spans.get(span_id as usize)?;
77
120
        Some(span.start_point.line + 1)
78
120
    }
79

            
80
    /// Helper to add to span and documentation hover info into the source map
81
9363285
    pub fn add_span_and_doc_hover(
82
9363285
        &mut self,
83
9363285
        node: &tree_sitter::Node,
84
9363285
        doc_key: &str, // name of the documentation file in Bits
85
9363285
        kind: SymbolKind,
86
9363285
        ty: Option<String>,
87
9363285
        decl_span: Option<u32>,
88
9363285
    ) {
89
9363285
        let hover = HoverInfo {
90
9363285
            description: String::new(),
91
9363285
            doc_key: Some(normalise_documentation_key(doc_key)),
92
9363285
            kind: Some(kind),
93
9363285
            ty,
94
9363285
            decl_span,
95
9363285
        };
96
9363285
        span_with_hover(node, self.source_code, self.source_map, hover);
97
9363285
    }
98
}
99

            
100
// Used to detect type mismatches during parsing.
101
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102
pub enum TypecheckingContext {
103
    Boolean,
104
    Arithmetic,
105
    Set,
106
    SetOrMatrix,
107
    MSet,
108
    Matrix,
109
    Tuple,
110
    Record,
111
    Partition,
112
    Permutation,
113
    Sequence,
114
    Function,
115
    Relation,
116
    /// Context is unknown or flexible
117
    Unknown,
118
}
119

            
120
/// Parse the given source code into a syntax tree using tree-sitter.
121
///
122
/// If successful, returns a tuple containing the syntax tree and the raw source code.
123
/// If the source code is not valid Essence, returns None.
124
1139963
pub fn get_tree(src: &str) -> Option<(Tree, String)> {
125
1139963
    let mut parser = Parser::new();
126
1139963
    parser.set_language(&LANGUAGE.into()).unwrap();
127

            
128
1139963
    parser.parse(src, None).and_then(|tree| {
129
1139963
        let root = tree.root_node();
130
1139963
        if root.is_error() {
131
            return None;
132
1139963
        }
133
1139963
        Some((tree, src.to_string()))
134
1139963
    })
135
1139963
}
136

            
137
/// Parse an expression fragment, allowing a dummy prefix for error recovery.
138
///
139
/// NOTE: The new source code may be different from the original source code.
140
///       See implementation for details.
141
2676
pub fn get_expr_tree(src: &str) -> Option<(Tree, String)> {
142
2676
    let mut parser = Parser::new();
143
2676
    parser.set_language(&LANGUAGE.into()).unwrap();
144

            
145
2676
    parser.parse(src, None).and_then(|tree| {
146
2676
        let root = tree.root_node();
147
2676
        if root.is_error() {
148
            return None;
149
2676
        }
150

            
151
2676
        let children: Vec<_> = named_children(&root).collect();
152
2676
        let first_child = children.first()?;
153

            
154
        // HACK: Tree-sitter can only parse a complete program from top to bottom, not an individual bit of syntax.
155
        // See: https://github.com/tree-sitter/tree-sitter/issues/711 and linked issues.
156
        // However, we can use a dummy _FRAGMENT_EXPRESSION prefix (which we insert as necessary)
157
        // to trick the parser into accepting an isolated expression.
158
        // This way we can parse an isolated expression and it is only slightly cursed :)
159
2676
        if first_child.is_error() {
160
1338
            if src.starts_with("_FRAGMENT_EXPRESSION") {
161
                None
162
            } else {
163
1338
                get_expr_tree(&format!("_FRAGMENT_EXPRESSION {src}"))
164
            }
165
        } else {
166
1338
            Some((tree, src.to_string()))
167
        }
168
2676
    })
169
2676
}
170

            
171
/// Get the named children of a node
172
12287772
pub fn named_children<'a>(node: &'a Node<'a>) -> impl Iterator<Item = Node<'a>> + 'a {
173
12287772
    (0..node.named_child_count())
174
17001762
        .filter_map(|i| u32::try_from(i).ok().and_then(|i| node.named_child(i)))
175
12287772
}
176

            
177
6694
pub fn node_is_expression(node: &Node) -> bool {
178
6694
    matches!(
179
6694
        node.kind(),
180
        "bool_expr" | "arithmetic_expr" | "comparison_expr" | "annotation_expr" | "atom"
181
    )
182
6694
}
183

            
184
/// Get all top-level nodes that match the given predicate
185
1338
pub fn query_toplevel<'a>(
186
1338
    node: &'a Node<'a>,
187
1338
    predicate: &'a dyn Fn(&Node<'a>) -> bool,
188
1338
) -> impl Iterator<Item = Node<'a>> + 'a {
189
4026
    WalkDFS::with_retract(node, predicate).filter(|n| n.is_named() && predicate(n))
190
1338
}
191

            
192
/// Get all meta-variable names in a node
193
1
pub fn get_metavars<'a>(node: &'a Node<'a>, src: &'a str) -> impl Iterator<Item = String> + 'a {
194
16
    query_toplevel(node, &|n| n.kind() == "metavar").filter_map(|child| {
195
1
        child
196
1
            .named_child(0)
197
1
            .map(|name| src[name.start_byte()..name.end_byte()].to_string())
198
1
    })
199
1
}
200

            
201
/// Fetch Essence syntax documentation from Conjure's `docs/bits/` folder on GitHub.
202
///
203
/// `name` is the name of the documentation file (without .md suffix). If the file is not found or an error occurs, returns None.
204
pub fn get_documentation(name: &str) -> Option<String> {
205
    static DOCUMENTATION_CACHE: OnceLock<Mutex<BTreeMap<String, Option<String>>>> = OnceLock::new();
206

            
207
    let base = normalise_documentation_key(name);
208
    let cache = DOCUMENTATION_CACHE.get_or_init(|| Mutex::new(BTreeMap::new()));
209

            
210
    if let Some(cached) = cache.lock().ok()?.get(&base).cloned() {
211
        return cached;
212
    }
213

            
214
    // This url is for raw Markdown bytes
215
    let url =
216
        format!("https://raw.githubusercontent.com/conjure-cp/conjure/main/docs/bits/{base}.md");
217

            
218
    let output = std::process::Command::new("curl")
219
        .args(["-fsSL", &url])
220
        .output()
221
        .ok();
222

            
223
    let documentation = output
224
        .filter(|output| output.status.success())
225
        .and_then(|output| String::from_utf8(output.stdout).ok());
226

            
227
    if let Ok(mut cache) = cache.lock() {
228
        cache.insert(base, documentation.clone());
229
    }
230

            
231
    documentation
232
}
233

            
234
9363285
fn normalise_documentation_key(name: &str) -> String {
235
9363285
    name.strip_suffix(".md").unwrap_or(name).to_string()
236
9363285
}
237

            
238
mod test {
239
    #[allow(unused)]
240
    use super::*;
241

            
242
    #[test]
243
1
    fn test_get_metavars() {
244
1
        let src = "such that &x = y";
245
1
        let (tree, _) = get_tree(src).unwrap();
246
1
        let root = tree.root_node();
247
1
        let metavars = get_metavars(&root, src).collect::<Vec<_>>();
248
1
        assert_eq!(metavars, vec!["x"]);
249
1
    }
250
}