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 crate::parser::keyword_checks::is_keyword_identifier;
14
use conjure_cp_core::ast::{DomainPtr, Name};
15

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

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

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

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

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

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

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

            
94
45464
        if is_keyword_identifier(variable_name) {
95
78
            ctx.errors.push(RecoverableParseError::new(
96
78
                format!("Keyword '{variable_name}' used as identifier"),
97
78
                Some(variable.range()),
98
78
            ));
99
            // still add variable to symbol table to avoid follow-up errors
100
45386
        }
101

            
102
        // Check for duplicate within the same statement
103
45464
        if vars.contains_key(&name) {
104
13
            ctx.errors.push(RecoverableParseError::new(
105
13
                format!(
106
                    "Variable '{}' is already declared in this {} statement",
107
                    variable_name,
108
13
                    match symbol_kind {
109
13
                        SymbolKind::FindVar => "find",
110
                        SymbolKind::GivenVar => "given",
111
                        _ => "declaration",
112
                    }
113
                ),
114
13
                Some(variable.range()),
115
            ));
116
            // don't return here, as we can still add the other variables to the symbol table
117
13
            continue;
118
45451
        }
119

            
120
        // Check for duplicate declaration across statements
121
45451
        if let Some(symbols) = &ctx.symbols
122
45451
            && symbols.read().lookup(&name).is_some()
123
        {
124
26
            let previous_line = ctx.lookup_decl_line(&name);
125
26
            ctx.errors.push(RecoverableParseError::new(
126
26
                match previous_line {
127
26
                    Some(line) => format!(
128
                        "Variable '{}' is already declared in a previous statement on line {}",
129
                        variable_name, line
130
                    ),
131
                    None => format!(
132
                        "Variable '{}' is already declared in a previous statement",
133
                        variable_name
134
                    ),
135
                },
136
26
                Some(variable.range()),
137
            ));
138
            // don't return here, as we can still add the other variables to the symbol table
139
26
            continue;
140
45425
        }
141

            
142
45425
        vars.insert(name.clone(), domain.clone());
143
45425
        let hover = HoverInfo {
144
45425
            description: format!(
145
                "{} variable: {variable_name}",
146
45425
                match symbol_kind {
147
45206
                    SymbolKind::FindVar => "Find",
148
219
                    SymbolKind::GivenVar => "Given",
149
                    _ => "Declaration",
150
                }
151
            ),
152
45425
            doc_key: None,
153
45425
            kind: Some(symbol_kind),
154
45425
            ty: Some(domain.to_string()),
155
45425
            decl_span: None,
156
        };
157
45425
        let span_id = span_with_hover(&variable, ctx.source_code, ctx.source_map, hover);
158
45425
        ctx.save_decl_span(name, span_id);
159
    }
160

            
161
39461
    Ok(vars)
162
39552
}