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 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 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 }
101
102 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 continue;
118 }
119
120 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 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}