conjure_cp_essence_parser/parser/
find.rs1#![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 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 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 }
125
126 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 continue;
143 }
144
145 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 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}