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::keyword_checks::is_keyword_identifier;
9
use super::util::named_children;
10
use crate::diagnostics::diagnostics_api::SymbolKind;
11
use crate::diagnostics::source_map::{HoverInfo, span_with_hover};
12
use crate::errors::{FatalParseError, RecoverableParseError};
13
use crate::expression::parse_expression;
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
2509
pub fn parse_letting_statement(
19
2509
    ctx: &mut ParseContext,
20
2509
    letting_statement: Node,
21
2509
) -> Result<Option<SymbolTable>, FatalParseError> {
22
2509
    let Some(keyword) = field!(recover, ctx, letting_statement, "letting_keyword") else {
23
        return Ok(None);
24
    };
25
2509
    span_with_hover(
26
2509
        &keyword,
27
2509
        ctx.source_code,
28
2509
        ctx.source_map,
29
2509
        HoverInfo {
30
2509
            description: "Letting keyword".to_string(),
31
2509
            doc_key: None,
32
2509
            kind: Some(SymbolKind::Letting),
33
2509
            ty: None,
34
2509
            decl_span: None,
35
2509
        },
36
    );
37

            
38
2509
    let mut symbol_table = SymbolTable::new();
39

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

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

            
49
2548
            if is_keyword_identifier(variable_name) {
50
                ctx.errors.push(RecoverableParseError::new(
51
                    format!("Keyword '{variable_name}' used as identifier"),
52
                    Some(variable.range()),
53
                ));
54
                // still add variable to symbol table to avoid follow-up errors
55
2548
            }
56

            
57
            // Check for duplicate within the same statement
58
2548
            if temp_symbols.contains(variable_name) {
59
13
                ctx.errors.push(RecoverableParseError::new(
60
13
                    format!(
61
                        "Variable '{}' is already declared in this letting statement",
62
                        variable_name
63
                    ),
64
13
                    Some(variable.range()),
65
                ));
66
                // don't return here, as we can still add the other variables to the symbol table
67
13
                continue;
68
2535
            }
69

            
70
            // Check for duplicate declaration across statements
71
2535
            let name = Name::user(variable_name);
72
2535
            if let Some(symbols) = &ctx.symbols
73
2535
                && symbols.read().lookup(&name).is_some()
74
            {
75
39
                let previous_line = ctx.lookup_decl_line(&name);
76
39
                ctx.errors.push(RecoverableParseError::new(
77
39
                    match previous_line {
78
39
                        Some(line) => format!(
79
                            "Variable '{}' is already declared in a previous statement on line {}",
80
                            variable_name, line
81
                        ),
82
                        None => format!(
83
                            "Variable '{}' is already declared in a previous statement",
84
                            variable_name
85
                        ),
86
                    },
87
39
                    Some(variable.range()),
88
                ));
89
                // don't return here, as we can still add the other variables to the symbol table
90
39
                continue;
91
2496
            }
92

            
93
2496
            temp_symbols.insert(variable_name);
94
2496
            let hover = HoverInfo {
95
2496
                description: format!("Letting variable: {variable_name}"),
96
2496
                doc_key: None,
97
2496
                kind: Some(SymbolKind::LettingVar),
98
2496
                ty: None,
99
2496
                decl_span: None,
100
2496
            };
101
2496
            let span_id = span_with_hover(&variable, ctx.source_code, ctx.source_map, hover);
102
2496
            ctx.save_decl_span(name, span_id);
103
        }
104

            
105
2522
        let Some(expr_or_domain) = field!(recover, ctx, variable_decl, "expr_or_domain") else {
106
            return Ok(None);
107
        };
108

            
109
2522
        if variable_decl.child_by_field_name("domain").is_some() {
110
377
            for name in temp_symbols {
111
377
                let Some(domain) = parse_domain(ctx, expr_or_domain)? else {
112
13
                    continue;
113
                };
114

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

            
127
2509
    Ok(Some(symbol_table))
128
2509
}