@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
@@ -0,0 +1,130 @@
1
+ use std::{ collections::HashMap, fs::File, io::BufReader };
2
+ use hound::{ SampleFormat, WavSpec, WavWriter };
3
+ use rodio::{ Decoder, Source };
4
+
5
+ use crate::core::{
6
+ parser::statement::Statement,
7
+ store::variable::VariableTable,
8
+ utils::path::normalize_path,
9
+ };
10
+
11
+ const SAMPLE_RATE: u32 = 44100;
12
+ const CHANNELS: u16 = 2;
13
+
14
+ #[derive(Debug, Clone, PartialEq)]
15
+ pub struct AudioEngine {
16
+ pub volume: f32,
17
+ pub variables: VariableTable,
18
+ pub buffer: Vec<i16>,
19
+ }
20
+
21
+ impl AudioEngine {
22
+ pub fn new() -> Self {
23
+ AudioEngine {
24
+ volume: 1.0,
25
+ buffer: vec![],
26
+ variables: VariableTable::new(),
27
+ }
28
+ }
29
+
30
+ pub fn mix(&mut self, other: &AudioEngine) {
31
+ let max_len = self.buffer.len().max(other.buffer.len());
32
+ self.buffer.resize(max_len, 0);
33
+
34
+ for (i, &sample) in other.buffer.iter().enumerate() {
35
+ self.buffer[i] = self.buffer[i].saturating_add(sample);
36
+ }
37
+ }
38
+
39
+ pub fn set_duration(&mut self, duration_secs: f32) {
40
+ let mut total_samples = (duration_secs * (SAMPLE_RATE as f32) * (CHANNELS as f32)) as usize;
41
+
42
+ if total_samples % (CHANNELS as usize) != 0 {
43
+ total_samples += 1;
44
+ }
45
+
46
+ self.buffer.resize(total_samples, 0);
47
+ }
48
+
49
+ pub fn set_variables(&mut self, variables: VariableTable) {
50
+ self.variables = variables;
51
+ }
52
+
53
+ pub fn generate_wav_file(&mut self, output_dir: &String) -> Result<(), String> {
54
+ if self.buffer.len() % (CHANNELS as usize) != 0 {
55
+ self.buffer.push(0);
56
+ println!("Completed buffer to respect stereo format.");
57
+ }
58
+
59
+ let spec = WavSpec {
60
+ channels: CHANNELS,
61
+ sample_rate: SAMPLE_RATE,
62
+ bits_per_sample: 16,
63
+ sample_format: SampleFormat::Int,
64
+ };
65
+
66
+ let mut writer = WavWriter::create(output_dir, spec).map_err(|e|
67
+ format!("Error creating WAV file: {}", e)
68
+ )?;
69
+
70
+ for sample in &self.buffer {
71
+ writer.write_sample(*sample).map_err(|e| format!("Error writing sample: {:?}", e))?;
72
+ }
73
+
74
+ writer.finalize().map_err(|e| format!("Error finalizing WAV: {:?}", e))?;
75
+
76
+ Ok(())
77
+ }
78
+
79
+ pub fn insert(
80
+ &mut self,
81
+ filepath: &str,
82
+ time_secs: f32,
83
+ dur_sec: f32,
84
+ effects: Option<HashMap<String, f32>>
85
+ ) {
86
+ let normalized_filepath = normalize_path(filepath);
87
+
88
+ let file = BufReader::new(
89
+ File::open(normalized_filepath).expect("Failed to open audio file")
90
+ );
91
+ let decoder = Decoder::new(file).expect("Failed to decode audio file");
92
+
93
+ // Mono or stereo reading possible here, we will duplicate in L/R
94
+ let max_mono_samples = (dur_sec * (SAMPLE_RATE as f32)) as usize;
95
+ let samples: Vec<i16> = decoder.convert_samples().take(max_mono_samples).collect();
96
+
97
+ if samples.is_empty() {
98
+ eprintln!("No samples found in the audio file: {}", filepath);
99
+ return;
100
+ }
101
+
102
+ // TODO Apply effects here if needed
103
+ let offset = (time_secs * (SAMPLE_RATE as f32) * (CHANNELS as f32)) as usize;
104
+ let required_len = offset + samples.len() * (CHANNELS as usize);
105
+ let padded_required_len = if required_len % 2 == 1 {
106
+ required_len + 1
107
+ } else {
108
+ required_len
109
+ };
110
+
111
+ self.buffer.resize(padded_required_len, 0);
112
+ self.pad_samples(&samples, time_secs);
113
+ }
114
+
115
+ fn pad_samples(&mut self, samples: &[i16], time_secs: f32) {
116
+ let offset = (time_secs * (SAMPLE_RATE as f32) * (CHANNELS as f32)) as usize;
117
+
118
+ for (i, &sample) in samples.iter().enumerate() {
119
+ let adjusted_sample = ((sample as f32) * self.volume).round() as i16;
120
+
121
+ let left_pos = offset + i * 2;
122
+ let right_pos = left_pos + 1;
123
+
124
+ if right_pos < self.buffer.len() {
125
+ self.buffer[left_pos] = self.buffer[left_pos].saturating_add(adjusted_sample); // gauche
126
+ self.buffer[right_pos] = self.buffer[right_pos].saturating_add(adjusted_sample); // droite
127
+ }
128
+ }
129
+ }
130
+ }
@@ -0,0 +1,143 @@
1
+ use crate::{
2
+ audio::{ engine::AudioEngine, loader::load_trigger },
3
+ core::{
4
+ parser::statement::{ Statement, StatementKind },
5
+ shared::value::Value,
6
+ store::variable::VariableTable,
7
+ },
8
+ };
9
+
10
+ pub fn interprete_statements(
11
+ statements: &Vec<Statement>,
12
+ audio_engine: AudioEngine,
13
+ entry: String,
14
+ output: String
15
+ ) -> (AudioEngine, f32, f32) {
16
+ let mut base_bpm = 120.0;
17
+ let mut base_duration = 60.0 / base_bpm;
18
+
19
+ let variable_table = audio_engine.variables.clone();
20
+
21
+ let (updated_audio_engine, base_bpm, max_end_time) = execute_audio_statements(
22
+ audio_engine.clone(),
23
+ variable_table.clone(),
24
+ statements.clone(),
25
+ base_bpm.clone(),
26
+ base_duration.clone(),
27
+ 0.0,
28
+ 0.0
29
+ );
30
+
31
+ (updated_audio_engine, base_bpm, max_end_time)
32
+ }
33
+
34
+ pub fn execute_audio_statements(
35
+ mut audio_engine: AudioEngine,
36
+ mut variable_table: VariableTable,
37
+ mut statements: Vec<Statement>,
38
+ mut base_bpm: f32,
39
+ mut base_duration: f32,
40
+ mut max_end_time: f32,
41
+ mut cursor_time: f32
42
+ ) -> (AudioEngine, f32, f32) {
43
+ for stmt in statements {
44
+ match &stmt.kind {
45
+ StatementKind::Load { source, alias } => {
46
+ variable_table.set(alias.to_string(), Value::String(source.clone()));
47
+ }
48
+
49
+ StatementKind::Let { name } => {
50
+ variable_table.set(name.to_string(), stmt.value.clone());
51
+ }
52
+
53
+ StatementKind::Tempo => {
54
+ if let Value::Number(bpm_) = &stmt.value {
55
+ base_bpm = *bpm_ as f32;
56
+ base_duration = 60.0 / base_bpm;
57
+ } else {
58
+ eprintln!("❌ Invalid tempo value: {:?}", stmt.value);
59
+ }
60
+ }
61
+
62
+ StatementKind::Trigger { entity, duration } => {
63
+ if let Some(trigger_val) = variable_table.get(entity) {
64
+ let (src, duration_secs) = load_trigger(
65
+ trigger_val,
66
+ duration,
67
+ base_duration,
68
+ variable_table.clone()
69
+ );
70
+
71
+ audio_engine.insert(&src, cursor_time, duration_secs, None);
72
+
73
+ cursor_time += duration_secs;
74
+
75
+ if cursor_time > max_end_time {
76
+ max_end_time = cursor_time;
77
+ }
78
+ } else {
79
+ eprintln!("❌ Unknown trigger entity: {}", entity);
80
+ }
81
+ }
82
+
83
+ StatementKind::Loop => {
84
+ if let Value::Map(loop_value) = &stmt.value {
85
+ let iterator = loop_value.get("iterator");
86
+ let body = loop_value.get("body");
87
+
88
+ let loop_count = if let Some(Value::Number(n)) = iterator {
89
+ *n as usize
90
+ } else {
91
+ eprintln!("❌ Loop iterator must be a number: {:?}", iterator);
92
+ continue;
93
+ };
94
+
95
+ let loop_body = if let Some(Value::Block(body)) = body {
96
+ body.clone()
97
+ } else {
98
+ eprintln!("❌ Loop body must be a block: {:?}", body);
99
+ continue;
100
+ };
101
+
102
+ for _ in 0..loop_count {
103
+ let (loop_engine, _, loop_end_time) = execute_audio_statements(
104
+ audio_engine.clone(),
105
+ variable_table.clone(),
106
+ loop_body.clone(),
107
+ base_bpm,
108
+ base_duration,
109
+ max_end_time,
110
+ cursor_time
111
+ );
112
+
113
+ audio_engine = loop_engine;
114
+
115
+ // Update time and max_end_time after each loop iteration
116
+ cursor_time = loop_end_time;
117
+ if loop_end_time > max_end_time {
118
+ max_end_time = loop_end_time;
119
+ }
120
+ }
121
+ }
122
+ }
123
+
124
+ StatementKind::Bank => {}
125
+
126
+ StatementKind::Import { names, source } => {}
127
+
128
+ StatementKind::Export { names, source } => {}
129
+
130
+ StatementKind::Unknown => {
131
+ // Ignore unknown statements
132
+ }
133
+
134
+ _ => {
135
+ eprintln!("Unsupported statement kind: {:?}", stmt);
136
+ }
137
+ }
138
+ }
139
+
140
+ audio_engine.set_variables(variable_table);
141
+
142
+ (audio_engine, base_bpm, max_end_time)
143
+ }
@@ -0,0 +1,46 @@
1
+ use crate::core::{ shared::{ duration::Duration, value::Value }, store::variable::VariableTable };
2
+
3
+ pub fn load_trigger(
4
+ trigger: &Value,
5
+ duration: &Duration,
6
+ base_duration: f32,
7
+ variable_table: VariableTable
8
+ ) -> (String, f32) {
9
+ let mut trigger_path = String::new();
10
+ let mut duration_as_secs = 0.0;
11
+
12
+ match trigger {
13
+ Value::String(src) => {
14
+ trigger_path = src.to_string();
15
+ }
16
+ _ => {
17
+ eprintln!("❌ Invalid trigger type. Expected a text variable.");
18
+ }
19
+ }
20
+
21
+ match duration {
22
+ Duration::Identifier(duration_identifier) => {
23
+ if duration_identifier == "auto" {
24
+ duration_as_secs = base_duration;
25
+ } else if let Some(Value::Number(num)) = variable_table.get(duration_identifier) {
26
+ duration_as_secs = *num;
27
+ } else {
28
+ eprintln!("❌ Invalid duration identifier: {}", duration_identifier);
29
+ }
30
+ }
31
+
32
+ Duration::Number(num) => {
33
+ duration_as_secs = *num;
34
+ }
35
+
36
+ Duration::Auto => {
37
+ duration_as_secs = base_duration;
38
+ }
39
+
40
+ _ => {
41
+ eprintln!("❌ Invalid duration type. Expected an identifier.");
42
+ }
43
+ }
44
+
45
+ (trigger_path, duration_as_secs)
46
+ }
package/rust/audio/mod.rs CHANGED
@@ -1 +1,5 @@
1
- // TODO Audio engine
1
+ pub mod engine;
2
+ pub mod interpreter;
3
+ pub mod loader;
4
+ pub mod player;
5
+ pub mod render;
@@ -0,0 +1,54 @@
1
+ use rodio::{ Decoder, OutputStream, OutputStreamHandle, Sink, Source };
2
+ use std::{ fs::File, io::BufReader };
3
+
4
+ pub struct AudioPlayer {
5
+ _stream: OutputStream,
6
+ handle: OutputStreamHandle,
7
+ sink: Sink,
8
+ last_path: Option<String>,
9
+ }
10
+
11
+ impl AudioPlayer {
12
+ pub fn new() -> Self {
13
+ let (stream, handle) = OutputStream::try_default().unwrap();
14
+ let sink = Sink::try_new(&handle).unwrap();
15
+
16
+ Self {
17
+ _stream: stream,
18
+ handle,
19
+ sink,
20
+ last_path: None,
21
+ }
22
+ }
23
+
24
+ fn load_source(&self, path: &str) -> impl Source<Item = f32> + Send + 'static {
25
+ let file = File::open(path).unwrap();
26
+ let reader = BufReader::new(file);
27
+
28
+ Decoder::new(reader).unwrap().convert_samples()
29
+ }
30
+
31
+ pub fn play_file_once(&mut self, path: &str) {
32
+ self.sink.stop();
33
+ self.sink = Sink::try_new(&self.handle).unwrap();
34
+
35
+ self.sink.set_volume(1.0);
36
+
37
+ let source = self.load_source(path);
38
+
39
+ self.sink.append(source);
40
+ self.last_path = Some(path.to_string());
41
+ }
42
+
43
+ pub fn replay_last(&mut self) {
44
+ if let Some(path) = self.last_path.clone() {
45
+ self.play_file_once(&path);
46
+ } else {
47
+ eprintln!("⚠️ No previous audio to replay.");
48
+ }
49
+ }
50
+
51
+ pub fn wait_until_end(&self) {
52
+ self.sink.sleep_until_end();
53
+ }
54
+ }
@@ -0,0 +1,57 @@
1
+ use std::collections::HashMap;
2
+
3
+ use crate::{
4
+ audio::{ engine::AudioEngine, interpreter::interprete_statements },
5
+ core::{
6
+ parser::statement::Statement,
7
+ store::global::GlobalStore,
8
+ },
9
+ utils::logger::{ LogLevel, Logger },
10
+ };
11
+
12
+ pub fn render_audio_with_modules(
13
+ modules: HashMap<String, Vec<Statement>>,
14
+ output_dir: &str,
15
+ global_store: &mut GlobalStore
16
+ ) -> HashMap<String, AudioEngine> {
17
+ let mut result = HashMap::new();
18
+
19
+ for (module_name, statements) in modules {
20
+ let mut global_max_end_time = 0.0;
21
+ let mut audio_engine = AudioEngine::new();
22
+
23
+ // Apply the module's variable table if it exists
24
+ if let Some(module) = global_store.get_module(&module_name) {
25
+ audio_engine.set_variables(module.variable_table.clone());
26
+ }
27
+
28
+ // Interpret the statements to fill the audio buffer
29
+ let (mut audio_engine, module_base_bpm, module_max_end_time) = interprete_statements(
30
+ &statements,
31
+ audio_engine,
32
+ module_name.clone(),
33
+ output_dir.to_string()
34
+ );
35
+
36
+ // Calculate the module's maximum duration
37
+ global_max_end_time = module_max_end_time.max(global_max_end_time);
38
+ audio_engine.set_duration(global_max_end_time);
39
+
40
+ // Check if the buffer contains at least one non-zero sample
41
+ if audio_engine.buffer.iter().all(|&s| s == 0) {
42
+ let logger = Logger::new();
43
+
44
+ logger.log_message(
45
+ LogLevel::Warning,
46
+ format!("Module '{}' ignored: silent buffer (no non-zero samples)", module_name).as_str()
47
+ );
48
+
49
+ continue;
50
+ }
51
+
52
+ // Insert only if the module produces sound
53
+ result.insert(module_name, audio_engine);
54
+ }
55
+
56
+ result
57
+ }
package/rust/cli/build.rs CHANGED
@@ -1,23 +1,19 @@
1
- use std::{ thread, time::Duration };
2
-
3
1
  use crate::{
2
+ audio::render::render_audio_with_modules,
3
+ config::Config,
4
4
  core::{
5
- builder::{ build_ast, write_ast_to_file },
6
- debugger::Debugger,
7
- preprocessor::module::load_all_modules,
8
- types::config::DevalangConfig,
9
- },
10
- runner::executer::execute_statements,
11
- utils::{
12
- loader::with_spinner,
13
- logger::log_message,
14
- path::{ find_entry_file, normalize_path },
15
- watcher::watch_directory,
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 },
16
10
  },
