@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.
- package/.devalang +1 -1
- package/Cargo.toml +46 -46
- package/README.md +48 -30
- package/docs/CHANGELOG.md +28 -6
- package/docs/COMMANDS.md +31 -0
- package/docs/CONFIG.md +6 -4
- package/docs/ROADMAP.md +5 -1
- package/docs/TODO.md +10 -35
- package/examples/exported.deva +1 -1
- package/examples/index.deva +8 -1
- package/examples/samples/hat-808.wav +0 -0
- package/out-tsc/bin/devalang.exe +0 -0
- package/package.json +41 -42
- package/project-version.json +5 -5
- 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 -1
- package/rust/audio/player.rs +54 -0
- package/rust/audio/render.rs +57 -0
- package/rust/cli/build.rs +73 -45
- package/rust/cli/check.rs +47 -111
- package/rust/cli/init.rs +1 -1
- package/rust/cli/mod.rs +203 -2
- package/rust/cli/play.rs +191 -0
- package/rust/{utils/config.rs → config/loader.rs} +3 -2
- package/rust/config/mod.rs +16 -0
- package/rust/core/builder/mod.rs +69 -27
- package/rust/core/debugger/lexer.rs +27 -0
- package/rust/core/debugger/mod.rs +12 -49
- package/rust/core/debugger/preprocessor.rs +27 -0
- package/rust/core/error/mod.rs +60 -0
- package/rust/core/lexer/{at.rs → handler/at.rs} +1 -1
- package/rust/core/lexer/{brace.rs → handler/brace.rs} +1 -1
- package/rust/core/lexer/{colon.rs → handler/colon.rs} +1 -1
- package/rust/core/lexer/{comment.rs → handler/comment.rs} +3 -3
- package/rust/core/lexer/{dot.rs → handler/dot.rs} +1 -1
- package/rust/core/lexer/{equal.rs → handler/equal.rs} +1 -1
- package/rust/core/lexer/{identifier.rs → handler/identifier.rs} +1 -1
- package/rust/core/lexer/{indent.rs → handler/indent.rs} +10 -5
- package/rust/core/lexer/handler/mod.rs +238 -0
- package/rust/core/lexer/{newline.rs → handler/newline.rs} +6 -10
- package/rust/core/lexer/{number.rs → handler/number.rs} +1 -1
- package/rust/core/lexer/handler/string.rs +66 -0
- package/rust/core/lexer/mod.rs +25 -14
- package/rust/core/lexer/token.rs +55 -0
- package/rust/core/mod.rs +5 -2
- package/rust/core/parser/handler/at.rs +166 -0
- package/rust/core/parser/handler/bank.rs +38 -0
- package/rust/core/parser/handler/dot.rs +112 -0
- package/rust/core/parser/handler/identifier.rs +134 -0
- package/rust/core/parser/handler/loop_.rs +55 -0
- package/rust/core/parser/handler/mod.rs +6 -0
- package/rust/core/parser/handler/tempo.rs +47 -0
- package/rust/core/parser/mod.rs +204 -166
- package/rust/core/parser/statement.rs +91 -0
- package/rust/core/preprocessor/loader.rs +116 -0
- package/rust/core/preprocessor/mod.rs +2 -24
- package/rust/core/preprocessor/module.rs +37 -56
- package/rust/core/preprocessor/processor.rs +41 -0
- package/rust/core/preprocessor/resolver/bank.rs +38 -51
- package/rust/core/preprocessor/resolver/loop_.rs +126 -65
- package/rust/core/preprocessor/resolver/mod.rs +119 -80
- package/rust/core/preprocessor/resolver/tempo.rs +40 -61
- package/rust/core/preprocessor/resolver/trigger.rs +93 -155
- package/rust/core/shared/duration.rs +8 -0
- package/rust/core/shared/mod.rs +2 -0
- package/rust/core/shared/value.rs +18 -0
- package/rust/core/store/export.rs +28 -0
- package/rust/core/store/global.rs +39 -0
- package/rust/core/store/import.rs +28 -0
- package/rust/core/store/mod.rs +4 -0
- package/rust/core/store/variable.rs +28 -0
- package/rust/core/utils/mod.rs +2 -0
- package/rust/core/utils/validation.rs +35 -0
- package/rust/lib.rs +0 -1
- package/rust/main.rs +22 -18
- package/rust/utils/logger.rs +69 -34
- package/rust/utils/mod.rs +3 -5
- package/rust/utils/watcher.rs +10 -2
- package/templates/minimal/.devalang +1 -1
- package/templates/welcome/.devalang +1 -1
- package/rust/core/lexer/bracket.rs +0 -41
- package/rust/core/lexer/driver.rs +0 -286
- package/rust/core/lexer/quote.rs +0 -61
- package/rust/core/parser/at.rs +0 -142
- package/rust/core/parser/bank.rs +0 -42
- package/rust/core/parser/dot.rs +0 -137
- package/rust/core/parser/identifer.rs +0 -91
- package/rust/core/parser/loop_.rs +0 -62
- package/rust/core/parser/tempo.rs +0 -42
- package/rust/core/parser/variable.rs +0 -129
- package/rust/core/preprocessor/dependencies.rs +0 -54
- package/rust/core/preprocessor/resolver/at.rs +0 -24
- package/rust/core/types/cli.rs +0 -182
- package/rust/core/types/config.rs +0 -15
- package/rust/core/types/mod.rs +0 -8
- package/rust/core/types/module.rs +0 -41
- package/rust/core/types/parser.rs +0 -73
- package/rust/core/types/statement.rs +0 -105
- package/rust/core/types/store.rs +0 -116
- package/rust/core/types/token.rs +0 -83
- package/rust/core/types/variable.rs +0 -32
- package/rust/runner/executer.rs +0 -44
- package/rust/runner/mod.rs +0 -1
- /package/rust/{utils → core/utils}/path.rs +0 -0
- /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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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<
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
70
|
-
|
|
70
|
+
// SECTION Begin check
|
|
71
|
+
if fetched_watch {
|
|
72
|
+
begin_check(entry_file.clone(), fetched_output.clone());
|
|
71
73
|
|
|
72
|
-
|
|
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("
|
|
76
|
-
|
|
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()
|
|
85
|
+
begin_check(entry_file.clone(), fetched_output.clone());
|
|
80
86
|
}
|
|
81
87
|
}
|
|
82
88
|
|
|
83
|
-
fn begin_check(entry: String, output: String
|
|
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 =
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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::
|
|
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
|
|
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
|
+
}
|
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
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
use std::{ fs, path::Path };
|
|
2
|
-
use crate::core::types::config::DevalangConfig;
|
|
3
2
|
|
|
4
|
-
|
|
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
|
+
}
|