@devaloop/devalang 0.0.1-alpha.2 → 0.0.1-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/.devalang +1 -1
  2. package/Cargo.toml +46 -46
  3. package/README.md +48 -30
  4. package/docs/CHANGELOG.md +28 -6
  5. package/docs/COMMANDS.md +31 -0
  6. package/docs/CONFIG.md +6 -4
  7. package/docs/ROADMAP.md +5 -1
  8. package/docs/TODO.md +10 -35
  9. package/examples/exported.deva +1 -1
  10. package/examples/index.deva +8 -1
  11. package/examples/samples/hat-808.wav +0 -0
  12. package/out-tsc/bin/devalang.exe +0 -0
  13. package/package.json +41 -42
  14. package/project-version.json +5 -5
  15. package/rust/audio/engine.rs +130 -0
  16. package/rust/audio/interpreter.rs +143 -0
  17. package/rust/audio/loader.rs +46 -0
  18. package/rust/audio/mod.rs +5 -1
  19. package/rust/audio/player.rs +54 -0
  20. package/rust/audio/render.rs +57 -0
  21. package/rust/cli/build.rs +73 -45
  22. package/rust/cli/check.rs +47 -111
  23. package/rust/cli/init.rs +1 -1
  24. package/rust/cli/mod.rs +203 -2
  25. package/rust/cli/play.rs +191 -0
  26. package/rust/{utils/config.rs → config/loader.rs} +3 -2
  27. package/rust/config/mod.rs +16 -0
  28. package/rust/core/builder/mod.rs +69 -27
  29. package/rust/core/debugger/lexer.rs +27 -0
  30. package/rust/core/debugger/mod.rs +12 -49
  31. package/rust/core/debugger/preprocessor.rs +27 -0
  32. package/rust/core/error/mod.rs +60 -0
  33. package/rust/core/lexer/{at.rs → handler/at.rs} +1 -1
  34. package/rust/core/lexer/{brace.rs → handler/brace.rs} +1 -1
  35. package/rust/core/lexer/{colon.rs → handler/colon.rs} +1 -1
  36. package/rust/core/lexer/{comment.rs → handler/comment.rs} +3 -3
  37. package/rust/core/lexer/{dot.rs → handler/dot.rs} +1 -1
  38. package/rust/core/lexer/{equal.rs → handler/equal.rs} +1 -1
  39. package/rust/core/lexer/{identifier.rs → handler/identifier.rs} +1 -1
  40. package/rust/core/lexer/{indent.rs → handler/indent.rs} +10 -5
  41. package/rust/core/lexer/handler/mod.rs +238 -0
  42. package/rust/core/lexer/{newline.rs → handler/newline.rs} +6 -10
  43. package/rust/core/lexer/{number.rs → handler/number.rs} +1 -1
  44. package/rust/core/lexer/handler/string.rs +66 -0
  45. package/rust/core/lexer/mod.rs +25 -14
  46. package/rust/core/lexer/token.rs +55 -0
  47. package/rust/core/mod.rs +5 -2
  48. package/rust/core/parser/handler/at.rs +166 -0
  49. package/rust/core/parser/handler/bank.rs +38 -0
  50. package/rust/core/parser/handler/dot.rs +112 -0
  51. package/rust/core/parser/handler/identifier.rs +134 -0
  52. package/rust/core/parser/handler/loop_.rs +55 -0
  53. package/rust/core/parser/handler/mod.rs +6 -0
  54. package/rust/core/parser/handler/tempo.rs +47 -0
  55. package/rust/core/parser/mod.rs +204 -166
  56. package/rust/core/parser/statement.rs +91 -0
  57. package/rust/core/preprocessor/loader.rs +116 -0
  58. package/rust/core/preprocessor/mod.rs +2 -24
  59. package/rust/core/preprocessor/module.rs +37 -56
  60. package/rust/core/preprocessor/processor.rs +41 -0
  61. package/rust/core/preprocessor/resolver/bank.rs +38 -51
  62. package/rust/core/preprocessor/resolver/loop_.rs +126 -65
  63. package/rust/core/preprocessor/resolver/mod.rs +119 -80
  64. package/rust/core/preprocessor/resolver/tempo.rs +40 -61
  65. package/rust/core/preprocessor/resolver/trigger.rs +93 -155
  66. package/rust/core/shared/duration.rs +8 -0
  67. package/rust/core/shared/mod.rs +2 -0
  68. package/rust/core/shared/value.rs +18 -0
  69. package/rust/core/store/export.rs +28 -0
  70. package/rust/core/store/global.rs +39 -0
  71. package/rust/core/store/import.rs +28 -0
  72. package/rust/core/store/mod.rs +4 -0
  73. package/rust/core/store/variable.rs +28 -0
  74. package/rust/core/utils/mod.rs +2 -0
  75. package/rust/core/utils/validation.rs +35 -0
  76. package/rust/lib.rs +0 -1
  77. package/rust/main.rs +22 -18
  78. package/rust/utils/logger.rs +69 -34
  79. package/rust/utils/mod.rs +3 -5
  80. package/rust/utils/watcher.rs +10 -2
  81. package/templates/minimal/.devalang +1 -1
  82. package/templates/welcome/.devalang +1 -1
  83. package/rust/core/lexer/bracket.rs +0 -41
  84. package/rust/core/lexer/driver.rs +0 -286
  85. package/rust/core/lexer/quote.rs +0 -61
  86. package/rust/core/parser/at.rs +0 -142
  87. package/rust/core/parser/bank.rs +0 -42
  88. package/rust/core/parser/dot.rs +0 -137
  89. package/rust/core/parser/identifer.rs +0 -91
  90. package/rust/core/parser/loop_.rs +0 -62
  91. package/rust/core/parser/tempo.rs +0 -42
  92. package/rust/core/parser/variable.rs +0 -129
  93. package/rust/core/preprocessor/dependencies.rs +0 -54
  94. package/rust/core/preprocessor/resolver/at.rs +0 -24
  95. package/rust/core/types/cli.rs +0 -182
  96. package/rust/core/types/config.rs +0 -15
  97. package/rust/core/types/mod.rs +0 -8
  98. package/rust/core/types/module.rs +0 -41
  99. package/rust/core/types/parser.rs +0 -73
  100. package/rust/core/types/statement.rs +0 -105
  101. package/rust/core/types/store.rs +0 -116
  102. package/rust/core/types/token.rs +0 -83
  103. package/rust/core/types/variable.rs +0 -32
  104. package/rust/runner/executer.rs +0 -44
  105. package/rust/runner/mod.rs +0 -1
  106. /package/rust/{utils → core/utils}/path.rs +0 -0
  107. /package/rust/utils/{loader.rs → spinner.rs} +0 -0
