1
#![allow(clippy::legacy_numeric_constants)]
2
use std::collections::BTreeSet;
3

            
4
use tree_sitter::Node;
5

            
6
use super::ParseContext;
7
use super::domain::parse_domain;
8
use super::util::named_children;
9
use crate::diagnostics::diagnostics_api::SymbolKind;
10
use crate::diagnostics::source_map::{HoverInfo, span_with_hover};
11
use crate::errors::{FatalParseError, RecoverableParseError};
12
use crate::expression::parse_expression;
13
use crate::field;
14
use conjure_cp_core::ast::DeclarationPtr;
15
use conjure_cp_core::ast::{Name, SymbolTable};
16

            
17
/// Parse a letting statement into a SymbolTable containing the declared symbols
18
1249
pub fn parse_letting_statement(
19
1249
    ctx: &mut ParseContext,
20
1249
    letting_statement: Node,
21
1249
) -> Result<Option<SymbolTable>, FatalParseError> {
22
1249
    let mut symbol_table = SymbolTable::new();
23

            
24
1249
    let mut temp_symbols = BTreeSet::new();
25

            
26
1249
    let variable_list = field!(letting_statement, "variable_list");
27
1283
    for variable in named_children(&variable_list) {
28
1283
        let variable_name = &ctx.source_code[variable.start_byte()..variable.end_byte()];
29

            
30
        // Check for duplicate within the same statement
31
1283
        if temp_symbols.contains(variable_name) {
32
34
            ctx.errors.push(RecoverableParseError::new(
33
34
                format!(
34
                    "Variable '{}' is already declared in this letting statement",
35
                    variable_name
36
                ),
37
34
                Some(variable.range()),
38
            ));
39
            // don't return here, as we can still add the other variables to the symbol table
40
34
            continue;
41
1249
        }
42

            
43
        // Check for duplicate declaration across statements
44
1249
        if let Some(symbols) = &ctx.symbols
45
1249
            && symbols.read().lookup(&Name::user(variable_name)).is_some()
46
        {
47
68
            ctx.errors.push(RecoverableParseError::new(
48
68
                format!(
49
                    "Variable '{}' is already declared in a previous statement",
50
                    variable_name
51
                ),
52
68
                Some(variable.range()),
53
            ));
54
            // don't return here, as we can still add the other variables to the symbol table
55
68
            continue;
56
1181
        }
57

            
58
1181
        temp_symbols.insert(variable_name);
59
1181
        let hover = HoverInfo {
60
1181
            description: format!("Letting variable: {variable_name}"),
61
1181
            kind: Some(SymbolKind::Letting),
62
1181
            ty: None,
63
1181
            decl_span: None,
64
1181
        };
65
1181
        span_with_hover(&variable, ctx.source_code, ctx.source_map, hover);
66
    }
67

            
68
1249
    let expr_or_domain = field!(letting_statement, "expr_or_domain");
69
1249
    match expr_or_domain.kind() {
70
1249
        "bool_expr" | "arithmetic_expr" | "atom" => {
71
1193
            for name in temp_symbols {
72
1125
                let Some(expr) = parse_expression(ctx, expr_or_domain)? else {
73
                    continue;
74
                };
75
1125
                symbol_table.insert(DeclarationPtr::new_value_letting(Name::user(name), expr));
76
            }
77
        }
78
56
        "domain" => {
79
56
            for name in temp_symbols {
80
56
                let Some(domain) = parse_domain(ctx, expr_or_domain)? else {
81
22
                    continue;
82
                };
83

            
84
                // If it's a record domain, add the field names to the symbol table
85
34
                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
34
                }
91

            
92
34
                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
1249
    Ok(Some(symbol_table))
107
1249
}