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