Skip to main content

conjure_cp_essence_parser/parser/
find.rs

1#![allow(clippy::legacy_numeric_constants)]
2use crate::field;
3
4use std::collections::BTreeMap;
5use tree_sitter::Node;
6
7use super::ParseContext;
8use super::domain::parse_domain;
9use super::util::named_children;
10use crate::diagnostics::diagnostics_api::SymbolKind;
11use crate::diagnostics::source_map::{HoverInfo, span_with_hover};
12use crate::errors::{FatalParseError, RecoverableParseError};
13use crate::parser::keyword_checks::is_keyword_identifier;
14use conjure_cp_core::ast::{DomainPtr, Name};
15
16/// Parsed find / findAux declarations and whether they are auxiliary.
17pub struct ParsedFindStatement {
18    pub declarations: BTreeMap<Name, DomainPtr>,
19    pub auxiliary: bool,
20}
21
22pub fn parse_find_statement(
23    ctx: &mut ParseContext,
24    find_statement: Node,
25) -> Result<ParsedFindStatement, FatalParseError> {
26    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    let keyword_text = &ctx.source_code[keyword.start_byte()..keyword.end_byte()];
34    let auxiliary = keyword_text == "findAux";
35    let (doc_key, symbol_kind, var_kind) = if auxiliary {
36        ("findAux", SymbolKind::Find, SymbolKind::FindVar)
37    } else {
38        ("find", SymbolKind::Find, SymbolKind::FindVar)
39    };
40    ctx.add_span_and_doc_hover(&keyword, doc_key, symbol_kind, None, None);
41
42    let mut declarations = BTreeMap::new();
43    for var_decl in named_children(&find_statement) {
44        if let Ok(mut decls) = parse_declaration_statement(ctx, var_decl, var_kind, auxiliary) {
45            declarations.append(&mut decls);
46        }
47    }
48    Ok(ParsedFindStatement {
49        declarations,
50        auxiliary,
51    })
52}
53
54pub fn parse_given_statement(
55    ctx: &mut ParseContext,
56    given_statement: Node,
57) -> Result<BTreeMap<Name, DomainPtr>, FatalParseError> {
58    let Some(keyword) = field!(recover, ctx, given_statement, "given_keyword") else {
59        return Ok(BTreeMap::new());
60    };
61    span_with_hover(
62        &keyword,
63        ctx.source_code,
64        ctx.source_map,
65        HoverInfo {
66            description: "Given keyword".to_string(),
67            doc_key: None,
68            kind: Some(SymbolKind::Given),
69            ty: None,
70            decl_span: None,
71        },
72    );
73
74    let mut var_hashmap = BTreeMap::new();
75    for var_decl in named_children(&given_statement) {
76        if let Ok(mut decls) =
77            parse_declaration_statement(ctx, var_decl, SymbolKind::GivenVar, false)
78        {
79            var_hashmap.append(&mut decls);
80        }
81    }
82    Ok(var_hashmap)
83}
84
85pub fn parse_declaration_statement(
86    ctx: &mut ParseContext,
87    statement_node: Node,
88    symbol_kind: SymbolKind,
89    auxiliary_find: bool,
90) -> Result<BTreeMap<Name, DomainPtr>, FatalParseError> {
91    let mut vars = BTreeMap::new();
92
93    let Some(domain_node) = field!(recover, ctx, statement_node, "domain") else {
94        return Ok(vars);
95    };
96
97    let Some(domain) = parse_domain(ctx, domain_node)? else {
98        return Ok(vars);
99    };
100
101    let Some(variable_list) = field!(recover, ctx, statement_node, "variables") else {
102        return Ok(vars);
103    };
104    for variable in named_children(&variable_list) {
105        // avoid the _FRAGMENT_EXPRESSION panic by checking range before slicing the source code
106        let start = variable.start_byte();
107        let end = variable.end_byte();
108        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        }
115        let variable_name = &ctx.source_code[start..end];
116        let name = Name::user(variable_name);
117
118        if is_keyword_identifier(variable_name) {
119            ctx.errors.push(RecoverableParseError::new(
120                format!("Keyword '{variable_name}' used as identifier"),
121                Some(variable.range()),
122            ));
123            // still add variable to symbol table to avoid follow-up errors
124        }
125
126        // Check for duplicate within the same statement
127        if vars.contains_key(&name) {
128            ctx.errors.push(RecoverableParseError::new(
129                format!(
130                    "Variable '{}' is already declared in this {} statement",
131                    variable_name,
132                    match symbol_kind {
133                        SymbolKind::FindVar if auxiliary_find => "findAux",
134                        SymbolKind::FindVar => "find",
135                        SymbolKind::GivenVar => "given",
136                        _ => "declaration",
137                    }
138                ),
139                Some(variable.range()),
140            ));
141            // don't return here, as we can still add the other variables to the symbol table
142            continue;
143        }
144
145        // Check for duplicate declaration across statements
146        if let Some(symbols) = &ctx.symbols
147            && symbols.read().lookup(&name).is_some()
148        {
149            let previous_line = ctx.lookup_decl_line(&name);
150            ctx.errors.push(RecoverableParseError::new(
151                match previous_line {
152                    Some(line) => format!(
153                        "Variable '{}' is already declared in a previous statement on line {}",
154                        variable_name, line
155                    ),
156                    None => format!(
157                        "Variable '{}' is already declared in a previous statement",
158                        variable_name
159                    ),
160                },
161                Some(variable.range()),
162            ));
163            // don't return here, as we can still add the other variables to the symbol table
164            continue;
165        }
166
167        vars.insert(name.clone(), domain.clone());
168        let hover = HoverInfo {
169            description: format!(
170                "{} variable: {variable_name}",
171                match symbol_kind {
172                    SymbolKind::FindVar if auxiliary_find => "FindAux",
173                    SymbolKind::FindVar => "Find",
174                    SymbolKind::GivenVar => "Given",
175                    _ => "Declaration",
176                }
177            ),
178            doc_key: None,
179            kind: Some(symbol_kind),
180            ty: Some(domain.to_string()),
181            decl_span: None,
182        };
183        let span_id = span_with_hover(&variable, ctx.source_code, ctx.source_map, hover);
184        ctx.save_decl_span(name, span_id);
185    }
186
187    Ok(vars)
188}