devalang_core/core/parser/handler/
tempo.rs

1use crate::core::{
2    lexer::token::TokenKind,
3    parser::{
4        driver::parser::Parser,
5        statement::{Statement, StatementKind},
6    },
7    store::global::GlobalStore,
8};
9use devalang_types::Value;
10
11pub fn parse_tempo_token(parser: &mut Parser, _global_store: &mut GlobalStore) -> Statement {
12    parser.advance(); // consume 'bpm'
13
14    let Some(tempo_token) = parser.previous_clone() else {
15        return Statement::unknown();
16    };
17
18    // Expect a number or identifier
19    let Some(value_token) = parser.peek_clone() else {
20        return Statement::error_with_pos(
21            tempo_token.indent,
22            tempo_token.line,
23            tempo_token.column,
24            "Expected a number or identifier after 'bpm'".to_string(),
25        );
26    };
27
28    let value = match value_token.kind {
29        TokenKind::Number => {
30            // support decimals and fraction forms
31            let mut num = value_token.lexeme.clone();
32            parser.advance();
33            if let Some(dot) = parser.peek_clone() {
34                if dot.kind == TokenKind::Dot {
35                    if let Some(next) = parser.peek_nth(1).cloned() {
36                        if next.kind == TokenKind::Number {
37                            parser.advance();
38                            parser.advance();
39                            num.push('.');
40                            num.push_str(&next.lexeme);
41                        }
42                    }
43                }
44            }
45
46            if let Some(slash) = parser.peek_clone() {
47                if slash.kind == TokenKind::Slash {
48                    parser.advance();
49                    if let Some(den) = parser.peek_clone() {
50                        if den.kind == TokenKind::Number || den.kind == TokenKind::Identifier {
51                            let frac = format!("{}/{}", num, den.lexeme);
52                            parser.advance();
53                            Value::Duration(devalang_types::Duration::Beat(frac))
54                        } else {
55                            return Statement::error_with_pos(
56                                slash.indent,
57                                slash.line,
58                                slash.column,
59                                "Expected denominator after '/' in bpm".to_string(),
60                            );
61                        }
62                    } else {
63                        return Statement::error_with_pos(
64                            slash.indent,
65                            slash.line,
66                            slash.column,
67                            "Expected denominator after '/' in bpm".to_string(),
68                        );
69                    }
70                } else {
71                    Value::Number(num.parse().unwrap_or(0.0))
72                }
73            } else {
74                Value::Number(num.parse().unwrap_or(0.0))
75            }
76        }
77        TokenKind::Identifier => {
78            parser.advance();
79            Value::Identifier(value_token.lexeme.clone())
80        }
81        TokenKind::String => {
82            parser.advance();
83            Value::String(value_token.lexeme.clone())
84        }
85        _ => {
86            return Statement::error_with_pos(
87                value_token.indent,
88                value_token.line,
89                value_token.column,
90                format!(
91                    "Expected a number, string or identifier after 'bpm', got {:?}",
92                    value_token.kind
93                ),
94            );
95        }
96    };
97
98    Statement {
99        kind: StatementKind::Tempo,
100        value,
101        indent: tempo_token.indent,
102        line: tempo_token.line,
103        column: tempo_token.column,
104    }
105}