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