@devaloop/devalang 0.0.1-alpha.4 β†’ 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/Cargo.toml CHANGED
@@ -1,20 +1,22 @@
1
1
  [package]
2
2
  name = "devalang"
3
- version = "0.0.1-alpha.4"
3
+ version = "0.0.1-alpha.5"
4
4
  authors = ["Devaloop <contact@devaloop.com>"]
5
5
  description = "Write music like code. Devalang is a domain-specific language (DSL) for sound designers and music hackers. Compose, automate, and control sound β€” in plain text."
6
6
  license = "MIT"
7
7
  repository = "https://github.com/devaloop-labs/devalang"
8
8
  keywords = ["music", "dsl", "audio", "cli"]
9
- categories = ["command-line-utilities", "audio", "development-tools"]
9
+ categories = ["command-line-utilities", "development-tools", "parser-implementations"]
10
10
  readme = "README.md"
11
11
  homepage = "https://devalang.com"
12
12
  documentation = "https://docs.devalang.com/"
13
+ license-file = "LICENSE"
13
14
  edition = "2024"
14
15
 
15
16
  [[bin]]
16
17
  name = "devalang"
17
18
  path = "rust/main.rs"
19
+ required-features = ["cli"]
18
20
 
19
21
  [lib]
20
22
  path = "rust/lib.rs"
@@ -25,7 +27,7 @@ opt-level = "s"
25
27
 
26
28
  [features]
27
29
  default = ["cli"]
28
- cli = ["crossterm"]
30
+ cli = ["crossterm", "indicatif", "inquire"]
29
31
 
30
32
  [dependencies]
31
33
  clap = { version = "4.5", features = ["derive"] }
@@ -42,5 +44,5 @@ serde-wasm-bindgen = "0.4"
42
44
  nom_locate = "4.0.0"
43
45
  chrono = "0.4"
44
46
  crossterm = { version = "0.27", optional = true }
