@devaloop/devalang 0.0.1-alpha.15 → 0.0.1-alpha.16

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 (173) hide show
  1. package/.devalang +2 -0
  2. package/.github/workflows/ci.yml +92 -0
  3. package/Cargo.toml +60 -58
  4. package/README.md +1 -1
  5. package/docs/CHANGELOG.md +34 -1
  6. package/docs/CONTRIBUTING.md +101 -1
  7. package/docs/ROADMAP.md +1 -1
  8. package/docs/TODO.md +1 -1
  9. package/examples/automation.deva +1 -3
  10. package/examples/bank.deva +4 -4
  11. package/examples/events.deva +12 -0
  12. package/examples/function.deva +4 -4
  13. package/examples/index.deva +3 -5
  14. package/examples/loop.deva +5 -11
  15. package/examples/pattern.deva +8 -0
  16. package/examples/plugin.deva +12 -11
  17. package/examples/variables.deva +1 -1
  18. package/out-tsc/bin/index.js +51 -7
  19. package/out-tsc/index.js +3 -1
  20. package/out-tsc/scripts/postbuild.js +9 -10
  21. package/out-tsc/scripts/postinstall.js +49 -0
  22. package/package.json +12 -4
  23. package/project-version.json +3 -3
  24. package/rust/cli/bank.rs +462 -455
  25. package/rust/cli/build.rs +252 -199
  26. package/rust/cli/check.rs +221 -180
  27. package/rust/cli/driver.rs +297 -292
  28. package/rust/cli/generator.rs +1 -0
  29. package/rust/cli/init.rs +87 -79
  30. package/rust/cli/install.rs +35 -32
  31. package/rust/cli/login.rs +127 -134
  32. package/rust/cli/mod.rs +13 -11
  33. package/rust/cli/play.rs +1123 -218
  34. package/rust/cli/telemetry.rs +19 -0
  35. package/rust/cli/template.rs +69 -57
  36. package/rust/cli/update.rs +6 -4
  37. package/rust/common/api.rs +5 -5
  38. package/rust/common/mod.rs +3 -3
  39. package/rust/config/driver.rs +118 -94
  40. package/rust/config/loader.rs +165 -156
  41. package/rust/config/mod.rs +4 -2
  42. package/rust/config/settings.rs +91 -0
  43. package/rust/config/stats.rs +257 -0
  44. package/rust/core/audio/engine.rs +696 -659
  45. package/rust/core/audio/evaluator.rs +263 -132
  46. package/rust/core/audio/interpreter/arrow_call.rs +198 -187
  47. package/rust/core/audio/interpreter/call.rs +98 -95
  48. package/rust/core/audio/interpreter/condition.rs +70 -71
  49. package/rust/core/audio/interpreter/driver.rs +487 -231
  50. package/rust/core/audio/interpreter/function.rs +26 -21
  51. package/rust/core/audio/interpreter/let_.rs +38 -26
  52. package/rust/core/audio/interpreter/load.rs +18 -18
  53. package/rust/core/audio/interpreter/loop_.rs +113 -106
  54. package/rust/core/audio/interpreter/mod.rs +14 -14
  55. package/rust/core/audio/interpreter/sleep.rs +27 -28
  56. package/rust/core/audio/interpreter/spawn.rs +105 -102
  57. package/rust/core/audio/interpreter/tempo.rs +19 -16
  58. package/rust/core/audio/interpreter/trigger.rs +239 -210
  59. package/rust/core/audio/loader/mod.rs +1 -1
  60. package/rust/core/audio/loader/trigger.rs +100 -94
  61. package/rust/core/audio/mod.rs +7 -7
  62. package/rust/core/audio/player.rs +64 -64
  63. package/rust/core/audio/renderer.rs +56 -53
  64. package/rust/core/audio/special/easing.rs +189 -120
  65. package/rust/core/audio/special/env.rs +43 -41
  66. package/rust/core/audio/special/math.rs +102 -92
  67. package/rust/core/audio/special/mod.rs +9 -9
  68. package/rust/core/audio/special/modulator.rs +143 -120
  69. package/rust/core/builder/mod.rs +80 -85
  70. package/rust/core/debugger/lexer.rs +27 -27
  71. package/rust/core/debugger/mod.rs +24 -23
  72. package/rust/core/debugger/module.rs +55 -47
  73. package/rust/core/debugger/preprocessor.rs +27 -27
  74. package/rust/core/debugger/store.rs +40 -39
  75. package/rust/core/error/mod.rs +80 -69
  76. package/rust/core/lexer/handler/arrow.rs +82 -82
  77. package/rust/core/lexer/handler/at.rs +21 -21
  78. package/rust/core/lexer/handler/brace.rs +41 -41
  79. package/rust/core/lexer/handler/colon.rs +21 -21
  80. package/rust/core/lexer/handler/comment.rs +30 -30
  81. package/rust/core/lexer/handler/dot.rs +21 -21
  82. package/rust/core/lexer/handler/driver.rs +337 -292
  83. package/rust/core/lexer/handler/identifier.rs +46 -43
  84. package/rust/core/lexer/handler/indent.rs +66 -66
  85. package/rust/core/lexer/handler/mod.rs +16 -16
  86. package/rust/core/lexer/handler/newline.rs +23 -23
  87. package/rust/core/lexer/handler/number.rs +31 -31
  88. package/rust/core/lexer/handler/operator.rs +46 -46
  89. package/rust/core/lexer/handler/parenthesis.rs +41 -41
  90. package/rust/core/lexer/handler/slash.rs +21 -21
  91. package/rust/core/lexer/handler/string.rs +63 -63
  92. package/rust/core/lexer/mod.rs +54 -51
  93. package/rust/core/lexer/token.rs +97 -94
  94. package/rust/core/mod.rs +11 -11
  95. package/rust/core/parser/driver.rs +513 -490
  96. package/rust/core/parser/handler/arrow_call.rs +233 -227
  97. package/rust/core/parser/handler/at.rs +245 -162
  98. package/rust/core/parser/handler/bank.rs +94 -69
  99. package/rust/core/parser/handler/condition.rs +80 -74
  100. package/rust/core/parser/handler/dot.rs +143 -135
  101. package/rust/core/parser/handler/identifier/automate.rs +257 -194
  102. package/rust/core/parser/handler/identifier/call.rs +91 -88
  103. package/rust/core/parser/handler/identifier/emit.rs +66 -0
  104. package/rust/core/parser/handler/identifier/function.rs +100 -91
  105. package/rust/core/parser/handler/identifier/group.rs +85 -75
  106. package/rust/core/parser/handler/identifier/let_.rs +158 -143
  107. package/rust/core/parser/handler/identifier/mod.rs +54 -56
  108. package/rust/core/parser/handler/identifier/on.rs +98 -0
  109. package/rust/core/parser/handler/identifier/print.rs +52 -29
  110. package/rust/core/parser/handler/identifier/sleep.rs +36 -33
  111. package/rust/core/parser/handler/identifier/spawn.rs +91 -88
  112. package/rust/core/parser/handler/identifier/synth.rs +65 -63
  113. package/rust/core/parser/handler/loop_.rs +170 -89
  114. package/rust/core/parser/handler/mod.rs +8 -8
  115. package/rust/core/parser/handler/tempo.rs +53 -47
  116. package/rust/core/parser/mod.rs +4 -4
  117. package/rust/core/parser/statement.rs +142 -113
  118. package/rust/core/plugin/loader.rs +123 -48
  119. package/rust/core/plugin/mod.rs +2 -1
  120. package/rust/core/plugin/runner.rs +296 -0
  121. package/rust/core/preprocessor/loader.rs +515 -326
  122. package/rust/core/preprocessor/mod.rs +4 -4
  123. package/rust/core/preprocessor/module.rs +60 -58
  124. package/rust/core/preprocessor/processor.rs +99 -101
  125. package/rust/core/preprocessor/resolver/bank.rs +51 -48
  126. package/rust/core/preprocessor/resolver/call.rs +100 -101
  127. package/rust/core/preprocessor/resolver/condition.rs +97 -97
  128. package/rust/core/preprocessor/resolver/driver.rs +310 -280
  129. package/rust/core/preprocessor/resolver/function.rs +69 -68
  130. package/rust/core/preprocessor/resolver/group.rs +96 -91
  131. package/rust/core/preprocessor/resolver/let_.rs +32 -28
  132. package/rust/core/preprocessor/resolver/loop_.rs +320 -121
  133. package/rust/core/preprocessor/resolver/mod.rs +15 -15
  134. package/rust/core/preprocessor/resolver/spawn.rs +76 -73
  135. package/rust/core/preprocessor/resolver/synth.rs +56 -50
  136. package/rust/core/preprocessor/resolver/tempo.rs +50 -49
  137. package/rust/core/preprocessor/resolver/trigger.rs +113 -115
  138. package/rust/core/preprocessor/resolver/value.rs +81 -81
  139. package/rust/core/shared/duration.rs +9 -9
  140. package/rust/core/shared/mod.rs +3 -3
  141. package/rust/core/shared/value.rs +35 -32
  142. package/rust/core/store/function.rs +34 -34
  143. package/rust/core/store/global.rs +55 -38
  144. package/rust/core/store/mod.rs +5 -5
  145. package/rust/core/store/variable.rs +37 -34
  146. package/rust/core/utils/mod.rs +2 -2
  147. package/rust/core/utils/path.rs +37 -31
  148. package/rust/core/utils/validation.rs +35 -36
  149. package/rust/installer/addon.rs +84 -80
  150. package/rust/installer/bank.rs +62 -65
  151. package/rust/installer/mod.rs +5 -5
  152. package/rust/installer/plugin.rs +54 -55
  153. package/rust/installer/utils.rs +56 -56
  154. package/rust/lib.rs +156 -164
  155. package/rust/main.rs +250 -144
  156. package/rust/utils/error.rs +200 -51
  157. package/rust/utils/file.rs +38 -35
  158. package/rust/utils/first_usage.rs +76 -0
  159. package/rust/utils/logger.rs +195 -143
  160. package/rust/utils/mod.rs +9 -7
  161. package/rust/utils/signature.rs +19 -17
  162. package/rust/utils/spinner.rs +22 -19
  163. package/rust/utils/telemetry.rs +292 -0
  164. package/rust/utils/watcher.rs +34 -33
  165. package/templates/minimal/README.md +97 -121
  166. package/templates/welcome/README.md +97 -121
  167. package/typescript/bin/index.ts +19 -5
  168. package/typescript/index.ts +3 -1
  169. package/typescript/scripts/postbuild.ts +10 -6
  170. package/typescript/scripts/postinstall.ts +56 -0
  171. package/typescript/scripts/version/bump.ts +0 -1
  172. package/typescript/scripts/version/index.ts +0 -1
  173. package/out-tsc/bin/devalang.exe +0 -0
