1use std::collections::{BTreeMap, HashMap};
2
3use tx3_tir::reduce::{Apply, ArgValue};
4
5use crate::{analyzing, ast, lowering, parsing};
6
7#[derive(Debug, thiserror::Error, miette::Diagnostic)]
8pub enum Error {
9 #[error("I/O error: {0}")]
10 Io(#[from] std::io::Error),
11
12 #[error("Missing main code")]
13 MissingMain,
14
15 #[error("Parsing error: {0}")]
16 #[diagnostic(transparent)]
17 Parsing(#[from] parsing::Error),
18
19 #[error("Analyzing error")]
20 Analyzing(#[from] analyzing::AnalyzeReport),
21
22 #[error("Apply error: {0}")]
23 Apply(#[from] tx3_tir::reduce::Error),
24}
25
26pub type Code = String;
27
28pub struct Workspace {
29 main: Option<Code>,
30 ast: Option<ast::Program>,
31 analisis: Option<analyzing::AnalyzeReport>,
32 tir: HashMap<String, tx3_tir::model::v1beta0::Tx>,
33}
34
35impl Workspace {
36 pub fn from_file(main: impl AsRef<std::path::Path>) -> Result<Self, Error> {
37 let main = std::fs::read_to_string(main.as_ref())?;
38
39 Ok(Self {
40 main: Some(main),
41 ast: None,
42 analisis: None,
43 tir: HashMap::new(),
44 })
45 }
46
47 pub fn from_string(main: Code) -> Self {
48 Self {
49 main: Some(main),
50 ast: None,
51 analisis: None,
52 tir: HashMap::new(),
53 }
54 }
55
56 fn ensure_main(&self) -> Result<&Code, Error> {
57 if self.main.is_none() {
58 return Err(Error::MissingMain);
59 }
60
61 Ok(self.main.as_ref().unwrap())
62 }
63
64 pub fn parse(&mut self) -> Result<(), Error> {
65 let main = self.ensure_main()?;
66 let ast = parsing::parse_string(main)?;
67 self.ast = Some(ast);
68 Ok(())
69 }
70
71 fn ensure_ast(&mut self) -> Result<(), Error> {
72 if self.ast.is_none() {
73 self.parse()?;
74 }
75
76 Ok(())
77 }
78
79 pub fn ast(&self) -> Option<&ast::Program> {
80 self.ast.as_ref()
81 }
82
83 pub fn analyze(&mut self) -> Result<(), Error> {
84 self.ensure_ast()?;
85
86 let ast = self.ast.as_mut().unwrap();
87
88 self.analisis = Some(analyzing::analyze(ast));
89
90 Ok(())
91 }
92
93 pub fn ensure_analisis(&mut self) -> Result<(), Error> {
94 if self.analisis.is_none() {
95 self.analyze()?;
96 }
97
98 Ok(())
99 }
100
101 pub fn analisis(&self) -> Option<&analyzing::AnalyzeReport> {
102 self.analisis.as_ref()
103 }
104
105 pub fn lower(&mut self) -> Result<(), Error> {
106 self.ensure_analisis()?;
107
108 let analisis = self.analisis().unwrap();
109
110 if !analisis.errors.is_empty() {
111 return Err(Error::from(analisis.clone()));
112 }
113
114 let ast = self.ast.as_ref().unwrap();
115
116 for tx in ast.txs.iter() {
117 let tir = lowering::lower(ast, &tx.name.value).unwrap();
118 self.tir.insert(tx.name.value.clone(), tir);
119 }
120
121 Ok(())
122 }
123
124 pub fn ensure_tir(&mut self) -> Result<(), Error> {
125 if self.tir.is_empty() {
126 self.lower()?;
127 }
128
129 Ok(())
130 }
131
132 pub fn tir(&self, name: &str) -> Option<&tx3_tir::model::v1beta0::Tx> {
133 self.tir.get(name)
134 }
135
136 pub fn apply_args(&mut self, args: &BTreeMap<String, ArgValue>) -> Result<(), Error> {
137 self.ensure_tir()?;
138
139 let values = self.tir.drain();
140 let mut new_tir = HashMap::new();
141
142 for (key, tir) in values {
143 let tir = tir.apply_args(args)?;
144 new_tir.insert(key, tir);
145 }
146
147 self.tir = new_tir;
148
149 Ok(())
150 }
151}
152
153#[cfg(test)]
154pub mod tests {
155 use super::*;
156
157 #[test]
158 fn smoke_test_happy_path() {
159 let manifest_dir = env!("CARGO_MANIFEST_DIR");
160
161 let mut workspace =
162 Workspace::from_file(&format!("{manifest_dir}/../..//examples/transfer.tx3")).unwrap();
163
164 workspace.parse().unwrap();
165 workspace.analyze().unwrap();
166 workspace.lower().unwrap();
167 }
168}