@devaloop/devalang 0.0.1-alpha.4 → 0.0.1-alpha.7

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 (54) hide show
  1. package/Cargo.toml +7 -5
  2. package/README.md +26 -7
  3. package/docs/CHANGELOG.md +34 -2
  4. package/docs/ROADMAP.md +3 -3
  5. package/docs/SYNTAX.md +42 -14
  6. package/docs/TODO.md +9 -8
  7. package/examples/group.deva +12 -0
  8. package/examples/index.deva +10 -9
  9. package/examples/loop.deva +15 -0
  10. package/examples/variables.deva +9 -0
  11. package/out-tsc/bin/devalang.exe +0 -0
  12. package/package.json +43 -41
  13. package/project-version.json +5 -5
  14. package/rust/cli/build.rs +4 -4
  15. package/rust/cli/check.rs +2 -1
  16. package/rust/cli/init.rs +4 -2
  17. package/rust/cli/play.rs +5 -3
  18. package/rust/cli/template.rs +2 -1
  19. package/rust/config/loader.rs +0 -1
  20. package/rust/{audio → core/audio}/engine.rs +1 -5
  21. package/rust/core/audio/interpreter.rs +317 -0
  22. package/rust/{audio → core/audio}/loader.rs +4 -0
  23. package/rust/{audio → core/audio}/render.rs +1 -5
  24. package/rust/core/builder/mod.rs +9 -8
  25. package/rust/core/lexer/handler/indent.rs +1 -1
  26. package/rust/core/lexer/handler/mod.rs +3 -4
  27. package/rust/core/lexer/handler/newline.rs +5 -1
  28. package/rust/core/lexer/handler/string.rs +3 -6
  29. package/rust/core/lexer/mod.rs +10 -5
  30. package/rust/core/mod.rs +2 -1
  31. package/rust/core/parser/handler/identifier.rs +127 -1
  32. package/rust/core/parser/handler/loop_.rs +11 -0
  33. package/rust/core/parser/mod.rs +0 -1
  34. package/rust/core/parser/statement.rs +9 -5
  35. package/rust/core/preprocessor/loader.rs +65 -3
  36. package/rust/core/preprocessor/module.rs +2 -0
  37. package/rust/core/preprocessor/processor.rs +28 -2
  38. package/rust/core/preprocessor/resolver/bank.rs +10 -9
  39. package/rust/core/preprocessor/resolver/group.rs +113 -0
  40. package/rust/core/preprocessor/resolver/loop_.rs +15 -13
  41. package/rust/core/preprocessor/resolver/mod.rs +11 -5
  42. package/rust/core/preprocessor/resolver/trigger.rs +0 -3
  43. package/rust/lib.rs +117 -0
  44. package/rust/main.rs +2 -1
  45. package/rust/utils/logger.rs +45 -6
  46. package/rust/utils/spinner.rs +2 -0
  47. package/templates/minimal/.devalang +2 -1
  48. package/templates/minimal/README.md +202 -0
  49. package/templates/welcome/.devalang +2 -1
  50. package/templates/welcome/README.md +48 -31
  51. package/examples/exported.deva +0 -7
  52. package/rust/audio/interpreter.rs +0 -143
  53. /package/rust/{audio → core/audio}/mod.rs +0 -0
  54. /package/rust/{audio → core/audio}/player.rs +0 -0
@@ -51,12 +51,16 @@ pub enum StatementKind {
51
51
  // Loop statements
52
52
  Loop,
53
53
 
54
+ // Group statements
55
+ Group,
56
+
57
+ // Special statements
58
+ Call,
59
+ Spawn,
60
+ Sleep,
61
+
54
62
  // Conditional statements
55
- // If {
56
- // // condition: ConditionParts,
57
- // condition_state: bool,
58
- // body: Vec<Statement>,
59
- // },
63
+ // If
60
64
 
61
65
  // Keyword statements
62
66
  Tempo,
@@ -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
 
@@ -1,6 +1,8 @@
1
+ use std::collections::HashMap;
2
+
1
3
  use crate::core::{
2
4
  parser::{ statement::StatementKind, Parser },
3
- preprocessor::loader::ModuleLoader,
5
+ preprocessor::{ loader::ModuleLoader, resolver::group },
4
6
  shared::value::Value,
5
7
  store::global::GlobalStore,
6
8
  };
@@ -33,7 +35,31 @@ pub fn process_modules(module_loader: &ModuleLoader, global_store: &mut GlobalSt
33
35
  module.import_table.add_import(name.clone(), Value::String(source.clone()));
34
36
  }
35
37
  }
