@devaloop/devalang 0.0.1-alpha.1 → 0.0.1-alpha.10

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 (155) hide show
  1. package/.devalang +4 -0
  2. package/Cargo.toml +49 -45
  3. package/README.md +127 -46
  4. package/docs/CHANGELOG.md +172 -0
  5. package/docs/COMMANDS.md +60 -6
  6. package/docs/CONFIG.md +30 -0
  7. package/docs/ROADMAP.md +10 -7
  8. package/docs/SYNTAX.md +100 -18
  9. package/docs/TODO.md +31 -28
  10. package/examples/condition.deva +20 -0
  11. package/examples/group.deva +12 -0
  12. package/examples/index.deva +13 -4
  13. package/examples/loop.deva +16 -0
  14. package/examples/samples/hat-808.wav +0 -0
  15. package/examples/synth.deva +14 -0
  16. package/examples/variables.deva +9 -0
  17. package/out-tsc/bin/devalang.exe +0 -0
  18. package/out-tsc/scripts/version/fetch.js +1 -5
  19. package/package.json +5 -4
  20. package/project-version.json +3 -3
  21. package/rust/cli/build.rs +114 -28
  22. package/rust/cli/check.rs +96 -103
  23. package/rust/cli/init.rs +79 -0
  24. package/rust/cli/mod.rs +203 -1
  25. package/rust/cli/play.rs +193 -0
  26. package/rust/cli/template.rs +57 -0
  27. package/rust/config/loader.rs +13 -0
  28. package/rust/config/mod.rs +16 -0
  29. package/rust/core/audio/engine.rs +214 -0
  30. package/rust/core/audio/evaluator.rs +31 -0
  31. package/rust/core/audio/interpreter/arrow_call.rs +129 -0
  32. package/rust/core/audio/interpreter/call.rs +70 -0
  33. package/rust/core/audio/interpreter/condition.rs +69 -0
  34. package/rust/core/audio/interpreter/driver.rs +236 -0
  35. package/rust/core/audio/interpreter/let_.rs +19 -0
  36. package/rust/core/audio/interpreter/load.rs +18 -0
  37. package/rust/core/audio/interpreter/loop_.rs +67 -0
  38. package/rust/core/audio/interpreter/mod.rs +12 -0
  39. package/rust/core/audio/interpreter/sleep.rs +36 -0
  40. package/rust/core/audio/interpreter/spawn.rs +84 -0
  41. package/rust/core/audio/interpreter/tempo.rs +16 -0
  42. package/rust/core/audio/interpreter/trigger.rs +69 -0
  43. package/rust/core/audio/loader/mod.rs +1 -0
  44. package/rust/core/audio/loader/trigger.rs +52 -0
  45. package/rust/core/audio/mod.rs +6 -0
  46. package/rust/core/audio/player.rs +54 -0
  47. package/rust/core/audio/renderer.rs +54 -0
  48. package/rust/core/builder/mod.rs +70 -27
  49. package/rust/core/debugger/lexer.rs +27 -0
  50. package/rust/core/debugger/mod.rs +13 -49
  51. package/rust/core/debugger/preprocessor.rs +27 -0
  52. package/rust/core/debugger/store.rs +25 -0
  53. package/rust/core/error/mod.rs +60 -0
  54. package/rust/core/lexer/handler/arrow.rs +31 -0
  55. package/rust/core/lexer/handler/at.rs +21 -0
  56. package/rust/core/lexer/handler/brace.rs +41 -0
  57. package/rust/core/lexer/handler/colon.rs +21 -0
  58. package/rust/core/lexer/handler/comment.rs +30 -0
  59. package/rust/core/lexer/handler/dot.rs +21 -0
  60. package/rust/core/lexer/handler/driver.rs +230 -0
  61. package/rust/core/lexer/handler/identifier.rs +41 -0
  62. package/rust/core/lexer/handler/indent.rs +52 -0
  63. package/rust/core/lexer/handler/mod.rs +14 -0
  64. package/rust/core/lexer/handler/newline.rs +23 -0
  65. package/rust/core/lexer/handler/number.rs +31 -0
  66. package/rust/core/lexer/handler/operator.rs +44 -0
  67. package/rust/core/lexer/handler/string.rs +63 -0
  68. package/rust/core/lexer/mod.rs +37 -319
  69. package/rust/core/lexer/token.rs +86 -0
  70. package/rust/core/mod.rs +6 -2
  71. package/rust/core/parser/driver.rs +331 -0
  72. package/rust/core/parser/handler/arrow_call.rs +126 -0
  73. package/rust/core/parser/handler/at.rs +162 -0
  74. package/rust/core/parser/handler/bank.rs +41 -0
  75. package/rust/core/parser/handler/condition.rs +74 -0
  76. package/rust/core/parser/handler/dot.rs +112 -0
  77. package/rust/core/parser/handler/identifier/call.rs +41 -0
  78. package/rust/core/parser/handler/identifier/group.rs +75 -0
  79. package/rust/core/parser/handler/identifier/let_.rs +133 -0
  80. package/rust/core/parser/handler/identifier/mod.rs +51 -0
  81. package/rust/core/parser/handler/identifier/sleep.rs +33 -0
  82. package/rust/core/parser/handler/identifier/spawn.rs +41 -0
  83. package/rust/core/parser/handler/identifier/synth.rs +65 -0
  84. package/rust/core/parser/handler/loop_.rs +72 -0
  85. package/rust/core/parser/handler/mod.rs +8 -0
  86. package/rust/core/parser/handler/tempo.rs +47 -0
  87. package/rust/core/parser/mod.rs +3 -200
  88. package/rust/core/parser/statement.rs +96 -0
  89. package/rust/core/preprocessor/loader.rs +229 -0
  90. package/rust/core/preprocessor/mod.rs +2 -24
  91. package/rust/core/preprocessor/module.rs +42 -56
  92. package/rust/core/preprocessor/processor.rs +76 -0
  93. package/rust/core/preprocessor/resolver/bank.rs +41 -51
  94. package/rust/core/preprocessor/resolver/call.rs +123 -0
  95. package/rust/core/preprocessor/resolver/condition.rs +92 -0
  96. package/rust/core/preprocessor/resolver/driver.rs +227 -0
  97. package/rust/core/preprocessor/resolver/group.rs +61 -0
  98. package/rust/core/preprocessor/resolver/let_.rs +31 -0
  99. package/rust/core/preprocessor/resolver/loop_.rs +76 -67
  100. package/rust/core/preprocessor/resolver/mod.rs +12 -111
  101. package/rust/core/preprocessor/resolver/spawn.rs +58 -0
  102. package/rust/core/preprocessor/resolver/synth.rs +50 -0
  103. package/rust/core/preprocessor/resolver/tempo.rs +40 -61
  104. package/rust/core/preprocessor/resolver/trigger.rs +90 -154
  105. package/rust/core/preprocessor/resolver/value.rs +78 -0
  106. package/rust/core/shared/duration.rs +8 -0
  107. package/rust/core/shared/mod.rs +2 -0
  108. package/rust/core/shared/value.rs +28 -0
  109. package/rust/core/store/export.rs +28 -0
  110. package/rust/core/store/global.rs +39 -0
  111. package/rust/core/store/import.rs +28 -0
  112. package/rust/core/store/mod.rs +4 -0
  113. package/rust/core/store/variable.rs +28 -0
  114. package/rust/core/utils/mod.rs +2 -0
  115. package/rust/core/utils/path.rs +31 -0
  116. package/rust/core/utils/validation.rs +37 -0
  117. package/rust/lib.rs +161 -1
  118. package/rust/main.rs +46 -30
  119. package/rust/utils/file.rs +35 -0
  120. package/rust/utils/logger.rs +108 -34
  121. package/rust/utils/mod.rs +3 -2
  122. package/rust/utils/{loader.rs → spinner.rs} +2 -0
  123. package/rust/utils/watcher.rs +33 -0
  124. package/templates/minimal/.devalang +5 -0
  125. package/templates/minimal/README.md +202 -0
  126. package/templates/minimal/src/index.deva +2 -0
  127. package/templates/welcome/.devalang +5 -0
  128. package/templates/welcome/README.md +202 -0
  129. package/templates/welcome/samples/kick-808.wav +0 -0
  130. package/templates/welcome/src/index.deva +13 -0
  131. package/templates/welcome/src/variables.deva +5 -0
  132. package/typescript/scripts/version/fetch.ts +1 -6
  133. package/examples/exported.deva +0 -7
  134. package/rust/audio/mod.rs +0 -1
  135. package/rust/cli/new.rs +0 -1
  136. package/rust/core/parser/at.rs +0 -142
  137. package/rust/core/parser/bank.rs +0 -42
  138. package/rust/core/parser/dot.rs +0 -107
  139. package/rust/core/parser/identifer.rs +0 -91
  140. package/rust/core/parser/loop_.rs +0 -62
  141. package/rust/core/parser/tempo.rs +0 -42
  142. package/rust/core/parser/variable.rs +0 -129
  143. package/rust/core/preprocessor/dependencies.rs +0 -54
  144. package/rust/core/preprocessor/resolver/at.rs +0 -24
  145. package/rust/core/types/cli.rs +0 -160
  146. package/rust/core/types/mod.rs +0 -7
  147. package/rust/core/types/module.rs +0 -41
  148. package/rust/core/types/parser.rs +0 -73
  149. package/rust/core/types/statement.rs +0 -105
  150. package/rust/core/types/store.rs +0 -116
  151. package/rust/core/types/token.rs +0 -83
  152. package/rust/core/types/variable.rs +0 -32
  153. package/rust/runner/executer.rs +0 -44
  154. package/rust/runner/mod.rs +0 -1
  155. package/rust/utils/path.rs +0 -46
