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
16pub fn parse_find_statement(
17    ctx: &mut ParseContext,
18    find_statement: Node,
19) -> Result<BTreeMap<Name, DomainPtr>, FatalParseError> {
20    let Some(keyword) = field!(recover, ctx, find_statement, "find_keyword") else {
21        return Ok(BTreeMap::new());
22    };
23    ctx.add_span_and_doc_hover(&keyword, "find", SymbolKind::Find, None, None);
24    let mut var_hashmap = BTreeMap::new();
25    for var_decl in named_children(&find_statement) {
26        if let Ok(mut decls) = parse_declaration_statement(ctx, var_decl, SymbolKind::FindVar) {
27            var_hashmap.append(&mut decls);
28        }
29    }
30    Ok(var_hashmap)
31}
32
33pub fn parse_given_statement(
34    ctx: &mut ParseContext,
35    given_statement: Node,
36) -> Result<BTreeMap<Name, DomainPtr>, FatalParseError> {
37    let Some(keyword) = field!(recover, ctx, given_statement, "given_keyword") else {
38        return Ok(BTreeMap::new());
39    };
40    span_with_hover(
41        &keyword,
42        ctx.source_code,
43        ctx.source_map,
44        HoverInfo {
45            description: "Given keyword".to_string(),
46            doc_key: None,
47            kind: Some(SymbolKind::Given),
48            ty: None,
49            decl_span: None,
50        },
51    );
52
53    let mut var_hashmap = BTreeMap::new();
54    for var_decl in named_children(&given_statement) {
55        if let Ok(mut decls) = parse_declaration_statement(ctx, var_decl, SymbolKind::GivenVar) {
56            var_hashmap.append(&mut decls);
57        }
58    }
59    Ok(var_hashmap)
60}
61
62pub fn parse_declaration_statement(
63    ctx: &mut ParseContext,
64    statement_node: Node,
65    symbol_kind: SymbolKind,
66) -> Result<BTreeMap<Name, DomainPtr>, FatalParseError> {
67    let mut vars = BTreeMap::new();
68
69    let Some(domain_node) = field!(recover, ctx, statement_node, "domain") else {
70        return Ok(vars);
71    };
72
73    let Some(domain) = parse_domain(ctx, domain_node)? else {
74        return Ok(vars);
75    };
76
77    let Some(variable_list) = field!(recover, ctx, statement_node, "variables") else {
78        return Ok(vars);
79    };
80    for variable in named_children(&variable_list) {
81        // avoid the _FRAGMENT_EXPRESSION panic by checking range before slicing the source code
82        let start = variable.start_byte();
83        let end = variable.end_byte();
84        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        }
91        let variable_name = &ctx.source_code[start..end];
92        let name = Name::user(variable_name);
93
94        if is_keyword_identifier(variable_name) {
95            ctx.errors.push(RecoverableParseError::new(
96                format!("Keyword '{variable_name}' used as identifier"),
97                Some(variable.range()),
98            ));
99            // still add variable to symbol table to avoid follow-up errors
100        }
101
102        // Check for duplicate within the same statement
103        if vars.contains_key(&name) {
104            ctx.errors.push(RecoverableParseError::new(
105                format!(
106                    "Variable '{}' is already declared in this {} statement",
107                    variable_name,
108                    match symbol_kind {
109                        SymbolKind::FindVar => "find",
110                        SymbolKind::GivenVar => "given",
111                        _ => "declaration",
112                    }
113                ),
114                Some(variable.range()),
115            ));
116            // don't return here, as we can still add the other variables to the symbol table
117            continue;
118        }
119
120        // Check for duplicate declaration across statements
121        if let Some(symbols) = &ctx.symbols
122            && symbols.read().lookup(&name).is_some()
123        {
124            let previous_line = ctx.lookup_decl_line(&name);
125            ctx.errors.push(RecoverableParseError::new(
126                match previous_line {
127                    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                Some(variable.range()),
137            ));
138            // don't return here, as we can still add the other variables to the symbol table
139            continue;
140        }
141
142        vars.insert(name.clone(), domain.clone());
143        let hover = HoverInfo {
144            description: format!(
145                "{} variable: {variable_name}",
146                match symbol_kind {
147                    SymbolKind::FindVar => "Find",
148                    SymbolKind::GivenVar => "Given",
149                    _ => "Declaration",
150                }
151            ),
152            doc_key: None,
153            kind: Some(symbol_kind),
154            ty: Some(domain.to_string()),
155            decl_span: None,
156        };
157        let span_id = span_with_hover(&variable, ctx.source_code, ctx.source_map, hover);
158        ctx.save_decl_span(name, span_id);
159    }
160
161    Ok(vars)
162}