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