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
/// Parsed find / findAux declarations and whether they are auxiliary.
17
pub struct ParsedFindStatement {
18
    pub declarations: BTreeMap<Name, DomainPtr>,
19
    pub auxiliary: bool,
20
}
21

            
22
1617870
pub fn parse_find_statement(
23
1617870
    ctx: &mut ParseContext,
24
1617870
    find_statement: Node,
25
1617870
) -> Result<ParsedFindStatement, FatalParseError> {
26
1617870
    let Some(keyword) = field!(recover, ctx, find_statement, "find_keyword") else {
27
        return Ok(ParsedFindStatement {
28
            declarations: BTreeMap::new(),
29
            auxiliary: false,
30
        });
31
    };
32

            
33
1617870
    let keyword_text = &ctx.source_code[keyword.start_byte()..keyword.end_byte()];
34
1617870
    let auxiliary = keyword_text == "findAux";
35
1617870
    let (doc_key, symbol_kind, var_kind) = if auxiliary {
36
36
        ("findAux", SymbolKind::Find, SymbolKind::FindVar)
37
    } else {
38
1617834
        ("find", SymbolKind::Find, SymbolKind::FindVar)
39
    };
40
1617870
    ctx.add_span_and_doc_hover(&keyword, doc_key, symbol_kind, None, None);
41

            
42
1617870
    let mut declarations = BTreeMap::new();
43
1617870
    for var_decl in named_children(&find_statement) {
44
1631910
        if let Ok(mut decls) = parse_declaration_statement(ctx, var_decl, var_kind, auxiliary) {
45
1631910
            declarations.append(&mut decls);
46
        }
47
    }
48
1617870
    Ok(ParsedFindStatement {
49
1617870
        declarations,
50
1617870
        auxiliary,
51
1617870
    })
52
1617870
}
53

            
54
187566
pub fn parse_given_statement(
55
187566
    ctx: &mut ParseContext,
56
187566
    given_statement: Node,
57
187566
) -> Result<BTreeMap<Name, DomainPtr>, FatalParseError> {
58
187566
    let Some(keyword) = field!(recover, ctx, given_statement, "given_keyword") else {
59
        return Ok(BTreeMap::new());
60
    };
61
187566
    span_with_hover(
62
187566
        &keyword,
63
187566
        ctx.source_code,
64
187566
        ctx.source_map,
65
187566
        HoverInfo {
66
187566
            description: "Given keyword".to_string(),
67
187566
            doc_key: None,
68
187566
            kind: Some(SymbolKind::Given),
69
187566
            ty: None,
70
187566
            decl_span: None,
71
187566
        },
72
187566
    );
73

            
74
187566
    let mut var_hashmap = BTreeMap::new();
75
187566
    for var_decl in named_children(&given_statement) {
76
187566
        if let Ok(mut decls) =
77
187566
            parse_declaration_statement(ctx, var_decl, SymbolKind::GivenVar, false)
78
        {
79
187566
            var_hashmap.append(&mut decls);
80
        }
81
    }
82
187566
    Ok(var_hashmap)
83
187566
}
84

            
85
1819476
pub fn parse_declaration_statement(
86
1819476
    ctx: &mut ParseContext,
87
1819476
    statement_node: Node,
88
1819476
    symbol_kind: SymbolKind,
89
1819476
    auxiliary_find: bool,
90
1819476
) -> Result<BTreeMap<Name, DomainPtr>, FatalParseError> {
91
1819476
    let mut vars = BTreeMap::new();
92

            
93
1819476
    let Some(domain_node) = field!(recover, ctx, statement_node, "domain") else {
94
24
        return Ok(vars);
95
    };
96

            
97
1819452
    let Some(domain) = parse_domain(ctx, domain_node)? else {
98
144
        return Ok(vars);
99
    };
100

            
101
1819308
    let Some(variable_list) = field!(recover, ctx, statement_node, "variables") else {
102
        return Ok(vars);
103
    };
104
1819308
    for variable in named_children(&variable_list) {
105
        // avoid the _FRAGMENT_EXPRESSION panic by checking range before slicing the source code
106
2464859
        let start = variable.start_byte();
107
2464859
        let end = variable.end_byte();
108
2464859
        if end > ctx.source_code.len() {
109
            ctx.record_error(RecoverableParseError::new(
110
                "Variable name extends beyond end of source code".to_string(),
111
                Some(variable.range()),
112
            ));
113
            continue;
114
2464859
        }
115
2464859
        let variable_name = &ctx.source_code[start..end];
116
2464859
        let name = Name::user(variable_name);
117

            
118
2464859
        if is_keyword_identifier(variable_name) {
119
144
            ctx.errors.push(RecoverableParseError::new(
120
144
                format!("Keyword '{variable_name}' used as identifier"),
121
144
                Some(variable.range()),
122
144
            ));
123
            // still add variable to symbol table to avoid follow-up errors
124
2464715
        }
125

            
126
        // Check for duplicate within the same statement
127
2464859
        if vars.contains_key(&name) {
128
24
            ctx.errors.push(RecoverableParseError::new(
129
24
                format!(
130
                    "Variable '{}' is already declared in this {} statement",
131
24
                    variable_name,
132
24
                    match symbol_kind {
133
24
                        SymbolKind::FindVar if auxiliary_find => "findAux",
134
24
                        SymbolKind::FindVar => "find",
135
                        SymbolKind::GivenVar => "given",
136
                        _ => "declaration",
137
                    }
138
                ),
139
24
                Some(variable.range()),
140
            ));
141
            // don't return here, as we can still add the other variables to the symbol table
142
24
            continue;
143
2464835
        }
144

            
145
        // Check for duplicate declaration across statements
146
2464835
        if let Some(symbols) = &ctx.symbols
147
2464835
            && symbols.read().lookup(&name).is_some()
148
        {
149
48
            let previous_line = ctx.lookup_decl_line(&name);
150
48
            ctx.errors.push(RecoverableParseError::new(
151
48
                match previous_line {
152
48
                    Some(line) => format!(
153
                        "Variable '{}' is already declared in a previous statement on line {}",
154
48
                        variable_name, line
155
                    ),
156
                    None => format!(
157
                        "Variable '{}' is already declared in a previous statement",
158
                        variable_name
159
                    ),
160
                },
161
48
                Some(variable.range()),
162
            ));
163
            // don't return here, as we can still add the other variables to the symbol table
164
48
            continue;
165
2464787
        }
166

            
167
2464787
        vars.insert(name.clone(), domain.clone());
168
        let hover = HoverInfo {
169
2464787
            description: format!(
170
2464787
                "{} variable: {variable_name}",
171
2464787
                match symbol_kind {
172
2263683
                    SymbolKind::FindVar if auxiliary_find => "FindAux",
173
2263647
                    SymbolKind::FindVar => "Find",
174
201104
                    SymbolKind::GivenVar => "Given",
175
                    _ => "Declaration",
176
                }
177
            ),
178
2464787
            doc_key: None,
179
2464787
            kind: Some(symbol_kind),
180
2464787
            ty: Some(domain.to_string()),
181
2464787
            decl_span: None,
182
        };
183
2464787
        let span_id = span_with_hover(&variable, ctx.source_code, ctx.source_map, hover);
184
2464787
        ctx.save_decl_span(name, span_id);
185
    }
186

            
187
1819308
    Ok(vars)
188
1819476
}