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::Find) {
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            kind: Some(SymbolKind::Given),
46            ty: None,
47            decl_span: None,
48        },
49    );
50
51    let mut var_hashmap = BTreeMap::new();
52    for var_decl in named_children(&given_statement) {
53        if let Ok(mut decls) = parse_declaration_statement(ctx, var_decl, SymbolKind::Given) {
54            var_hashmap.append(&mut decls);
55        }
56    }
57    Ok(var_hashmap)
58}
59
60pub fn parse_declaration_statement(
61    ctx: &mut ParseContext,
62    statement_node: Node,
63    symbol_kind: SymbolKind,
64) -> Result<BTreeMap<Name, DomainPtr>, FatalParseError> {
65    let mut vars = BTreeMap::new();
66
67    let Some(domain_node) = field!(recover, ctx, statement_node, "domain") else {
68        return Ok(vars);
69    };
70
71    let Some(domain) = parse_domain(ctx, domain_node)? else {
72        return Ok(vars);
73    };
74
75    let Some(variable_list) = field!(recover, ctx, statement_node, "variables") else {
76        return Ok(vars);
77    };
78    for variable in named_children(&variable_list) {
79        // avoid the _FRAGMENT_EXPRESSION panic by checking range before slicing the source code
80        let start = variable.start_byte();
81        let end = variable.end_byte();
82        if end > ctx.source_code.len() {
83            ctx.record_error(RecoverableParseError::new(
84                "Variable name extends beyond end of source code".to_string(),
85                Some(variable.range()),
86            ));
87            continue;
88        }
89        let variable_name = &ctx.source_code[start..end];
90        let name = Name::user(variable_name);
91
92        // Check for duplicate within the same statement
93        if vars.contains_key(&name) {
94            ctx.errors.push(RecoverableParseError::new(
95                format!(
96                    "Variable '{}' is already declared in this {} statement",
97                    variable_name,
98                    match symbol_kind {
99                        SymbolKind::Find => "find",
100                        SymbolKind::Given => "given",
101                        _ => "declaration",
102                    }
103                ),
104                Some(variable.range()),
105            ));
106            // don't return here, as we can still add the other variables to the symbol table
107            continue;
108        }
109
110        // Check for duplicate declaration across statements
111        if let Some(symbols) = &ctx.symbols
112            && symbols.read().lookup(&name).is_some()
113        {
114            let previous_line = ctx.lookup_decl_line(&name);
115            ctx.errors.push(RecoverableParseError::new(
116                match previous_line {
117                    Some(line) => format!(
118                        "Variable '{}' is already declared in a previous statement on line {}",
119                        variable_name, line
120                    ),
121                    None => format!(
122                        "Variable '{}' is already declared in a previous statement",
123                        variable_name
124                    ),
125                },
126                Some(variable.range()),
127            ));
128            // don't return here, as we can still add the other variables to the symbol table
129            continue;
130        }
131
132        vars.insert(name.clone(), domain.clone());
133        let hover = HoverInfo {
134            description: format!(
135                "{} variable: {variable_name}",
136                match symbol_kind {
137                    SymbolKind::Find => "Find",
138                    SymbolKind::Given => "Given",
139                    _ => "Declaration",
140                }
141            ),
142            kind: Some(symbol_kind),
143            ty: Some(domain.to_string()),
144            decl_span: None,
145        };
146        let span_id = span_with_hover(&variable, ctx.source_code, ctx.source_map, hover);
147        ctx.save_decl_span(name, span_id);
148    }
149
150    Ok(vars)
151}