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,
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(LogLevel::Warning, &format!("Empty entity after '.' at line {}", dot_token.line));
61        String::new()
62    };
63
64    // Optional duration and effects map
65    let mut duration = Duration::Auto;
66    let mut value = Value::Null;
67
68    if let Some(token) = parser.peek_clone() {
69        // Duration and effects map are only valid on the same line
70        if token.line == current_line {
71            match token.kind {
72                TokenKind::Number => {
73                    let numerator = token.lexeme.clone();
74                    parser.advance();
75                    if let Some(peek) = parser.peek_clone() {
76                        if peek.line == current_line {
77                            if let Some(TokenKind::Slash) = parser.peek_kind() {
78                                parser.advance();
79                                if let Some(denominator_token) = parser.peek_clone() {
80                                    if denominator_token.line == current_line
81                                        && denominator_token.kind == TokenKind::Number
82                                    {
83                                        let denominator = denominator_token.lexeme.clone();
84                                        parser.advance();
85                                        duration = Duration::Beat(format!(
86                                            "{}/{}",
87                                            numerator, denominator
88                                        ));
89                                    }
90                                }
91                            } else {
92                                duration = parse_duration(numerator);
93                            }
94                        } else {
95                            duration = parse_duration(numerator);
96                        }
97                    } else {
98                        duration = parse_duration(numerator);
99                    }
100                    if let Some(next) = parser.peek_clone() {
101                        if next.line == current_line && next.kind == TokenKind::LBrace {
102                            value = parser.parse_map_value().unwrap_or(Value::Null);
103                        }
104                    }
105                }
106                TokenKind::Identifier => {
107                    let id = token.lexeme.clone();
108                    parser.advance();
109                    duration = parse_duration(id);
110                    if let Some(next) = parser.peek_clone() {
111                        if next.line == current_line && next.kind == TokenKind::LBrace {
112                            value = parser.parse_map_value().unwrap_or(Value::Null);
113                        }
114                    }
115                }
116                TokenKind::LBrace => {
117                    value = parser.parse_map_value().unwrap_or(Value::Null);
118                }
119                _ => {}
120            }
121        }
122    }
123
124    Statement {
125        kind: StatementKind::Trigger {
126            entity,
127            duration,
128            effects: Some(value.clone()),
129        },
130        value: Value::Null,
131        indent: dot_token.indent,
132        line: dot_token.line,
133        column: dot_token.column,
134    }
135}
136
137fn parse_duration(s: String) -> Duration {
138    if s == "auto" {
139        Duration::Auto
140    } else if let Ok(num) = s.parse::<f32>() {
141        Duration::Number(num)
142    } else {
143        Duration::Identifier(s)
144    }
145}