@devaloop/devalang 0.0.1-alpha.3 → 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.
- package/.devalang +1 -1
- package/Cargo.toml +3 -3
- package/README.md +42 -25
- package/docs/CHANGELOG.md +17 -0
- package/docs/COMMANDS.md +31 -0
- package/docs/CONFIG.md +6 -4
- package/docs/ROADMAP.md +1 -1
- package/docs/TODO.md +4 -4
- package/examples/index.deva +7 -1
- package/examples/samples/hat-808.wav +0 -0
- package/out-tsc/bin/devalang.exe +0 -0
- package/package.json +41 -41
- package/project-version.json +6 -6
- package/rust/audio/engine.rs +130 -0
- package/rust/audio/interpreter.rs +143 -0
- package/rust/audio/loader.rs +46 -0
- package/rust/audio/mod.rs +5 -0
- package/rust/audio/player.rs +54 -0
- package/rust/audio/render.rs +57 -0
- package/rust/cli/build.rs +17 -6
- package/rust/cli/mod.rs +29 -0
- package/rust/cli/play.rs +191 -0
- package/rust/config/mod.rs +3 -2
- package/rust/core/builder/mod.rs +48 -0
- package/rust/core/debugger/lexer.rs +20 -5
- package/rust/core/debugger/preprocessor.rs +9 -5
- package/rust/core/preprocessor/loader.rs +26 -15
- package/rust/core/preprocessor/resolver/bank.rs +46 -0
- package/rust/core/preprocessor/resolver/loop_.rs +143 -0
- package/rust/core/preprocessor/resolver/mod.rs +152 -0
- package/rust/core/preprocessor/resolver/tempo.rs +49 -0
- package/rust/core/preprocessor/resolver/trigger.rs +114 -0
- package/rust/main.rs +6 -0
- package/rust/utils/watcher.rs +10 -2
- package/rust/core/preprocessor/resolver.rs +0 -372
|
@@ -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
|
+
}
|
|
@@ -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,7 +1,9 @@
|
|
|
1
1
|
use crate::{
|
|
2
|
+
audio::render::render_audio_with_modules,
|
|
2
3
|
config::Config,
|
|
3
4
|
core::{
|
|
4
5
|
builder::Builder,
|
|
6
|
+
debugger::{ lexer::write_lexer_log_file, preprocessor::write_preprocessor_log_file },
|
|
5
7
|
preprocessor::loader::ModuleLoader,
|
|
6
8
|
store::global::GlobalStore,
|
|
7
9
|
utils::path::{ find_entry_file, normalize_path },
|
|
@@ -102,20 +104,29 @@ fn begin_build(entry: String, output: String) {
|
|
|
102
104
|
|
|
103
105
|
// SECTION Load
|
|
104
106
|
// NOTE: We use modules in the build command, so we need to load them
|
|
105
|
-
let
|
|
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
|
+
);
|
|
106
116
|
|
|
107
|
-
// SECTION
|
|
117
|
+
// SECTION Building AST and Audio
|
|
108
118
|
let builder = Builder::new();
|
|
109
|
-
builder.build_ast(&
|
|
110
|
-
|
|
111
|
-
// TODO: Implement debugging
|
|
119
|
+
builder.build_ast(&modules_statements);
|
|
120
|
+
builder.build_audio(&modules_statements, &normalized_output_dir, &mut global_store);
|
|
112
121
|
|
|
122
|
+
// SECTION Logging
|
|
123
|
+
let logger = Logger::new();
|
|
124
|
+
|
|
113
125
|
let success_message = format!(
|
|
114
126
|
"Build completed successfully in {:.2?}. Output files written to: '{}'",
|
|
115
127
|
duration.elapsed(),
|
|
116
128
|
normalized_output_dir
|
|
117
129
|
);
|
|
118
130
|
|
|
119
|
-
let logger = Logger::new();
|
|
120
131
|
logger.log_message(LogLevel::Success, &success_message);
|
|
121
132
|
}
|
package/rust/cli/mod.rs
CHANGED
|
@@ -2,6 +2,7 @@ pub mod check;
|
|
|
2
2
|
pub mod build;
|
|
3
3
|
pub mod init;
|
|
4
4
|
pub mod template;
|
|
5
|
+
pub mod play;
|
|
5
6
|
|
|
6
7
|
use clap::{ Parser, Subcommand };
|
|
7
8
|
use crate::utils::version::get_version;
|
|
@@ -173,4 +174,32 @@ pub enum Commands {
|
|
|
173
174
|
///
|
|
174
175
|
debug: bool,
|
|
175
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
|
+
},
|
|
176
205
|
}
|
package/rust/cli/play.rs
ADDED
|
@@ -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, ¤t_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
|
+
}
|
package/rust/config/mod.rs
CHANGED
|
@@ -2,14 +2,15 @@ pub mod loader;
|
|
|
2
2
|
|
|
3
3
|
use serde::Deserialize;
|
|
4
4
|
|
|
5
|
-
#[derive(Debug, Deserialize)]
|
|
5
|
+
#[derive(Debug, Deserialize, Clone)]
|
|
6
6
|
pub struct Config {
|
|
7
7
|
pub defaults: ConfigDefaults,
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
#[derive(Debug, Deserialize)]
|
|
10
|
+
#[derive(Debug, Deserialize, Clone)]
|
|
11
11
|
pub struct ConfigDefaults {
|
|
12
12
|
pub entry: Option<String>,
|
|
13
13
|
pub output: Option<String>,
|
|
14
14
|
pub watch: Option<bool>,
|
|
15
|
+
pub repeat: Option<bool>,
|
|
15
16
|
}
|
package/rust/core/builder/mod.rs
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
use crate::audio::render::render_audio_with_modules;
|
|
1
2
|
use crate::core::parser::statement::Statement;
|
|
3
|
+
use crate::core::store::global::GlobalStore;
|
|
4
|
+
use crate::utils::logger::Logger;
|
|
2
5
|
use std::{ collections::HashMap, fs::create_dir_all };
|
|
3
6
|
use std::io::Write;
|
|
4
7
|
|
|
@@ -28,4 +31,49 @@ impl Builder {
|
|
|
28
31
|
file.write_all(content.as_bytes()).expect("Failed to write AST to file");
|
|
29
32
|
}
|
|
30
33
|
}
|
|
34
|
+
|
|
35
|
+
pub fn build_audio(
|
|
36
|
+
&self,
|
|
37
|
+
modules: &HashMap<String, Vec<Statement>>,
|
|
38
|
+
normalized_output_dir: &str,
|
|
39
|
+
global_store: &mut GlobalStore
|
|
40
|
+
) {
|
|
41
|
+
let logger = Logger::new();
|
|
42
|
+
|
|
43
|
+
let audio_engines = render_audio_with_modules(
|
|
44
|
+
modules.clone(),
|
|
45
|
+
&normalized_output_dir,
|
|
46
|
+
global_store
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
create_dir_all(format!("{}/audio", normalized_output_dir)).expect("Failed to create audio directory");
|
|
50
|
+
|
|
51
|
+
for (module_name, mut audio_engine) in audio_engines {
|
|
52
|
+
let formatted_module_name = module_name
|
|
53
|
+
.split('/')
|
|
54
|
+
.last()
|
|
55
|
+
.unwrap_or(&module_name)
|
|
56
|
+
.replace(".deva", "");
|
|
57
|
+
|
|
58
|
+
let output_path = format!(
|
|
59
|
+
"{}/audio/{}.wav",
|
|
60
|
+
normalized_output_dir,
|
|
61
|
+
formatted_module_name
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
match audio_engine.generate_wav_file(&output_path) {
|
|
65
|
+
Ok(_) => {}
|
|
66
|
+
Err(msg) => {
|
|
67
|
+
logger.log_error_with_stacktrace(
|
|
68
|
+
&format!(
|
|
69
|
+
"Unable to generate WAV file for module '{}': {}",
|
|
70
|
+
formatted_module_name,
|
|
71
|
+
msg
|
|
72
|
+
),
|
|
73
|
+
&module_name
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
31
79
|
}
|
|
@@ -1,12 +1,27 @@
|
|
|
1
|
-
use
|
|
1
|
+
use std::{ collections::HashMap, fs::create_dir_all };
|
|
2
|
+
use crate::core::{ debugger::Debugger, lexer::token::Token, parser::statement::Statement };
|
|
2
3
|
|
|
3
|
-
pub fn write_lexer_log_file(
|
|
4
|
+
pub fn write_lexer_log_file(
|
|
5
|
+
output_dir: &str,
|
|
6
|
+
file_name: &str,
|
|
7
|
+
modules: HashMap<String, Vec<Token>>
|
|
8
|
+
) {
|
|
4
9
|
let debugger = Debugger::new();
|
|
5
10
|
let mut content = String::new();
|
|
6
11
|
|
|
7
|
-
|
|
8
|
-
|
|
12
|
+
let log_directory = format!("{}/logs", output_dir);
|
|
13
|
+
|
|
14
|
+
create_dir_all(&log_directory).expect("Failed to create log directory");
|
|
15
|
+
|
|
16
|
+
for (path, tokens) in modules {
|
|
17
|
+
content.push_str(&format!("--- Resolved Tokens for {} ---\n", path));
|
|
18
|
+
|
|
19
|
+
for token in tokens {
|
|
20
|
+
content.push_str(&format!("{:?}\n", token));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
content.push_str("\n");
|
|
9
24
|
}
|
|
10
25
|
|
|
11
|
-
debugger.write_log_file(
|
|
26
|
+
debugger.write_log_file(&log_directory, file_name, &content);
|
|
12
27
|
}
|
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
use std::collections::HashMap;
|
|
1
|
+
use std::{ collections::HashMap, fs::create_dir_all };
|
|
2
2
|
use crate::core::{ debugger::Debugger, parser::statement::Statement };
|
|
3
3
|
|
|
4
4
|
pub fn write_preprocessor_log_file(
|
|
5
5
|
output_dir: &str,
|
|
6
6
|
file_name: &str,
|
|
7
|
-
|
|
7
|
+
modules: HashMap<String, Vec<Statement>>
|
|
8
8
|
) {
|
|
9
9
|
let debugger = Debugger::new();
|
|
10
10
|
let mut content = String::new();
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
let log_directory = format!("{}/logs", output_dir);
|
|
13
|
+
|
|
14
|
+
create_dir_all(&log_directory).expect("Failed to create log directory");
|
|
15
|
+
|
|
16
|
+
for (path, stmts) in modules {
|
|
13
17
|
content.push_str(&format!("--- Resolved Statements for {} ---\n", path));
|
|
14
|
-
|
|
18
|
+
|
|
15
19
|
for stmt in stmts {
|
|
16
20
|
content.push_str(&format!("{:?}\n", stmt));
|
|
17
21
|
}
|
|
@@ -19,5 +23,5 @@ pub fn write_preprocessor_log_file(
|
|
|
19
23
|
content.push_str("\n");
|
|
20
24
|
}
|
|
21
25
|
|
|
22
|
-
debugger.write_log_file(
|
|
26
|
+
debugger.write_log_file(&log_directory, file_name, &content);
|
|
23
27
|
}
|
|
@@ -2,9 +2,8 @@ use std::collections::HashMap;
|
|
|
2
2
|
|
|
3
3
|
use crate::{
|
|
4
4
|
core::{
|
|
5
|
-
debugger::{ lexer::write_lexer_log_file, preprocessor::write_preprocessor_log_file },
|
|
6
5
|
error::ErrorHandler,
|
|
7
|
-
lexer::Lexer,
|
|
6
|
+
lexer::{ token::Token, Lexer },
|
|
8
7
|
parser::{ statement::{ Statement, StatementKind }, Parser },
|
|
9
8
|
preprocessor::{
|
|
10
9
|
module::Module,
|
|
@@ -13,7 +12,7 @@ use crate::{
|
|
|
13
12
|
},
|
|
14
13
|
store::global::GlobalStore,
|
|
15
14
|
},
|
|
16
|
-
utils::logger::{
|
|
15
|
+
utils::logger::{ Logger },
|
|
17
16
|
};
|
|
18
17
|
|
|
19
18
|
pub struct ModuleLoader {
|
|
@@ -29,26 +28,29 @@ impl ModuleLoader {
|
|
|
29
28
|
}
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
pub fn load_all(
|
|
31
|
+
pub fn load_all(
|
|
32
|
+
&self,
|
|
33
|
+
global_store: &mut GlobalStore
|
|
34
|
+
) -> (HashMap<String, Vec<Token>>, HashMap<String, Vec<Statement>>) {
|
|
33
35
|
// SECTION Load the entry module and its dependencies
|
|
34
|
-
self.load_module_recursively(&self.entry, global_store);
|
|
36
|
+
let tokens_by_module = self.load_module_recursively(&self.entry, global_store);
|
|
35
37
|
|
|
36
38
|
// SECTION Process and resolve modules
|
|
37
39
|
process_modules(self, global_store);
|
|
38
40
|
resolve_all_modules(self, global_store);
|
|
39
41
|
|
|
40
|
-
let
|
|
42
|
+
let statemnts_by_module = resolve_and_flatten_all_modules(global_store);
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
write_preprocessor_log_file(&self.output, "resolved_statements.log", resolved.clone());
|
|
44
|
-
|
|
45
|
-
// Return the resolved statements
|
|
46
|
-
resolved
|
|
44
|
+
(tokens_by_module, statemnts_by_module)
|
|
47
45
|
}
|
|
48
46
|
|
|
49
|
-
fn load_module_recursively(
|
|
47
|
+
fn load_module_recursively(
|
|
48
|
+
&self,
|
|
49
|
+
path: &str,
|
|
50
|
+
global_store: &mut GlobalStore
|
|
51
|
+
) -> HashMap<String, Vec<Token>> {
|
|
50
52
|
if global_store.modules.contains_key(path) {
|
|
51
|
-
return;
|
|
53
|
+
return HashMap::new();
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
let lexer = Lexer::new();
|
|
@@ -75,13 +77,22 @@ impl ModuleLoader {
|
|
|
75
77
|
|
|
76
78
|
// SECTION Module creation
|
|
77
79
|
let mut module = Module::new(path);
|
|
78
|
-
module.tokens = tokens;
|
|
79
|
-
module.statements = statements;
|
|
80
|
+
module.tokens = tokens.clone();
|
|
81
|
+
module.statements = statements.clone();
|
|
80
82
|
|
|
81
83
|
global_store.insert_module(path.to_string(), module);
|
|
82
84
|
|
|
83
85
|
// Then load the imports recursively
|
|
84
86
|
self.load_module_imports(&path.to_string(), global_store);
|
|
87
|
+
|
|
88
|
+
// Return all tokens by module
|
|
89
|
+
let mut tokens_by_module = HashMap::new();
|
|
90
|
+
|
|
91
|
+
global_store.modules.iter().for_each(|(path, module)| {
|
|
92
|
+
tokens_by_module.insert(path.clone(), module.tokens.clone());
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
tokens_by_module
|
|
85
96
|
}
|
|
86
97
|
|
|
87
98
|
fn load_module_imports(&self, path: &String, global_store: &mut GlobalStore) {
|