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