1
#![allow(clippy::legacy_numeric_constants)]
2
use crate::field;
3
use std::collections::BTreeSet;
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 conjure_cp_core::ast::DeclarationPtr;
14
use conjure_cp_core::ast::{Name, SymbolTable};
15

            
16
/// Parse a letting statement into a SymbolTable containing the declared symbols
17
2373
pub fn parse_letting_statement(
18
2373
    ctx: &mut ParseContext,
19
2373
    letting_statement: Node,
20
2373
) -> Result<Option<SymbolTable>, FatalParseError> {
21
2373
    let Some(keyword) = field!(recover, ctx, letting_statement, "letting_keyword") else {
22
        return Ok(None);
23
    };
24
2373
    span_with_hover(
25
2373
        &keyword,
26
2373
        ctx.source_code,
27
2373
        ctx.source_map,
28
2373
        HoverInfo {
29
2373
            description: "Letting keyword".to_string(),
30
2373
            kind: Some(SymbolKind::Letting),
31
2373
            ty: None,
32
2373
            decl_span: None,
33
2373
        },
34
    );
35

            
36
2373
    let mut symbol_table = SymbolTable::new();
37

            
38
2386
    for variable_decl in named_children(&letting_statement) {
39
2386
        let mut temp_symbols = BTreeSet::new();
40

            
41
2386
        let Some(variable_list) = field!(recover, ctx, variable_decl, "variable_list") else {
42
            return Ok(None);
43
        };
44
2412
        for variable in named_children(&variable_list) {
45
2412
            let variable_name = &ctx.source_code[variable.start_byte()..variable.end_byte()];
46

            
47
            // Check for duplicate within the same statement
48
2412
            if temp_symbols.contains(variable_name) {
49
13
                ctx.errors.push(RecoverableParseError::new(
50
13
                    format!(
51
                        "Variable '{}' is already declared in this letting statement",
52
                        variable_name
53
                    ),
54
13
                    Some(variable.range()),
55
                ));
56
                // don't return here, as we can still add the other variables to the symbol table
57
13
                continue;
58
2399
            }
59

            
60
            // Check for duplicate declaration across statements
61
2399
            let name = Name::user(variable_name);
62
2399
            if let Some(symbols) = &ctx.symbols
63
2399
                && symbols.read().lookup(&name).is_some()
64
            {
65
39
                let previous_line = ctx.lookup_decl_line(&name);
66
39
                ctx.errors.push(RecoverableParseError::new(
67
39
                    match previous_line {
68
39
                        Some(line) => format!(
69
                            "Variable '{}' is already declared in a previous statement on line {}",
70
                            variable_name, line
71
                        ),
72
                        None => format!(
73
                            "Variable '{}' is already declared in a previous statement",
74
                            variable_name
75
                        ),
76
                    },
77
39
                    Some(variable.range()),
78
                ));
79
                // don't return here, as we can still add the other variables to the symbol table
80
39
                continue;
81
2360
            }
82

            
83
2360
            temp_symbols.insert(variable_name);
84
2360
            let hover = HoverInfo {
85
2360
                description: format!("Letting variable: {variable_name}"),
86
2360
                kind: Some(SymbolKind::Letting),
87
2360
                ty: None,
88
2360
                decl_span: None,
89
2360
            };
90
2360
            let span_id = span_with_hover(&variable, ctx.source_code, ctx.source_map, hover);
91
2360
            ctx.save_decl_span(name, span_id);
92
        }
93

            
94
2386
        let Some(expr_or_domain) = field!(recover, ctx, variable_decl, "expr_or_domain") else {
95
            return Ok(None);
96
        };
97

            
98
2386
        if variable_decl.child_by_field_name("domain").is_some() {
99
377
            for name in temp_symbols {
100
377
                let Some(domain) = parse_domain(ctx, expr_or_domain)? else {
101
13
                    continue;
102
                };
103

            
104
                // If it's a record domain, add the field names to the symbol table
105
364
                if let Some(entries) = domain.as_record() {
106
52
                    for entry in entries {
107
                        // Add each field name as a record field declaration
108
52
                        symbol_table.insert(DeclarationPtr::new_record_field(entry.clone()));
109
52
                    }
110
338
                }
111

            
112
364
                symbol_table.insert(DeclarationPtr::new_domain_letting(Name::user(name), domain));
113
            }
114
        } else {
115
2022
            for name in temp_symbols {
116
1983
                let Some(expr) = parse_expression(ctx, expr_or_domain)? else {
117
13
                    continue;
118
                };
119
1970
                symbol_table.insert(DeclarationPtr::new_value_letting(Name::user(name), expr));
120
            }
121
        }
122
    }
123

            
124
2373
    Ok(Some(symbol_table))
125
2373
}