conjure_cp_essence_parser/parser/
letting.rs1#![allow(clippy::legacy_numeric_constants)]
2use crate::field;
3use std::collections::BTreeSet;
4use tree_sitter::Node;
5
6use super::ParseContext;
7use super::domain::parse_domain;
8use super::util::named_children;
9use crate::diagnostics::diagnostics_api::SymbolKind;
10use crate::diagnostics::source_map::{HoverInfo, span_with_hover};
11use crate::errors::{FatalParseError, RecoverableParseError};
12use crate::expression::parse_expression;
13use conjure_cp_core::ast::DeclarationPtr;
14use conjure_cp_core::ast::{Name, SymbolTable};
15
16pub fn parse_letting_statement(
18 ctx: &mut ParseContext,
19 letting_statement: Node,
20) -> Result<Option<SymbolTable>, FatalParseError> {
21 let Some(keyword) = field!(recover, ctx, letting_statement, "letting_keyword") else {
22 return Ok(None);
23 };
24 span_with_hover(
25 &keyword,
26 ctx.source_code,
27 ctx.source_map,
28 HoverInfo {
29 description: "Letting keyword".to_string(),
30 doc_key: None,
31 kind: Some(SymbolKind::Letting),
32 ty: None,
33 decl_span: None,
34 },
35 );
36
37 let mut symbol_table = SymbolTable::new();
38
39 for variable_decl in named_children(&letting_statement) {
40 let mut temp_symbols = BTreeSet::new();
41
42 let Some(variable_list) = field!(recover, ctx, variable_decl, "variable_list") else {
43 return Ok(None);
44 };
45 for variable in named_children(&variable_list) {
46 let variable_name = &ctx.source_code[variable.start_byte()..variable.end_byte()];
47
48 if temp_symbols.contains(variable_name) {
50 ctx.errors.push(RecoverableParseError::new(
51 format!(
52 "Variable '{}' is already declared in this letting statement",
53 variable_name
54 ),
55 Some(variable.range()),
56 ));
57 continue;
59 }
60
61 let name = Name::user(variable_name);
63 if let Some(symbols) = &ctx.symbols
64 && symbols.read().lookup(&name).is_some()
65 {
66 let previous_line = ctx.lookup_decl_line(&name);
67 ctx.errors.push(RecoverableParseError::new(
68 match previous_line {
69 Some(line) => format!(
70 "Variable '{}' is already declared in a previous statement on line {}",
71 variable_name, line
72 ),
73 None => format!(
74 "Variable '{}' is already declared in a previous statement",
75 variable_name
76 ),
77 },
78 Some(variable.range()),
79 ));
80 continue;
82 }
83
84 temp_symbols.insert(variable_name);
85 let hover = HoverInfo {
86 description: format!("Letting variable: {variable_name}"),
87 doc_key: None,
88 kind: Some(SymbolKind::LettingVar),
89 ty: None,
90 decl_span: None,
91 };
92 let span_id = span_with_hover(&variable, ctx.source_code, ctx.source_map, hover);
93 ctx.save_decl_span(name, span_id);
94 }
95
96 let Some(expr_or_domain) = field!(recover, ctx, variable_decl, "expr_or_domain") else {
97 return Ok(None);
98 };
99
100 if variable_decl.child_by_field_name("domain").is_some() {
101 for name in temp_symbols {
102 let Some(domain) = parse_domain(ctx, expr_or_domain)? else {
103 continue;
104 };
105
106 if let Some(entries) = domain.as_record() {
108 for entry in entries {
109 symbol_table.insert(DeclarationPtr::new_record_field(entry.clone()));
111 }
112 }
113
114 symbol_table.insert(DeclarationPtr::new_domain_letting(Name::user(name), domain));
115 }
116 } else {
117 for name in temp_symbols {
118 let Some(expr) = parse_expression(ctx, expr_or_domain)? else {
119 continue;
120 };
121 symbol_table.insert(DeclarationPtr::new_value_letting(Name::user(name), expr));
122 }
123 }
124 }
125
126 Ok(Some(symbol_table))
127}