devalang_core/core/parser/handler/
dot.rs

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