devalang_wasm/language/syntax/parser/driver/
directive.rs1use crate::language::syntax::ast::{Statement, StatementKind, Value};
2use anyhow::{Result, anyhow};
3use std::path::Path;
4
5pub fn parse_directive_keyword(
7 line: &str,
8 keyword: &str,
9 line_number: usize,
10 file_path: &std::path::Path,
11) -> Result<Statement> {
12 match keyword {
13 "use" => parse_use_directive(line, line_number),
14 "load" => parse_load_directive(line, line_number, file_path),
15 "import" => parse_import_directive(line, line_number, file_path),
16 "export" => parse_export_directive(line, line_number),
17 _ => Err(anyhow!("Unknown directive: {}", keyword)),
18 }
19}
20
21fn parse_import_directive(
22 line: &str,
23 line_number: usize,
24 file_path: &std::path::Path,
25) -> Result<Statement> {
26 let rest = line["import".len()..].trim();
28 if let Some(open) = rest.find('{') {
29 if let Some(close) = rest.find('}') {
30 let names = rest[open + 1..close]
31 .split(',')
32 .map(|s| s.trim().to_string())
33 .filter(|s| !s.is_empty())
34 .collect::<Vec<_>>();
35 let after = rest[close + 1..].trim();
36 let from_prefix = "from";
37 if after.starts_with(from_prefix) {
38 let path_part = after[from_prefix.len()..].trim();
39 let raw = path_part.trim().trim_matches('"');
40 let base = file_path
42 .parent()
43 .unwrap_or_else(|| std::path::Path::new("."));
44 let joined = base.join(raw);
45 let path = joined.to_string_lossy().to_string();
46 return Ok(Statement::new(
47 StatementKind::Import {
48 names,
49 source: path,
50 },
51 Value::Null,
52 0,
53 line_number,
54 1,
55 ));
56 }
57 }
58 }
59 Err(anyhow!(
60 "Invalid import syntax. Use: import {{ a, b }} from \"path\""
61 ))
62}
63
64fn parse_export_directive(line: &str, line_number: usize) -> Result<Statement> {
65 let rest = line["export".len()..].trim();
67 if let Some(open) = rest.find('{') {
68 if let Some(close) = rest.find('}') {
69 let names = rest[open + 1..close]
70 .split(',')
71 .map(|s| s.trim().to_string())
72 .filter(|s| !s.is_empty())
73 .collect::<Vec<_>>();
74 return Ok(Statement::new(
75 StatementKind::Export {
76 names,
77 source: String::new(),
78 },
79 Value::Null,
80 0,
81 line_number,
82 1,
83 ));
84 }
85 }
86 Err(anyhow!("Invalid export syntax. Use: export {{ a, b }}"))
87}
88
89fn parse_load_directive(
90 line: &str,
91 line_number: usize,
92 file_path: &std::path::Path,
93) -> Result<Statement> {
94 let mut rest = line["load".len()..].trim();
95 if rest.is_empty() {
96 return Err(anyhow!("load directive requires a path"));
97 }
98
99 let path;
100 if rest.starts_with('"') {
101 if let Some(end) = rest[1..].find('"') {
102 let raw = rest[1..1 + end].to_string();
103 let base = file_path
105 .parent()
106 .unwrap_or_else(|| std::path::Path::new("."));
107 let joined = base.join(&raw);
108 path = joined.to_string_lossy().to_string();
109 rest = rest[1 + end + 1..].trim();
110 } else {
111 return Err(anyhow!("unterminated string literal in load"));
112 }
113 } else {
114 let mut split = rest.splitn(2, char::is_whitespace);
115 path = split.next().unwrap_or_default().trim().to_string();
116 rest = split.next().unwrap_or("").trim();
117 }
118
119 let alias = if rest.starts_with("as ") {
120 rest[3..].trim().to_string()
121 } else {
122 Path::new(&path)
123 .file_stem()
124 .map(|s| s.to_string_lossy().to_string())
125 .unwrap_or_else(|| path.clone())
126 };
127
128 Ok(Statement::new(
129 StatementKind::Load {
130 source: path,
131 alias,
132 },
133 Value::Null,
134 0,
135 line_number,
136 1,
137 ))
138}
139
140fn parse_use_directive(line: &str, line_number: usize) -> Result<Statement> {
143 let rest = line["use".len()..].trim();
144 if rest.is_empty() {
145 return Err(anyhow!(
146 "use directive requires a plugin reference (author.plugin)"
147 ));
148 }
149
150 let plugin_ref;
152 let mut parts = rest.splitn(2, " as ");
153 plugin_ref = parts.next().unwrap_or_default().trim().to_string();
154
155 if !plugin_ref.contains('.') {
156 return Err(anyhow!("use directive requires format: author.plugin"));
157 }
158
159 let plugin_parts: Vec<&str> = plugin_ref.split('.').collect();
160 if plugin_parts.len() != 2 {
161 return Err(anyhow!(
162 "use directive requires format: author.plugin (got: {})",
163 plugin_ref
164 ));
165 }
166
167 let author = plugin_parts[0].to_string();
168 let plugin_name = plugin_parts[1].to_string();
169
170 let alias = if let Some(alias_part) = parts.next() {
172 alias_part.trim().to_string()
173 } else {
174 plugin_name.clone()
175 };
176
177 Ok(Statement::new(
178 StatementKind::UsePlugin {
179 author,
180 name: plugin_name,
181 alias,
182 },
183 Value::Null,
184 0,
185 line_number,
186 1,
187 ))
188}
189
190pub fn parse_directive(
192 line: &str,
193 line_number: usize,
194 file_path: &std::path::Path,
195) -> Result<Statement> {
196 eprintln!(
197 "⚠️ WARNING: @ prefix for directives is deprecated. Use 'import', 'export', 'use', 'load' instead."
198 );
199
200 if line.starts_with("@use") {
201 return parse_use_directive(line, line_number);
202 }
203
204 if !line.starts_with("@load") {
205 if line.starts_with("@import") {
206 let rest = line["@import".len()..].trim();
207 if let Some(open) = rest.find('{') {
208 if let Some(close) = rest.find('}') {
209 let names = rest[open + 1..close]
210 .split(',')
211 .map(|s| s.trim().to_string())
212 .filter(|s| !s.is_empty())
213 .collect::<Vec<_>>();
214 let after = rest[close + 1..].trim();
215 let from_prefix = "from";
216 if after.starts_with(from_prefix) {
217 let path_part = after[from_prefix.len()..].trim();
218 let raw = path_part.trim().trim_matches('"');
219 let base = file_path
220 .parent()
221 .unwrap_or_else(|| std::path::Path::new("."));
222 let joined = base.join(raw);
223 let path = joined.to_string_lossy().to_string();
224 return Ok(Statement::new(
225 StatementKind::Import {
226 names,
227 source: path,
228 },
229 Value::Null,
230 0,
231 line_number,
232 1,
233 ));
234 }
235 }
236 }
237 }
238
239 if line.starts_with("@export") {
240 let rest = line["@export".len()..].trim();
241 if let Some(open) = rest.find('{') {
242 if let Some(close) = rest.find('}') {
243 let names = rest[open + 1..close]
244 .split(',')
245 .map(|s| s.trim().to_string())
246 .filter(|s| !s.is_empty())
247 .collect::<Vec<_>>();
248 return Ok(Statement::new(
249 StatementKind::Export {
250 names,
251 source: String::new(),
252 },
253 Value::Null,
254 0,
255 line_number,
256 1,
257 ));
258 }
259 }
260 }
261
262 return Ok(Statement::new(
263 StatementKind::Unknown,
264 Value::String(line.to_string()),
265 0,
266 line_number,
267 1,
268 ));
269 }
270
271 let mut rest = line["@load".len()..].trim();
272 if rest.is_empty() {
273 return Err(anyhow!("@load directive requires a path"));
274 }
275
276 let path;
277 if rest.starts_with('"') {
278 if let Some(end) = rest[1..].find('"') {
279 let raw = rest[1..1 + end].to_string();
280 let base = file_path
281 .parent()
282 .unwrap_or_else(|| std::path::Path::new("."));
283 let joined = base.join(&raw);
284 path = joined.to_string_lossy().to_string();
285 rest = rest[1 + end + 1..].trim();
286 } else {
287 return Err(anyhow!("unterminated string literal in @load"));
288 }
289 } else {
290 let mut split = rest.splitn(2, char::is_whitespace);
291 path = split.next().unwrap_or_default().trim().to_string();
292 rest = split.next().unwrap_or("").trim();
293 }
294
295 let alias = if rest.starts_with("as ") {
296 rest[3..].trim().to_string()
297 } else {
298 Path::new(&path)
299 .file_stem()
300 .map(|s| s.to_string_lossy().to_string())
301 .unwrap_or_else(|| path.clone())
302 };
303
304 Ok(Statement::new(
305 StatementKind::Load {
306 source: path,
307 alias,
308 },
309 Value::Null,
310 0,
311 line_number,
312 1,
313 ))
314}