1use std::sync::{Arc, Mutex};
18
19use nomiscript::{Compiler, Program, Reader, SymbolTable};
20use scripting::runtime::{EngineError, classify_runtime_error};
21use thiserror::Error;
22use wasmtime::{AnyRef, Linker, Rooted, Store, Val};
23
24use crate::ctx::ScriptCtx;
25use crate::draft::TransactionDraft;
26use crate::session::SessionData;
27use crate::wasm::{EngineOpts, build_engine};
28
29const RENDER_EPOCH_TICKS: u64 = 1;
30
31#[derive(Debug, Error)]
32pub enum TemplateError {
33 #[error("template compile error: {0}")]
36 Compile(String),
37 #[error("template engine error: {0}")]
39 Engine(String),
40 #[error("template render error: {0}")]
42 Runtime(String),
43}
44
45pub fn compile_template(source: &str) -> Result<Vec<u8>, TemplateError> {
50 let (_, bytes) = compile_render_program(source)?;
51 Ok(bytes)
52}
53
54fn compile_render_program(source: &str) -> Result<(Program, Vec<u8>), TemplateError> {
55 let host_fns = crate::natives::render_compiler_specs();
56 let mut symbols = SymbolTable::with_builtins();
57 symbols.register_host_fns(&host_fns);
58 crate::host_prelude::load(&mut symbols);
59 let mut compiler = Compiler::with_host_fns(host_fns);
60 let program = Reader::parse(source).map_err(|err| TemplateError::Compile(err.to_string()))?;
61 let (bytes, _ty) = compiler
62 .compile_eval_with_type(&program, &mut symbols)
63 .map_err(|err| TemplateError::Compile(err.to_string()))?;
64 Ok((program, bytes))
65}
66
67pub async fn render_template(
72 ctx: &ScriptCtx,
73 source: &str,
74) -> Result<TransactionDraft, TemplateError> {
75 let host_fns = crate::natives::render_compiler_specs();
76 let mut symbols = SymbolTable::with_builtins();
77 symbols.register_host_fns(&host_fns);
78 crate::host_prelude::load(&mut symbols);
79 let mut compiler = Compiler::with_host_fns(host_fns);
80 let program = Reader::parse(source).map_err(|err| TemplateError::Compile(err.to_string()))?;
81
82 let engine = build_engine(EngineOpts::baseline().with_fuel())
83 .map_err(|err| TemplateError::Engine(err.to_string()))?;
84 let mut linker: Linker<SessionData> = Linker::new(&engine);
85 crate::natives::link_render(&mut linker)
86 .map_err(|err| TemplateError::Engine(err.to_string()))?;
87
88 let output = Arc::new(Mutex::new(String::new()));
89 let mut store: Store<SessionData> =
90 Store::new(&engine, SessionData::for_render(ctx.clone(), output));
91 store
92 .set_fuel(ctx.limits.fuel)
93 .map_err(|err| TemplateError::Engine(err.to_string()))?;
94 store.set_epoch_deadline(RENDER_EPOCH_TICKS);
95
96 for form in program.exprs {
97 let single = Program::new(vec![form]);
98 let (bytes, _ty) = compiler
99 .compile_eval_with_type(&single, &mut symbols)
100 .map_err(|err| TemplateError::Compile(err.to_string()))?;
101 let module = scripting::runtime::compile_module(&engine, &bytes)
102 .map_err(|err| TemplateError::Engine(err.to_string()))?;
103 let instance = linker
104 .instantiate_async(&mut store, &module)
105 .await
106 .map_err(|err| TemplateError::Engine(classify_engine(&err)))?;
107 let func = instance.get_func(&mut store, "nomi-eval").ok_or_else(|| {
108 TemplateError::Engine("render module missing nomi-eval export".to_string())
109 })?;
110 let mut results = [Val::AnyRef(None)];
111 func.call_async(&mut store, &[], &mut results)
112 .await
113 .map_err(|err| TemplateError::Runtime(classify_engine(&err)))?;
114 let _: Option<Rooted<AnyRef>> = match results[0] {
115 Val::AnyRef(a) => a,
116 _ => None,
117 };
118 }
119
120 Ok(store.into_data().into_draft().unwrap_or_default())
121}
122
123fn classify_engine(err: &wasmtime::Error) -> String {
124 match classify_runtime_error(err) {
125 EngineError::ScriptRaised { code, message } => format!("{code}: {message}"),
126 other => other.to_string(),
127 }
128}