Skip to main content

conjure_cp_essence_parser/parser/
letting.rs

1#![allow(clippy::legacy_numeric_constants)]
2use crate::field;
3use std::collections::BTreeSet;
4use tree_sitter::Node;
5
6use super::ParseContext;
7use super::domain::parse_domain;
8use super::keyword_checks::is_keyword_identifier;
9use super::util::named_children;
10use crate::diagnostics::diagnostics_api::SymbolKind;
11use crate::diagnostics::source_map::{HoverInfo, span_with_hover};
12use crate::errors::{FatalParseError, RecoverableParseError};
13use crate::expression::parse_expression;
14use conjure_cp_core::ast::DeclarationPtr;
15use conjure_cp_core::ast::{Name, SymbolTable};
16
17/// Parse a letting statement into a SymbolTable containing the declared symbols
18pub fn parse_letting_statement(
19    ctx: &mut ParseContext,
20    letting_statement: Node,
21) -> Result<Option<SymbolTable>, FatalParseError> {
22    let Some(keyword) = field!(recover, ctx, letting_statement, "letting_keyword") else {
23        return Ok(None);
24    };
25    span_with_hover(
26        &keyword,
27        ctx.source_code,
28        ctx.source_map,
29        HoverInfo {
30            description: "Letting keyword".to_string(),
31            doc_key: None,
32            kind: Some(SymbolKind::Letting),
33            ty: None,
34            decl_span: None,
35        },
36    );
37
38    let mut symbol_table = SymbolTable::new();
39
40    for variable_decl in named_children(&letting_statement) {
41        let mut temp_symbols = BTreeSet::new();
42
43        let Some(variable_list) = field!(recover, ctx, variable_decl, "variable_list") else {
44            return Ok(None);
45        };
46        for variable in named_children(&variable_list) {
47            let variable_name = &ctx.source_code[variable.start_byte()..variable.end_byte()];
48
49            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            }
56
57            // Check for duplicate within the same statement
58            if temp_symbols.contains(variable_name) {
59                ctx.errors.push(RecoverableParseError::new(
60                    format!(
61                        "Variable '{}' is already declared in this letting statement",
62                        variable_name
63                    ),
64                    Some(variable.range()),
65                ));
66                // don't return here, as we can still add the other variables to the symbol table
67                continue;
68            }
69
70            // Check for duplicate declaration across statements
71            let name = Name::user(variable_name);
72            if let Some(symbols) = &ctx.symbols
73                && symbols.read().lookup(&name).is_some()
74            {
75                let previous_line = ctx.lookup_decl_line(&name);
76                ctx.errors.push(RecoverableParseError::new(
77                    match previous_line {
78                        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                    Some(variable.range()),
88                ));
89                // don't return here, as we can still add the other variables to the symbol table
90                continue;
91            }
92
93            temp_symbols.insert(variable_name);
94            let hover = HoverInfo {
95                description: format!("Letting variable: {variable_name}"),
96                doc_key: None,
97                kind: Some(SymbolKind::LettingVar),
98                ty: None,
99                decl_span: None,
100            };
101            let span_id = span_with_hover(&variable, ctx.source_code, ctx.source_map, hover);
102            ctx.save_decl_span(name, span_id);
103        }
104
105        let Some(expr_or_domain) = field!(recover, ctx, variable_decl, "expr_or_domain") else {
106            return Ok(None);
107        };
108
109        if variable_decl.child_by_field_name("domain").is_some() {
110            for name in temp_symbols {
111                let Some(domain) = parse_domain(ctx, expr_or_domain)? else {
112                    continue;
113                };
114
115                symbol_table.insert(DeclarationPtr::new_domain_letting(Name::user(name), domain));
116            }
117        } else {
118            for name in temp_symbols {
119                let Some(expr) = parse_expression(ctx, expr_or_domain)? else {
120                    continue;
121                };
122                symbol_table.insert(DeclarationPtr::new_value_letting(Name::user(name), expr));
123            }
124        }
125    }
126
127    Ok(Some(symbol_table))
128}