@@ -1,8 +1,8 @@
1
- pub mod at;
2
- pub mod identifier;
3
- pub mod dot;
4
- pub mod tempo;
5
- pub mod bank;
6
- pub mod loop_;
7
- pub mod condition;
8
- pub mod arrow_call;
1
+ pub mod arrow_call;
2
+ pub mod at;
3
+ pub mod bank;
4
+ pub mod condition;
5
+ pub mod dot;
6
+ pub mod identifier;
7
+ pub mod loop_;
8
+ pub mod tempo;
@@ -1,47 +1,53 @@
1
- use crate::core::{
2
- lexer::token::TokenKind,
3
- parser::{ statement::{ Statement, StatementKind }, driver::Parser },
4
- shared::value::Value,
5
- store::global::GlobalStore,
6
- };
7
-
8
- pub fn parse_tempo_token(parser: &mut Parser, _global_store: &mut GlobalStore) -> Statement {
9
- parser.advance(); // consume 'bpm'
10
-
11
- let Some(tempo_token) = parser.previous_clone() else {
12
- return Statement::unknown();
13
- };
14
-
15
- // Expect a number or identifier
16
- let Some(value_token) = parser.peek_clone() else {
17
- return Statement::error(
18
- tempo_token,
19
- "Expected a number or identifier after 'bpm'".to_string()
20
- );
21
- };
22
-
23
- let value = match value_token.kind {
24
- TokenKind::Number => {
25
- parser.advance();
26
- Value::Number(value_token.lexeme.parse().unwrap_or(0.0))
27
- }
28
- TokenKind::Identifier => {
29
- parser.advance();
30
- Value::Identifier(value_token.lexeme.clone())
31
- }
32
- _ => {
33
- return Statement::error(
34
- value_token.clone(),
35
- format!("Expected a number or identifier after 'bpm', got {:?}", value_token.kind)
36
- );
37
- }
38
- };
39
-
40
- Statement {
41
- kind: StatementKind::Tempo,
42
- value,
43
- indent: tempo_token.indent,
44
- line: tempo_token.line,
45
- column: tempo_token.column,
46
- }
47
- }
1
+ use crate::core::{
2
+ lexer::token::TokenKind,
3
+ parser::{
4
+ driver::Parser,
5
+ statement::{Statement, StatementKind},
6
+ },
7
+ shared::value::Value,
8
+ store::global::GlobalStore,
9
+ };
10
+
11
+ pub fn parse_tempo_token(parser: &mut Parser, _global_store: &mut GlobalStore) -> Statement {
12
+ parser.advance(); // consume 'bpm'
13
+
14
+ let Some(tempo_token) = parser.previous_clone() else {
15
+ return Statement::unknown();
16
+ };
17
+
18
+ // Expect a number or identifier
19
+ let Some(value_token) = parser.peek_clone() else {
20
+ return Statement::error(
21
+ tempo_token,
22
+ "Expected a number or identifier after 'bpm'".to_string(),
23
+ );
24
+ };
25
+
26
+ let value = match value_token.kind {
27
+ TokenKind::Number => {
28
+ parser.advance();
29
+ Value::Number(value_token.lexeme.parse().unwrap_or(0.0))
30
+ }
31
+ TokenKind::Identifier => {
32
+ parser.advance();
33
+ Value::Identifier(value_token.lexeme.clone())
34
+ }
35
+ _ => {
36
+ return Statement::error(
37
+ value_token.clone(),
38
+ format!(
39
+ "Expected a number or identifier after 'bpm', got {:?}",
40
+ value_token.kind
41
+ ),
42
+ );
43
+ }
44
+ };
45
+
46
+ Statement {
47
+ kind: StatementKind::Tempo,
48
+ value,
49
+ indent: tempo_token.indent,
50
+ line: tempo_token.line,
51
+ column: tempo_token.column,
52
+ }
53
+ }
@@ -1,4 +1,4 @@
1
- pub mod driver;
2
-
3
- pub mod statement;
4
- pub mod handler;
1
+ pub mod driver;
2
+
3
+ pub mod handler;
4
+ pub mod statement;
@@ -1,113 +1,142 @@
1
- use serde::{ Deserialize, Serialize };
2
- use crate::core::{ lexer::token::Token, shared::{ duration::Duration, value::Value } };
3
-
4
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5
- pub struct Statement {
6
- pub kind: StatementKind,
7
- pub value: Value,
8
- pub indent: usize,
9
- pub line: usize,
10
- pub column: usize,
11
- }
12
-
13
- impl Statement {
14
- pub fn unknown() -> Self {
15
- Statement {
16
- kind: StatementKind::Unknown,
17
- value: Value::Null,
18
- indent: 0,
19
- line: 0,
20
- column: 0,
21
- }
22
- }
23
-
24
- pub fn error(token: Token, message: String) -> Self {
25
- Statement {
26
- kind: StatementKind::Error { message },
27
- value: Value::Null,
28
- indent: token.indent,
29
- line: token.line,
30
- column: token.column,
31
- }
32
- }
33
- }
34
-
35
- #[derive(Debug, Serialize, Clone, Deserialize, PartialEq)]
36
- pub enum StatementKind {
37
- // ───── Core Instructions ─────
38
- Tempo,
39
- Bank,
40
- Print,
41
- Load {
42
- source: String,
43
- alias: String,
44
- },
45
- Let {
46
- name: String,
47
- },
48
- // Automation of parameters over time (percent-based envelopes)
49
- Automate {
50
- target: String,
51
- },
52
- ArrowCall {
53
- target: String,
54
- method: String,
55
- args: Vec<Value>,
56
- },
57
- Function {
58
- name: String,
59
- parameters: Vec<String>,
60
- body: Vec<Statement>,
61
- },
62
-
63
- // ───── Instruments ─────
64
- Synth,
65
-
66
- // ───── Playback / Scheduling ─────
67
- Trigger {
68
- entity: String,
69
- duration: Duration,
70
- effects: Option<Value>,
71
- },
72
- Sleep,
73
- Call {
74
- name: String,
75
- args: Vec<Value>,
76
- },
77
- Spawn {
78
- name: String,
79
- args: Vec<Value>,
80
- },
81
- Loop,
82
-
83
- // ───── Structure & Logic ─────
84
- Group,
85
-
86
- // ───── Module System ─────
87
- Include(String),
88
- Export {
89
- names: Vec<String>,
90
- source: String,
91
- },
92
- Import {
93
- names: Vec<String>,
94
- source: String,
95
- },
96
-
97
- // ───── Conditions ─────
98
- If,
99
- Else,
100
- ElseIf,
101
-
102
- // ───── Internal / Utility ─────
103
- Comment,
104
- Indent,
105
- Dedent,
106
- NewLine,
107
-
108
- // ───── Error Handling ─────
109
- Unknown,
110
- Error {
111
- message: String,
112
- },
113
- }
1
+ use crate::core::{
2
+ lexer::token::Token,
3
+ shared::{duration::Duration, value::Value},
4
+ };
5
+ use serde::{Deserialize, Serialize};
6
+
7
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
8
+ pub struct Statement {
9
+ pub kind: StatementKind,
10
+ pub value: Value,
11
+ pub indent: usize,
12
+ pub line: usize,
13
+ pub column: usize,
14
+ }
15
+
16
+ impl Statement {
17
+ pub fn unknown() -> Self {
18
+ Statement {
19
+ kind: StatementKind::Unknown,
20
+ value: Value::Null,
21
+ indent: 0,
22
+ line: 0,
23
+ column: 0,
24
+ }
25
+ }
26
+
27
+ pub fn unknown_from_token(token: &Token) -> Self {
28
+ Statement {
29
+ kind: StatementKind::Unknown,
30
+ value: Value::Null,
31
+ indent: token.indent,
32
+ line: token.line,
33
+ column: token.column,
34
+ }
35
+ }
36
+
37
+ pub fn error(token: Token, message: String) -> Self {
38
+ Statement {
39
+ kind: StatementKind::Error { message },
40
+ value: Value::Null,
41
+ indent: token.indent,
42
+ line: token.line,
43
+ column: token.column,
44
+ }
45
+ }
46
+ }
47
+
48
+ #[derive(Debug, Serialize, Clone, Deserialize, PartialEq)]
49
+ pub enum StatementKind {
50
+ // ───── Core Instructions ─────
51
+ Tempo,
52
+ Bank {
53
+ alias: Option<String>,
54
+ },
55
+ Print,
56
+ Load {
57
+ source: String,
58
+ alias: String,
59
+ },
60
+ Use {
61
+ name: String,
62
+ alias: Option<String>,
63
+ },
64
+ Let {
65
+ name: String,
66
+ },
67
+ Automate {
68
+ target: String,
69
+ },
70
+ ArrowCall {
71
+ target: String,
72
+ method: String,
73
+ args: Vec<Value>,
74
+ },
75
+ Function {
76
+ name: String,
77
+ parameters: Vec<String>,
78
+ body: Vec<Statement>,
79
+ },
80
+
81
+ // ───── Instruments ─────
82
+ Synth,
83
+
84
+ // ───── Playback / Scheduling ─────
85
+ Trigger {
86
+ entity: String,
87
+ duration: Duration,
88
+ effects: Option<Value>,
89
+ },
90
+ Sleep,
91
+ Call {
92
+ name: String,
93
+ args: Vec<Value>,
94
+ },
95
+ Spawn {
96
+ name: String,
97
+ args: Vec<Value>,
98
+ },
99
+ Loop,
100
+
101
+ // ───── Structure & Logic ─────
102
+ Group,
103
+
104
+ // ───── Module System ─────
105
+ Include(String),
106
+ Export {
107
+ names: Vec<String>,
108
+ source: String,
109
+ },
110
+ Import {
111
+ names: Vec<String>,
112
+ source: String,
113
+ },
114
+
115
+ // ───── Conditions ─────
116
+ If,
117
+ Else,
118
+ ElseIf,
119
+
120
+ // ───── Internal / Utility ─────
121
+ Comment,
122
+ Indent,
123
+ Dedent,
124
+ NewLine,
125
+
126
+ // ───── Events / Live coding ─────
127
+ On {
128
+ event: String,
129
+ args: Option<Vec<Value>>,
130
+ body: Vec<Statement>,
131
+ },
132
+ Emit {
133
+ event: String,
134
+ payload: Option<Value>,
135
+ },
136
+
137
+ // ───── Error Handling ─────
138
+ Unknown,
139
+ Error {
140
+ message: String,
141
+ },
142
+ }
@@ -1,48 +1,123 @@
1
- use std::path::Path;
2
- use serde::Deserialize;
3
-
4
- #[derive(Debug, Deserialize, Clone)]
5
- pub struct PluginInfo {
6
- pub name: String,
7
- pub version: Option<String>,
8
- pub description: Option<String>,
9
- pub author: Option<String>,
10
- }
11
-
12
- #[derive(Debug, Deserialize, Clone)]
13
- pub struct PluginFile {
14
- pub plugin: PluginInfo,
15
- }
16
-
17
- pub fn load_plugin(name: &str) -> Result<(PluginInfo, Vec<u8>), String> {
18
- let root = Path::new(env!("CARGO_MANIFEST_DIR"));
19
- let plugin_dir = root.join(".deva").join("plugin").join(name);
20
- let toml_path = plugin_dir.join("plugin.toml");
21
- let wasm_path = plugin_dir.join(format!("{}_bg.wasm", name));
22
-
23
- if !toml_path.exists() {
24
- return Err(format!("❌ Plugin file not found: {}", toml_path.display()));
25
- }
26
- if !wasm_path.exists() {
27
- return Err(format!("❌ Plugin wasm not found: {}", wasm_path.display()));
28
- }
29
-
30
- let toml_content = std::fs::read_to_string(&toml_path)
31
- .map_err(|e| format!("Failed to read '{}': {}", toml_path.display(), e))?;
32
- let plugin_file: PluginFile = toml::from_str(&toml_content)
33
- .map_err(|e| format!("Failed to parse '{}': {}", toml_path.display(), e))?;
34
-
35
- let wasm_bytes = std::fs::read(&wasm_path)
36
- .map_err(|e| format!("Failed to read '{}': {}", wasm_path.display(), e))?;
37
-
38
- Ok((plugin_file.plugin, wasm_bytes))
39
- }
40
-
41
- pub fn load_plugin_from_uri(uri: &str) -> Result<(PluginInfo, Vec<u8>), String> {
42
- if !uri.starts_with("devalang://plugin/") {
43
- return Err("Invalid plugin URI".into());
44
- }
45
-
46
- let name = uri.trim_start_matches("devalang://plugin/");
47
- load_plugin(name)
48
- }
1
+ use serde::Deserialize;
2
+ use std::path::Path;
3
+ use toml::Value as TomlValue;
4
+
5
+ #[derive(Debug, Deserialize, Clone)]
6
+ pub struct PluginInfo {
7
+ pub name: String,
8
+ pub version: Option<String>,
9
+ pub description: Option<String>,
10
+ pub author: Option<String>,
11
+ #[serde(skip)]
12
+ pub exports: Vec<ExportEntry>,
13
+ }
14
+
15
+ #[derive(Debug, Deserialize, Clone)]
16
+ pub struct ExportEntry {
17
+ pub name: String,
18
+ #[serde(rename = "type")]
19
+ pub kind: String,
20
+ #[serde(default)]
21
+ pub default: Option<TomlValue>,
22
+ }
23
+
24
+ #[derive(Debug, Deserialize, Clone)]
25
+ pub struct PluginFile {
26
+ pub plugin: PluginInfo,
27
+ #[serde(default)]
28
+ pub export: Vec<ExportEntry>,
29
+ }
30
+
31
+ /// Load a plugin from local .deva directory given author and name
32
+ pub fn load_plugin(author: &str, name: &str) -> Result<(PluginInfo, Vec<u8>), String> {
33
+ // Align with other loaders (banks) that use relative ./.deva paths
34
+ let root = Path::new("./.deva");
35
+ // Preferred layout: ./.deva/plugin/<author>.<name>/
36
+ let plugin_dir_preferred = root.join("plugin").join(format!("{}.{}", author, name));
37
+ let toml_path_preferred = plugin_dir_preferred.join("plugin.toml");
38
+ let wasm_path_preferred_bg = plugin_dir_preferred.join(format!("{}_bg.wasm", name));
39
+ let wasm_path_preferred_plain = plugin_dir_preferred.join(format!("{}.wasm", name));
40
+
41
+ // Legacy layout (fallback): ./.deva/plugin/<author>/<name>/
42
+ let plugin_dir_fallback = root.join("plugin").join(author).join(name);
43
+ let toml_path_fallback = plugin_dir_fallback.join("plugin.toml");
44
+ let wasm_path_fallback_bg = plugin_dir_fallback.join(format!("{}_bg.wasm", name));
45
+ let wasm_path_fallback_plain = plugin_dir_fallback.join(format!("{}.wasm", name));
46
+
47
+ // Resolve actual paths to use
48
+ let (toml_path, wasm_path) = if toml_path_preferred.exists() && wasm_path_preferred_bg.exists()
49
+ {
50
+ (toml_path_preferred, wasm_path_preferred_bg)
51
+ } else if toml_path_preferred.exists() && wasm_path_preferred_plain.exists() {
52
+ (toml_path_preferred, wasm_path_preferred_plain)
53
+ } else if toml_path_fallback.exists() && wasm_path_fallback_bg.exists() {
54
+ (toml_path_fallback, wasm_path_fallback_bg)
55
+ } else if toml_path_fallback.exists() && wasm_path_fallback_plain.exists() {
56
+ (toml_path_fallback, wasm_path_fallback_plain)
57
+ } else {
58
+ // If either file is missing in both layouts, produce specific errors for missing files in preferred layout
59
+ if !toml_path_preferred.exists() {
60
+ return Err(format!(
61
+ "❌ Plugin file not found: {}",
62
+ toml_path_preferred.display()
63
+ ));
64
+ }
65
+ if !wasm_path_preferred_bg.exists() && !wasm_path_preferred_plain.exists() {
66
+ return Err(format!(
67
+ "❌ Plugin wasm not found: '{}' or '{}'",
68
+ wasm_path_preferred_bg.display(),
69
+ wasm_path_preferred_plain.display()
70
+ ));
71
+ }
72
+ unreachable!();
73
+ };
74
+
75
+ let toml_content = std::fs::read_to_string(&toml_path)
76
+ .map_err(|e| format!("Failed to read '{}': {}", toml_path.display(), e))?;
77
+ let plugin_file: PluginFile = toml::from_str(&toml_content)
78
+ .map_err(|e| format!("Failed to parse '{}': {}", toml_path.display(), e))?;
79
+
80
+ let wasm_bytes = std::fs::read(&wasm_path)
81
+ .map_err(|e| format!("Failed to read '{}': {}", wasm_path.display(), e))?;
82
+
83
+ let mut info = plugin_file.plugin.clone();
84
+ info.exports = plugin_file.export.clone();
85
+
86
+ Ok((info, wasm_bytes))
87
+ }
88
+
89
+ /// Load a plugin from dot notation: "author.name"
90
+ pub fn load_plugin_from_dot(dot: &str) -> Result<(PluginInfo, Vec<u8>), String> {
91
+ let mut parts = dot.split('.');
92
+ let author = parts
93
+ .next()
94
+ .ok_or_else(|| "Invalid plugin name, missing author".to_string())?;
95
+ let name = parts
96
+ .next()
97
+ .ok_or_else(|| "Invalid plugin name, missing name".to_string())?;
98
+ if parts.next().is_some() {
99
+ return Err("Invalid plugin name format, expected <author>.<name>".into());
100
+ }
101
+ load_plugin(author, name)
102
+ }
103
+
104
+ pub fn load_plugin_from_uri(uri: &str) -> Result<(PluginInfo, Vec<u8>), String> {
105
+ if !uri.starts_with("devalang://plugin/") {
106
+ return Err("Invalid plugin URI".into());
107
+ }
108
+
109
+ // Expect format: devalang://plugin/author.name
110
+ let payload = uri.trim_start_matches("devalang://plugin/");
111
+ let mut parts = payload.split('.');
112
+ let author = parts
113
+ .next()
114
+ .ok_or_else(|| "Invalid plugin URI, missing author".to_string())?;
115
+ let name = parts
116
+ .next()
117
+ .ok_or_else(|| "Invalid plugin URI, missing name".to_string())?;
118
+ if parts.next().is_some() {
119
+ return Err("Invalid plugin URI format, expected devalang://plugin/<author>.<name>".into());
120
+ }
121
+
122
+ load_plugin(author, name)
123
+ }
@@ -1 +1,2 @@
1
- pub mod loader;
1
+ pub mod loader;
2
+ pub mod runner;