tx3_lang/
loading.rs

1use std::{
2    io::BufRead as _,
3    path::{Path, PathBuf},
4};
5
6use crate::{analyzing, ast, parsing, ArgValue, Protocol};
7
8#[derive(Debug, thiserror::Error, miette::Diagnostic)]
9pub enum Error {
10    #[error("I/O error: {0}")]
11    Io(#[from] std::io::Error),
12
13    #[error("Parsing error: {0}")]
14    #[diagnostic(transparent)]
15    Parsing(#[from] parsing::Error),
16
17    #[error("Analyzing error: {0}")]
18    Analyzing(#[from] analyzing::AnalyzeReport),
19
20    #[error("Invalid environment file: {0}")]
21    InvalidEnvFile(String),
22}
23
24/// Parses a Tx3 source file into a Program AST.
25///
26/// # Arguments
27///
28/// * `path` - Path to the Tx3 source file to parse
29///
30/// # Returns
31///
32/// * `Result<Program, Error>` - The parsed Program AST or an error
33///
34/// # Errors
35///
36/// Returns an error if:
37/// - The file cannot be read
38/// - The file contents are not valid Tx3 syntax
39/// - The AST construction fails
40///
41/// # Example
42///
43/// ```no_run
44/// use tx3_lang::loading::parse_file;
45/// let program = parse_file("path/to/program.tx3").unwrap();
46/// ```
47pub fn parse_file(path: &str) -> Result<ast::Program, Error> {
48    let input = std::fs::read_to_string(path)?;
49    let program = parsing::parse_string(&input)?;
50    Ok(program)
51}
52
53pub type ArgMap = std::collections::HashMap<String, ArgValue>;
54
55fn load_env_file(path: &Path) -> Result<ArgMap, Error> {
56    let file = std::fs::File::open(path)?;
57    let reader = std::io::BufReader::new(file);
58    let mut env = std::collections::HashMap::new();
59
60    for line in reader.lines() {
61        let line = line?;
62        let line = line.trim();
63
64        // Skip empty lines and comments
65        if line.is_empty() || line.starts_with('#') {
66            continue;
67        }
68
69        // Split on first equals sign
70        let mut parts = line.splitn(2, '=');
71
72        let var_name = parts
73            .next()
74            .ok_or_else(|| Error::InvalidEnvFile("Missing variable name".into()))?
75            .trim()
76            .to_string();
77
78        let var_value = parts
79            .next()
80            .ok_or_else(|| Error::InvalidEnvFile("Missing value".into()))?
81            .trim()
82            .to_string();
83
84        env.insert(var_name, ArgValue::String(var_value));
85    }
86
87    Ok(env)
88}
89
90pub struct ProtocolLoader {
91    code_file: Option<PathBuf>,
92    code_string: Option<String>,
93    env_file: Option<PathBuf>,
94    env_args: std::collections::HashMap<String, ArgValue>,
95    analyze: bool,
96}
97
98impl ProtocolLoader {
99    pub fn from_file(file: impl AsRef<std::path::Path>) -> Self {
100        Self {
101            code_file: Some(file.as_ref().to_owned()),
102            code_string: None,
103            env_file: None,
104            env_args: std::collections::HashMap::new(),
105            analyze: true,
106        }
107    }
108
109    pub fn from_string(code: String) -> Self {
110        Self {
111            code_file: None,
112            code_string: Some(code),
113            env_file: None,
114            env_args: std::collections::HashMap::new(),
115            analyze: true,
116        }
117    }
118
119    pub fn with_env_file(mut self, env_file: PathBuf) -> Self {
120        self.env_file = Some(env_file);
121        self
122    }
123
124    pub fn with_env_arg(mut self, name: impl Into<String>, value: impl Into<ArgValue>) -> Self {
125        self.env_args.insert(name.into(), value.into());
126        self
127    }
128
129    pub fn skip_analyze(mut self) -> Self {
130        self.analyze = false;
131        self
132    }
133
134    pub fn load(self) -> Result<Protocol, Error> {
135        let code = match (self.code_file, self.code_string) {
136            (Some(file), None) => std::fs::read_to_string(file)?,
137            (None, Some(code)) => code,
138            _ => unreachable!(),
139        };
140
141        let mut ast = parsing::parse_string(&code)?;
142
143        if self.analyze {
144            analyzing::analyze(&mut ast).ok()?;
145        }
146
147        let mut env_args = std::collections::HashMap::new();
148
149        if let Some(env_file) = &self.env_file {
150            let env = load_env_file(env_file)?;
151
152            for (key, value) in env {
153                env_args.insert(key, value);
154            }
155        }
156
157        for (key, value) in self.env_args {
158            env_args.insert(key, value);
159        }
160
161        let proto = Protocol { ast, env_args };
162
163        Ok(proto)
164    }
165}
166
167#[cfg(test)]
168pub mod tests {
169    use super::*;
170
171    #[test]
172    fn smoke_test_parse_file() {
173        let manifest_dir = env!("CARGO_MANIFEST_DIR");
174        let _ = parse_file(&format!("{}/../..//examples/transfer.tx3", manifest_dir)).unwrap();
175    }
176}