@devaloop/devalang 0.0.1-alpha.12 → 0.0.1-alpha.14

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 (93) hide show
  1. package/.devalang +8 -9
  2. package/Cargo.toml +8 -3
  3. package/README.md +36 -34
  4. package/docs/CHANGELOG.md +65 -1
  5. package/docs/CONTRIBUTING.md +1 -0
  6. package/docs/ROADMAP.md +2 -2
  7. package/docs/TODO.md +6 -5
  8. package/examples/bank.deva +2 -4
  9. package/examples/function.deva +15 -0
  10. package/examples/index.deva +25 -14
  11. package/out-tsc/bin/devalang.exe +0 -0
  12. package/package.json +6 -6
  13. package/project-version.json +3 -3
  14. package/rust/cli/bank.rs +2 -1
  15. package/rust/cli/build.rs +76 -14
  16. package/rust/cli/check.rs +71 -8
  17. package/rust/cli/driver.rs +40 -28
  18. package/rust/cli/install.rs +22 -7
  19. package/rust/cli/login.rs +134 -0
  20. package/rust/cli/mod.rs +2 -1
  21. package/rust/cli/play.rs +45 -20
  22. package/rust/common/api.rs +8 -0
  23. package/rust/common/cdn.rs +2 -5
  24. package/rust/common/mod.rs +3 -1
  25. package/rust/common/sso.rs +8 -0
  26. package/rust/config/driver.rs +19 -1
  27. package/rust/config/loader.rs +56 -10
  28. package/rust/core/audio/engine.rs +254 -91
  29. package/rust/core/audio/interpreter/arrow_call.rs +34 -15
  30. package/rust/core/audio/interpreter/call.rs +72 -47
  31. package/rust/core/audio/interpreter/condition.rs +14 -12
  32. package/rust/core/audio/interpreter/driver.rs +90 -128
  33. package/rust/core/audio/interpreter/function.rs +21 -0
  34. package/rust/core/audio/interpreter/load.rs +1 -1
  35. package/rust/core/audio/interpreter/loop_.rs +24 -18
  36. package/rust/core/audio/interpreter/mod.rs +2 -1
  37. package/rust/core/audio/interpreter/sleep.rs +0 -6
  38. package/rust/core/audio/interpreter/spawn.rs +78 -60
  39. package/rust/core/audio/interpreter/trigger.rs +157 -70
  40. package/rust/core/audio/loader/trigger.rs +37 -4
  41. package/rust/core/audio/player.rs +20 -10
  42. package/rust/core/audio/renderer.rs +24 -25
  43. package/rust/core/builder/mod.rs +11 -6
  44. package/rust/core/debugger/mod.rs +2 -0
  45. package/rust/core/debugger/module.rs +47 -0
  46. package/rust/core/debugger/store.rs +25 -11
  47. package/rust/core/error/mod.rs +6 -0
  48. package/rust/core/lexer/handler/driver.rs +23 -1
  49. package/rust/core/lexer/handler/identifier.rs +1 -0
  50. package/rust/core/lexer/handler/indent.rs +16 -2
  51. package/rust/core/lexer/handler/mod.rs +1 -0
  52. package/rust/core/lexer/handler/parenthesis.rs +41 -0
  53. package/rust/core/lexer/token.rs +4 -0
  54. package/rust/core/mod.rs +2 -1
  55. package/rust/core/parser/driver.rs +47 -4
  56. package/rust/core/parser/handler/arrow_call.rs +78 -18
  57. package/rust/core/parser/handler/bank.rs +35 -7
  58. package/rust/core/parser/handler/dot.rs +81 -123
  59. package/rust/core/parser/handler/identifier/call.rs +69 -22
  60. package/rust/core/parser/handler/identifier/function.rs +92 -0
  61. package/rust/core/parser/handler/identifier/let_.rs +13 -19
  62. package/rust/core/parser/handler/identifier/mod.rs +1 -0
  63. package/rust/core/parser/handler/identifier/spawn.rs +74 -27
  64. package/rust/core/parser/statement.rs +16 -4
  65. package/rust/core/plugin/loader.rs +48 -0
  66. package/rust/core/plugin/mod.rs +1 -0
  67. package/rust/core/preprocessor/loader.rs +50 -32
  68. package/rust/core/preprocessor/module.rs +3 -1
  69. package/rust/core/preprocessor/processor.rs +26 -1
  70. package/rust/core/preprocessor/resolver/call.rs +61 -84
  71. package/rust/core/preprocessor/resolver/condition.rs +11 -6
  72. package/rust/core/preprocessor/resolver/driver.rs +52 -6
  73. package/rust/core/preprocessor/resolver/function.rs +78 -0
  74. package/rust/core/preprocessor/resolver/group.rs +43 -13
  75. package/rust/core/preprocessor/resolver/let_.rs +7 -10
  76. package/rust/core/preprocessor/resolver/mod.rs +2 -1
  77. package/rust/core/preprocessor/resolver/spawn.rs +64 -30
  78. package/rust/core/preprocessor/resolver/trigger.rs +7 -3
  79. package/rust/core/preprocessor/resolver/value.rs +10 -1
  80. package/rust/core/shared/value.rs +4 -1
  81. package/rust/core/store/function.rs +34 -0
  82. package/rust/core/store/global.rs +9 -10
  83. package/rust/core/store/mod.rs +2 -1
  84. package/rust/core/store/variable.rs +6 -0
  85. package/rust/installer/addon.rs +80 -0
  86. package/rust/installer/bank.rs +24 -14
  87. package/rust/installer/mod.rs +4 -1
  88. package/rust/installer/plugin.rs +55 -0
  89. package/rust/lib.rs +10 -7
  90. package/rust/main.rs +32 -9
  91. package/rust/utils/logger.rs +16 -0
  92. package/rust/utils/mod.rs +45 -1
  93. package/rust/utils/spinner.rs +2 -4
