Skip to main content

conjure_cp_essence_parser/parser/
letting.rs

1#![allow(clippy::legacy_numeric_constants)]
2use std::collections::BTreeSet;
3
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 crate::field;
14use conjure_cp_core::ast::DeclarationPtr;
15use conjure_cp_core::ast::{Name, SymbolTable};
16
17/// Parse a letting statement into a SymbolTable containing the declared symbols
18pub fn parse_letting_statement(
19    ctx: &mut ParseContext,
20    letting_statement: Node,
21) -> Result<Option<SymbolTable>, FatalParseError> {
22    let mut symbol_table = SymbolTable::new();
23
24    let mut temp_symbols = BTreeSet::new();
25
26    let variable_list = field!(letting_statement, "variable_list");
27    for variable in named_children(&variable_list) {
28        let variable_name = &ctx.source_code[variable.start_byte()..variable.end_byte()];
29
30        // Check for duplicate within the same statement
31        if temp_symbols.contains(variable_name) {
32            ctx.errors.push(RecoverableParseError::new(
33                format!(
34                    "Variable '{}' is already declared in this letting statement",
35                    variable_name
36                ),
37                Some(variable.range()),
38            ));
39            // don't return here, as we can still add the other variables to the symbol table
40            continue;
41        }
42
43        // Check for duplicate declaration across statements
44        if let Some(symbols) = &ctx.symbols
45            && symbols.read().lookup(&Name::user(variable_name)).is_some()
46        {
47            ctx.errors.push(RecoverableParseError::new(
48                format!(
49                    "Variable '{}' is already declared in a previous statement",
50                    variable_name
51                ),
52                Some(variable.range()),
53            ));
54            // don't return here, as we can still add the other variables to the symbol table
55            continue;
56        }
57
58        temp_symbols.insert(variable_name);
59        let hover = HoverInfo {
60            description: format!("Letting variable: {variable_name}"),
61            kind: Some(SymbolKind::Letting),
62            ty: None,
63            decl_span: None,
64        };
65        span_with_hover(&variable, ctx.source_code, ctx.source_map, hover);
66    }
67
68    let expr_or_domain = field!(letting_statement, "expr_or_domain");
69    match expr_or_domain.kind() {
70        "bool_expr" | "arithmetic_expr" | "atom" => {
71            for name in temp_symbols {
72                let Some(expr) = parse_expression(ctx, expr_or_domain)? else {
73                    continue;
74                };
75                symbol_table.insert(DeclarationPtr::new_value_letting(Name::user(name), expr));
76            }
77        }
78        "domain" => {
79            for name in temp_symbols {
80                let Some(domain) = parse_domain(ctx, expr_or_domain)? else {
81                    continue;
82                };
83
84                // If it's a record domain, add the field names to the symbol table
85                if let Some(entries) = domain.as_record() {
86                    for entry in entries {
87                        // Add each field name as a record field declaration
88                        symbol_table.insert(DeclarationPtr::new_record_field(entry.clone()));
89                    }
90                }
91
92                symbol_table.insert(DeclarationPtr::new_domain_letting(Name::user(name), domain));
93            }
94        }
95        _ => {
96            return Err(FatalParseError::internal_error(
97                format!(
98                    "Expected letting expression, got '{}'",
99                    expr_or_domain.kind()
100                ),
101                Some(expr_or_domain.range()),
102            ));
103        }
104    }
105
106    Ok(Some(symbol_table))
107}