@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 +7 -5
- package/README.md +9 -2
- package/docs/CHANGELOG.md +15 -2
- package/docs/ROADMAP.md +3 -3
- package/examples/index.deva +5 -4
- package/out-tsc/bin/devalang.exe +0 -0
- package/package.json +4 -2
- package/project-version.json +3 -3
- package/rust/audio/engine.rs +1 -5
- package/rust/cli/build.rs +4 -3
- package/rust/cli/check.rs +2 -1
- package/rust/cli/init.rs +4 -2
- package/rust/cli/play.rs +3 -2
- package/rust/cli/template.rs +2 -1
- package/rust/config/loader.rs +0 -1
- package/rust/core/builder/mod.rs +9 -9
- package/rust/core/lexer/handler/mod.rs +2 -2
- package/rust/core/lexer/handler/newline.rs +5 -1
- package/rust/core/lexer/mod.rs +10 -5
- package/rust/core/parser/handler/loop_.rs +11 -0
- package/rust/core/parser/mod.rs +0 -1
- package/rust/core/preprocessor/loader.rs +65 -3
- package/rust/core/preprocessor/module.rs +2 -0
- package/rust/core/preprocessor/resolver/loop_.rs +12 -7
- package/rust/core/preprocessor/resolver/mod.rs +4 -5
- package/rust/lib.rs +118 -0
- package/rust/main.rs +2 -0
- package/rust/utils/logger.rs +45 -6
- package/rust/utils/spinner.rs +2 -0
- package/templates/minimal/.devalang +2 -1
- package/templates/minimal/README.md +202 -0
- package/templates/welcome/.devalang +2 -1
- package/templates/welcome/README.md +48 -31
package/Cargo.toml
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "devalang"
|
|
3
|
-
version = "0.0.1-alpha.
|
|
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", "
|
|
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
|

|
|
13
13
|
|
|
14
14
|