@@ -0,0 +1,80 @@
1
+ use std::path::Path;
2
+ use crate::{
3
+ common::{ api::get_api_url },
4
+ installer::{ bank::install_bank, plugin::install_plugin },
5
+ };
6
+ use dirs::home_dir;
7
+
8
+ #[derive(Debug, Clone)]
9
+ pub enum AddonType {
10
+ Bank,
11
+ Plugin,
12
+ Preset,
13
+ }
14
+
15
+ pub async fn install_addon(
16
+ addon_type: AddonType,
17
+ name: &str,
18
+ target_dir: &Path
19
+ ) -> Result<(), String> {
20
+ match addon_type {
21
+ AddonType::Bank => install_bank(name, target_dir).await,
22
+ AddonType::Plugin => install_plugin(name, target_dir).await,
23
+ AddonType::Preset => Err("Preset installation not implemented".into()),
24
+ }
25
+ }
26
+
27
+ pub async fn ask_api_for_signed_url(addon_type: AddonType, slug: &str) -> Result<String, String> {
28
+ let api_url = get_api_url();
29
+
30
+ let mut stored_token_path = home_dir().unwrap();
31
+ stored_token_path.push(".devalang");
32
+ stored_token_path.push("session_token.json");
33
+
34
+ let stored_token = std::fs::read_to_string(&stored_token_path).unwrap_or_default();
35
+
36
+ let request_url = format!(
37
+ "{}/v1/assets/url?type={}&slug={}&token={}",
38
+ api_url,
39
+ match addon_type {
40
+ AddonType::Bank => "bank",
41
+ AddonType::Plugin => "plugin",
42
+ AddonType::Preset => "preset",
43
+ },
44
+ slug,
45
+ stored_token
46
+ );
47
+
48
+ let mut headers = reqwest::header::HeaderMap::new();
49
+
50
+ headers.insert("Authorization", format!("Bearer {}", stored_token).parse().unwrap());
51
+
52
+ let client: reqwest::Client = reqwest::Client
53
+ ::builder()
54
+ .default_headers(headers)
55
+ .build()
56
+ .map_err(|_| "Failed to build HTTP client".to_string())?;
57
+
58
+ let req = client
59
+ .get(&request_url)
60
+ .send().await
61
+ .map_err(|_| "Failed to receive response".to_string())?;
62
+
63
+ let response_body: serde_json::Value = req
64
+ .json().await
65
+ .map_err(|_| "Failed to read response body".to_string())?;
66
+
67
+ let signed_url: String = serde_json
68
+ ::from_value(
69
+ response_body
70
+ .get("payload")
71
+ .cloned()
72
+ .unwrap_or_default()
73
+ .get("url")
74
+ .cloned()
75
+ .unwrap_or_default()
76
+ )
77
+ .map_err(|_| "Failed to parse response body".to_string())?;
78
+
79
+ Ok(signed_url)
80
+ }
@@ -1,33 +1,39 @@
1
1
  use std::path::{ Path, PathBuf };
2
2
  use crate::{
3
- common::cdn::get_cdn_url,
4
3
  config::loader::{ add_bank_to_config, load_config },
5
- installer::utils::{ download_file, extract_archive },
4
+ installer::{
5
+ addon::{ ask_api_for_signed_url, AddonType },
6
+ utils::{ download_file, extract_archive },
7
+ },
8
+ utils::logger::{ LogLevel, Logger },
6
9
  };
