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(line: &str, line_number: usize, file_path: &std::path::Path) -> Result<Statement> {
6 if line.starts_with("@use") {
8 return parse_use_directive(line, line_number);
9 }
10
11 if !line.starts_with("@load") {
12 if line.starts_with("@import") {
14 let rest = line["@import".len()..].trim();
16 if let Some(open) = rest.find('{') {
17 if let Some(close) = rest.find('}') {
18 let names = rest[open+1..close].split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect::<Vec<_>>();
19 let after = rest[close+1..].trim();
20 let from_prefix = "from";
21 if after.starts_with(from_prefix) {
22 let path_part = after[from_prefix.len()..].trim();
23 let raw = path_part.trim().trim_matches('"');
24 let base = file_path.parent().unwrap_or_else(|| std::path::Path::new("."));
26 let joined = base.join(raw);
27 let path = joined.to_string_lossy().to_string();
28 return Ok(Statement::new(StatementKind::Import { names, source: path }, Value::Null, 0, line_number, 1));
29 }
30 }
31 }
32 }
33
34 if line.starts_with("@export") {
35 let rest = line["@export".len()..].trim();
37 if let Some(open) = rest.find('{') {
38 if let Some(close) = rest.find('}') {
39 let names = rest[open+1..close].split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect::<Vec<_>>();
40 return Ok(Statement::new(StatementKind::Export { names, source: String::new() }, Value::Null, 0, line_number, 1));
41 }
42 }
43 }
44
45 return Ok(Statement::new(
46 StatementKind::Unknown,
47 Value::String(line.to_string()),
48 0,
49 line_number,
50 1,
51 ));
52 }
53
54 let mut rest = line["@load".len()..].trim();
55 if rest.is_empty() {
56 return Err(anyhow!("@load directive requires a path"));
57 }
58
59 let path;
60 if rest.starts_with('"') {
61 if let Some(end) = rest[1..].find('"') {
62 let raw = rest[1..1 + end].to_string();
63 let base = file_path.parent().unwrap_or_else(|| std::path::Path::new("."));
65 let joined = base.join(&raw);
66 path = joined.to_string_lossy().to_string();
67 rest = rest[1 + end + 1..].trim();
68 } else {
69 return Err(anyhow!("unterminated string literal in @load"));
70 }
71 } else {
72 let mut split = rest.splitn(2, char::is_whitespace);
73 path = split.next().unwrap_or_default().trim().to_string();
74 rest = split.next().unwrap_or("").trim();
75 }
76
77 let alias = if rest.starts_with("as ") {
78 rest[3..].trim().to_string()
79 } else {
80 Path::new(&path)
81 .file_stem()
82 .map(|s| s.to_string_lossy().to_string())
83 .unwrap_or_else(|| path.clone())
84 };
85
86 Ok(Statement::new(
87 StatementKind::Load {
88 source: path,
89 alias,
90 },
91 Value::Null,
92 0,
93 line_number,
94 1,
95 ))
96}
97
98fn parse_use_directive(line: &str, line_number: usize) -> Result<Statement> {
101 let rest = line["@use".len()..].trim();
102 if rest.is_empty() {
103 return Err(anyhow!("@use directive requires a plugin reference (author.plugin)"));
104 }
105
106 let plugin_ref;
108 let mut parts = rest.splitn(2, " as ");
109 plugin_ref = parts.next().unwrap_or_default().trim().to_string();
110
111 if !plugin_ref.contains('.') {
112 return Err(anyhow!("@use directive requires format: author.plugin"));
113 }
114
115 let plugin_parts: Vec<&str> = plugin_ref.split('.').collect();
116 if plugin_parts.len() != 2 {
117 return Err(anyhow!("@use directive requires format: author.plugin (got: {})", plugin_ref));
118 }
119
120 let author = plugin_parts[0].to_string();
121 let plugin_name = plugin_parts[1].to_string();
122
123 let alias = if let Some(alias_part) = parts.next() {
125 alias_part.trim().to_string()
126 } else {
127 plugin_name.clone()
128 };
129
130 Ok(Statement::new(
131 StatementKind::UsePlugin {
132 author,
133 name: plugin_name,
134 alias,
135 },
136 Value::Null,
137 0,
138 line_number,
139 1,
140 ))
141}