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
1015
pub fn parse_letting_statement(
19
1015
    ctx: &mut ParseContext,
20
1015
    letting_statement: Node,
21
1015
) -> Result<Option<SymbolTable>, FatalParseError> {
22
1015
    let mut symbol_table = SymbolTable::new();
23

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

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

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

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

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

            
68
1015
    let expr_or_domain = field!(letting_statement, "expr_or_domain");
69
1015
    match expr_or_domain.kind() {
70
1015
        "bool_expr" | "arithmetic_expr" | "atom" => {
71
982
            for name in temp_symbols {
72
960
                let Some(expr) = parse_expression(ctx, expr_or_domain)? else {
73
                    continue;
74
                };
75
960
                symbol_table.insert(DeclarationPtr::new_value_letting(Name::user(name), expr));
76
            }
77
        }
78
33
        "domain" => {
79
33
            for name in temp_symbols {
80
33
                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
33
                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
33
                }
91

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