devalang_core/core/parser/handler/
dot.rs

1use devalang_types::{Duration, Value};
2
3use crate::core::{
4    lexer::token::TokenKind,
5    parser::{
6        driver::parser::Parser,
7        statement::{Statement, StatementKind},
8    },
9};
10
11pub fn parse_dot_token(
12    parser: &mut Parser,
13    _global_store: &mut crate::core::store::global::GlobalStore,
14) -> Statement {
15    parser.advance(); // consume '.'
16    let logger = devalang_utils::logger::Logger::new();
17    use devalang_utils::logger::LogLevel;
18    let Some(dot_token) = parser.previous_clone() else {
19        return Statement::unknown();
20    };
21
22    // Parse a single entity (namespace-friendly, stops at newline)
23    let mut parts = Vec::new();
24    let current_line = dot_token.line;
25
26    while let Some(token) = parser.peek_clone() {
27        // Never cross a newline
28        if token.line != current_line {
29            break;
30        }
31        match token.kind {
32            TokenKind::Identifier | TokenKind::Number => {
33                parts.push(token.lexeme.clone());
34                parser.advance();
35                // The separator must be a '.' on the same line, otherwise stop
36                if let Some(next) = parser.peek_clone() {
37                    if next.line != current_line || next.kind != TokenKind::Dot {
38                        break;
39                    }
40                } else {
41                    break;
42                }
43            }
44            TokenKind::Dot => {
45                parser.advance();
46            }
47            TokenKind::Newline | TokenKind::EOF | TokenKind::Indent | TokenKind::Dedent => {
48                break; // Stop at newline or dedent
49            }
50            _ => {
51                break;
52            }
53        }
54    }
55
56    // Build entity name properly
57    let entity = if !parts.is_empty() {
58        parts.join(".") // only join within the same line
59    } else {
60        logger.log_message(
61            LogLevel::Warning,
62            &format!("Empty entity after '.' at line {}", dot_token.line),
63        );
64        String::new()
65    };
66
67    // Optional duration and effects map
68    let mut duration = Duration::Auto;
69    let mut value = Value::Null;
70
71    if let Some(token) = parser.peek_clone() {
72        // Duration and effects map are only valid on the same line
73        if token.line == current_line {
74            match token.kind {
75                TokenKind::Number => {
76                    let numerator = token.lexeme.clone();
77                    parser.advance();
78                    if let Some(peek) = parser.peek_clone() {
79                        if peek.line == current_line {
80                            if let Some(TokenKind::Slash) = parser.peek_kind() {
81                                parser.advance();
82                                if let Some(denominator_token) = parser.peek_clone() {
83                                    if denominator_token.line == current_line
84                                        && denominator_token.kind == TokenKind::Number
85                                    {
86                                        let denominator = denominator_token.lexeme.clone();
87                                        parser.advance();
88                                        duration = Duration::Beat(format!(
89                                            "{}/{}",
90                                            numerator, denominator
91                                        ));
92                                    }
93                                }
94                            } else {
95                                duration = parse_duration(numerator);
96                            }
97                        } else {
98                            duration = parse_duration(numerator);
99                        }
100                    } else {
101                        duration = parse_duration(numerator);
102                    }
103                    if let Some(next) = parser.peek_clone() {
104                        if next.line == current_line && next.kind == TokenKind::LBrace {
105                            value = parser.parse_map_value().unwrap_or(Value::Null);
106                        }
107                    }
108                }
109                TokenKind::Identifier => {
110                    let id = token.lexeme.clone();
111                    parser.advance();
112                    duration = parse_duration(id);
113                    if let Some(next) = parser.peek_clone() {
114                        if next.line == current_line && next.kind == TokenKind::LBrace {
115                            value = parser.parse_map_value().unwrap_or(Value::Null);
116                        }
117                    }
118                }
119                TokenKind::LBrace => {
120                    value = parser.parse_map_value().unwrap_or(Value::Null);
121                }
122                _ => {}
123            }
124        }
125    }
126
127    Statement {
128        kind: StatementKind::Trigger {
129            entity,
130            duration,
131            effects: Some(value.clone()),
132        },
133        value: Value::Null,
134        indent: dot_token.indent,
135        line: dot_token.line,
136        column: dot_token.column,
137    }
138}
139
140fn parse_duration(s: String) -> Duration {
141    if s == "auto" {
142        Duration::Auto
143    } else if let Ok(num) = s.parse::<f32>() {
144        Duration::Number(num)
145    } else {
146        Duration::Identifier(s)
147    }
148}