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 kind: Some(SymbolKind::Letting),
31 ty: None,
32 decl_span: None,
33 },
34 );
35
36 let mut symbol_table = SymbolTable::new();
37
38 for variable_decl in named_children(&letting_statement) {
39 let mut temp_symbols = BTreeSet::new();
40
41 let Some(variable_list) = field!(recover, ctx, variable_decl, "variable_list") else {
42 return Ok(None);
43 };
44 for variable in named_children(&variable_list) {
45 let variable_name = &ctx.source_code[variable.start_byte()..variable.end_byte()];
46
47 if temp_symbols.contains(variable_name) {
49 ctx.errors.push(RecoverableParseError::new(
50 format!(
51 "Variable '{}' is already declared in this letting statement",
52 variable_name
53 ),
54 Some(variable.range()),
55 ));
56 continue;
58 }
59
60 let name = Name::user(variable_name);
62 if let Some(symbols) = &ctx.symbols
63 && symbols.read().lookup(&name).is_some()
64 {
65 let previous_line = ctx.lookup_decl_line(&name);
66 ctx.errors.push(RecoverableParseError::new(
67 match previous_line {
68 Some(line) => format!(
69 "Variable '{}' is already declared in a previous statement on line {}",
70 variable_name, line
71 ),
72 None => format!(
73 "Variable '{}' is already declared in a previous statement",
74 variable_name
75 ),
76 },
77 Some(variable.range()),
78 ));
79 continue;
81 }
82
83 temp_symbols.insert(variable_name);
84 let hover = HoverInfo {
85 description: format!("Letting variable: {variable_name}"),
86 kind: Some(SymbolKind::Letting),
87 ty: None,
88 decl_span: None,
89 };
90 let span_id = span_with_hover(&variable, ctx.source_code, ctx.source_map, hover);
91 ctx.save_decl_span(name, span_id);
92 }
93
94 let Some(expr_or_domain) = field!(recover, ctx, variable_decl, "expr_or_domain") else {
95 return Ok(None);
96 };
97
98 if variable_decl.child_by_field_name("domain").is_some() {
99 for name in temp_symbols {
100 let Some(domain) = parse_domain(ctx, expr_or_domain)? else {
101 continue;
102 };
103
104 if let Some(entries) = domain.as_record() {
106 for entry in entries {
107 symbol_table.insert(DeclarationPtr::new_record_field(entry.clone()));
109 }
110 }
111
112 symbol_table.insert(DeclarationPtr::new_domain_letting(Name::user(name), domain));
113 }
114 } else {
115 for name in temp_symbols {
116 let Some(expr) = parse_expression(ctx, expr_or_domain)? else {
117 continue;
118 };
119 symbol_table.insert(DeclarationPtr::new_value_letting(Name::user(name), expr));
120 }
121 }
122 }
123
124 Ok(Some(symbol_table))
125}