11
+ utils::{ logger::{ LogLevel, Logger }, spinner::with_spinner, watcher::watch_directory },
17
12
  };
13
+ use std::{ thread, time::Duration };
18
14
 
19
15
  pub fn handle_build_command(
20
- config: Option<DevalangConfig>,
16
+ config: Option<Config>,
21
17
  entry: Option<String>,
22
18
  output: Option<String>,
23
19
  watch: bool
@@ -49,26 +45,51 @@ pub fn handle_build_command(
49
45
  .unwrap_or(false)
50
46
  };
51
47
 
48
+ let logger = Logger::new();
49
+
50
+ if fetched_entry.is_empty() {
51
+ logger.log_message(
52
+ LogLevel::Error,
53
+ "Entry path is not specified. Please provide a valid entry path."
54
+ );
55
+ std::process::exit(1);
56
+ }
57
+ if fetched_output.is_empty() {
58
+ logger.log_message(
59
+ LogLevel::Error,
60
+ "Output directory is not specified. Please provide a valid output directory."
61
+ );
62
+ std::process::exit(1);
63
+ }
64
+
52
65
  let entry_file = find_entry_file(&fetched_entry).unwrap_or_else(|| {
53
- eprintln!("❌ index.deva not found in directory: {}", fetched_entry);
66
+ logger.log_message(
67
+ LogLevel::Error,
68
+ &format!("❌ index.deva not found in directory: {}", fetched_entry)
69
+ );
54
70
  std::process::exit(1);
55
71
  });
56
72
 
57
- if watch == true {
58
- log_message("Watch mode enabled, waiting for file changes...", "INFO");
73
+ // SECTION Begin build
74
+ if fetched_watch {
75
+ begin_build(entry_file.clone(), fetched_output.clone());
59
76
 
60
- begin_build(entry_file.clone(), fetched_output.clone(), watch);
77
+ logger.log_message(
78
+ LogLevel::Watcher,
79
+ &format!("Watching for changes in '{}'...", fetched_entry)
80
+ );
61
81
 
62
82
  watch_directory(entry_file.clone(), move || {
63
- log_message("File change detected, rebuilding...", "INFO");
64
- begin_build(entry_file.clone(), fetched_output.clone(), watch);
83
+ logger.log_message(LogLevel::Watcher, "Detected changes, re-building...");
84
+
85
+ begin_build(entry_file.clone(), fetched_output.clone());
65
86
  }).unwrap();
66
87
  } else {
67
- begin_build(entry_file.clone(), fetched_output.clone(), watch);
88
+ begin_build(entry_file.clone(), fetched_output.clone());
68
89
  }
69
90
  }
70
91
 
71
- fn begin_build(entry: String, output: String, watch: bool) {
92
+ fn begin_build(entry: String, output: String) {
72
93
  let spinner = with_spinner("Building...", || {
73
94
  thread::sleep(Duration::from_millis(800));
74
95
  });
@@ -78,27 +99,34 @@ fn begin_build(entry: String, output: String, watch: bool) {
78
99
  let normalized_entry_file = normalize_path(&entry);
79
100
  let normalized_output_dir = normalize_path(&output);
80
101
 
81
- let global_store = load_all_modules(&normalized_entry_file);
82
-
83
- if let Some(module) = global_store.modules.get(&normalized_entry_file) {
84
- let mut module_clone = module.clone();
85
-
86
- let resolved_statements = execute_statements(&mut module_clone);
87
-
88
- let ast = build_ast(&resolved_statements);
89
-
90
- let ast_dir = format!("{}/json", normalized_output_dir.clone());
91
- write_ast_to_file(&ast, &ast_dir);
92
-
93
- let debugger = Debugger::new(&module_clone);
94
- let debug_dir = format!("{}/debug/", normalized_output_dir.clone());
95
- debugger.write_files(debug_dir.as_str(), resolved_statements);
96
-
97
- let success_message = format!(
98
- "Build completed successfully in {:.2?}. Output files written to: '{}'",
99
- duration.elapsed(),
100
- normalized_output_dir
101
- );
102
- log_message(&success_message, "SUCCESS");
103
- }
102
+ let mut global_store = GlobalStore::new();
103
+ let module_loader = ModuleLoader::new(&normalized_entry_file, &normalized_output_dir);
104
+
105
+ // SECTION Load
106
+ // NOTE: We use modules in the build command, so we need to load them
107
+ let (modules_tokens, modules_statements) = module_loader.load_all(&mut global_store);
108
+
109
+ // SECTION Write logs
110
+ write_lexer_log_file(&normalized_output_dir, "lexer_tokens.log", modules_tokens.clone());
111
+ write_preprocessor_log_file(
112
+ &normalized_output_dir,
113
+ "resolved_statements.log",
114
+ modules_statements.clone()
115
+ );
116
+
117
+ // SECTION Building AST and Audio
118
+ let builder = Builder::new();
119
+ builder.build_ast(&modules_statements);
120
+ builder.build_audio(&modules_statements, &normalized_output_dir, &mut global_store);
121
+
122
+ // SECTION Logging
123
+ let logger = Logger::new();
124
+
125
+ let success_message = format!(
126
+ "Build completed successfully in {:.2?}. Output files written to: '{}'",
127
+ duration.elapsed(),
128
+ normalized_output_dir
129
+ );
130
+
131
+ logger.log_message(LogLevel::Success, &success_message);
104
132
  }