45
- indicatif = "0.17"
46
- inquire = "0.7.5"
47
+ indicatif = { version = "0.17", optional = true }
48
+ inquire = { version = "0.7.5", optional = true }
package/README.md CHANGED
@@ -12,6 +12,10 @@
12
12
  ![Platform](https://img.shields.io/badge/platform-Windows-blue)
13
13
 
14
14
  ![npm](https://img.shields.io/npm/dt/@devaloop/devalang)
15
+ ![crates](https://img.shields.io/crates/d/devalang)
16
+
17
+ [![VSCode Extension](https://img.shields.io/visual-studio-marketplace/v/devaloop.devalang-vscode?label=VSCode%20Extension)](https://marketplace.visualstudio.com/items?itemName=devaloop.devalang-vscode)
18
+
15
19
 
16
20
  ## 🎼 Devalang, by **Devaloop Labs**
17
21
 
@@ -24,9 +28,10 @@ Compose loops, control samples, render and play audio β€” all in clean, readable
24
28
 
25
29
  From studio sketches to live sets, Devalang gives you rhythmic control β€” with the elegance of code.
26
30
 
27
- > 🚧 **v0.0.1-alpha.4 Notice** 🚧
31
+ > 🚧 **v0.0.1-alpha.5 Notice** 🚧
28
32
  >
29
- > **Audio Engine** is now integrated, enabling audio playback and rendering capabilities.
33
+ > NEW: Devalang VSCode extension is now available !
34
+ > [Get it here](https://marketplace.visualstudio.com/items?itemName=devaloop.devalang-vscode).
30
35
  >
31
36
  > Currently, Devalang CLI is only available for **Windows**.
32
37
  > Linux and macOS binaries will be added in future releases via cross-platform builds.
@@ -37,6 +42,8 @@ From studio sketches to live sets, Devalang gives you rhythmic control β€” with
37
42
 
38
43
  - [πŸ“– Documentation](./docs/)
39
44
  - [πŸ’‘ Examples](./examples/)
45
+ - [🧩 VSCode Extension](https://marketplace.visualstudio.com/items?itemName=devaloop.devalang-vscode)
46
+ - [🎨 Prettier Plugin](https://www.npmjs.com/package/@devaloop/prettier-plugin-devalang)
40
47
  - [🌐 Project Website](https://devalang.com)
41
48
 
42
49
  ## πŸš€ Features
package/docs/CHANGELOG.md CHANGED
@@ -4,6 +4,19 @@
4
4
 
5
5
  # Changelog
6
6
 
7
+ ## Version 0.0.1-alpha.5 (2025-07-05)
8
+
9
+ ### Syntax
10
+
11
+ - Fixed block parsing issues caused by missing or incorrect `Indent` / `Dedent` token detection.
12
+ - Indentation handling now triggers correctly at each newline.
13
+ - Improved reliability of nested blocks (e.g., inside `loop`) with consistent `Dedent` termination.
14
+
15
+ ### Core Components
16
+
17
+ - Added full **WebAssembly (WASM)** support β€” Devalang can now be compiled for browser or Node.js environments.
18
+ - Prepared the ground for future IDE integrations (e.g., VSCode extension) by stabilizing core syntax parsing.
19
+
7
20
  ## Version 0.0.1-alpha.4 (2025-07-03)
8
21
 
9
22
  ### Audio Engine
@@ -15,11 +28,11 @@
15
28
  ### Commands
16
29
 
17
30
  - Implemented `play` command to play Devalang files.
31
+
18
32
  - Added `--watch` option to watch for changes in files and automatically rebuild and play them. (once)
19
33
  - Added `--repeat` option to repeat the playback of the audio file. (infinite)
20
-
34
+
21
35
  Note : You cannot use `--watch` and `--repeat` options together. Use `--repeat` instead.
22
-
23
36
 
24
37
  ## Version 0.0.1-alpha.3 (2025-07-01)
25
38
 
package/docs/ROADMAP.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  Devalang is a work in progress. Here’s what we’re planning next:
8
8
 
9
- ### Stable
9
+ ## Completed
10
10
 
11
11
  - βœ… **Audio engine**: Integrate the audio engine for sound playback.
12
12
  - βœ… **Basic syntax**: Implement the core syntax for Devalang, including data types and basic statements.
@@ -20,11 +20,11 @@ Devalang is a work in progress. Here’s what we’re planning next:
20
20
  - βœ… **Instruction calls**: Add support for instruction calls with parameters (e.g. `.kick auto {reverb:10, decay:20}`).
21
21
  - βœ… **Let assignments**: Implement `let` assignments for storing reusable values.
22
22
  - βœ… **Sample loading**: Add `@load` assignment to load samples (.mp3, .wav) for use as values.
23
+ - βœ… **WASM support**: Compile Devalang to WebAssembly for use in web applications and other environments.
23
24
 
24
- ### Upcoming
25
+ ## Upcoming
25
26
 
26
27
  - ⏳ **VSCode extension**: Create a VSCode extension for syntax highlighting and code completion.
27
- - ⏳ **WASM support**: Compile Devalang to WebAssembly for use in web applications.
28
28
  - ⏳ **Other statements**: Implement `if`, `else`, and other control structures.
29
29
  - ⏳ **Pattern and group statements**: Add support for `@pattern` and `@group` to organize code.
30
30
  - ⏳ **Functions**: Add support for defining and calling functions.
@@ -1,16 +1,17 @@
1
1
  @import { duration, default_bank, params, loopCount, tempo } from "./examples/exported.deva"
2
2
 
3
- @load "./examples/samples/kick-808.wav" as sample
4
- @load "./examples/samples/hat-808.wav" as hat
3
+ @load "./examples/samples/kick-808.wav" as kickCustom
4
+ @load "./examples/samples/hat-808.wav" as hatCustom
5
5
 
6
6
  bpm tempo
7
7
 
8
8
  bank default_bank
9
9
 
10
10
  loop loopCount:
11
- .sample duration params
11
+ .kickCustom duration params
12
12
 
13
13
  # Uncomment the next line (.hat) while executing "play" command
14
14
  # with `--repeat` option to see magic happen !
15
15
 
16
- # .hat duration params
16
+ # .hatCustom duration params
17
+
Binary file
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@devaloop/devalang",
3
3
  "private": false,
4
- "version": "0.0.1-alpha.4",
4
+ "version": "0.0.1-alpha.5",
5
5
  "description": "Write music like code. Devalang is a domain-specific language (DSL) for sound designers and music hackers. Compose, automate, and control sound β€” in plain text.",
6
6
  "main": "out-tsc/index.js",
7
7
  "bin": {
@@ -11,6 +11,8 @@
11
11
  "prepublish": "cargo build --release && npm run script:postbuild",
12
12
  "rust:dev:build": "cargo run build --entry examples --output output",
13
13
  "rust:dev:check": "cargo run check --entry examples --output output",
14
+ "rust:wasm:web": "wasm-pack build --target=web --no-default-features",
15
+ "rust:wasm:node": "wasm-pack build --target=nodejs --no-default-features",
14
16
  "script:postbuild": "tsc && node out-tsc/scripts/postbuild.js",
15
17
  "script:version:bump": "tsc && node out-tsc/scripts/version/index.js"
16
18
  },
@@ -39,4 +41,4 @@
39
41
  "dependencies": {
40
42
  "@types/node": "^24.0.3"
41
43
  }
42
- }
44
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "0.0.1-alpha.4",
2
+ "version": "0.0.1-alpha.5",
3
3
  "channel": "alpha",
4
- "lastCommit": "ca5336b3cd3f2189971e9e99a93a66042faff007",
5
- "build": 3
4
+ "lastCommit": "1df08dbb62484586f67fbf42d34d6011cf019a12",
5
+ "build": 4
6
6
  }
@@ -2,11 +2,7 @@ use std::{ collections::HashMap, fs::File, io::BufReader };
2
2
  use hound::{ SampleFormat, WavSpec, WavWriter };
3
3
  use rodio::{ Decoder, Source };
4
4
 
5
- use crate::core::{
6
- parser::statement::Statement,
7
- store::variable::VariableTable,
8
- utils::path::normalize_path,
9
- };
5
+ use crate::core::{ store::variable::VariableTable, utils::path::normalize_path };
10
6
 
11
7
  const SAMPLE_RATE: u32 = 44100;
12
8
  const CHANNELS: u16 = 2;
package/rust/cli/build.rs CHANGED
@@ -12,6 +12,7 @@ use crate::{
12
12
  };
13
13
  use std::{ thread, time::Duration };
14
14
 
15
+ #[cfg(feature = "cli")]
15
16
  pub fn handle_build_command(
16
17
  config: Option<Config>,
17
18
  entry: Option<String>,
@@ -104,7 +105,7 @@ fn begin_build(entry: String, output: String) {
104
105
 
105
106
  // SECTION Load
106
107
  // NOTE: We use modules in the build command, so we need to load them
107
- let (modules_tokens, modules_statements) = module_loader.load_all(&mut global_store);
108
+ let (modules_tokens, modules_statements) = module_loader.load_all_modules(&mut global_store);
108
109
 
109
110
  // SECTION Write logs
110
111
  write_lexer_log_file(&normalized_output_dir, "lexer_tokens.log", modules_tokens.clone());
@@ -116,12 +117,12 @@ fn begin_build(entry: String, output: String) {
116
117
 
117
118
  // SECTION Building AST and Audio
118
119
  let builder = Builder::new();
119
- builder.build_ast(&modules_statements);
120
+ builder.build_ast(&modules_statements, &normalized_output_dir);
120
121
  builder.build_audio(&modules_statements, &normalized_output_dir, &mut global_store);
121
122
 
122
123
  // SECTION Logging
123
124
  let logger = Logger::new();
124
-
125
+
125
126
  let success_message = format!(
126
127
  "Build completed successfully in {:.2?}. Output files written to: '{}'",
127
128
  duration.elapsed(),
package/rust/cli/check.rs CHANGED
@@ -9,6 +9,7 @@ use crate::{
9
9
  };
10
10
  use std::{ thread, time::Duration };
11
11
 
12
+ #[cfg(feature = "cli")]
12
13
  pub fn handle_check_command(
13
14
  config: Option<Config>,
14
15
  entry: Option<String>,
@@ -101,7 +102,7 @@ fn begin_check(entry: String, output: String) {
101
102
 
102
103
  // SECTION Load
103
104
  // NOTE: We don't use modules in the check command, but we still need to load them
104
- let modules = module_loader.load_all(&mut global_store);
105
+ let modules = module_loader.load_all_modules(&mut global_store);
105
106
 
106
107
  // TODO: Implement debugging
107
108
 
package/rust/cli/init.rs CHANGED
@@ -1,11 +1,13 @@
1
1
  use std::{ fs, path::Path };
2
2
  use include_dir::{ include_dir, Dir };
3
- use inquire::{ Select, Confirm };
4
-
5
3
  use crate::{ cli::template::get_available_templates, utils::file::copy_dir_recursive };
6
4
 
5
+ #[cfg(feature = "cli")]
6
+ use inquire::{ Select, Confirm };
7
+
7
8
  static TEMPLATES_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates");
8
9
 
10
+ #[cfg(feature = "cli")]
9
11
  pub fn handle_init_command(name: Option<String>, template: Option<String>) {
10
12
  let current_dir = std::env::current_dir().unwrap();
11
13
  let project_name = name
package/rust/cli/play.rs CHANGED
@@ -15,6 +15,7 @@ use std::{ path::Path, sync::mpsc::channel, thread, time::Duration };
15
15
  use std::fs;
16
16
  use std::collections::HashMap;
17
17
 
18
+ #[cfg(feature = "cli")]
18
19
  pub fn handle_play_command(
19
20
  config: Option<Config>,
20
21
  entry: Option<String>,
@@ -144,7 +145,7 @@ fn begin_play(config: &Option<Config>, entry_file: &str, output: &str) {
144
145
  let duration = std::time::Instant::now();
145
146
  let mut global_store = GlobalStore::new();
146
147
  let loader = ModuleLoader::new(&normalized_entry, &normalized_output_dir);
147
- let (modules_tokens, modules_statements) = loader.load_all(&mut global_store);
148
+ let (modules_tokens, modules_statements) = loader.load_all_modules(&mut global_store);
148
149
 
149
150
  // SECTION Write logs
150
151
  write_lexer_log_file(&normalized_output_dir, "lexer_tokens.log", modules_tokens.clone());
@@ -156,7 +157,7 @@ fn begin_play(config: &Option<Config>, entry_file: &str, output: &str) {
156
157
 
157
158
  // SECTION Building AST and Audio
158
159
  let builder = Builder::new();
159
- builder.build_ast(&modules_statements);
160
+ builder.build_ast(&modules_statements, &output);
160
161
  builder.build_audio(&modules_statements, &output, &mut global_store);
161
162
 
162
163
  // SECTION Logging
@@ -1,9 +1,9 @@
1
1
  use include_dir::{ include_dir, Dir, DirEntry };
2
-
3
2
  use crate::utils::file::format_file_size;
4
3
 
5
4
  static TEMPLATES_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates");
6
5
 
6
+ #[cfg(feature = "cli")]
7
7
  pub fn handle_template_list_command() {
8
8
  let available_templates = get_available_templates();
9
9
 
@@ -16,6 +16,7 @@ pub fn handle_template_list_command() {
16
16
  println!("\nUsage : devalang init --name <project-name> --template <template-name>");
17
17
  }
18
18
 
19
+ #[cfg(feature = "cli")]
19
20
  pub fn handle_template_info_command(name: String) {
20
21
  let template_dir = TEMPLATES_DIR.get_dir(name.clone()).unwrap_or_else(|| {
21
22
  println!("❌ The template '{}' is not found.", name);
@@ -1,5 +1,4 @@
1
1
  use std::{ fs, path::Path };
2
-
3
2
  use crate::config::Config;
4
3
 
5
4
  pub fn load_config(path: Option<&Path>) -> Option<Config> {
@@ -1,10 +1,10 @@
1
- use crate::audio::render::render_audio_with_modules;
2
- use crate::core::parser::statement::Statement;
1
+ use crate::{ audio::render::render_audio_with_modules, core::parser::statement::Statement };
3
2
  use crate::core::store::global::GlobalStore;
4
- use crate::utils::logger::Logger;
5
3
  use std::{ collections::HashMap, fs::create_dir_all };
6
4
  use std::io::Write;
7
5
 
6
+ use crate::utils::logger::Logger;
7
+
8
8
  pub struct Builder {}
9
9
 
10
10
  impl Builder {
@@ -12,16 +12,14 @@ impl Builder {
12
12
  Builder {}
13
13
  }
14
14
 
15
- pub fn build_ast(&self, modules: &HashMap<String, Vec<Statement>>) {
16
- let output_path = "./output";
17
-
15
+ pub fn build_ast(&self, modules: &HashMap<String, Vec<Statement>>, out_dir: &str) {
18
16
  for (name, statements) in modules {
19
17
  let formatted_name = name.split("/").last().unwrap_or(name);
20
18
  let formatted_name = formatted_name.replace(".deva", "");
21
19
 
22
- create_dir_all(format!("{}/ast", output_path)).expect("Failed to create AST directory");
20
+ create_dir_all(format!("{}/ast", out_dir)).expect("Failed to create AST directory");
23
21
 
24
- let file_path = format!("{}/ast/{}.json", output_path, formatted_name);
22
+ let file_path = format!("{}/ast/{}.json", out_dir, formatted_name);
25
23
  let mut file = std::fs::File::create(file_path).expect("Failed to create AST file");
26
24
 
27
25
  let content = serde_json
@@ -46,7 +44,9 @@ impl Builder {
46
44
  global_store
47
45
  );
48
46
 
49
- create_dir_all(format!("{}/audio", normalized_output_dir)).expect("Failed to create audio directory");
47
+ create_dir_all(format!("{}/audio", normalized_output_dir)).expect(
48
+ "Failed to create audio directory"
49
+ );
50
50
 
51
51
  for (module_name, mut audio_engine) in audio_engines {
52
52
  let formatted_module_name = module_name
@@ -42,7 +42,7 @@ fn advance_char<I: Iterator<Item = char>>(
42
42
  Some(c)
43
43
  }
44
44
 
45
- pub fn handle_content_lexing(content: String) -> Vec<Token> {
45
+ pub fn handle_content_lexing(content: String) -> Result<Vec<Token>, String> {
46
46
  let mut tokens = Vec::new();
47
47
 
48
48
  let mut line = 1;
@@ -234,5 +234,5 @@ pub fn handle_content_lexing(content: String) -> Vec<Token> {
234
234
  // );
235
235
  // }
236
236
 
237
- tokens
237
+ Ok(tokens)
238
238
  }
@@ -14,6 +14,10 @@ pub fn handle_newline_lexer(
14
14
  lexeme: ch.to_string(),
15
15
  line: *line,
16
16
  column: 0,
17
- indent: 0,
17
+ indent: *current_indent,
18
18
  });
19
+
20
+ *line += 1;
21
+ *column = 1;
22
+ *at_line_start = true;
19
23
  }
@@ -2,7 +2,10 @@ pub mod handler;
2
2
  pub mod token;
3
3
 
4
4
  use std::fs;
5
- use crate::core::{ lexer::{ handler::handle_content_lexing, token::Token }, utils::path::normalize_path };
5
+ use crate::core::{
6
+ lexer::{ handler::handle_content_lexing, token::Token },
7
+ utils::path::normalize_path,
8
+ };
6
9
 
7
10
  pub struct Lexer {}
8
11
 
@@ -11,14 +14,16 @@ impl Lexer {
11
14
  Lexer {}
12
15
  }
13
16
 
17
+ pub fn lex_from_source(&self, source: &str) -> Result<Vec<Token>, String> {
18
+ handle_content_lexing(source.to_string())
19
+ }
20
+
14
21
  pub fn lex_tokens(&self, entrypoint: &str) -> Vec<Token> {
15
22
  let path = normalize_path(entrypoint);
16
23
 
17
- let file_content = fs
18
- ::read_to_string(&path)
19
- .expect("Failed to read the entrypoint file");
24
+ let file_content = fs::read_to_string(&path).expect("Failed to read the entrypoint file");
20
25
 
21
- let tokens = handle_content_lexing(file_content);
26
+ let tokens = handle_content_lexing(file_content).expect("Failed to lex the content");
22
27
 
23
28
  tokens
24
29
  }
@@ -40,6 +40,17 @@ pub fn parse_loop_token(parser: &mut Parser, global_store: &mut GlobalStore) ->
40
40
  );
41
41
  let loop_body = parser.parse_block(tokens.clone(), global_store);
42
42
 
43
+ // Peek for dedent
44
+ if let Some(token) = parser.peek() {
45
+ if token.kind == TokenKind::Dedent {
46
+ parser.advance();
47
+ } else {
48
+ // Unexpected token after loop body
49
+ }
50
+ } else {
51
+ // EOF or unexpected end of input
52
+ }
53
+
43
54
  let mut value_map = HashMap::new();
44
55
 
45
56
  value_map.insert("iterator".to_string(), Value::Identifier(iterator_name));
@@ -225,7 +225,6 @@ impl Parser {
225
225
  break;
226
226
  }
227
227
  if condition(token) {
228
- self.advance(); // Consume the token that matches the condition
229
228
  break;
230
229
  }
231
230
  collected.push(self.advance().unwrap().clone());
@@ -1,5 +1,4 @@
1
1
  use std::collections::HashMap;
2
-
3
2
  use crate::{
4
3
  core::{
5
4
  error::ErrorHandler,
@@ -11,8 +10,9 @@ use crate::{
11
10
  resolver::{ resolve_all_modules, resolve_and_flatten_all_modules },
12
11
  },
13
12
  store::global::GlobalStore,
13
+ utils::path::normalize_path,
14
14
  },
15
- utils::logger::{ Logger },
15
+ utils::logger::Logger,
16
16
  };
17
17
 
18
18
  pub struct ModuleLoader {
@@ -28,7 +28,67 @@ impl ModuleLoader {
28
28
  }
29
29
  }
30
30
 
31
- pub fn load_all(
31
+ pub fn from_raw_source(
32
+ entry_path: &str,
33
+ output_path: &str,
34
+ content: &str,
35
+ global_store: &mut GlobalStore
36
+ ) -> Self {
37
+ let normalized_entry_path = normalize_path(entry_path);
38
+
39
+ let mut module = Module::new(&entry_path);
40
+ module.content = content.to_string();
41
+
42
+ println!("Loading module from raw source: {}", normalized_entry_path);
43
+
44
+ global_store.insert_module(normalized_entry_path.to_string(), module);
45
+
46
+ Self {
47
+ entry: normalized_entry_path.to_string(),
48
+ output: output_path.to_string(),
49
+ }
50
+ }
51
+
52
+ pub fn extract_statements_map(
53
+ &self,
54
+ global_store: &GlobalStore
55
+ ) -> HashMap<String, Vec<Statement>> {
56
+ global_store.modules
57
+ .iter()
58
+ .map(|(path, module)| (path.clone(), module.statements.clone()))
59
+ .collect()
60
+ }
61
+
62
+ pub fn load_single_module(&self, global_store: &mut GlobalStore) -> Result<Module, String> {
63
+ let mut module = global_store.modules
64
+ .remove(&self.entry)
65
+ .ok_or_else(|| format!("Module not found in store for path: {}", self.entry))?;
66
+
67
+ // SECTION Lexing the module content
68
+ let lexer = Lexer::new();
69
+ let tokens = lexer
70
+ .lex_from_source(&module.content)
71
+ .map_err(|e| format!("Lexer failed: {}", e))?;
72
+
73
+ module.tokens = tokens.clone();
74
+
75
+ // SECTION Parsing tokens into statements
76
+ let mut parser = Parser::new();
77
+ parser.set_current_module(self.entry.clone());
78
+ let statements = parser.parse_tokens(tokens, global_store);
79
+ module.statements = statements;
80
+
81
+ // SECTION Error handling
82
+ let mut error_handler = ErrorHandler::new();
83
+ error_handler.detect_from_statements(&mut parser, &module.statements);
84
+
85
+ global_store.modules.insert(self.entry.clone(), module.clone());
86
+
87
+ Ok(module)
88
+ }
89
+
90
+ #[cfg(feature = "cli")]
91
+ pub fn load_all_modules(
32
92
  &self,
33
93
  global_store: &mut GlobalStore
34
94
  ) -> (HashMap<String, Vec<Token>>, HashMap<String, Vec<Statement>>) {
@@ -44,6 +104,7 @@ impl ModuleLoader {
44
104
  (tokens_by_module, statemnts_by_module)
45
105
  }
46
106
 
107
+ #[cfg(feature = "cli")]
47
108
  fn load_module_recursively(
48
109
  &self,
49
110
  path: &str,
@@ -95,6 +156,7 @@ impl ModuleLoader {
95
156
  tokens_by_module
96
157
  }
97
158
 
159
+ #[cfg(feature = "cli")]
98
160
  fn load_module_imports(&self, path: &String, global_store: &mut GlobalStore) {
99
161
  let imports = global_store.modules
100
162
  .get(path)
@@ -18,6 +18,7 @@ pub struct Module {
18
18
  pub variable_table: VariableTable,
19
19
  pub export_table: ExportTable,
20
20
  pub import_table: ImportTable,
21
+ pub content: String,
21
22
  }
22
23
 
23
24
  impl Module {
@@ -30,6 +31,7 @@ impl Module {
30
31
  export_table: ExportTable::new(),
31
32
  import_table: ImportTable::new(),
32
33
  resolved: false,
34
+ content: String::new(),
33
35
  }
34
36
  }
35
37
 
@@ -33,13 +33,18 @@ pub fn resolve_loop(
33
33
  Value::Null
34
34
  }
35
35
  None => {
36
- log_type_error(
37
- &logger,
38
- module,
39
- stmt,
40
- format!("Loop iterator '{ident}' not found")
41
- );
42
- Value::Null
36
+ // Value is not a variable so we assume it's a number
37
+ if let Ok(n) = ident.parse::<f32>() {
38
+ Value::Number(n)
39
+ } else {
40
+ log_type_error(
41
+ &logger,
42
+ module,
43
+ stmt,
44
+ format!("Loop iterator '{ident}' is not a valid number")
45
+ );
46
+ Value::Null
47
+ }
43
48
  }
44
49
  }
45
50
  }
@@ -63,9 +63,9 @@ pub fn resolve_and_flatten_all_modules(
63
63
  global_store: &mut GlobalStore
64
64
  ) -> HashMap<String, Vec<Statement>> {
65
65
  let logger = Logger::new();
66
- let snapshot = global_store.clone(); // pour Γ©viter les emprunts mutables
66
+ let snapshot = global_store.clone();
67
67
 
68
- // 1. RΓ©solution des imports
68
+ // 1. Imports resolution
69
69
  for (module_path, module) in global_store.modules.iter_mut() {
70
70
  for (name, source_path) in &module.import_table.imports {
71
71
  if let Value::String(source_path_str) = source_path {
@@ -96,8 +96,8 @@ pub fn resolve_and_flatten_all_modules(
96
96
  }
97
97
  }
98
98
 
99
- // 2. RΓ©solution des statements
100
- let mut resolved_map = HashMap::new();
99
+ // 2. Statements resolution
100
+ let mut resolved_map: HashMap<String, Vec<Statement>> = HashMap::new();
101
101
  let store_snapshot = global_store.clone();
102
102
 
103
103
  for (path, module) in &store_snapshot.modules {
@@ -135,7 +135,6 @@ pub fn resolve_and_flatten_all_modules(
135
135
  }
136
136
 
137
137
  StatementKind::Import { .. } | StatementKind::Export { .. } => {
138
- // Rien Γ  faire
139
138
  resolved.push(stmt.clone());
140
139
  }
141
140
 
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,3 +1,5 @@
1
+ #![cfg(feature = "cli")]
2
+
1
3
  pub mod core;
2
4
  pub mod cli;
3
5
  pub mod utils;
@@ -1,5 +1,6 @@
1
- use crossterm::style::{ Attribute, Color, ResetColor, SetAttribute, SetForegroundColor };
2
- use std::{ fmt::Write };
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
  }
@@ -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(
@@ -1,4 +1,5 @@
1
1
  [defaults]
2
2
  entry = "./src"
3
3
  output = "./output"
4
- watch = false
4
+ watch = false
5
+ repeat = false
@@ -0,0 +1,202 @@
1
+ <div align="center">
2
+ <img src="https://firebasestorage.googleapis.com/v0/b/devaloop-labs.firebasestorage.app/o/devalang-teal-logo.svg?alt=media&token=d2a5705a-1eba-4b49-88e6-895a761fb7f7" alt="Devalang Logo">
3
+ </div>
4
+
5
+ ![Rust](https://img.shields.io/badge/Made%20with-Rust-orange?logo=rust)
6
+ ![TypeScript](https://img.shields.io/badge/Built%20with-TypeScript-blue?logo=typescript)
7
+ ![Node.js](https://img.shields.io/badge/Node.js-18%2B-brightgreen?logo=node.js)
8
+
9
+ ![Project Status](https://img.shields.io/badge/status-alpha-red)
10
+ ![Version](https://img.shields.io/badge/version-0.0.1-blue)
11
+ ![License: MIT](https://img.shields.io/badge/license-MIT-green)
12
+ ![Platform](https://img.shields.io/badge/platform-Windows-blue)
13
+
14
+ ![npm](https://img.shields.io/npm/dt/@devaloop/devalang)
15
+ ![crates](https://img.shields.io/crates/d/devalang)
16
+
17
+ ## 🎼 Devalang, by **Devaloop Labs**
18
+
19
+ 🎢 Compose music with code β€” simple, structured, sonic.
20
+
21
+ Devalang is a tiny domain-specific language (DSL) for music makers, sound designers, and audio hackers.
22
+ Compose loops, control samples, render and play audio β€” all in clean, readable text.
23
+
24
+ 🦊 Whether you're building a track, shaping textures, or performing live, Devalang helps you think in rhythms. It’s designed to be simple, expressive, and fast β€” because your ideas shouldn’t wait.
25
+
26
+ From studio sketches to live sets, Devalang gives you rhythmic control β€” with the elegance of code.
27
+
28
+ > 🚧 **v0.0.1-alpha.5 Notice** 🚧
29
+ >
30
+ > Currently, Devalang CLI is only available for **Windows**.
31
+ > Linux and macOS binaries will be added in future releases via cross-platform builds.
32
+
33
+ ---
34
+
35
+ ## πŸ“š Quick Access
36
+
37
+ - [πŸ“– Documentation](./docs/)
38
+ - [πŸ’‘ Examples](./examples/)
39
+ - [🌐 Project Website](https://devalang.com)
40
+
41
+ ## πŸš€ Features
42
+
43
+ - 🎡 **Audio Engine**: Integrated audio playback and rendering
44
+ - 🧩 **Module system** for importing and exporting variables between files
45
+ - πŸ“œ **Structured AST** generation for debugging and future compilation
46
+ - πŸ”’ **Basic data types**: strings, numbers, booleans, maps, arrays
47
+ - πŸ‘οΈ **Watch mode** for `build`, `check` and `play` commands
48
+ - πŸ“‚ **Project templates** for quick setup
49
+
50
+ ## πŸ“† Installation
51
+
52
+ ### For users
53
+
54
+ > - ⚠️ Requires [Node.js 18+](https://nodejs.org/en/download)
55
+
56
+ Install the package globally (NPM)
57
+
58
+ ```bash
59
+ npm install -g @devaloop/devalang
60
+ ```
61
+
62
+ Usage without install (NPX)
63
+
64
+ ```bash
65
+ npx @devaloop/devalang <command>
66
+ ```
67
+
68
+ ### For contributors
69
+
70
+ > - ⚠️ Requires [Node.js 18+](https://nodejs.org/en/download)
71
+ > - ⚠️ Requires [Rust 1.70+](https://www.rust-lang.org/learn/get-started#installing-rust)
72
+
73
+ ```bash
74
+ > git clone https://github.com/devaloop-labs/devalang.git
75
+ > cd devalang
76
+ > npm install
77
+ > cargo install --path .
78
+ ```
79
+
80
+ Development usage (you can customize arguments in package.json)
81
+
82
+ ```bash
83
+ # For syntax checking test
84
+ npm run rust:dev:check
85
+ # For building test
86
+ npm run rust:dev:build
87
+ ```
88
+
89
+ ## ❔ Usage
90
+
91
+ NOTE: Commands are available via `devalang` or `npx @devaloop/devalang`.
92
+
93
+ NOTE: Arguments can be passed to commands using `--<argument>` syntax. You can also use a configuration file to set default values for various settings, making it easier to manage your Devalang project.
94
+
95
+ NOTE: Some commands require a mandatory `--entry` argument to specify the input folder, and a `--output` argument to specify the output folder. If not specified, they default to `./src` and `./output` respectively.
96
+
97
+ For more examples, see [docs/COMMANDS.md](./docs/COMMANDS.md)
98
+
99
+ ### Initialize a new project
100
+
101
+ In the current directory
102
+
103
+ ```bash
104
+ devalang init
105
+ ```
106
+
107
+ Or use optional arguments to specify a directory name and a template
108
+
109
+ ```bash
110
+ devalang init --name <project-name> --template <template-name>
111
+ ```
112
+
113
+ ### Checking syntax only
114
+
115
+ ```bash
116
+ devalang check --watch
117
+ ```
118
+
119
+ ### Building output files
120
+
121
+ ```bash
122
+ devalang build --watch
123
+ ```
124
+
125
+ ### Playing audio files (once by file change)
126
+
127
+ ```bash
128
+ devalang play --watch
129
+ ```
130
+
131
+ ### Playing audio files (continuous playback, even without file changes)
132
+
133
+ ```bash
134
+ devalang play --repeat
135
+ ```
136
+
137
+ ## βš™οΈ Configuration
138
+
139
+ You can use a configuration file to set default values for various settings, making it easier to manage your Devalang project.
140
+
141
+ To do this, create a `.devalang` file in the root of your project directory.
142
+
143
+ See [docs/CONFIG.md](./docs/CONFIG.md) for more information.
144
+
145
+ ## πŸ“„ Syntax example
146
+
147
+ For more examples, see [docs/SYNTAX.md](./docs/SYNTAX.md)
148
+
149
+ ```deva
150
+ # index.deva
151
+
152
+ @import { globalBpm, globalBank, kickDuration } from "global.deva"
153
+
154
+ @load "./examples/samples/kick-808.wav" as customKick
155
+
156
+ bpm globalBpm
157
+ # Will declare the tempo at the globalBpm variable beats per minute
158
+
159
+ bank globalBank
160
+ # Will declare a custom instrument bank using the globalBank variable
161
+
162
+ loop 5:
163
+ .customKick kickDuration {reverb=50, drive=25}
164
+ # Will play 5 times a kick for the duration of the kickDuration variable with reverb and drive effects
165
+ ```
166
+
167
+ ```deva
168
+ # global.deva
169
+
170
+ let globalBpm = 120
171
+ let globalBank = 808
172
+ let kickDuration = 500
173
+
174
+ @export { globalBpm, globalBank, kickDuration }
175
+ ```
176
+
177
+ ## 🧯 Known issues
178
+
179
+ - No support yet for `if`, `else`, `else if` statements
180
+ - No support yet for `@group`, `@pattern`, `@function` statements
181
+ - No support yet for cross-platform builds (Linux, macOS)
182
+
183
+ ## πŸ§ͺ Roadmap Highlights
184
+
185
+ For more info, see [docs/ROADMAP.md](./docs/ROADMAP.md)
186
+
187
+ - ⏳ Other statements (e.g `if`, `@group`, ...)
188
+ - ⏳ Cross-platform support (Linux, macOS)
189
+ - ⏳ More built-in instruments (e.g. snare, hi-hat, etc.)
190
+
191
+ ## πŸ›‘οΈ License
192
+
193
+ MIT β€” see [LICENSE](./LICENSE)
194
+
195
+ ## 🀝 Contributing
196
+
197
+ Contributions, bug reports and suggestions are welcome !
198
+ Feel free to open an issue or submit a pull request.
199
+
200
+ ## πŸ“’ Contact
201
+
202
+ πŸ“§ [contact@devaloop.com](mailto:contact@devaloop.com)
@@ -1,4 +1,5 @@
1
1
  [defaults]
2
2
  entry = "./src"
3
3
  output = "./output"
4
- watch = false
4
+ watch = false
5
+ repeat = false
@@ -11,44 +11,41 @@
11
11
  ![License: MIT](https://img.shields.io/badge/license-MIT-green)
12
12
  ![Platform](https://img.shields.io/badge/platform-Windows-blue)
13
13
 
14
- ![npm](https://img.shields.io/npm/dm/@devaloop/devalang)
14
+ ![npm](https://img.shields.io/npm/dt/@devaloop/devalang)
15
+ ![crates](https://img.shields.io/crates/d/devalang)
15
16
 
16
17
  ## 🎼 Devalang, by **Devaloop Labs**
17
18
 
18
19
  🎢 Compose music with code β€” simple, structured, sonic.
19
20
 
20
21
  Devalang is a tiny domain-specific language (DSL) for music makers, sound designers, and audio hackers.
21
- Compose loops, control samples, and automate parameters β€” all in clean, readable text.
22
+ Compose loops, control samples, render and play audio β€” all in clean, readable text.
22
23
 
23
24
  🦊 Whether you're building a track, shaping textures, or performing live, Devalang helps you think in rhythms. It’s designed to be simple, expressive, and fast β€” because your ideas shouldn’t wait.
24
25
 
25
26
  From studio sketches to live sets, Devalang gives you rhythmic control β€” with the elegance of code.
26
27
 
27
- > 🚧 **v0.0.1-alpha.1 Notice** 🚧
28
- >
29
- > Devalang is still in early development. This version does not yet include **sound rendering**.
30
- >
31
- > You can parse code, generate the AST, and validate syntax β€” all essential building blocks for the upcoming audio engine.
32
- >
33
- > Currently, only `.kick` is included as a built-in trigger.
34
- > Custom instruments can be defined with `@load`, allowing any sound sample to be triggered with the same syntax.
28
+ > 🚧 **v0.0.1-alpha.5 Notice** 🚧
35
29
  >
36
30
  > Currently, Devalang CLI is only available for **Windows**.
37
31
  > Linux and macOS binaries will be added in future releases via cross-platform builds.
38
32
 
33
+ ---
34
+
35
+ ## πŸ“š Quick Access
36
+
37
+ - [πŸ“– Documentation](./docs/)
38
+ - [πŸ’‘ Examples](./examples/)
39
+ - [🌐 Project Website](https://devalang.com)
40
+
39
41
  ## πŸš€ Features
40
42
 
41
- - 🧩 Module system for importing and exporting variables between files (`@import`, `@export`)
42
- - πŸ“œ Structured AST generation for debugging and future compilation
43
- - πŸ”’ Basic data types: strings, numbers, booleans, maps, arrays
44
- - πŸ‘οΈ Watch mode for `build` and `check` commands
45
- - ⏱️ `bpm` assignment for setting tempo
46
- - 🧱 `bank` declaration to define the instrument set
47
- - πŸ” Looping system with fixed repetitions (`loop 4:`)
48
- - πŸ§ͺ Instruction calls with parameters (e.g. `.kick auto {reverb:10, decay:20}`) for testing pattern syntax
49
- - πŸ“„ `let` assignments for storing reusable values
50
- - πŸ”„ `@load` assignment to load a sample (.mp3, .wav) to use it as a value
51
- - πŸ› οΈ CLI tools for syntax checking (`check`), AST output (`build`)
43
+ - 🎡 **Audio Engine**: Integrated audio playback and rendering
44
+ - 🧩 **Module system** for importing and exporting variables between files
45
+ - πŸ“œ **Structured AST** generation for debugging and future compilation
46
+ - πŸ”’ **Basic data types**: strings, numbers, booleans, maps, arrays
47
+ - πŸ‘οΈ **Watch mode** for `build`, `check` and `play` commands
48
+ - πŸ“‚ **Project templates** for quick setup
52
49
 
53
50
  ## πŸ“† Installation
54
51
 
@@ -80,15 +77,23 @@ npx @devaloop/devalang <command>
80
77
  > cargo install --path .
81
78
  ```
82
79
 
83
- Usage for development (feel free to change arguments in package.json)
80
+ Development usage (you can customize arguments in package.json)
84
81
 
85
82
  ```bash
86
83
  # For syntax checking test
87
- npm run rust:dev <command>
84
+ npm run rust:dev:check
85
+ # For building test
86
+ npm run rust:dev:build
88
87
  ```
89
88
 
90
89
  ## ❔ Usage
91
90
 
91
+ NOTE: Commands are available via `devalang` or `npx @devaloop/devalang`.
92
+
93
+ NOTE: Arguments can be passed to commands using `--<argument>` syntax. You can also use a configuration file to set default values for various settings, making it easier to manage your Devalang project.
94
+
95
+ NOTE: Some commands require a mandatory `--entry` argument to specify the input folder, and a `--output` argument to specify the output folder. If not specified, they default to `./src` and `./output` respectively.
96
+
92
97
  For more examples, see [docs/COMMANDS.md](./docs/COMMANDS.md)
93
98
 
94
99
  ### Initialize a new project
@@ -105,16 +110,28 @@ Or use optional arguments to specify a directory name and a template
105
110
  devalang init --name <project-name> --template <template-name>
106
111
  ```
107
112
 
108
- ### Checking syntax only and output debug files
113
+ ### Checking syntax only
109
114
 
110
115
  ```bash
111
- devalang check --entry <entry-directory> --output <output-directory> --watch
116
+ devalang check --watch
112
117
  ```
113
118
 
114
- ### Building output file(s) (AST generation for the moment)
119
+ ### Building output files
115
120
 
116
121
  ```bash
117
- devalang build --entry <entry-directory> --output <output-directory> --watch
122
+ devalang build --watch
123
+ ```
124
+
125
+ ### Playing audio files (once by file change)
126
+
127
+ ```bash
128
+ devalang play --watch
129
+ ```
130
+
131
+ ### Playing audio files (continuous playback, even without file changes)
132
+
133
+ ```bash
134
+ devalang play --repeat
118
135
  ```
119
136
 
120
137
  ## βš™οΈ Configuration
@@ -134,6 +151,8 @@ For more examples, see [docs/SYNTAX.md](./docs/SYNTAX.md)
134
151
 
135
152
  @import { globalBpm, globalBank, kickDuration } from "global.deva"
136
153
 
154
+ @load "./examples/samples/kick-808.wav" as customKick
155
+
137
156
  bpm globalBpm
138
157
  # Will declare the tempo at the globalBpm variable beats per minute
139
158
 
@@ -141,7 +160,7 @@ bank globalBank
141
160
  # Will declare a custom instrument bank using the globalBank variable
142
161
 
143
162
  loop 5:
144
- .kick kickDuration {reverb=50, drive=25}
163
+ .customKick kickDuration {reverb=50, drive=25}
145
164
  # Will play 5 times a kick for the duration of the kickDuration variable with reverb and drive effects
146
165
  ```
147
166
 
@@ -157,16 +176,14 @@ let kickDuration = 500
157
176
 
158
177
  ## 🧯 Known issues
159
178
 
160
- - No support yet for Audio Engine
161
179
  - No support yet for `if`, `else`, `else if` statements
162
180
  - No support yet for `@group`, `@pattern`, `@function` statements
163
- - Nested loops and conditions may not be fully tested
181
+ - No support yet for cross-platform builds (Linux, macOS)
164
182
 
165
183
  ## πŸ§ͺ Roadmap Highlights
166
184
 
167
185
  For more info, see [docs/ROADMAP.md](./docs/ROADMAP.md)
168
186
 
169
- - ⏳ Audio engine integration
170
187
  - ⏳ Other statements (e.g `if`, `@group`, ...)
171
188
  - ⏳ Cross-platform support (Linux, macOS)
172
189
  - ⏳ More built-in instruments (e.g. snare, hi-hat, etc.)