7
10
 
8
11
  pub async fn install_bank(name: &str, target_dir: &Path) -> Result<(), String> {
9
- let cdn_url = get_cdn_url();
10
- let url = format!("{}/bank/{}", cdn_url, name);
12
+ let logger = Logger::new();
13
+
14
+ let signed_url = ask_api_for_signed_url(AddonType::Bank, name).await?;
11
15
 
12
16
  let bank_dir = target_dir.join("bank");
13
17
  let archive_path = PathBuf::from(format!("./.deva/tmp/{}.devabank", name));
14
18
  let extract_path = bank_dir.join(name);
15
19
 
20
+ download_file(&signed_url, &archive_path).await.map_err(|e|
21
+ format!("Failed to download: {}", e)
22
+ )?;
23
+
16
24
  if extract_path.exists() {
17
- println!(
18
- "Bank '{}' already exists at '{}'. Skipping install.",
19
- name,
20
- extract_path.display()
25
+ logger.log_message(
26
+ LogLevel::Warning,
27
+ &format!(
28
+ "Bank '{}' already exists at '{}'. Skipping install.",
29
+ name,
30
+ extract_path.display()
31
+ )
21
32
  );
33
+
22
34
  return Ok(());
23
35
  }
24
36
 
25
- download_file(&url, &archive_path).await.map_err(|e| format!("Failed to download: {}", e))?;
26
-
27
- extract_archive(&archive_path, &extract_path).await.map_err(|e|
28
- format!("Failed to extract: {}", e)
29
- )?;
30
-
31
37
  // Add the bank to the config
32
38
  let root_dir = target_dir
33
39
  .parent()
@@ -49,6 +55,10 @@ pub async fn install_bank(name: &str, target_dir: &Path) -> Result<(), String> {
49
55
 
50
56
  let dependency_path = &format!("devalang://bank/{}", name);
51
57
 
58
+ extract_archive(&archive_path, &extract_path).await.map_err(|e|
59
+ format!("Failed to extract: {}", e)
60
+ )?;
61
+
52
62
  add_bank_to_config(&mut config, &extract_path, &dependency_path);
53
63
 
54
64
  Ok(())
@@ -1,2 +1,5 @@
1
1
  pub mod bank;
2
- pub mod utils;
2
+ pub mod plugin;
3
+
4
+ pub mod utils;
5
+ pub mod addon;
@@ -0,0 +1,55 @@
1
+ use std::path::{ Path, PathBuf };
2
+ use crate::{
3
+ common::cdn::get_cdn_url,
4
+ config::loader::{ add_plugin_to_config, load_config },
5
+ installer::utils::{ download_file, extract_archive },
6
+ };
7
+
8
+ pub async fn install_plugin(name: &str, target_dir: &Path) -> Result<(), String> {
9
+ let cdn_url = get_cdn_url();
10
+ let url = format!("{}/plugin/{}/download", cdn_url, name);
11
+
12
+ let plugin_dir = target_dir.join("plugin");
13
+ let archive_path = PathBuf::from(format!("./.deva/tmp/{}.devaplugin", name));
14
+ let extract_path = plugin_dir.join(name);
15
+
16
+ if extract_path.exists() {
17
+ println!(
18
+ "Plugin '{}' already exists at '{}'. Skipping install.",
19
+ name,
20
+ extract_path.display()
21
+ );
22
+ return Ok(());
23
+ }
24
+
25
+ download_file(&url, &archive_path).await.map_err(|e| format!("Failed to download: {}", e))?;
26
+
27
+ extract_archive(&archive_path, &extract_path).await.map_err(|e|
28
+ format!("Failed to extract: {}", e)
29
+ )?;
30
+
31
+ // Add the plugin to the config
32
+ let root_dir = target_dir
33
+ .parent()
34
+ .ok_or_else(|| "Failed to determine root directory".to_string())?;
35
+
36
+ let config_path = root_dir.join(".devalang");
37
+ if (!config_path.exists()) {
38
+ return Err(
39
+ format!(
40
+ "Config file not found at '{}'. Please run 'devalang init' before adding an addon",
41
+ config_path.display()
42
+ )
43
+ );
44
+ }
45
+
46
+ let mut config = load_config(Some(&config_path)).ok_or_else(||
47
+ format!("Failed to load config from '{}'", config_path.display())
48
+ )?;
49
+
50
+ let dependency_path = &format!("devalang://plugin/{}", name);
51
+
52
+ add_plugin_to_config(&mut config, &extract_path, &dependency_path);
53
+
54
+ Ok(())
55
+ }
package/rust/lib.rs CHANGED
@@ -7,11 +7,11 @@ use wasm_bindgen::prelude::*;
7
7
  use serde_wasm_bindgen::to_value;
8
8
 
9
9
  use crate::core::{
10
- audio::{ engine::AudioEngine, interpreter::driver::{ run_audio_program } },
10
+ audio::{ engine::AudioEngine, interpreter::driver::run_audio_program },
11
11
  parser::statement::{ Statement, StatementKind },
12
12
  preprocessor::loader::ModuleLoader,
13
13
  shared::value::Value,
14
- store::global::GlobalStore,
14
+ store::{ function::FunctionTable, global::GlobalStore, variable::VariableTable },
15
15
  utils::path::normalize_path,
16
16
  };
17
17
 
@@ -69,16 +69,19 @@ pub fn render_audio(user_code: &str) -> Result<js_sys::Float32Array, JsValue> {
69
69
  .ok_or(JsValue::from_str("❌ No statements found for entry module"))?
70
70
  .clone();
71
71
 
72
- let audio_engine = AudioEngine::new("wasm_output".to_string());
72
+ let mut audio_engine = AudioEngine::new("wasm_output".to_string());
73
73
 
74
- let (final_engine, _, _) = run_audio_program(
74
+ let _ = run_audio_program(
75
75
  &main_statements,
76
- audio_engine,
76
+ &mut audio_engine,
77
77
  "playground".to_string(),
78
- "wasm_output".to_string()
78
+ "wasm_output".to_string(),
79
+ VariableTable::new(),
80
+ FunctionTable::new(),
81
+ &mut global_store
79
82
  );
80
83
 
81
- let samples = final_engine.get_normalized_buffer();
84
+ let samples = audio_engine.get_normalized_buffer();
82
85
 
83
86
  if samples.is_empty() {
84
87
  return Err(JsValue::from_str("❌ Audio buffer is empty"));
package/rust/main.rs CHANGED
@@ -15,18 +15,21 @@ use crate::{
15
15
  handle_bank_available_command,
16
16
  handle_bank_info_command,
17
17
  handle_bank_list_command,
18
- handle_remove_bank_command, handle_update_bank_command,
18
+ handle_remove_bank_command,
19
+ handle_update_bank_command,
19
20
  },
20
21
  build::handle_build_command,
21
22
  check::handle_check_command,
22
23
  driver::{ BankCommand, Cli, Commands, InstallCommand, TemplateCommand },
23
24
  init::handle_init_command,
24
- install::handle_install_bank_command,
25
+ install::handle_install_command,
26
+ login::handle_login_command,
25
27
  play::handle_play_command,
26
28
  template::{ handle_template_info_command, handle_template_list_command },
27
29
  update::handle_update_command,
28
30
  },
29
31
  config::{ driver::Config, loader::load_config },
32
+ installer::addon::AddonType,
30
33
  };
31
34
 
32
35
  #[tokio::main]
@@ -55,25 +58,35 @@ async fn main() -> io::Result<()> {
55
58
  }
56
59
  }
57
60
 
58
- Commands::Check { entry, output, watch, compilation_mode, debug } => {
59
- handle_check_command(config, entry, output, watch);
61
+ Commands::Check { entry, output, watch, debug } => {
62
+ handle_check_command(config, entry, output, watch, debug);
60
63
  }
61
64
 
62
- Commands::Build { entry, output, watch, compilation_mode, debug, compress } => {
63
- handle_build_command(config, entry, output, watch);
65
+ Commands::Build { entry, output, watch, debug, compress } => {
66
+ handle_build_command(config, entry, output, watch, debug, compress);
64
67
  }
65
68
 
66
- Commands::Play { entry, output, watch, repeat } => {
67
- handle_play_command(config, entry, output, watch, repeat);
69
+ Commands::Play { entry, output, watch, repeat, debug } => {
70
+ handle_play_command(config, entry, output, watch, repeat, debug);
68
71
  }
69
72
 
70
73
  Commands::Install { command } =>
71
74
  match command {
72
75
  InstallCommand::Bank { name } => {
73
- if let Err(err) = handle_install_bank_command(name).await {
76
+ if let Err(err) = handle_install_command(name, AddonType::Bank).await {
74
77
  eprintln!("❌ Failed to install bank: {}", err);
75
78
  }
76
79
  }
80
+ InstallCommand::Plugin { name } => {
81
+ if let Err(err) = handle_install_command(name, AddonType::Plugin).await {
82
+ eprintln!("❌ Failed to install plugin: {}", err);
83
+ }
84
+ }
85
+ InstallCommand::Preset { name } => {
86
+ if let Err(err) = handle_install_command(name, AddonType::Preset).await {
87
+ eprintln!("❌ Failed to install preset: {}", err);
88
+ }
89
+ }
77
90
  }
78
91
 
79
92
  Commands::Bank { command } =>
@@ -115,6 +128,16 @@ async fn main() -> io::Result<()> {
115
128
  }
116
129
  }
117
130
 
131
+ Commands::Login { .. } => {
132
+ if let Err(err) = handle_login_command().await {
133
+ eprintln!("❌ Login failed: {}", err);
134
+ }
135
+ }
136
+
137
+ Commands::Logout { .. } => {
138
+ eprintln!("❌ Logout command is not implemented yet.");
139
+ }
140
+
118
141
  _ => {}
119
142
  }
120
143
 
@@ -33,6 +33,22 @@ impl Logger {
33
33
  // no-op for WASM
34
34
  }
35
35
 
36
+ // --- log_message_with_trace ---
37
+
38
+ #[cfg(feature = "cli")]
39
+ pub fn log_message_with_trace(&self, level: LogLevel, message: &str, trace: Vec<&str>) {
40
+ let formatted_status = self.format_status(level);
41
+ println!("🦊 {} {} {}", self.language_signature(), formatted_status, message);
42
+ for t in trace {
43
+ println!(" ↳ {}", t);
44
+ }
45
+ }
46
+
47
+ #[cfg(not(feature = "cli"))]
48
+ pub fn log_message_with_trace(&self, _level: LogLevel, _message: &str, _trace: Vec<&str>) {
49
+ // no-op for WASM
50
+ }
51
+
36
52
  // --- log_error_with_stacktrace ---
37
53
 
38
54
  #[cfg(feature = "cli")]
package/rust/utils/mod.rs CHANGED
@@ -3,4 +3,48 @@ pub mod signature;
3
3
  pub mod spinner;
4
4
  pub mod watcher;
5
5
  pub mod file;
6
- pub mod logger;
6
+ pub mod logger;
7
+
8
+ use crate::core::parser::statement::{Statement, StatementKind};
9
+ use crate::core::error::ErrorResult;
10
+ use crate::core::shared::value::Value;
11
+
12
+ pub fn collect_errors_recursively(statements: &[Statement]) -> Vec<ErrorResult> {
13
+ let mut errors: Vec<ErrorResult> = Vec::new();
14
+
15
+ for stmt in statements {
16
+ match &stmt.kind {
17
+ StatementKind::Unknown => {
18
+ errors.push(ErrorResult {
19
+ message: format!("Unknown statement at line {}:{}", stmt.line, stmt.column),
20
+ line: stmt.line,
21
+ column: stmt.column,
22
+ });
23
+ }
24
+ StatementKind::Error { message } => {
25
+ errors.push(ErrorResult {
26
+ message: message.clone(),
27
+ line: stmt.line,
28
+ column: stmt.column,
29
+ });
30
+ }
31
+ StatementKind::Loop => {
32
+ if let Some(body_statements) = extract_loop_body_statements(&stmt.value) {
33
+ errors.extend(collect_errors_recursively(body_statements));
34
+ }
35
+ }
36
+ _ => {}
37
+ }
38
+ }
39
+
40
+ errors
41
+ }
42
+
43
+ pub fn extract_loop_body_statements(value: &Value) -> Option<&[Statement]> {
44
+ if let Value::Map(map) = value {
45
+ if let Some(Value::Block(statements)) = map.get("body") {
46
+ return Some(statements);
47
+ }
48
+ }
49
+ None
50
+ }
@@ -3,7 +3,7 @@ use indicatif::{ ProgressBar, ProgressStyle };
3
3
  use std::{ time::Duration };
4
4
 
5
5
  #[cfg(feature = "cli")]
6
- pub fn with_spinner<T, F>(start_msg: &str, f: F) -> T where F: FnOnce() -> T {
6
+ pub fn with_spinner<T, F>(start_msg: &str, f: F) -> ProgressBar where F: FnOnce() -> T {
7
7
  let spinner = ProgressBar::new_spinner();
8
8
  spinner.set_style(
9
9
  ProgressStyle::with_template("{spinner:.green} {msg}")
@@ -15,7 +15,5 @@ pub fn with_spinner<T, F>(start_msg: &str, f: F) -> T where F: FnOnce() -> T {
15
15
 
16
16
  let result = f();
17
17
 
18
- spinner.finish_and_clear();
19
-
20
- result
18
+ spinner
21
19
  }