package/rust/cli/check.rs CHANGED
@@ -1,29 +1,20 @@
1
- use std::{ thread, time::Duration };
2
-
3
1
  use crate::{
2
+ config::Config,
4
3
  core::{
5
- debugger::Debugger,
6
- preprocessor::module::load_all_modules,
7
- types::{
8
- config::{ DevalangConfig },
9
- statement::{ StatementKind, StatementResolved, StatementResolvedValue },
10
- },
11
- },
12
- runner::executer::execute_statements,
13
- utils::{
14
- loader::with_spinner,
15
- logger::log_message,
16
- path::{ find_entry_file, normalize_path },
17
- watcher::watch_directory,
4
+ preprocessor::loader::ModuleLoader,
5
+ store::global::GlobalStore,
6
+ utils::path::{ find_entry_file, normalize_path },
18
7
  },
8
+ utils::{ logger::{ LogLevel, Logger }, spinner::with_spinner, watcher::watch_directory },
19
9
  };
10
+ use std::{ thread, time::Duration };
20
11
 
21
12
  pub fn handle_check_command(
22
- config: Option<DevalangConfig>,
13
+ config: Option<Config>,
23
14
  entry: Option<String>,
24
15
  output: Option<String>,
25
16
  watch: bool
26
- ) -> () {
17
+ ) {
27
18
  let fetched_entry = if entry.is_none() {
28
19
  config
29
20
  .as_ref()
@@ -51,36 +42,51 @@ pub fn handle_check_command(
51
42
  .unwrap_or(false)
52
43
  };
53
44
 
45
+ let logger = Logger::new();
46
+
54
47
  if fetched_entry.is_empty() {
55
- eprintln!("❌ Entry path is not specified. Please provide a valid entry path.");
48
+ logger.log_message(
49
+ LogLevel::Error,
50
+ "Entry path is not specified. Please provide a valid entry path."
51
+ );
56
52
  std::process::exit(1);
57
53
  }
58
-
59
54
  if fetched_output.is_empty() {
60
- eprintln!("❌ Output directory is not specified. Please provide a valid output directory.");
55
+ logger.log_message(
56
+ LogLevel::Error,
57
+ "Output directory is not specified. Please provide a valid output directory."
58
+ );
61
59
  std::process::exit(1);
62
60
  }
63
61
 
64
62
  let entry_file = find_entry_file(&fetched_entry).unwrap_or_else(|| {
65
- eprintln!("❌ index.deva not found in directory: {}", fetched_entry);
63
+ logger.log_message(
64
+ LogLevel::Error,
65
+ &format!("❌ index.deva not found in directory: {}", fetched_entry)
66
+ );
66
67
  std::process::exit(1);
67
68
  });
68
69
 
69
- if fetched_watch.clone() == true {
70
- log_message("Watch mode enabled, waiting for file changes...", "INFO");
70
+ // SECTION Begin check
71
+ if fetched_watch {
72
+ begin_check(entry_file.clone(), fetched_output.clone());
71
73
 
72
- begin_check(entry_file.clone(), fetched_output.clone(), fetched_watch.clone());
74
+ logger.log_message(
75
+ LogLevel::Watcher,
76
+ &format!("Watching for changes in '{}'...", fetched_entry)
77
+ );
73
78
 
74
79
  watch_directory(entry_file.clone(), move || {
75
- log_message("File change detected, rebuilding...", "INFO");
76
- begin_check(entry_file.clone(), fetched_output.clone(), fetched_watch.clone());
80
+ logger.log_message(LogLevel::Watcher, "Detected changes, re-checking...");
81
+
82
+ begin_check(entry_file.clone(), fetched_output.clone());
77
83
  }).unwrap();
78
84
  } else {
79
- begin_check(entry_file.clone(), fetched_output.clone(), fetched_watch.clone());
85
+ begin_check(entry_file.clone(), fetched_output.clone());
80
86
  }
81
87
  }
82
88
 
83
- fn begin_check(entry: String, output: String, watch: bool) {
89
+ fn begin_check(entry: String, output: String) {
84
90
  let spinner = with_spinner("Checking...", || {
85
91
  thread::sleep(Duration::from_millis(800));
86
92
  });
@@ -90,91 +96,21 @@ fn begin_check(entry: String, output: String, watch: bool) {
90
96
  let normalized_entry_file = normalize_path(&entry);
91
97
  let normalized_output_dir = normalize_path(&output);
92
98
 
93
- let global_store = load_all_modules(&normalized_entry_file);
94
-
95
- if let Some(module) = global_store.modules.get(&normalized_entry_file) {
96
- let mut module_clone = module.clone();
97
-
98
- let resolved_statements = execute_statements(&mut module_clone);
99
-
100
- let debugger = Debugger::new(&module_clone);
101
- let debug_dir = format!("{}/debug/", normalized_output_dir.clone());
102
- debugger.write_files(debug_dir.as_str(), resolved_statements.clone());
99
+ let mut global_store = GlobalStore::new();
100
+ let module_loader = ModuleLoader::new(&normalized_entry_file, &normalized_output_dir);
103
101
 
104
- let has_errors = resolved_statements
105
- .iter()
106
- .any(|stmt| { match_error_recursively_resolved(&stmt.clone()) });
102
+ // SECTION Load
103
+ // NOTE: We don't use modules in the check command, but we still need to load them
104
+ let modules = module_loader.load_all(&mut global_store);
107
105
 
108
- if has_errors {
109
- let warning_message = format!(
110
- "Check completed with errors in {:.2?}. Output files written to: '{}'",
111
- duration.elapsed(),
112
- normalized_output_dir
113
- );
106
+ // TODO: Implement debugging
114
107
 
115
- log_message(&warning_message, "WARNING");
116
- } else {
117
- let success_message = format!(
118
- "Check completed successfully in {:.2?}. Output files written to: '{}'",
119
- duration.elapsed(),
120
- normalized_output_dir
121
- );
122
-
123
- log_message(&success_message, "SUCCESS");
124
- }
125
- }
126
- }
127
-
128
- fn match_error_recursively_resolved(stmt: &StatementResolved) -> bool {
129
- match stmt.value.clone() {
130
- // TODO Other statement value types here
131
-
132
- StatementResolvedValue::Map(map) => {
133
- for (key, value) in map {
134
- if match_error_recursively_resolved_value(&value) {
135
- return true;
136
- }
137
- }
138
- }
139
-
140
- StatementResolvedValue::Array(array) => {
141
- for item in array {
142
- if match_error_recursively_resolved(&item) {
143
- return true;
144
- }
145
- }
146
- }
147
-
148
- _ => {
149
- if let StatementKind::Error = stmt.kind {
150
- eprintln!("❌ Error found in statement: {:?}", stmt);
151
- return true;
152
- }
153
- }
154
- }
155
-
156
- false
157
- }
158
-
159
- fn match_error_recursively_resolved_value(value: &StatementResolvedValue) -> bool {
160
- match value {
161
- StatementResolvedValue::Map(map) => {
162
- for (_, v) in map {
163
- if match_error_recursively_resolved_value(v) {
164
- return true;
165
- }
166
- }
167
- }
168
-
169
- StatementResolvedValue::Array(array) => {
170
- for item in array {
171
- if match_error_recursively_resolved(item) {
172
- return true;
173
- }
174
- }
175
- }
176
- _ => {}
177
- }
108
+ let success_message = format!(
109
+ "Check completed successfully in {:.2?}. Output files written to: '{}'",
110
+ duration.elapsed(),
111
+ normalized_output_dir
112
+ );
178
113
 
179
- false
114
+ let logger = Logger::new();
115
+ logger.log_message(LogLevel::Success, &success_message);
180
116
  }
package/rust/cli/init.rs CHANGED
@@ -2,7 +2,7 @@ use std::{ fs, path::Path };
2
2
  use include_dir::{ include_dir, Dir };
3
3
  use inquire::{ Select, Confirm };
4
4
 
5
- use crate::{ cli::template::{ get_available_templates }, utils::file::copy_dir_recursive };
5
+ use crate::{ cli::template::get_available_templates, utils::file::copy_dir_recursive };
6
6
 
7
7
  static TEMPLATES_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates");
8
8
 
package/rust/cli/mod.rs CHANGED
@@ -1,4 +1,205 @@
1
1
  pub mod check;
2
- pub mod init;
3
2
  pub mod build;
4
- pub mod template;
3
+ pub mod init;
4
+ pub mod template;
5
+ pub mod play;
6
+
7
+ use clap::{ Parser, Subcommand };
8
+ use crate::utils::version::get_version;
9
+
10
+ #[derive(Parser)]
11
+ #[command(name = "devalang")]
12
+ #[command(author = "Devaloop")]
13
+ #[command(version = get_version())]
14
+ #[command(about = "🦊 Devalang – A programming language for music and sound.")]
15
+ pub struct Cli {
16
+ #[arg(long, global = true)]
17
+ /// Skips loading the configuration file.
18
+ pub no_config: bool,
19
+
20
+ #[command(subcommand)]
21
+ pub command: Commands,
22
+ }
23
+
24
+ #[derive(Subcommand)]
25
+ pub enum TemplateCommand {
26
+ /// Lists all available templates for Devalang projects.
27
+ List,
28
+ /// Displays information about a specific template.
29
+ Info {
30
+ name: String,
31
+ },
32
+ }
33
+
34
+ #[derive(Subcommand)]
35
+ pub enum Commands {
36
+ /// Create a new Devalang project.
37
+ ///
38
+ /// ### Arguments
39
+ /// - `name` - The name of the project to create.
40
+ /// - `template` - The template to use for the project. Defaults to "default".
41
+ ///
42
+ /// ### Example
43
+ /// ```bash
44
+ /// devalang init --name my_project --template default
45
+ ///
46
+ Init {
47
+ #[arg(short, long)]
48
+ /// The optional name (directory) of the project to create.
49
+ name: Option<String>,
50
+
51
+ #[arg(short, long)]
52
+ /// The template to use for the project.
53
+ ///
54
+ /// ### Default value
55
+ /// - `default`
56
+ ///
57
+ template: Option<String>,
58
+ },
59
+
60
+ Template {
61
+ #[command(subcommand)]
62
+ /// The template command to execute.
63
+ command: TemplateCommand,
64
+ },
65
+
66
+ /// Build the program and generate output files.
67
+ ///
68
+ /// ### Arguments
69
+ /// - `entry` - The entry point of the program to build. Defaults to "./src".
70
+ /// - `output` - The directory where the output files will be generated. Defaults to "./output".
71
+ /// - `watch` - Whether to watch for changes and rebuild. Defaults to "true".
72
+ ///
73
+ /// ### Example
74
+ /// ```bash
75
+ /// devalang build --entry ./src --output ./output --watch true
76
+ /// ```
77
+ ///
78
+ Build {
79
+ #[arg(short, long)]
80
+ /// The entry point of the program to build.
81
+ ///
82
+ entry: Option<String>,
83
+
84
+ #[arg(short, long)]
85
+ /// The directory where the output files will be generated.
86
+ ///
87
+ output: Option<String>,
88
+
89
+ #[arg(long, default_value_t = false)]
90
+ /// Whether to watch for changes and rebuild.
91
+ ///
92
+ /// ### Default value
93
+ /// - `false`
94
+ ///
95
+ watch: bool,
96
+
97
+ #[arg(long, default_value = "real-time")]
98
+ /// The mode of compilation.
99
+ ///
100
+ /// ### Default value
101
+ /// - `real-time`
102
+ ///
103
+ /// ### Possible values
104
+ /// - `real-time` - Compiles files as soon as possible.
105
+ /// - `batch` - Compiles files one by one.
106
+ /// - `check` - Analyzes the code without compiling it.
107
+ ///
108
+ compilation_mode: String,
109
+
110
+ #[arg(short, long, default_value_t = false)]
111
+ /// Whether to print debug information.
112
+ ///
113
+ /// ### Default value
114
+ /// - `false`
115
+ ///
116
+ debug: bool,
117
+
118
+ #[arg(short, long, default_value_t = false)]
119
+ /// Whether to compress the output files.
120
+ ///
121
+ /// ### Default value
122
+ /// - `false`
123
+ ///
124
+ compress: bool,
125
+ },
126
+
127
+ /// Analyze the program for errors and warnings.
128
+ ///
129
+ /// ### Arguments
130
+ /// - `entry` - The entry point of the program to analyze. Defaults to "./src".
131
+ /// - `watch` - Whether to watch for changes and re-analyze. Defaults to "true".
132
+ ///
133
+ /// ### Example
134
+ /// ```bash
135
+ /// devalang check --entry ./src --watch true --compilation-mode real-time
136
+ /// ```
137
+ Check {
138
+ #[arg(short, long)]
139
+ /// The entry point of the program to analyze.
140
+ ///
141
+ entry: Option<String>,
142
+
143
+ #[arg(short, long)]
144
+ /// The directory where the output files will be generated.
145
+ ///
146
+ output: Option<String>,
147
+
148
+ #[arg(long, default_value_t = false)]
149
+ /// Whether to watch for changes and re-analyze.
150
+ ///
151
+ /// ### Default value
152
+ /// - `false`
153
+ ///
154
+ watch: bool,
155
+
156
+ #[arg(short, long, default_value = "real-time")]
157
+ /// The mode of compilation.
158
+ ///
159
+ /// ### Default value
160
+ /// - `real-time`
161
+ ///
162
+ /// ### Possible values
163
+ /// - `real-time` - Analyzes files as soon as possible.
164
+ /// - `batch` - Analyzes files one by one.
165
+ /// - `check` - Analyzes the code without compiling it.
166
+ ///
167
+ compilation_mode: String,
168
+
169
+ #[arg(short, long, default_value_t = false)]
170
+ /// Whether to print debug information.
171
+ ///
172
+ /// ### Default value
173
+ /// - `false`
174
+ ///
175
+ debug: bool,
176
+ },
177
+
178
+ Play {
179
+ #[arg(short, long)]
180
+ /// The entry point of the program to play.
181
+ ///
182
+ entry: Option<String>,
183
+
184
+ #[arg(short, long)]
185
+ /// The directory where the output files will be generated.
186
+ ///
187
+ output: Option<String>,
188
+
189
+ #[arg(long, default_value_t = false)]
190
+ /// Whether to watch for changes and re-play.
191
+ ///
192
+ /// ### Default value
193
+ /// - `false`
194
+ ///
195
+ watch: bool,
196
+
197
+ #[arg(long, default_value_t = false)]
198
+ /// Whether to replay the program after it finishes.
199
+ ///
200
+ /// ### Default value
201
+ /// - `false`
202
+ ///
203
+ repeat: bool,
204
+ },
205
+ }
@@ -0,0 +1,191 @@
1
+ use crate::{
2
+ audio::player::AudioPlayer,
3
+ config::Config,
4
+ core::{
5
+ builder::Builder,
6
+ debugger::{ lexer::write_lexer_log_file, preprocessor::write_preprocessor_log_file },
7
+ preprocessor::loader::ModuleLoader,
8
+ store::global::GlobalStore,
9
+ utils::path::{ find_entry_file, normalize_path },
10
+ },
11
+ utils::{ logger::{ LogLevel, Logger }, spinner::with_spinner, watcher::watch_directory },
12
+ };
13
+
14
+ use std::{ path::Path, sync::mpsc::channel, thread, time::Duration };
15
+ use std::fs;
16
+ use std::collections::HashMap;
17
+
18
+ pub fn handle_play_command(
19
+ config: Option<Config>,
20
+ entry: Option<String>,
21
+ output: Option<String>,
22
+ watch: bool,
23
+ repeat: bool
24
+ ) {
25
+ let logger = Logger::new();
26
+
27
+ let entry_path = entry
28
+ .or_else(|| config.as_ref().and_then(|c| c.defaults.entry.clone()))
29
+ .unwrap_or_else(|| "".to_string());
30
+
31
+ let output_path = output
32
+ .or_else(|| config.as_ref().and_then(|c| c.defaults.output.clone()))
33
+ .unwrap_or_else(|| "".to_string());
34
+
35
+ let fetched_repeat = if repeat {
36
+ true
37
+ } else {
38
+ config
39
+ .as_ref()
40
+ .and_then(|c| c.defaults.repeat)
41
+ .unwrap_or(false)
42
+ };
43
+
44
+ if entry_path.is_empty() || output_path.is_empty() {
45
+ logger.log_message(LogLevel::Error, "Entry or output path not specified.");
46
+ std::process::exit(1);
47
+ }
48
+
49
+ let entry_file = find_entry_file(&entry_path).unwrap_or_else(|| {
50
+ logger.log_message(LogLevel::Error, "index.deva not found");
51
+ std::process::exit(1);
52
+ });
53
+
54
+ let audio_file = format!("{}/audio/index.wav", normalize_path(&output_path));
55
+ let mut audio_player = AudioPlayer::new();
56
+
57
+ if watch && fetched_repeat {
58
+ logger.log_message(
59
+ LogLevel::Error,
60
+ "Watch and repeat cannot be used together. Use repeat instead."
61
+ );
62
+ std::process::exit(1);
63
+ }
64
+
65
+ if watch {
66
+ let (tx, rx) = channel::<()>();
67
+
68
+ // Thread 1 : Watcher sending changes
69
+ let entry_clone = entry_path.clone();
70
+ thread::spawn(move || {
71
+ let _ = watch_directory(entry_clone, move || {
72
+ let _ = tx.send(()); // signal a change
73
+ });
74
+ });
75
+
76
+ // Main thread: build + play in a loop
77
+ begin_play(&config, &entry_file, &output_path);
78
+ audio_player.play_file_once(&audio_file);
79
+
80
+ logger.log_message(LogLevel::Watcher, "Watching for changes... Press Ctrl+C to exit.");
81
+
82
+ while let Ok(_) = rx.recv() {
83
+ logger.log_message(LogLevel::Watcher, "Change detected, rebuilding...");
84
+
85
+ begin_play(&config, &entry_file, &output_path);
86
+
87
+ logger.log_message(LogLevel::Info, "🎵 Playback started (once mode)...");
88
+
89
+ audio_player.play_file_once(&audio_file);
90
+ }
91
+ } else if fetched_repeat {
92
+ // Initial build to start from a clean slate
93
+ begin_play(&config, &entry_file, &output_path);
94
+
95
+ logger.log_message(LogLevel::Info, "🎵 Playback started (repeat mode)...");
96
+
97
+ let mut last_snapshot = snapshot_files(&entry_path);
98
+ let mut audio_player = AudioPlayer::new();
99
+ audio_player.play_file_once(&audio_file);
100
+
101
+ loop {
102
+ let current_snapshot = snapshot_files(&entry_path);
103
+ let has_changed = files_changed(&last_snapshot, &current_snapshot);
104
+
105
+ if has_changed {
106
+ logger.log_message(LogLevel::Info, "Change detected, rebuilding in background...");
107
+ let entry_file = entry_file.clone();
108
+ let output_path = output_path.clone();
109
+ let config_clone = config.clone();
110
+
111
+ // Rebuild in a separate thread
112
+ std::thread::spawn(move || {
113
+ begin_play(&config_clone, &entry_file, &output_path);
114
+ });
115
+
116
+ last_snapshot = current_snapshot;
117
+ }
118
+
119
+ // Wait for the audio to finish without blocking the current playback
120
+ audio_player.wait_until_end();
121
+
122
+ // Then replay the audio (rebuilt or not)
123
+ audio_player.play_file_once(&audio_file);
124
+ }
125
+ } else {
126
+ // Single execution
127
+ begin_play(&config, &entry_file, &output_path);
128
+
129
+ logger.log_message(LogLevel::Info, "🎵 Playback started (once mode)...");
130
+
131
+ audio_player.play_file_once(&audio_file);
132
+ audio_player.wait_until_end();
133
+ }
134
+ }
135
+
136
+ fn begin_play(config: &Option<Config>, entry_file: &str, output: &str) {
137
+ let spinner = with_spinner("Building...", || {
138
+ thread::sleep(Duration::from_millis(800));
139
+ });
140
+
141
+ let normalized_entry = normalize_path(entry_file);
142
+ let normalized_output_dir = normalize_path(&output);
143
+
144
+ let duration = std::time::Instant::now();
145
+ let mut global_store = GlobalStore::new();
146
+ let loader = ModuleLoader::new(&normalized_entry, &normalized_output_dir);
147
+ let (modules_tokens, modules_statements) = loader.load_all(&mut global_store);
148
+
149
+ // SECTION Write logs
150
+ write_lexer_log_file(&normalized_output_dir, "lexer_tokens.log", modules_tokens.clone());
151
+ write_preprocessor_log_file(
152
+ &normalized_output_dir,
153
+ "resolved_statements.log",
154
+ modules_statements.clone()
155
+ );
156
+
157
+ // SECTION Building AST and Audio
158
+ let builder = Builder::new();
159
+ builder.build_ast(&modules_statements);
160
+ builder.build_audio(&modules_statements, &output, &mut global_store);
161
+
162
+ // SECTION Logging
163
+ let logger = Logger::new();
164
+ let success_message = format!(
165
+ "Build completed successfully in {:.2?}. Output files written to: '{}'",
166
+ duration.elapsed(),
167
+ normalized_output_dir
168
+ );
169
+
170
+ logger.log_message(LogLevel::Success, &success_message);
171
+ }
172
+
173
+ fn snapshot_files<P: AsRef<Path>>(dir: P) -> HashMap<String, u64> {
174
+ let mut map = HashMap::new();
175
+ if let Ok(entries) = fs::read_dir(dir) {
176
+ for entry in entries.flatten() {
177
+ if let Ok(meta) = entry.metadata() {
178
+ if let Ok(mtime) = meta.modified() {
179
+ if let Ok(duration) = mtime.duration_since(std::time::UNIX_EPOCH) {
180
+ map.insert(entry.path().display().to_string(), duration.as_secs());
181
+ }
182
+ }
183
+ }
184
+ }
185
+ }
186
+ map
187
+ }
188
+
189
+ fn files_changed(old: &HashMap<String, u64>, new: &HashMap<String, u64>) -> bool {
190
+ old != new
191
+ }
@@ -1,7 +1,8 @@
1
1
  use std::{ fs, path::Path };
2
- use crate::core::types::config::DevalangConfig;
3
2
 
4
- pub fn load_config(path: Option<&Path>) -> Option<DevalangConfig> {
3
+ use crate::config::Config;
4
+
5
+ pub fn load_config(path: Option<&Path>) -> Option<Config> {
5
6
  let config_path = path.unwrap_or_else(|| Path::new(".devalang"));
6
7
 
7
8
  if config_path.exists() {
@@ -0,0 +1,16 @@
1
+ pub mod loader;
2
+
3
+ use serde::Deserialize;
4
+
5
+ #[derive(Debug, Deserialize, Clone)]
6
+ pub struct Config {
7
+ pub defaults: ConfigDefaults,
8
+ }
9
+
10
+ #[derive(Debug, Deserialize, Clone)]
11
+ pub struct ConfigDefaults {
12
+ pub entry: Option<String>,
13
+ pub output: Option<String>,
14
+ pub watch: Option<bool>,
15
+ pub repeat: Option<bool>,
16
+ }