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
2399
pub fn parse_letting_statement(
18
2399
    ctx: &mut ParseContext,
19
2399
    letting_statement: Node,
20
2399
) -> Result<Option<SymbolTable>, FatalParseError> {
21
2399
    let Some(keyword) = field!(recover, ctx, letting_statement, "letting_keyword") else {
22
        return Ok(None);
23
    };
24
2399
    span_with_hover(
25
2399
        &keyword,
26
2399
        ctx.source_code,
27
2399
        ctx.source_map,
28
2399
        HoverInfo {
29
2399
            description: "Letting keyword".to_string(),
30
2399
            doc_key: None,
31
2399
            kind: Some(SymbolKind::Letting),
32
2399
            ty: None,
33
2399
            decl_span: None,
34
2399
        },
35
    );
36

            
37
2399
    let mut symbol_table = SymbolTable::new();
38

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

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

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

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

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

            
96
2412
        let Some(expr_or_domain) = field!(recover, ctx, variable_decl, "expr_or_domain") else {
97
            return Ok(None);
98
        };
99

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

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

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

            
126
2399
    Ok(Some(symbol_table))
127
2399
}