1use std::{collections::HashMap, fs, io, path::Path, rc::Rc};
2
3use chialisp::{
4 classic::clvm_tools::clvmc::compile_clvm_text,
5 compiler::{compiler::DefaultCompilerOpts, comptypes::CompilerOpts},
6};
7use clvm_traits::ToClvmError;
8use clvm_utils::{TreeHash, tree_hash};
9use clvmr::{Allocator, error::EvalErr, serde::node_to_bytes};
10use rue_compiler::{Compiler, FileTree, normalize_path};
11use rue_options::find_project;
12use thiserror::Error;
13
14#[derive(Debug, Error)]
15pub enum LoadClvmError {
16 #[error("IO error: {0}")]
17 Io(#[from] io::Error),
18
19 #[error("CLVM error: {0}")]
20 Clvm(#[from] EvalErr),
21
22 #[error("Invalid file name")]
23 InvalidFileName,
24
25 #[error("Compiler error: {0}")]
26 Compiler(String),
27
28 #[error("Conversion error: {0}")]
29 Conversion(#[from] ToClvmError),
30
31 #[error("Project error: {0}")]
32 Project(#[from] rue_options::Error),
33
34 #[error("Project not found")]
35 ProjectNotFound,
36
37 #[error("Main not found")]
38 MainNotFound,
39
40 #[error("Export not found: {0}")]
41 ExportNotFound(String),
42
43 #[error("Rue error: {0}")]
44 Rue(#[from] rue_compiler::Error),
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, Hash)]
48pub struct Compilation {
49 pub reveal: Vec<u8>,
50 pub hash: TreeHash,
51}
52
53pub fn compile_chialisp(
54 path: &Path,
55 include_paths: &[String],
56) -> Result<Compilation, LoadClvmError> {
57 let mut allocator = Allocator::new();
58
59 let opts = Rc::new(DefaultCompilerOpts::new(
60 path.file_name()
61 .ok_or(LoadClvmError::InvalidFileName)?
62 .to_str()
63 .ok_or(LoadClvmError::InvalidFileName)?,
64 ))
65 .set_search_paths(include_paths);
66
67 let text = fs::read_to_string(path)?;
68
69 let ptr = compile_clvm_text(
70 &mut allocator,
71 opts,
72 &mut HashMap::new(),
73 &text,
74 path.to_str().ok_or(LoadClvmError::InvalidFileName)?,
75 false,
76 )
77 .map_err(|error| LoadClvmError::Compiler(format!("{error:?}")))?;
78
79 let hash = tree_hash(&allocator, ptr);
80 let reveal = node_to_bytes(&allocator, ptr)?;
81
82 Ok(Compilation { reveal, hash })
83}
84
85pub fn compile_rue(
86 path: &Path,
87 debug: bool,
88 export_name: Option<String>,
89) -> Result<Compilation, LoadClvmError> {
90 let mut allocator = Allocator::new();
91
92 let path = path.canonicalize()?;
93 let project = find_project(&path, debug)?;
94
95 let Some(project) = project else {
96 return Err(LoadClvmError::ProjectNotFound);
97 };
98
99 let main_kind = if project.entrypoint.join("main.rue").exists() {
100 Some(normalize_path(&project.entrypoint.join("main.rue"))?)
101 } else {
102 None
103 };
104
105 let mut ctx = Compiler::new(project.options);
106
107 let tree = FileTree::compile_path(&mut ctx, &project.entrypoint, &mut HashMap::new())?;
108
109 let ptr = if let Some(export_name) = export_name {
110 if let Some(export) = tree
111 .exports(
112 &mut ctx,
113 &mut allocator,
114 main_kind.as_ref(),
115 Some(&export_name),
116 )?
117 .into_iter()
118 .next()
119 {
120 export.ptr
121 } else {
122 return Err(LoadClvmError::ExportNotFound(export_name));
123 }
124 } else if let Some(main_kind) = main_kind
125 && let Some(main) = tree.main(&mut ctx, &mut allocator, &main_kind)?
126 {
127 main
128 } else if let Some(main) = tree.main(&mut ctx, &mut allocator, &normalize_path(&path)?)? {
129 main
130 } else {
131 return Err(LoadClvmError::MainNotFound);
132 };
133
134 let hash = tree_hash(&allocator, ptr);
135 let reveal = node_to_bytes(&allocator, ptr)?;
136
137 Ok(Compilation { reveal, hash })
138}
139
140#[macro_export]
141macro_rules! compile_chialisp {
142 ( $args:ty = $mod_name:ident, $path:literal ) => {
143 compile_chialisp!(impl $args = $mod_name $path);
144 };
145
146 ( impl $args:ty = $mod_name:ident $path:literal ) => {
147 static $mod_name: ::std::sync::LazyLock<Compilation> =
148 ::std::sync::LazyLock::new(|| $crate::compile_chialisp(::std::path::Path::new($path), &[".".to_string(), "include".to_string()]).unwrap());
149
150 impl $crate::Mod for $args {
151 fn mod_reveal() -> ::std::borrow::Cow<'static, [u8]> {
152 ::std::borrow::Cow::Owned($mod_name.reveal.clone())
153 }
154
155 fn mod_hash() -> $crate::__internals::TreeHash {
156 $mod_name.hash
157 }
158 }
159 };
160}
161
162#[macro_export]
163macro_rules! compile_rue {
164 ( $args:ty = $mod_name:ident, $path:literal ) => {
165 compile_rue!(impl $args = $mod_name $path false None);
166 };
167
168 ( $args:ty = $mod_name:ident, $path:literal, $export_name:literal ) => {
169 compile_rue!(impl $args = $mod_name $path false Some($export_name));
170 };
171
172 ( debug $args:ty = $mod_name:ident, $path:literal ) => {
173 compile_rue!(impl $args = $mod_name $path true None);
174 };
175
176 ( debug $args:ty = $mod_name:ident, $path:literal, $export_name:literal ) => {
177 compile_rue!(impl $args = $mod_name $path true Some($export_name));
178 };
179
180 ( impl $args:ty = $mod_name:ident $path:literal $debug:literal $export_name:expr ) => {
181 static $mod_name: ::std::sync::LazyLock<Compilation> =
182 ::std::sync::LazyLock::new(|| $crate::compile_rue(::std::path::Path::new($path), $debug, $export_name).unwrap());
183
184 impl $crate::Mod for $args {
185 fn mod_reveal() -> ::std::borrow::Cow<'static, [u8]> {
186 ::std::borrow::Cow::Owned($mod_name.reveal.clone())
187 }
188
189 fn mod_hash() -> $crate::__internals::TreeHash {
190 $mod_name.hash
191 }
192 }
193 };
194}
195
196#[cfg(test)]
197mod tests {
198 use clvm_traits::{FromClvm, ToClvm};
199 use clvm_utils::CurriedProgram;
200 use clvmr::{NodePtr, serde::node_from_bytes};
201
202 use crate::{Mod, run_puzzle};
203
204 use super::*;
205
206 #[test]
207 fn test_compile_chialisp() -> anyhow::Result<()> {
208 #[derive(Debug, Clone, PartialEq, Eq, Hash, ToClvm, FromClvm)]
209 #[clvm(curry)]
210 struct TestArgs {
211 a: u64,
212 b: u64,
213 }
214
215 compile_chialisp!(TestArgs = TEST_MOD, "compile_chialisp_test.clsp");
216
217 let args = TestArgs { a: 10, b: 20 };
218
219 let mut allocator = Allocator::new();
220
221 let mod_ptr = node_from_bytes(&mut allocator, TestArgs::mod_reveal().as_ref())?;
222
223 let ptr = CurriedProgram {
224 program: mod_ptr,
225 args,
226 }
227 .to_clvm(&mut allocator)?;
228
229 let output = run_puzzle(&mut allocator, ptr, NodePtr::NIL)?;
230
231 assert_eq!(hex::encode(node_to_bytes(&allocator, output)?), "8200e6");
232
233 Ok(())
234 }
235
236 #[test]
237 fn test_compile_rue() -> anyhow::Result<()> {
238 #[derive(Debug, Clone, PartialEq, Eq, Hash, ToClvm, FromClvm)]
239 #[clvm(curry)]
240 struct TestArgs {
241 a: u64,
242 b: u64,
243 }
244
245 compile_rue!(debug TestArgs = TEST_MOD, "compile_rue_test.rue");
246
247 let args = TestArgs { a: 10, b: 20 };
248
249 let mut allocator = Allocator::new();
250
251 let mod_ptr = node_from_bytes(&mut allocator, TestArgs::mod_reveal().as_ref())?;
252
253 let ptr = CurriedProgram {
254 program: mod_ptr,
255 args,
256 }
257 .to_clvm(&mut allocator)?;
258
259 let output = run_puzzle(&mut allocator, ptr, NodePtr::NIL)?;
260
261 assert_eq!(hex::encode(node_to_bytes(&allocator, output)?), "8200e6");
262
263 Ok(())
264 }
265}