package/rust/cli/check.rs CHANGED
@@ -1,124 +1,117 @@
1
- use std::{ thread, time::Duration };
2
-
3
1
  use crate::{
2
+ config::Config,
4
3
  core::{
5
- debugger::Debugger,
6
- preprocessor::module::load_all_modules,
7
- types::{
8
- statement::{
9
- Statement,
10
- StatementIterator,
11
- StatementKind,
12
- StatementResolved,
13
- StatementResolvedValue,
14
- },
15
- variable::VariableValue,
16
- },
4
+ preprocessor::loader::ModuleLoader,
5
+ store::global::GlobalStore,
6
+ utils::path::{ find_entry_file, normalize_path },
17
7
  },
18
- runner::executer::execute_statements,
19
- utils::{ loader::with_spinner, logger::log_message, path::{ find_entry_file, normalize_path } },
8
+ utils::{ logger::{ LogLevel, Logger }, spinner::with_spinner, watcher::watch_directory },
20
9
  };
10
+ use std::{ thread, time::Duration };
21
11
 
22
- pub fn handle_check_command(entry: String, output: String) -> () {
23
- let entry_file = find_entry_file(&entry).unwrap_or_else(|| {
24
- eprintln!("❌ index.deva not found in directory: {}", entry);
12
+ #[cfg(feature = "cli")]
13
+ pub fn handle_check_command(
14
+ config: Option<Config>,
15
+ entry: Option<String>,
16
+ output: Option<String>,
17
+ watch: bool
18
+ ) {
19
+ let fetched_entry = if entry.is_none() {
20
+ config
21
+ .as_ref()
22
+ .and_then(|c| c.defaults.entry.clone())
23
+ .unwrap_or_else(|| "".to_string())
24
+ } else {
25
+ entry.clone().unwrap_or_else(|| "".to_string())
26
+ };
27
+
28
+ let fetched_output = if output.is_none() {
29
+ config
30
+ .as_ref()
31
+ .and_then(|c| c.defaults.output.clone())
32
+ .unwrap_or_else(|| "".to_string())
33
+ } else {
34
+ output.clone().unwrap_or_else(|| "".to_string())
35
+ };
36
+
37
+ let fetched_watch = if watch {
38
+ watch
39
+ } else {
40
+ config
41
+ .as_ref()
42
+ .and_then(|c| c.defaults.watch)
43
+ .unwrap_or(false)
44
+ };
45
+
46
+ let logger = Logger::new();
47
+
48
+ if fetched_entry.is_empty() {
49
+ logger.log_message(
50
+ LogLevel::Error,
51
+ "Entry path is not specified. Please provide a valid entry path."
52
+ );
25
53
  std::process::exit(1);
26
- });
54
+ }
55
+ if fetched_output.is_empty() {
56
+ logger.log_message(
57
+ LogLevel::Error,
58
+ "Output directory is not specified. Please provide a valid output directory."
59
+ );
60
+ std::process::exit(1);
61
+ }
27
62
 
28
- let spinner = with_spinner("Checking...", || {
29
- thread::sleep(Duration::from_millis(800));
63
+ let entry_file = find_entry_file(&fetched_entry).unwrap_or_else(|| {
64
+ logger.log_message(
65
+ LogLevel::Error,
66
+ &format!("❌ index.deva not found in directory: {}", fetched_entry)
67
+ );
68
+ std::process::exit(1);
30
69
  });
31
70
 
32
- let duration = std::time::Instant::now();
33
-
34
- let normalized_entry_file = normalize_path(&entry_file);
35
- let normalized_output_dir = normalize_path(&output);
36
-
37
- let global_store = load_all_modules(&normalized_entry_file);
71
+ // SECTION Begin check
72
+ if fetched_watch {
73
+ begin_check(entry_file.clone(), fetched_output.clone());
38
74
 
39
- if let Some(module) = global_store.modules.get(&normalized_entry_file) {
40
- let mut module_clone = module.clone();
75
+ logger.log_message(
76
+ LogLevel::Watcher,
77
+ &format!("Watching for changes in '{}'...", fetched_entry)
78
+ );
41
79
 
42
- let resolved_statements = execute_statements(&mut module_clone);
80
+ watch_directory(entry_file.clone(), move || {
81
+ logger.log_message(LogLevel::Watcher, "Detected changes, re-checking...");
43
82
 
44
- let debugger = Debugger::new(&module_clone);
45
- let debug_dir = format!("{}/debug/", normalized_output_dir.clone());
46
- debugger.write_files(debug_dir.as_str(), resolved_statements.clone());
83
+ begin_check(entry_file.clone(), fetched_output.clone());
84
+ }).unwrap();
85
+ } else {
86
+ begin_check(entry_file.clone(), fetched_output.clone());
87
+ }
88
+ }
47
89
 
48
- let has_errors = resolved_statements.iter().any(|stmt| {
49
- match_error_recursively_resolved(&stmt.clone())
50
- });
90
+ fn begin_check(entry: String, output: String) {
91
+ let spinner = with_spinner("Checking...", || {
92
+ thread::sleep(Duration::from_millis(800));
93
+ });
51
94
 
52
- if has_errors {
53
- let warning_message = format!(
54
- "Check completed with errors in {:.2?}. Output files written to: '{}'",
55
- duration.elapsed(),
56
- normalized_output_dir
57
- );
95
+ let duration = std::time::Instant::now();
58
96
 
59
- log_message(&warning_message, "WARNING");
60
- } else {
61
- let success_message = format!(
62
- "Check completed successfully in {:.2?}. Output files written to: '{}'",
63
- duration.elapsed(),
64
- normalized_output_dir
65
- );
97
+ let normalized_entry_file = normalize_path(&entry);
98
+ let normalized_output_dir = normalize_path(&output);
66
99
 
67
- log_message(&success_message, "SUCCESS");
68
- }
69
- }
70
- }
100
+ let mut global_store = GlobalStore::new();
101
+ let module_loader = ModuleLoader::new(&normalized_entry_file, &normalized_output_dir);
71
102
 
72
- fn match_error_recursively_resolved(stmt: &StatementResolved) -> bool {
73
- match stmt.value.clone() {
74
- // TODO Other statement value types here
75
-
76
- StatementResolvedValue::Map(map) => {
77
- for (key, value) in map {
78
- if match_error_recursively_resolved_value(&value) {
79
- return true;
80
- }
81
- }
82
- }
83
-
84
- StatementResolvedValue::Array(array) => {
85
- for item in array {
86
- if match_error_recursively_resolved(&item) {
87
- return true;
88
- }
89
- }
90
- }
91
-
92
- _ => {
93
- if let StatementKind::Error = stmt.kind {
94
- eprintln!("❌ Error found in statement: {:?}", stmt);
95
- return true;
96
- }
97
- }
98
- }
103
+ // SECTION Load
104
+ // NOTE: We don't use modules in the check command, but we still need to load them
105
+ let modules = module_loader.load_all_modules(&mut global_store);
99
106
 
100
- false
101
- }
107
+ // TODO: Implement debugging
102
108
 
103
- fn match_error_recursively_resolved_value(value: &StatementResolvedValue) -> bool {
104
- match value {
105
- StatementResolvedValue::Map(map) => {
106
- for (_, v) in map {
107
- if match_error_recursively_resolved_value(v) {
108
- return true;
109
- }
110
- }
111
- }
112
-
113
- StatementResolvedValue::Array(array) => {
114
- for item in array {
115
- if match_error_recursively_resolved(item) {
116
- return true;
117
- }
118
- }
119
- }
120
- _ => {}
121
- }
109
+ let success_message = format!(
110
+ "Check completed successfully in {:.2?}. Output files written to: '{}'",
111
+ duration.elapsed(),
112
+ normalized_output_dir
113
+ );
122
114
 
123
- false
115
+ let logger = Logger::new();
116
+ logger.log_message(LogLevel::Success, &success_message);
124
117
  }
@@ -0,0 +1,79 @@
1
+ use std::{ fs, path::Path };
2
+ use include_dir::{ include_dir, Dir };
3
+ use crate::{ cli::template::get_available_templates, utils::file::copy_dir_recursive };
4
+
5
+ #[cfg(feature = "cli")]
6
+ use inquire::{ Select, Confirm };
7
+
8
+ static TEMPLATES_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates");
9
+
10
+ #[cfg(feature = "cli")]
11
+ pub fn handle_init_command(name: Option<String>, template: Option<String>) {
12
+ let current_dir = std::env::current_dir().unwrap();
13
+ let project_name = name
14
+ .clone()
15
+ .unwrap_or_else(|| {
16
+ current_dir.file_name().unwrap_or_default().to_string_lossy().to_string()
17
+ });
18
+
19
+ // Select a template if not provided
20
+ let selected_template = template.unwrap_or_else(|| {
21
+ Select::new("Select a template for your project:", get_available_templates())
22
+ .prompt()
23
+ .unwrap_or_else(|_| {
24
+ eprintln!("No template selected. Exiting...");
25
+ std::process::exit(1);
26
+ })
27
+ });
28
+
29
+ if selected_template.is_empty() {
30
+ eprintln!("Template cannot be empty.");
31
+ std::process::exit(1);
32
+ }
33
+
34
+ if name.is_none() {
35
+ // Case of initialization in the current directory
36
+ if fs::read_dir(&current_dir).unwrap().next().is_some() {
37
+ let confirm = Confirm::new(
38
+ "The current directory is not empty. Do you want to continue?"
39
+ )
40
+ .with_default(false)
41
+ .prompt()
42
+ .unwrap_or(false);
43
+
44
+ if !confirm {
45
+ eprintln!("Operation cancelled by the user.");
46
+ std::process::exit(0);
47
+ }
48
+ }
49
+
50
+ scaffold_project_current_dir(current_dir.as_path(), selected_template);
51
+ println!(
52
+ "✅ Initialized '{}' project in current directory: {}",
53
+ project_name,
54
+ current_dir.display()
55
+ );
56
+ } else {
57
+ // Case of initialization in a new directory
58
+ let target_path = current_dir.join(&project_name);
59
+
60
+ if target_path.exists() {
61
+ eprintln!("❌ A folder named '{}' already exists.", project_name);
62
+ std::process::exit(1);
63
+ }
64
+
65
+ fs::create_dir_all(&target_path).expect("Error creating project directory");
66
+
67
+ scaffold_project_current_dir(&target_path, selected_template);
68
+ println!("✅ Initialized '{}' project in: {}", project_name, target_path.display());
69
+ }
70
+ }
71
+
72
+ fn scaffold_project_current_dir(path: &Path, template: String) {
73
+ let template_dir = TEMPLATES_DIR.get_dir(&template).unwrap_or_else(|| {
74
+ eprintln!("❌ The template '{}' doesn't exist.", template);
75
+ std::process::exit(1);
76
+ });
77
+
78
+ copy_dir_recursive(template_dir, path, &template_dir.path());
79
+ }
package/rust/cli/mod.rs CHANGED
@@ -1,3 +1,205 @@
1
1
  pub mod check;
2
- pub mod new;
3
2
  pub mod build;
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
+ }
@@ -0,0 +1,193 @@
1
+ use crate::{
2
+ config::Config,
3
+ core::{
4
+ builder::Builder,
5
+ debugger::{ lexer::write_lexer_log_file, preprocessor::write_preprocessor_log_file },
6
+ preprocessor::loader::ModuleLoader,
7
+ store::global::GlobalStore,
8
+ utils::path::{ find_entry_file, normalize_path },
9
+ },
10
+ utils::{ logger::{ LogLevel, Logger }, spinner::with_spinner, watcher::watch_directory },
11
+ };
12
+
13
+ use std::{ path::Path, sync::mpsc::channel, thread, time::Duration };
14
+ use std::fs;
15
+ use std::collections::HashMap;
16
+
17
+ #[cfg(feature = "cli")]
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
+ use crate::core::audio::player::AudioPlayer;
26
+
27
+ let logger = Logger::new();
28
+
29
+ let entry_path = entry
30
+ .or_else(|| config.as_ref().and_then(|c| c.defaults.entry.clone()))
31
+ .unwrap_or_else(|| "".to_string());
32
+
33
+ let output_path = output
34
+ .or_else(|| config.as_ref().and_then(|c| c.defaults.output.clone()))
35
+ .unwrap_or_else(|| "".to_string());
36
+
37
+ let fetched_repeat = if repeat {
38
+ true
39
+ } else {
40
+ config
41
+ .as_ref()
42
+ .and_then(|c| c.defaults.repeat)
43
+ .unwrap_or(false)
44
+ };
45
+
46
+ if entry_path.is_empty() || output_path.is_empty() {
47
+ logger.log_message(LogLevel::Error, "Entry or output path not specified.");
48
+ std::process::exit(1);
49
+ }
50
+
51
+ let entry_file = find_entry_file(&entry_path).unwrap_or_else(|| {
52
+ logger.log_message(LogLevel::Error, "index.deva not found");
53
+ std::process::exit(1);
54
+ });
55
+
56
+ let audio_file = format!("{}/audio/index.wav", normalize_path(&output_path));
57
+ let mut audio_player = AudioPlayer::new();
58
+
59
+ if watch && fetched_repeat {
60
+ logger.log_message(
61
+ LogLevel::Error,
62
+ "Watch and repeat cannot be used together. Use repeat instead."
63
+ );
64
+ std::process::exit(1);
65
+ }
66
+
67
+ if watch {
68
+ let (tx, rx) = channel::<()>();
69
+
70
+ // Thread 1 : Watcher sending changes
71
+ let entry_clone = entry_path.clone();
72
+ thread::spawn(move || {
73
+ let _ = watch_directory(entry_clone, move || {
74
+ let _ = tx.send(()); // signal a change
75
+ });
76
+ });
77
+
78
+ // Main thread: build + play in a loop
79
+ begin_play(&config, &entry_file, &output_path);
80
+ audio_player.play_file_once(&audio_file);
81
+
82
+ logger.log_message(LogLevel::Watcher, "Watching for changes... Press Ctrl+C to exit.");
83
+
84
+ while let Ok(_) = rx.recv() {
85
+ logger.log_message(LogLevel::Watcher, "Change detected, rebuilding...");
86
+
87
+ begin_play(&config, &entry_file, &output_path);
88
+
89
+ logger.log_message(LogLevel::Info, "🎵 Playback started (once mode)...");
90
+
91
+ audio_player.play_file_once(&audio_file);
92
+ }
93
+ } else if fetched_repeat {
94
+ // Initial build to start from a clean slate
95
+ begin_play(&config, &entry_file, &output_path);
96
+
97
+ logger.log_message(LogLevel::Info, "🎵 Playback started (repeat mode)...");
98
+
99
+ let mut last_snapshot = snapshot_files(&entry_path);
100
+ let mut audio_player = AudioPlayer::new();
101
+ audio_player.play_file_once(&audio_file);
102
+
103
+ loop {
104
+ let current_snapshot = snapshot_files(&entry_path);
105
+ let has_changed = files_changed(&last_snapshot, &current_snapshot);
106
+
107
+ if has_changed {
108
+ logger.log_message(LogLevel::Info, "Change detected, rebuilding in background...");
109
+ let entry_file = entry_file.clone();
110
+ let output_path = output_path.clone();
111
+ let config_clone = config.clone();
112
+
113
+ // Rebuild in a separate thread
114
+ std::thread::spawn(move || {
115
+ begin_play(&config_clone, &entry_file, &output_path);
116
+ });
117
+
118
+ last_snapshot = current_snapshot;
119
+ }
120
+
121
+ // Wait for the audio to finish without blocking the current playback
122
+ audio_player.wait_until_end();
123
+
124
+ // Then replay the audio (rebuilt or not)
125
+ audio_player.play_file_once(&audio_file);
126
+ }
127
+ } else {
128
+ // Single execution
129
+ begin_play(&config, &entry_file, &output_path);
130
+
131
+ logger.log_message(LogLevel::Info, "🎵 Playback started (once mode)...");
132
+
133
+ audio_player.play_file_once(&audio_file);
134
+ audio_player.wait_until_end();
135
+ }
136
+ }
137
+
138
+ fn begin_play(config: &Option<Config>, entry_file: &str, output: &str) {
139
+ let spinner = with_spinner("Building...", || {
140
+ thread::sleep(Duration::from_millis(800));
141
+ });
142
+
143
+ let normalized_entry = normalize_path(entry_file);
144
+ let normalized_output_dir = normalize_path(&output);
145
+
146
+ let duration = std::time::Instant::now();
147
+ let mut global_store = GlobalStore::new();
148
+ let loader = ModuleLoader::new(&normalized_entry, &normalized_output_dir);
149
+ let (modules_tokens, modules_statements) = loader.load_all_modules(&mut global_store);
150
+
151
+ // SECTION Write logs
152
+ write_lexer_log_file(&normalized_output_dir, "lexer_tokens.log", modules_tokens.clone());
153
+ write_preprocessor_log_file(
154
+ &normalized_output_dir,
155
+ "resolved_statements.log",
156
+ modules_statements.clone()
157
+ );
158
+
159
+ // SECTION Building AST and Audio
160
+ let builder = Builder::new();
161
+ builder.build_ast(&modules_statements, &output);
162
+ builder.build_audio(&modules_statements, &output, &mut global_store);
163
+
164
+ // SECTION Logging
165
+ let logger = Logger::new();
166
+ let success_message = format!(
167
+ "Build completed successfully in {:.2?}. Output files written to: '{}'",
168
+ duration.elapsed(),
169
+ normalized_output_dir
170
+ );
171
+
172
+ logger.log_message(LogLevel::Success, &success_message);
173
+ }
174
+
175
+ fn snapshot_files<P: AsRef<Path>>(dir: P) -> HashMap<String, u64> {
176
+ let mut map = HashMap::new();
177
+ if let Ok(entries) = fs::read_dir(dir) {
178
+ for entry in entries.flatten() {
179
+ if let Ok(meta) = entry.metadata() {
180
+ if let Ok(mtime) = meta.modified() {
181
+ if let Ok(duration) = mtime.duration_since(std::time::UNIX_EPOCH) {
182
+ map.insert(entry.path().display().to_string(), duration.as_secs());
183
+ }
184
+ }
185
+ }
186
+ }
187
+ }
188
+ map
189
+ }
190
+
191
+ fn files_changed(old: &HashMap<String, u64>, new: &HashMap<String, u64>) -> bool {
192
+ old != new
193
+ }