1
#![allow(clippy::legacy_numeric_constants)]
2
use crate::field;
3

            
4
use std::collections::BTreeMap;
5
use tree_sitter::Node;
6

            
7
use super::ParseContext;
8
use super::domain::parse_domain;
9
use super::util::named_children;
10
use crate::diagnostics::diagnostics_api::SymbolKind;
11
use crate::diagnostics::source_map::{HoverInfo, span_with_hover};
12
use crate::errors::{FatalParseError, RecoverableParseError};
13
use conjure_cp_core::ast::{DomainPtr, Name};
14

            
15
38422
pub fn parse_find_statement(
16
38422
    ctx: &mut ParseContext,
17
38422
    find_statement: Node,
18
38422
) -> Result<BTreeMap<Name, DomainPtr>, FatalParseError> {
19
38422
    let Some(keyword) = field!(recover, ctx, find_statement, "find_keyword") else {
20
        return Ok(BTreeMap::new());
21
    };
22
38422
    ctx.add_span_and_doc_hover(&keyword, "find", SymbolKind::Find, None, None);
23
38422
    let mut var_hashmap = BTreeMap::new();
24
39241
    for var_decl in named_children(&find_statement) {
25
39241
        if let Ok(mut decls) = parse_declaration_statement(ctx, var_decl, SymbolKind::FindVar) {
26
39241
            var_hashmap.append(&mut decls);
27
39241
        }
28
    }
29
38422
    Ok(var_hashmap)
30
38422
}
31

            
32
142
pub fn parse_given_statement(
33
142
    ctx: &mut ParseContext,
34
142
    given_statement: Node,
35
142
) -> Result<BTreeMap<Name, DomainPtr>, FatalParseError> {
36
142
    let Some(keyword) = field!(recover, ctx, given_statement, "given_keyword") else {
37
        return Ok(BTreeMap::new());
38
    };
39
142
    span_with_hover(
40
142
        &keyword,
41
142
        ctx.source_code,
42
142
        ctx.source_map,
43
142
        HoverInfo {
44
142
            description: "Given keyword".to_string(),
45
142
            doc_key: None,
46
142
            kind: Some(SymbolKind::Given),
47
142
            ty: None,
48
142
            decl_span: None,
49
142
        },
50
    );
51

            
52
142
    let mut var_hashmap = BTreeMap::new();
53
142
    for var_decl in named_children(&given_statement) {
54
142
        if let Ok(mut decls) = parse_declaration_statement(ctx, var_decl, SymbolKind::GivenVar) {
55
142
            var_hashmap.append(&mut decls);
56
142
        }
57
    }
58
142
    Ok(var_hashmap)
59
142
}
60

            
61
39383
pub fn parse_declaration_statement(
62
39383
    ctx: &mut ParseContext,
63
39383
    statement_node: Node,
64
39383
    symbol_kind: SymbolKind,
65
39383
) -> Result<BTreeMap<Name, DomainPtr>, FatalParseError> {
66
39383
    let mut vars = BTreeMap::new();
67

            
68
39383
    let Some(domain_node) = field!(recover, ctx, statement_node, "domain") else {
69
13
        return Ok(vars);
70
    };
71

            
72
39370
    let Some(domain) = parse_domain(ctx, domain_node)? else {
73
78
        return Ok(vars);
74
    };
75

            
76
39292
    let Some(variable_list) = field!(recover, ctx, statement_node, "variables") else {
77
        return Ok(vars);
78
    };
79
45343
    for variable in named_children(&variable_list) {
80
        // avoid the _FRAGMENT_EXPRESSION panic by checking range before slicing the source code
81
45343
        let start = variable.start_byte();
82
45343
        let end = variable.end_byte();
83
45343
        if end > ctx.source_code.len() {
84
            ctx.record_error(RecoverableParseError::new(
85
                "Variable name extends beyond end of source code".to_string(),
86
                Some(variable.range()),
87
            ));
88
            continue;
89
45343
        }
90
45343
        let variable_name = &ctx.source_code[start..end];
91
45343
        let name = Name::user(variable_name);
92

            
93
        // Check for duplicate within the same statement
94
45343
        if vars.contains_key(&name) {
95
13
            ctx.errors.push(RecoverableParseError::new(
96
13
                format!(
97
                    "Variable '{}' is already declared in this {} statement",
98
                    variable_name,
99
13
                    match symbol_kind {
100
13
                        SymbolKind::FindVar => "find",
101
                        SymbolKind::GivenVar => "given",
102
                        _ => "declaration",
103
                    }
104
                ),
105
13
                Some(variable.range()),
106
            ));
107
            // don't return here, as we can still add the other variables to the symbol table
108
13
            continue;
109
45330
        }
110

            
111
        // Check for duplicate declaration across statements
112
45330
        if let Some(symbols) = &ctx.symbols
113
45330
            && symbols.read().lookup(&name).is_some()
114
        {
115
26
            let previous_line = ctx.lookup_decl_line(&name);
116
26
            ctx.errors.push(RecoverableParseError::new(
117
26
                match previous_line {
118
26
                    Some(line) => format!(
119
                        "Variable '{}' is already declared in a previous statement on line {}",
120
                        variable_name, line
121
                    ),
122
                    None => format!(
123
                        "Variable '{}' is already declared in a previous statement",
124
                        variable_name
125
                    ),
126
                },
127
26
                Some(variable.range()),
128
            ));
129
            // don't return here, as we can still add the other variables to the symbol table
130
26
            continue;
131
45304
        }
132

            
133
45304
        vars.insert(name.clone(), domain.clone());
134
45304
        let hover = HoverInfo {
135
45304
            description: format!(
136
                "{} variable: {variable_name}",
137
45304
                match symbol_kind {
138
45175
                    SymbolKind::FindVar => "Find",
139
129
                    SymbolKind::GivenVar => "Given",
140
                    _ => "Declaration",
141
                }
142
            ),
143
45304
            doc_key: None,
144
45304
            kind: Some(symbol_kind),
145
45304
            ty: Some(domain.to_string()),
146
45304
            decl_span: None,
147
        };
148
45304
        let span_id = span_with_hover(&variable, ctx.source_code, ctx.source_map, hover);
149
45304
        ctx.save_decl_span(name, span_id);
150
    }
151

            
152
39292
    Ok(vars)
153
39383
}