36
-
38
+
39
+ StatementKind::Group => {
40
+ if let Value::Map(map) = &stmt.value {
41
+ if
42
+ let (Some(Value::String(name)), Some(Value::Block(body))) = (
43
+ map.get("identifier"),
44
+ map.get("body"),
45
+ )
46
+ {
47
+ let mut stored_map = HashMap::new();
48
+
49
+ stored_map.insert(
50
+ "identifier".to_string(),
51
+ Value::String(name.clone())
52
+ );
53
+
54
+ stored_map.insert("body".to_string(), Value::Block(body.clone()));
55
+
56
+ module.variable_table.set(name.to_string(), Value::Map(stored_map));
57
+ } else {
58
+ eprintln!("❌ Invalid group definition: {:?}", stmt.value);
59
+ }
60
+ }
61
+ }
62
+
37
63
  _ => {}
38
64
  }
39
65
  }
@@ -1,9 +1,12 @@
1
- use crate::{core::{
2
- parser::statement::{Statement, StatementKind},
3
- preprocessor::module::Module,
4
- shared::value::Value,
5
- store::global::GlobalStore,
6
- }, utils::logger::Logger};
1
+ use crate::{
2
+ core::{
3
+ parser::statement::{ Statement, StatementKind },
4
+ preprocessor::module::Module,
5
+ shared::value::Value,
6
+ store::global::GlobalStore,
7
+ },
8
+ utils::logger::Logger,
9
+ };
7
10
 
8
11
  pub fn resolve_bank(
9
12
  stmt: &Statement,
@@ -28,9 +31,7 @@ pub fn resolve_bank(
28
31
  }
29
32
  }
30
33
 
31
- Value::String(_) => {
32
- // Pas de résolution nécessaire
33
- }
34
+ Value::String(_) => {}
34
35
 
