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