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
24pub 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 if line.is_empty() || line.starts_with('#') {
66 continue;
67 }
68
69 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}