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