35
36
  other => {
36
37
  let message = format!("Expected a string or identifier for bank, found {:?}", other);
@@ -0,0 +1,113 @@
1
+ use std::collections::HashMap;
2
+
3
+ use crate::{
4
+ core::{
5
+ parser::statement::{ Statement, StatementKind },
6
+ preprocessor::{ module::Module, resolver::trigger::resolve_trigger },
7
+ shared::value::Value,
8
+ store::global::GlobalStore,
9
+ },
10
+ utils::logger::Logger,
11
+ };
12
+
13
+ pub fn resolve_group(
14
+ stmt: &Statement,
15
+ module: &Module,
16
+ path: &str,
17
+ global_store: &GlobalStore
18
+ ) -> Statement {
19
+ let logger = Logger::new();
20
+
21
+ if let Value::Map(value_map) = &stmt.value {
22
+ let group_name = match value_map.get("identifier") {
23
+ Some(Value::String(name)) => name.clone(),
24
+ Some(other) => {
25
+ log_type_error(
26
+ &logger,
27
+ module,
28
+ stmt,
29
+ format!("Group name must be a string, found {:?}", other)
30
+ );
31
+ return stmt.clone();
32
+ }
33
+ None => {
34
+ log_type_error(&logger, module, stmt, "Group name is required".to_string());
35
+ return stmt.clone();
36
+ }
37
+ };
38
+
39
+ let body_value = match value_map.get("body") {
40
+ Some(Value::Block(block)) => {
41
+ let mut resolved_block = Vec::new();
42
+ for ref statement in block.clone() {
43
+ match &statement.kind {
44
+ StatementKind::Trigger { entity, duration } => {
45
+ let resolved = resolve_trigger(
46
+ &mut statement.clone(),
47
+ &entity,
48
+ &mut duration.clone(),
49
+ module,
50
+ path,
51
+ global_store
52
+ );
53
+ resolved_block.push(resolved);
54
+ }
55
+ _ => {
56
+ println!("Unhandled group body statement: {:?}", statement);
57
+ }
58
+ }
59
+ }
60
+ Value::Block(resolved_block)
61
+ }
62
+ Some(other) => {
63
+ log_type_error(
64
+ &logger,
65
+ module,
66
+ stmt,
67
+ format!("Unexpected value for group body: {:?}", other)
68
+ );
69
+ Value::Null
70
+ }
71
+ None => {
72
+ log_type_error(
73
+ &logger,
74
+ module,
75
+ stmt,
76
+ "Missing 'body' key in group statement map".to_string()
77
+ );
78
+ Value::Null
79
+ }
80
+ };
81
+
82
+ let mut resolved_map = HashMap::new();
83
+
84
+ resolved_map.insert("identifier".to_string(), Value::String(group_name));
85
+ resolved_map.insert("body".to_string(), body_value);
86
+
87
+ return Statement {
88
+ kind: StatementKind::Group,
89
+ value: Value::Map(resolved_map),
90
+ ..stmt.clone()
91
+ };
92
+ } else {
93
+ log_type_error(
94
+ &logger,
95
+ module,
96
+ stmt,
97
+ format!("Expected Map for group statement, found {:?}", stmt.value)
98
+ );
99
+
100
+ Statement {
101
+ kind: StatementKind::Error {
102
+ message: "Expected a map for group statement".to_string(),
103
+ },
104
+ value: Value::Null,
105
+ ..stmt.clone()
106
+ }
107
+ }
108
+ }
109
+
110
+ fn log_type_error(logger: &Logger, module: &Module, stmt: &Statement, message: String) {
111
+ let stacktrace = format!("{}:{}:{}", module.path, stmt.line, stmt.column);
112
+ logger.log_error_with_stacktrace(&message, &stacktrace);
113
+ }
@@ -1,3 +1,5 @@
1
+ use std::collections::HashMap;
2
+
1
3
  use crate::{
2
4
  core::{
3
5
  parser::statement::{ Statement, StatementKind },
@@ -16,9 +18,7 @@ pub fn resolve_loop(
16
18
  ) -> Statement {
17
19
  let logger = Logger::new();
18
20
 
19
- // Vérifie que stmt.value est bien une Map
20
21
  if let Value::Map(value_map) = &stmt.value {
21
- // Résolution de l'iterator
22
22
  let iterator_value = match value_map.get("iterator") {
23
23
  Some(Value::Identifier(ident)) => {
24
24
  match module.variable_table.get(ident) {
@@ -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
  }
@@ -64,7 +69,6 @@ pub fn resolve_loop(
64
69
  }
65
70
  };
66
71
 
67
- // Résolution du body
68
72
  let body_value = match value_map.get("body") {
69
73
  Some(Value::Block(block)) => {
70
74
  let mut resolved_block = Vec::new();
@@ -108,12 +112,10 @@ pub fn resolve_loop(
108
112
  }
109
113
  };
110
114
 
111
- // Reconstruit proprement la valeur résolue
112
- let mut resolved_map = std::collections::HashMap::new();
115
+ let mut resolved_map = HashMap::new();
113
116
  resolved_map.insert("iterator".to_string(), iterator_value);
114
117
  resolved_map.insert("body".to_string(), body_value);
115
118
 
116
- // ✅ Reconstruit le StatementLoop à partir des éléments résolus
117
119
  Statement {
118
120
  kind: StatementKind::Loop,
119
121
  value: Value::Map(resolved_map),
@@ -2,6 +2,7 @@ pub mod trigger;
2
2
  pub mod loop_;
3
3
  pub mod bank;
4
4
  pub mod tempo;
5
+ pub mod group;
5
6
 
6
7
  use std::collections::HashMap;
7
8
  use crate::{
@@ -11,6 +12,7 @@ use crate::{
11
12
  loader::ModuleLoader,
12
13
  resolver::{
13
14
  bank::resolve_bank,
15
+ group::resolve_group,
14
16
  loop_::resolve_loop,
15
17
  tempo::resolve_tempo,
16
18
  trigger::resolve_trigger,
@@ -63,9 +65,9 @@ pub fn resolve_and_flatten_all_modules(
63
65
  global_store: &mut GlobalStore
64
66
  ) -> HashMap<String, Vec<Statement>> {
65
67
  let logger = Logger::new();
66
- let snapshot = global_store.clone(); // pour éviter les emprunts mutables
68
+ let snapshot = global_store.clone();
67
69
 
68
- // 1. Résolution des imports
70
+ // 1. Imports resolution
69
71
  for (module_path, module) in global_store.modules.iter_mut() {
70
72
  for (name, source_path) in &module.import_table.imports {
71
73
  if let Value::String(source_path_str) = source_path {
@@ -96,8 +98,8 @@ pub fn resolve_and_flatten_all_modules(
96
98
  }
97
99
  }
98
100
 
99
- // 2. Résolution des statements
100
- let mut resolved_map = HashMap::new();
101
+ // 2. Statements resolution
102
+ let mut resolved_map: HashMap<String, Vec<Statement>> = HashMap::new();
101
103
  let store_snapshot = global_store.clone();
102
104
 
103
105
  for (path, module) in &store_snapshot.modules {
@@ -135,10 +137,14 @@ pub fn resolve_and_flatten_all_modules(
135
137
  }
136
138
 
137
139
  StatementKind::Import { .. } | StatementKind::Export { .. } => {
138
- // Rien à faire
139
140
  resolved.push(stmt.clone());
140
141
  }
141
142
 
143
+ StatementKind::Group => {
144
+ let resolved_stmt = resolve_group(&stmt, &module, &path, &store_snapshot);
145
+ resolved.push(resolved_stmt);
146
+ }
147
+
142
148
  _ => {
143
149
  resolved.push(stmt);
144
150
  }
@@ -38,7 +38,6 @@ pub fn resolve_trigger(
38
38
  };
39
39
  }
40
40
 
41
- // ✅ Résolution de duration si c'est un identifiant
42
41
  if let Duration::Identifier(ident) = duration {
43
42
  if let Some(val) = module.variable_table.get(ident) {
44
43
  match val {
@@ -56,7 +55,6 @@ pub fn resolve_trigger(
56
55
  }
57
56
  }
58
57
 
59
- // ✅ Résolution de value (params, effets)
60
58
  final_value = match &stmt.value {
61
59
  Value::Identifier(ident) => {
62
60
  match module.variable_table.get(ident) {
@@ -87,7 +85,6 @@ pub fn resolve_trigger(
87
85
  other => other.clone(),
88
86
  };
89
87
 
90
- // ✅ On reconstruit le Statement avec Trigger résolu
91
88
  if let StatementKind::Trigger { entity, .. } = &stmt.kind {
92
89
  return Statement {
93
90
  kind: StatementKind::Trigger {
package/rust/lib.rs CHANGED
@@ -0,0 +1,117 @@
1
+ pub mod core;
2
+ pub mod utils;
3
+ pub mod config;
4
+
5
+ use serde::{ Deserialize, Serialize };
6
+ use wasm_bindgen::prelude::*;
7
+ use serde_wasm_bindgen::to_value;
8
+
9
+ use crate::core::{
10
+ parser::statement::{ Statement, StatementKind },
11
+ preprocessor::loader::ModuleLoader,
12
+ shared::value::Value,
13
+ store::global::GlobalStore,
14
+ utils::path::normalize_path,
15
+ };
16
+
17
+ #[derive(Serialize, Deserialize)]
18
+ struct ParseResult {
19
+ ok: bool,
20
+ ast: String,
21
+ errors: Vec<ErrorResult>,
22
+ }
23
+
24
+ #[derive(Serialize, Deserialize)]
25
+ struct ErrorResult {
26
+ message: String,
27
+ line: usize,
28
+ column: usize,
29
+ }
30
+
31
+ #[wasm_bindgen]
32
+ pub fn parse(entry_path: &str, source: &str) -> Result<JsValue, JsValue> {
33
+ let statements = parse_internal_from_string(entry_path, source);
34
+
35
+ match statements {
36
+ Ok(value) => {
37
+ let ast_string = value;
38
+ to_value(&ast_string).map_err(|e|
39
+ JsValue::from_str(&format!("Error converting AST to JS value: {}", e))
40
+ )
41
+ }
42
+ Err(e) => { Err(JsValue::from_str(&format!("Error: {}", e))) }
43
+ }
44
+ }
45
+
46
+ fn parse_internal_from_string(virtual_path: &str, source: &str) -> Result<ParseResult, String> {
47
+ let entry_path = normalize_path(virtual_path);
48
+ let output_path = normalize_path("./temp");
49
+
50
+ let mut global_store = GlobalStore::new();
51
+ let loader = ModuleLoader::from_raw_source(
52
+ &entry_path,
53
+ &output_path,
54
+ source,
55
+ &mut global_store
56
+ );
57
+
58
+ let module = loader
59
+ .load_single_module(&mut global_store)
60
+ .map_err(|e| format!("Error loading module: {}", e))?;
61
+
62
+ let raw_ast = ast_to_string(module.statements.clone());
63
+
64
+ let found_errors = collect_errors_recursively(&module.statements);
65
+
66
+ let result = ParseResult {
67
+ ok: true,
68
+ ast: raw_ast,
69
+ errors: found_errors,
70
+ };
71
+
72
+ Ok(result)
73
+ }
74
+
75
+ fn collect_errors_recursively(statements: &[Statement]) -> Vec<ErrorResult> {
76
+ let mut errors: Vec<ErrorResult> = Vec::new();
77
+
78
+ for stmt in statements {
79
+ match &stmt.kind {
80
+ StatementKind::Unknown => {
81
+ errors.push(ErrorResult {
82
+ message: format!("Unknown statement at line {}:{}", stmt.line, stmt.column),
83
+ line: stmt.line,
84
+ column: stmt.column,
85
+ });
86
+ }
87
+ StatementKind::Error { message } => {
88
+ errors.push(ErrorResult {
89
+ message: message.clone(),
90
+ line: stmt.line,
91
+ column: stmt.column,
92
+ });
93
+ }
94
+ StatementKind::Loop => {
95
+ if let Some(body_statements) = extract_loop_body_statements(&stmt.value) {
96
+ errors.extend(collect_errors_recursively(body_statements));
97
+ }
98
+ }
99
+ _ => {}
100
+ }
101
+ }
102
+
103
+ errors
104
+ }
105
+
106
+ fn extract_loop_body_statements(value: &Value) -> Option<&[Statement]> {
107
+ if let Value::Map(map) = value {
108
+ if let Some(Value::Block(statements)) = map.get("body") {
109
+ return Some(statements);
110
+ }
111
+ }
112
+ None
113
+ }
114
+
115
+ fn ast_to_string(statements: Vec<Statement>) -> String {
116
+ serde_json::to_string_pretty(&statements).expect("Failed to serialize AST")
117
+ }
package/rust/main.rs CHANGED
@@ -1,8 +1,9 @@
1
+ #![cfg(feature = "cli")]
2
+
1
3
  pub mod core;
2
4
  pub mod cli;
3
5
  pub mod utils;
4
6
  pub mod config;
5
- pub mod audio;
6
7
 
7
8
  use std::io;
8
9
  use cli::{ Cli };
@@ -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
  }