|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
[](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.
|
|
31
|
+
> π§ **v0.0.1-alpha.5 Notice** π§
|
|
28
32
|
>
|
|
29
|
-
>
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
package/examples/index.deva
CHANGED
|
@@ -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
|
|
4
|
-
@load "./examples/samples/hat-808.wav" as
|
|
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
|
-
.
|
|
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
|
-
# .
|
|
16
|
+
# .hatCustom duration params
|
|
17
|
+
|
package/out-tsc/bin/devalang.exe
CHANGED
|
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
|
+
"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
|
+
}
|
package/project-version.json
CHANGED
package/rust/audio/engine.rs
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
package/rust/cli/template.rs
CHANGED
|
@@ -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);
|
package/rust/config/loader.rs
CHANGED
package/rust/core/builder/mod.rs
CHANGED
|
@@ -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",
|
|
20
|
+
create_dir_all(format!("{}/ast", out_dir)).expect("Failed to create AST directory");
|
|
23
21
|
|
|
24
|
-
let file_path = format!("{}/ast/{}.json",
|
|
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(
|
|
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
|
}
|
package/rust/core/lexer/mod.rs
CHANGED
|
@@ -2,7 +2,10 @@ pub mod handler;
|
|
|
2
2
|
pub mod token;
|
|
3
3
|
|
|
4
4
|
use std::fs;
|
|
5
|
-
use crate::core::{
|
|
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));
|
package/rust/core/parser/mod.rs
CHANGED
|
@@ -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::
|
|
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
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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();
|
|
66
|
+
let snapshot = global_store.clone();
|
|
67
67
|
|
|
68
|
-
// 1.
|
|
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.
|
|
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
package/rust/utils/logger.rs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
use
|
|
1
|
+
#[cfg(feature = "cli")]
|
|
2
|
+
use crossterm::style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor};
|
|
3
|
+
use std::fmt::Write;
|
|
3
4
|
|
|
4
5
|
#[derive(Debug, Clone, PartialEq)]
|
|
5
6
|
pub enum LogLevel {
|
|
@@ -12,24 +13,43 @@ pub enum LogLevel {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
#[derive(Debug, Clone)]
|
|
15
|
-
pub struct Logger
|
|
16
|
+
pub struct Logger;
|
|
16
17
|
|
|
17
18
|
impl Logger {
|
|
18
19
|
pub fn new() -> Self {
|
|
19
|
-
Logger
|
|
20
|
+
Logger
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
// --- log_message ---
|
|
24
|
+
|
|
25
|
+
#[cfg(feature = "cli")]
|
|
22
26
|
pub fn log_message(&self, level: LogLevel, message: &str) {
|
|
23
27
|
let formatted_status = self.format_status(level);
|
|
24
28
|
println!("π¦ {} {} {}", self.language_signature(), formatted_status, message);
|
|
25
29
|
}
|
|
26
30
|
|
|
31
|
+
#[cfg(not(feature = "cli"))]
|
|
32
|
+
pub fn log_message(&self, _level: LogLevel, _message: &str) {
|
|
33
|
+
// no-op for WASM
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// --- log_error_with_stacktrace ---
|
|
37
|
+
|
|
38
|
+
#[cfg(feature = "cli")]
|
|
27
39
|
pub fn log_error_with_stacktrace(&self, message: &str, stacktrace: &str) {
|
|
28
40
|
let formatted_status = self.format_status(LogLevel::Error);
|
|
29
41
|
println!("π¦ {} {} {}", self.language_signature(), formatted_status, message);
|
|
30
42
|
println!(" β³ {}", stacktrace);
|
|
31
43
|
}
|
|
32
44
|
|
|
45
|
+
#[cfg(not(feature = "cli"))]
|
|
46
|
+
pub fn log_error_with_stacktrace(&self, _message: &str, _stacktrace: &str) {
|
|
47
|
+
// no-op for WASM
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// --- language_signature ---
|
|
51
|
+
|
|
52
|
+
#[cfg(feature = "cli")]
|
|
33
53
|
fn language_signature(&self) -> String {
|
|
34
54
|
let mut s = String::new();
|
|
35
55
|
|
|
@@ -43,12 +63,19 @@ impl Logger {
|
|
|
43
63
|
|
|
44
64
|
write!(&mut s, "{}", SetForegroundColor(Color::Grey)).unwrap();
|
|
45
65
|
s.push(']');
|
|
46
|
-
|
|
47
66
|
write!(&mut s, "{}", ResetColor).unwrap();
|
|
48
67
|
|
|
49
68
|
s
|
|
50
69
|
}
|
|
51
70
|
|
|
71
|
+
#[cfg(not(feature = "cli"))]
|
|
72
|
+
fn language_signature(&self) -> String {
|
|
73
|
+
"[Devalang]".to_string()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// --- format_status ---
|
|
77
|
+
|
|
78
|
+
#[cfg(feature = "cli")]
|
|
52
79
|
fn format_status(&self, level: LogLevel) -> String {
|
|
53
80
|
let mut s = String::new();
|
|
54
81
|
|
|
@@ -76,9 +103,21 @@ impl Logger {
|
|
|
76
103
|
s.push_str(status);
|
|
77
104
|
write!(&mut s, "{}", SetAttribute(Attribute::Reset)).unwrap();
|
|
78
105
|
s.push(']');
|
|
79
|
-
|
|
80
106
|
write!(&mut s, "{}", ResetColor).unwrap();
|
|
81
107
|
|
|
82
108
|
s
|
|
83
109
|
}
|
|
110
|
+
|
|
111
|
+
#[cfg(not(feature = "cli"))]
|
|
112
|
+
fn format_status(&self, level: LogLevel) -> String {
|
|
113
|
+
match level {
|
|
114
|
+
LogLevel::Success => "[SUCCESS]",
|
|
115
|
+
LogLevel::Error => "[ERROR]",
|
|
116
|
+
LogLevel::Info => "[INFO]",
|
|
117
|
+
LogLevel::Warning => "[WARNING]",
|
|
118
|
+
LogLevel::Watcher => "[WATCHER]",
|
|
119
|
+
LogLevel::Debug => "[DEBUG]",
|
|
120
|
+
}
|
|
121
|
+
.to_string()
|
|
122
|
+
}
|
|
84
123
|
}
|
package/rust/utils/spinner.rs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
#[cfg(feature = "cli")]
|
|
1
2
|
use indicatif::{ ProgressBar, ProgressStyle };
|
|
2
3
|
use std::{ time::Duration };
|
|
3
4
|
|
|
5
|
+
#[cfg(feature = "cli")]
|
|
4
6
|
pub fn with_spinner<T, F>(start_msg: &str, f: F) -> T where F: FnOnce() -> T {
|
|
5
7
|
let spinner = ProgressBar::new_spinner();
|
|
6
8
|
spinner.set_style(
|
|
@@ -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
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+

|
|
10
|
+

|
|
11
|
+

|
|
12
|
+

|
|
13
|
+
|
|
14
|
+

|
|
15
|
+

|
|
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)
|
|
@@ -11,44 +11,41 @@
|
|
|
11
11
|

|
|
12
12
|

|
|
13
13
|
|
|
14
|
-

|
|
15
|
+

|
|
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
|
|
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.
|
|
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
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
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
|
-
|
|
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
|
|
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
|
|
113
|
+
### Checking syntax only
|
|
109
114
|
|
|
110
115
|
```bash
|
|
111
|
-
devalang check --
|
|
116
|
+
devalang check --watch
|
|
112
117
|
```
|
|
113
118
|
|
|
114
|
-
### Building output
|
|
119
|
+
### Building output files
|
|
115
120
|
|
|
116
121
|
```bash
|
|
117
|
-
devalang build --
|
|
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
|
-
.
|
|
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
|
-
-
|
|
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.)
|