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

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

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

            
30
        // Check for duplicate within the same statement
31
408
        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
397
        }
42

            
43
        // Check for duplicate declaration across statements
44
397
        if let Some(symbols) = &ctx.symbols
45
397
            && 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
375
        }
57

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

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

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