devalang_wasm/language/syntax/parser/driver/
directive.rs

1use 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    // Handle @use directive for plugins: @use author.plugin as alias
7    if line.starts_with("@use") {
8        return parse_use_directive(line, line_number);
9    }
10    
11    if !line.starts_with("@load") {
12        // handle @import and @export directives
13        if line.starts_with("@import") {
14            // syntax: @import { a, b } from "path"
15            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                        // Resolve relative to file_path
25                        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            // syntax: @export { a, b }
36            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            // Resolve relative paths relative to the file location
64            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
98/// Parse @use directive for plugins: @use author.plugin as alias
99/// Example: @use devaloop.acid as acid
100fn 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    // Parse plugin reference (author.plugin)
107    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    // Parse alias if provided
124    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}