@devaloop/devalang 0.0.1-alpha.3 → 0.0.1-alpha.5
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 +9 -7
- package/README.md +49 -25
- package/docs/CHANGELOG.md +30 -0
- package/docs/COMMANDS.md +31 -0
- package/docs/CONFIG.md +6 -4
- package/docs/ROADMAP.md +4 -4
- package/docs/TODO.md +4 -4
- package/examples/index.deva +9 -2
- package/examples/samples/hat-808.wav +0 -0
- package/out-tsc/bin/devalang.exe +0 -0
- package/package.json +44 -42
- package/project-version.json +6 -6
- package/rust/audio/engine.rs +126 -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 -5
- package/rust/cli/check.rs +2 -1
- package/rust/cli/init.rs +4 -2
- package/rust/cli/mod.rs +29 -0
- package/rust/cli/play.rs +192 -0
- package/rust/cli/template.rs +2 -1
- package/rust/config/loader.rs +0 -1
- package/rust/config/mod.rs +3 -2
- package/rust/core/builder/mod.rs +54 -6
- package/rust/core/debugger/lexer.rs +20 -5
- package/rust/core/debugger/preprocessor.rs +9 -5
- package/rust/core/lexer/handler/mod.rs +2 -2
- package/rust/core/lexer/handler/newline.rs +5 -1
- package/rust/core/lexer/mod.rs +10 -5
- package/rust/core/parser/handler/loop_.rs +11 -0
- package/rust/core/parser/mod.rs +0 -1
- package/rust/core/preprocessor/loader.rs +89 -16
- package/rust/core/preprocessor/module.rs +2 -0
- package/rust/core/preprocessor/resolver/bank.rs +46 -0
- package/rust/core/preprocessor/resolver/loop_.rs +148 -0
- package/rust/core/preprocessor/resolver/mod.rs +151 -0
- package/rust/core/preprocessor/resolver/tempo.rs +49 -0
- package/rust/core/preprocessor/resolver/trigger.rs +114 -0
- package/rust/lib.rs +118 -0
- package/rust/main.rs +8 -0
- package/rust/utils/logger.rs +45 -6
- package/rust/utils/spinner.rs +2 -0
- package/rust/utils/watcher.rs +10 -2
- package/templates/minimal/.devalang +2 -1
- package/templates/minimal/README.md +202 -0
- package/templates/welcome/.devalang +2 -1
- package/templates/welcome/README.md +48 -31
- package/rust/core/preprocessor/resolver.rs +0 -372
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
pub mod trigger;
|
|
2
|
+
pub mod loop_;
|
|
3
|
+
pub mod bank;
|
|
4
|
+
pub mod tempo;
|
|
5
|
+
|
|
6
|
+
use std::collections::HashMap;
|
|
7
|
+
use crate::{
|
|
8
|
+
core::{
|
|
9
|
+
parser::statement::{ self, Statement, StatementKind },
|
|
10
|
+
preprocessor::{
|
|
11
|
+
loader::ModuleLoader,
|
|
12
|
+
resolver::{
|
|
13
|
+
bank::resolve_bank,
|
|
14
|
+
loop_::resolve_loop,
|
|
15
|
+
tempo::resolve_tempo,
|
|
16
|
+
trigger::resolve_trigger,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
shared::{ duration::Duration, value::Value },
|
|
20
|
+
store::global::GlobalStore,
|
|
21
|
+
utils::validation::{ is_valid_entity, is_valid_identifier },
|
|
22
|
+
},
|
|
23
|
+
utils::logger::Logger,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
pub fn resolve_all_modules(module_loader: &ModuleLoader, global_store: &mut GlobalStore) {
|
|
27
|
+
for module in global_store.clone().modules.values_mut() {
|
|
28
|
+
resolve_imports(module_loader, global_store);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
pub fn resolve_imports(module_loader: &ModuleLoader, global_store: &mut GlobalStore) {
|
|
33
|
+
for (module_path, module) in global_store.clone().modules.iter_mut() {
|
|
34
|
+
for (name, source_path) in &module.import_table.imports {
|
|
35
|
+
match source_path {
|
|
36
|
+
Value::String(source_path) => {
|
|
37
|
+
if let Some(source_module) = global_store.modules.get(source_path) {
|
|
38
|
+
if let Some(value) = source_module.export_table.get_export(name) {
|
|
39
|
+
module.variable_table.set(name.clone(), value.clone());
|
|
40
|
+
} else {
|
|
41
|
+
println!(
|
|
42
|
+
"[warn] '{module_path}': '{name}' not found in exports of '{source_path}'"
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
println!(
|
|
47
|
+
"[warn] '{module_path}': cannot find source module '{source_path}'"
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
_ => {
|
|
52
|
+
println!(
|
|
53
|
+
"[warn] '{module_path}': expected string for import source, found {:?}",
|
|
54
|
+
source_path
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
pub fn resolve_and_flatten_all_modules(
|
|
63
|
+
global_store: &mut GlobalStore
|
|
64
|
+
) -> HashMap<String, Vec<Statement>> {
|
|
65
|
+
let logger = Logger::new();
|
|
66
|
+
let snapshot = global_store.clone();
|
|
67
|
+
|
|
68
|
+
// 1. Imports resolution
|
|
69
|
+
for (module_path, module) in global_store.modules.iter_mut() {
|
|
70
|
+
for (name, source_path) in &module.import_table.imports {
|
|
71
|
+
if let Value::String(source_path_str) = source_path {
|
|
72
|
+
match snapshot.modules.get(source_path_str) {
|
|
73
|
+
Some(source_module) => {
|
|
74
|
+
if let Some(value) = source_module.export_table.get_export(name) {
|
|
75
|
+
module.variable_table.set(name.clone(), value.clone());
|
|
76
|
+
} else {
|
|
77
|
+
logger.log_error_with_stacktrace(
|
|
78
|
+
&format!("'{name}' not found in exports of '{source_path_str}'"),
|
|
79
|
+
module_path
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
None => {
|
|
84
|
+
logger.log_error_with_stacktrace(
|
|
85
|
+
&format!("Cannot find source module '{source_path_str}'"),
|
|
86
|
+
module_path
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
logger.log_error_with_stacktrace(
|
|
92
|
+
&format!("Expected string for import source, found {:?}", source_path),
|
|
93
|
+
module_path
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 2. Statements resolution
|
|
100
|
+
let mut resolved_map: HashMap<String, Vec<Statement>> = HashMap::new();
|
|
101
|
+
let store_snapshot = global_store.clone();
|
|
102
|
+
|
|
103
|
+
for (path, module) in &store_snapshot.modules {
|
|
104
|
+
let mut resolved = Vec::new();
|
|
105
|
+
|
|
106
|
+
for stmt in &module.statements {
|
|
107
|
+
let mut stmt = stmt.clone();
|
|
108
|
+
|
|
109
|
+
match &stmt.kind {
|
|
110
|
+
StatementKind::Trigger { entity, duration } => {
|
|
111
|
+
let resolved_stmt = resolve_trigger(
|
|
112
|
+
&stmt,
|
|
113
|
+
entity.as_str(),
|
|
114
|
+
&mut duration.clone(),
|
|
115
|
+
&module,
|
|
116
|
+
&path,
|
|
117
|
+
&store_snapshot
|
|
118
|
+
);
|
|
119
|
+
resolved.push(resolved_stmt);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
StatementKind::Loop => {
|
|
123
|
+
let resolved_stmt = resolve_loop(&stmt, &module, &path, &store_snapshot);
|
|
124
|
+
resolved.push(resolved_stmt);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
StatementKind::Bank => {
|
|
128
|
+
let resolved_stmt = resolve_bank(&stmt, &module, &path, &store_snapshot);
|
|
129
|
+
resolved.push(resolved_stmt);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
StatementKind::Tempo => {
|
|
133
|
+
let resolved_stmt = resolve_tempo(&stmt, &module, &path, &store_snapshot);
|
|
134
|
+
resolved.push(resolved_stmt);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
StatementKind::Import { .. } | StatementKind::Export { .. } => {
|
|
138
|
+
resolved.push(stmt.clone());
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
_ => {
|
|
142
|
+
resolved.push(stmt);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
resolved_map.insert(path.clone(), resolved);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
resolved_map
|
|
151
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
use crate::{
|
|
2
|
+
core::{
|
|
3
|
+
parser::statement::{ Statement, StatementKind },
|
|
4
|
+
preprocessor::module::Module,
|
|
5
|
+
shared::value::Value,
|
|
6
|
+
store::global::GlobalStore,
|
|
7
|
+
},
|
|
8
|
+
utils::logger::Logger,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
pub fn resolve_tempo(
|
|
12
|
+
stmt: &Statement,
|
|
13
|
+
module: &Module,
|
|
14
|
+
path: &str,
|
|
15
|
+
_global_store: &GlobalStore
|
|
16
|
+
) -> Statement {
|
|
17
|
+
let mut new_stmt = stmt.clone();
|
|
18
|
+
let logger = Logger::new();
|
|
19
|
+
|
|
20
|
+
match &stmt.value {
|
|
21
|
+
Value::Identifier(ident) => {
|
|
22
|
+
if let Some(val) = module.variable_table.get(ident) {
|
|
23
|
+
new_stmt.value = val.clone();
|
|
24
|
+
} else {
|
|
25
|
+
let message = format!("Tempo identifier '{ident}' not found in variable table");
|
|
26
|
+
logger.log_error_with_stacktrace(&message, &module.path);
|
|
27
|
+
new_stmt.kind = StatementKind::Error {
|
|
28
|
+
message,
|
|
29
|
+
};
|
|
30
|
+
new_stmt.value = Value::Null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
Value::Number(_) => {
|
|
35
|
+
// Already resolved, no modification needed
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
other => {
|
|
39
|
+
let message = format!("Expected a number or identifier for tempo, found {:?}", other);
|
|
40
|
+
logger.log_error_with_stacktrace(&message, &module.path);
|
|
41
|
+
new_stmt.kind = StatementKind::Error {
|
|
42
|
+
message: "Expected a number or identifier for tempo".to_string(),
|
|
43
|
+
};
|
|
44
|
+
new_stmt.value = Value::Null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
new_stmt
|
|
49
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
use std::collections::HashMap;
|
|
2
|
+
|
|
3
|
+
use crate::{
|
|
4
|
+
core::{
|
|
5
|
+
parser::statement::{ Statement, StatementKind },
|
|
6
|
+
preprocessor::module::Module,
|
|
7
|
+
shared::{ duration::Duration, value::Value },
|
|
8
|
+
store::global::GlobalStore,
|
|
9
|
+
utils::validation::is_valid_entity,
|
|
10
|
+
},
|
|
11
|
+
utils::logger::Logger,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
pub fn resolve_trigger(
|
|
15
|
+
stmt: &Statement,
|
|
16
|
+
entity: &str,
|
|
17
|
+
duration: &mut Duration,
|
|
18
|
+
module: &Module,
|
|
19
|
+
path: &str,
|
|
20
|
+
global_store: &GlobalStore
|
|
21
|
+
) -> Statement {
|
|
22
|
+
let logger = Logger::new();
|
|
23
|
+
|
|
24
|
+
let mut final_duration = duration.clone();
|
|
25
|
+
let mut final_value = stmt.value.clone();
|
|
26
|
+
|
|
27
|
+
if !is_valid_entity(entity, module, global_store) {
|
|
28
|
+
let message = format!("Invalid entity '{}', expected a valid identifier", entity);
|
|
29
|
+
let stacktrace = format!("{}:{}:{}", module.path, stmt.line, stmt.column);
|
|
30
|
+
logger.log_error_with_stacktrace(&message, &stacktrace);
|
|
31
|
+
|
|
32
|
+
return Statement {
|
|
33
|
+
kind: stmt.kind.clone(),
|
|
34
|
+
value: Value::Null,
|
|
35
|
+
line: stmt.line,
|
|
36
|
+
column: stmt.column,
|
|
37
|
+
indent: stmt.indent,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ✅ Résolution de duration si c'est un identifiant
|
|
42
|
+
if let Duration::Identifier(ident) = duration {
|
|
43
|
+
if let Some(val) = module.variable_table.get(ident) {
|
|
44
|
+
match val {
|
|
45
|
+
Value::Number(num) => {
|
|
46
|
+
final_duration = Duration::Number(*num);
|
|
47
|
+
}
|
|
48
|
+
Value::String(s) => {
|
|
49
|
+
final_duration = Duration::Identifier(s.clone());
|
|
50
|
+
}
|
|
51
|
+
Value::Identifier(id) if id == "auto" => {
|
|
52
|
+
final_duration = Duration::Auto;
|
|
53
|
+
}
|
|
54
|
+
_ => {}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ✅ Résolution de value (params, effets)
|
|
60
|
+
final_value = match &stmt.value {
|
|
61
|
+
Value::Identifier(ident) => {
|
|
62
|
+
match module.variable_table.get(ident) {
|
|
63
|
+
Some(val) => val.clone(),
|
|
64
|
+
None => {
|
|
65
|
+
let stacktrace = format!("{}:{}:{}", module.path, stmt.line, stmt.column);
|
|
66
|
+
let message = format!(
|
|
67
|
+
"'{path}': value identifier '{ident}' not found in variable table"
|
|
68
|
+
);
|
|
69
|
+
logger.log_error_with_stacktrace(&message, &stacktrace);
|
|
70
|
+
Value::Null
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
Value::Map(map) => {
|
|
75
|
+
let mut resolved_map = HashMap::new();
|
|
76
|
+
for (k, v) in map.iter() {
|
|
77
|
+
let resolved_v = match v {
|
|
78
|
+
Value::Identifier(id) => {
|
|
79
|
+
module.variable_table.get(id).cloned().unwrap_or(Value::Null)
|
|
80
|
+
}
|
|
81
|
+
other => other.clone(),
|
|
82
|
+
};
|
|
83
|
+
resolved_map.insert(k.clone(), resolved_v);
|
|
84
|
+
}
|
|
85
|
+
Value::Map(resolved_map)
|
|
86
|
+
}
|
|
87
|
+
other => other.clone(),
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// ✅ On reconstruit le Statement avec Trigger résolu
|
|
91
|
+
if let StatementKind::Trigger { entity, .. } = &stmt.kind {
|
|
92
|
+
return Statement {
|
|
93
|
+
kind: StatementKind::Trigger {
|
|
94
|
+
entity: entity.to_string(),
|
|
95
|
+
duration: final_duration,
|
|
96
|
+
},
|
|
97
|
+
value: final_value,
|
|
98
|
+
line: stmt.line,
|
|
99
|
+
column: stmt.column,
|
|
100
|
+
indent: stmt.indent,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return Statement {
|
|
105
|
+
kind: StatementKind::Trigger {
|
|
106
|
+
entity: entity.to_string(),
|
|
107
|
+
duration: final_duration,
|
|
108
|
+
},
|
|
109
|
+
value: final_value,
|
|
110
|
+
line: stmt.line,
|
|
111
|
+
column: stmt.column,
|
|
112
|
+
indent: stmt.indent,
|
|
113
|
+
};
|
|
114
|
+
}
|
package/rust/lib.rs
CHANGED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
pub mod core;
|
|
2
|
+
pub mod utils;
|
|
3
|
+
pub mod config;
|
|
4
|
+
pub mod audio;
|
|
5
|
+
|
|
6
|
+
use serde::{ Deserialize, Serialize };
|
|
7
|
+
use wasm_bindgen::prelude::*;
|
|
8
|
+
use serde_wasm_bindgen::to_value;
|
|
9
|
+
|
|
10
|
+
use crate::core::{
|
|
11
|
+
parser::statement::{ Statement, StatementKind },
|
|
12
|
+
preprocessor::loader::ModuleLoader,
|
|
13
|
+
shared::value::Value,
|
|
14
|
+
store::global::GlobalStore,
|
|
15
|
+
utils::path::normalize_path,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
#[derive(Serialize, Deserialize)]
|
|
19
|
+
struct ParseResult {
|
|
20
|
+
ok: bool,
|
|
21
|
+
ast: String,
|
|
22
|
+
errors: Vec<ErrorResult>,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#[derive(Serialize, Deserialize)]
|
|
26
|
+
struct ErrorResult {
|
|
27
|
+
message: String,
|
|
28
|
+
line: usize,
|
|
29
|
+
column: usize,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#[wasm_bindgen]
|
|
33
|
+
pub fn parse(entry_path: &str, source: &str) -> Result<JsValue, JsValue> {
|
|
34
|
+
let statements = parse_internal_from_string(entry_path, source);
|
|
35
|
+
|
|
36
|
+
match statements {
|
|
37
|
+
Ok(value) => {
|
|
38
|
+
let ast_string = value;
|
|
39
|
+
to_value(&ast_string).map_err(|e|
|
|
40
|
+
JsValue::from_str(&format!("Error converting AST to JS value: {}", e))
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
Err(e) => { Err(JsValue::from_str(&format!("Error: {}", e))) }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fn parse_internal_from_string(virtual_path: &str, source: &str) -> Result<ParseResult, String> {
|
|
48
|
+
let entry_path = normalize_path(virtual_path);
|
|
49
|
+
let output_path = normalize_path("./temp");
|
|
50
|
+
|
|
51
|
+
let mut global_store = GlobalStore::new();
|
|
52
|
+
let loader = ModuleLoader::from_raw_source(
|
|
53
|
+
&entry_path,
|
|
54
|
+
&output_path,
|
|
55
|
+
source,
|
|
56
|
+
&mut global_store
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
let module = loader
|
|
60
|
+
.load_single_module(&mut global_store)
|
|
61
|
+
.map_err(|e| format!("Error loading module: {}", e))?;
|
|
62
|
+
|
|
63
|
+
let raw_ast = ast_to_string(module.statements.clone());
|
|
64
|
+
|
|
65
|
+
let found_errors = collect_errors_recursively(&module.statements);
|
|
66
|
+
|
|
67
|
+
let result = ParseResult {
|
|
68
|
+
ok: true,
|
|
69
|
+
ast: raw_ast,
|
|
70
|
+
errors: found_errors,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
Ok(result)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fn collect_errors_recursively(statements: &[Statement]) -> Vec<ErrorResult> {
|
|
77
|
+
let mut errors: Vec<ErrorResult> = Vec::new();
|
|
78
|
+
|
|
79
|
+
for stmt in statements {
|
|
80
|
+
match &stmt.kind {
|
|
81
|
+
StatementKind::Unknown => {
|
|
82
|
+
errors.push(ErrorResult {
|
|
83
|
+
message: format!("Unknown statement at line {}:{}", stmt.line, stmt.column),
|
|
84
|
+
line: stmt.line,
|
|
85
|
+
column: stmt.column,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
StatementKind::Error { message } => {
|
|
89
|
+
errors.push(ErrorResult {
|
|
90
|
+
message: message.clone(),
|
|
91
|
+
line: stmt.line,
|
|
92
|
+
column: stmt.column,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
StatementKind::Loop => {
|
|
96
|
+
if let Some(body_statements) = extract_loop_body_statements(&stmt.value) {
|
|
97
|
+
errors.extend(collect_errors_recursively(body_statements));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
_ => {}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
errors
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
fn extract_loop_body_statements(value: &Value) -> Option<&[Statement]> {
|
|
108
|
+
if let Value::Map(map) = value {
|
|
109
|
+
if let Some(Value::Block(statements)) = map.get("body") {
|
|
110
|
+
return Some(statements);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
None
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
fn ast_to_string(statements: Vec<Statement>) -> String {
|
|
117
|
+
serde_json::to_string_pretty(&statements).expect("Failed to serialize AST")
|
|
118
|
+
}
|
package/rust/main.rs
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
#![cfg(feature = "cli")]
|
|
2
|
+
|
|
1
3
|
pub mod core;
|
|
2
4
|
pub mod cli;
|
|
3
5
|
pub mod utils;
|
|
4
6
|
pub mod config;
|
|
7
|
+
pub mod audio;
|
|
5
8
|
|
|
6
9
|
use std::io;
|
|
7
10
|
use cli::{ Cli };
|
|
@@ -11,6 +14,7 @@ use crate::{
|
|
|
11
14
|
build::handle_build_command,
|
|
12
15
|
check::handle_check_command,
|
|
13
16
|
init::handle_init_command,
|
|
17
|
+
play::handle_play_command,
|
|
14
18
|
template::{ handle_template_info_command, handle_template_list_command },
|
|
15
19
|
Commands,
|
|
16
20
|
TemplateCommand,
|
|
@@ -51,6 +55,10 @@ fn main() -> io::Result<()> {
|
|
|
51
55
|
handle_build_command(config, entry, output, watch);
|
|
52
56
|
}
|
|
53
57
|
|
|
58
|
+
Commands::Play { entry, output, watch, repeat } => {
|
|
59
|
+
handle_play_command(config, entry, output, watch, repeat);
|
|
60
|
+
}
|
|
61
|
+
|
|
54
62
|
_ => {}
|
|
55
63
|
}
|
|
56
64
|
|
package/rust/utils/logger.rs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
use
|
|
1
|
+
#[cfg(feature = "cli")]
|
|
2
|
+
use crossterm::style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor};
|
|
3
|
+
use std::fmt::Write;
|
|
3
4
|
|
|
4
5
|
#[derive(Debug, Clone, PartialEq)]
|
|
5
6
|
pub enum LogLevel {
|
|
@@ -12,24 +13,43 @@ pub enum LogLevel {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
#[derive(Debug, Clone)]
|
|
15
|
-
pub struct Logger
|
|
16
|
+
pub struct Logger;
|
|
16
17
|
|
|
17
18
|
impl Logger {
|
|
18
19
|
pub fn new() -> Self {
|
|
19
|
-
Logger
|
|
20
|
+
Logger
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
// --- log_message ---
|
|
24
|
+
|
|
25
|
+
#[cfg(feature = "cli")]
|
|
22
26
|
pub fn log_message(&self, level: LogLevel, message: &str) {
|
|
23
27
|
let formatted_status = self.format_status(level);
|
|
24
28
|
println!("🦊 {} {} {}", self.language_signature(), formatted_status, message);
|
|
25
29
|
}
|
|
26
30
|
|
|
31
|
+
#[cfg(not(feature = "cli"))]
|
|
32
|
+
pub fn log_message(&self, _level: LogLevel, _message: &str) {
|
|
33
|
+
// no-op for WASM
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// --- log_error_with_stacktrace ---
|
|
37
|
+
|
|
38
|
+
#[cfg(feature = "cli")]
|
|
27
39
|
pub fn log_error_with_stacktrace(&self, message: &str, stacktrace: &str) {
|
|
28
40
|
let formatted_status = self.format_status(LogLevel::Error);
|
|
29
41
|
println!("🦊 {} {} {}", self.language_signature(), formatted_status, message);
|
|
30
42
|
println!(" ↳ {}", stacktrace);
|
|
31
43
|
}
|
|
32
44
|
|
|
45
|
+
#[cfg(not(feature = "cli"))]
|
|
46
|
+
pub fn log_error_with_stacktrace(&self, _message: &str, _stacktrace: &str) {
|
|
47
|
+
// no-op for WASM
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// --- language_signature ---
|
|
51
|
+
|
|
52
|
+
#[cfg(feature = "cli")]
|
|
33
53
|
fn language_signature(&self) -> String {
|
|
34
54
|
let mut s = String::new();
|
|
35
55
|
|
|
@@ -43,12 +63,19 @@ impl Logger {
|
|
|
43
63
|
|
|
44
64
|
write!(&mut s, "{}", SetForegroundColor(Color::Grey)).unwrap();
|
|
45
65
|
s.push(']');
|
|
46
|
-
|
|
47
66
|
write!(&mut s, "{}", ResetColor).unwrap();
|
|
48
67
|
|
|
49
68
|
s
|
|
50
69
|
}
|
|
51
70
|
|
|
71
|
+
#[cfg(not(feature = "cli"))]
|
|
72
|
+
fn language_signature(&self) -> String {
|
|
73
|
+
"[Devalang]".to_string()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// --- format_status ---
|
|
77
|
+
|
|
78
|
+
#[cfg(feature = "cli")]
|
|
52
79
|
fn format_status(&self, level: LogLevel) -> String {
|
|
53
80
|
let mut s = String::new();
|
|
54
81
|
|
|
@@ -76,9 +103,21 @@ impl Logger {
|
|
|
76
103
|
s.push_str(status);
|
|
77
104
|
write!(&mut s, "{}", SetAttribute(Attribute::Reset)).unwrap();
|
|
78
105
|
s.push(']');
|
|
79
|
-
|
|
80
106
|
write!(&mut s, "{}", ResetColor).unwrap();
|
|
81
107
|
|
|
82
108
|
s
|
|
83
109
|
}
|
|
110
|
+
|
|
111
|
+
#[cfg(not(feature = "cli"))]
|
|
112
|
+
fn format_status(&self, level: LogLevel) -> String {
|
|
113
|
+
match level {
|
|
114
|
+
LogLevel::Success => "[SUCCESS]",
|
|
115
|
+
LogLevel::Error => "[ERROR]",
|
|
116
|
+
LogLevel::Info => "[INFO]",
|
|
117
|
+
LogLevel::Warning => "[WARNING]",
|
|
118
|
+
LogLevel::Watcher => "[WATCHER]",
|
|
119
|
+
LogLevel::Debug => "[DEBUG]",
|
|
120
|
+
}
|
|
121
|
+
.to_string()
|
|
122
|
+
}
|
|
84
123
|
}
|
package/rust/utils/spinner.rs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
#[cfg(feature = "cli")]
|
|
1
2
|
use indicatif::{ ProgressBar, ProgressStyle };
|
|
2
3
|
use std::{ time::Duration };
|
|
3
4
|
|
|
5
|
+
#[cfg(feature = "cli")]
|
|
4
6
|
pub fn with_spinner<T, F>(start_msg: &str, f: F) -> T where F: FnOnce() -> T {
|
|
5
7
|
let spinner = ProgressBar::new_spinner();
|
|
6
8
|
spinner.set_style(
|
package/rust/utils/watcher.rs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
use notify::{ Watcher, RecursiveMode, Config, RecommendedWatcher };
|
|
2
2
|
use std::sync::mpsc::channel;
|
|
3
3
|
|
|
4
|
+
use std::time::{ Duration, Instant };
|
|
5
|
+
|
|
4
6
|
pub fn watch_directory<F>(entry: String, callback: F) -> notify::Result<()>
|
|
5
7
|
where F: Fn() + Send + 'static
|
|
6
8
|
{
|
|
@@ -9,13 +11,19 @@ pub fn watch_directory<F>(entry: String, callback: F) -> notify::Result<()>
|
|
|
9
11
|
let mut watcher: RecommendedWatcher = Watcher::new(tx, Config::default())?;
|
|
10
12
|
watcher.watch(&entry.as_ref(), RecursiveMode::Recursive)?;
|
|
11
13
|
|
|
14
|
+
let mut last_trigger = Instant::now();
|
|
15
|
+
|
|
12
16
|
loop {
|
|
13
17
|
match rx.recv() {
|
|
14
18
|
Ok(_) => {
|
|
15
|
-
|
|
19
|
+
let now = Instant::now();
|
|
20
|
+
if now.duration_since(last_trigger) > Duration::from_millis(200) {
|
|
21
|
+
callback();
|
|
22
|
+
last_trigger = now;
|
|
23
|
+
}
|
|
16
24
|
}
|
|
17
25
|
Err(e) => {
|
|
18
|
-
eprintln!("Channel error
|
|
26
|
+
eprintln!("Channel error: {:?}", e);
|
|
19
27
|
break;
|
|
20
28
|
}
|
|
21
29
|
}
|