1#![warn(clippy::pedantic)]
2#![warn(missing_docs)]
3#![doc = include_str!("../README.md")]
4
5use ariadne::{Cache, Source};
6use ast::{SourceFile, Template};
7use builtins::{StandardContextWorker, StandardContextualizer, StandardEscaper};
8use itertools::Itertools;
9use maybe_sync::{dyn_maybe_send_sync, Rc};
10#[cfg(feature = "serde")]
11use rendertime::value::serde_values;
12use std::fmt::{Debug, Display};
13use std::hash::Hash;
14use std::marker::PhantomData;
15use std::{collections::HashMap, fmt};
16
17#[doc(hidden)]
18#[allow(non_snake_case)]
19pub mod __internal__tests_util;
20
21mod ast;
22
23mod parse;
24pub use parse::Error as ParseError;
25
26pub(crate) mod rendertime;
27
28pub use rendertime::Error as RendertimeError;
29pub use rendertime::Value;
30
31#[cfg(feature = "serde")]
32pub use serde_values::Error as SerializationError;
33
34mod type_checking;
35pub use type_checking::{Error as TypeCheckingError, Indentation, Type};
36
37use crate::ast::TypeDefinition;
38
39pub mod builtins;
40
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub enum ContextualizationError {
44 UnknownTextType(String),
46 InvalidStatic,
48 IncompatibleTextType {
50 target_ty: String,
52 source_ty: String,
54 },
55 StartAndEndDifferentContext(String, String),
57}
58
59impl Display for ContextualizationError {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 match self {
62 Self::UnknownTextType(ty) => write!(f, "Text type {ty:?} unknown"),
63 Self::InvalidStatic => write!(f, "Static text invalid for given text type"),
64 Self::IncompatibleTextType {
65 target_ty,
66 source_ty: dynamic_ty,
67 } => write!(
68 f,
69 "The target type {target_ty:?} is incompatible with the source type {dynamic_ty:?}"
70 ),
71 Self::StartAndEndDifferentContext(start, end) => write!(
72 f,
73 "The static text starts in type {start:?} but ends in {end:?}"
74 ),
75 }
76 }
77}
78
79impl std::error::Error for ContextualizationError {}
80
81pub trait Contextualizer<Esc, Wrk>: Debug
89where
90 Esc: Escaper,
91 Wrk: ContextWorker<Esc>,
92{
93 fn default_text_type(&self) -> String;
96
97 fn contextualize(&self, text_ty: &str) -> Result<Wrk, ContextualizationError>;
103
104 fn escaper(&self, target_ty: &str, source_ty: &str) -> Result<Esc, ContextualizationError> {
115 self.contextualize(target_ty)?.dynamic(source_ty)
116 }
117}
118
119pub trait ContextWorker<Esc: Escaper> {
122 fn push_static(&mut self, s: &str) -> Result<(), ContextualizationError>;
129
130 fn dynamic(&mut self, input_ty: &str) -> Result<Esc, ContextualizationError>;
137}
138
139pub trait Escaper: Debug + Clone {
142 fn escape(&self, inp: Value) -> Result<Value, String>;
150}
151
152pub trait Function {
154 fn accepts(&self, args: Vec<Type>) -> Option<Type>;
159
160 fn run(
168 &self,
169 args: Vec<Value>,
170 ) -> Result<Value, Box<maybe_sync::dyn_maybe_send_sync!(std::error::Error)>>;
171}
172
173pub struct Templates<Esc = StandardEscaper> {
181 tmpl_names: HashMap<String, usize>,
183 tmpls: Vec<type_checking::Template<Esc>>,
184}
185
186impl<Esc> Debug for Templates<Esc> {
187 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188 f.debug_struct("Templates")
189 .field("templates", &self.tmpl_names.keys().collect_vec())
190 .finish_non_exhaustive()
191 }
192}
193
194#[derive(Clone, Copy, Hash, PartialEq, Eq)]
197pub struct Span<'s> {
198 start: usize,
199 end: usize,
200 path_and_source: (&'s str, &'s str),
201}
202
203impl Debug for Span<'_> {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 write!(f, "{start}..{end}", start = self.start, end = self.end)
206 }
207}
208
209impl<'s> chumsky::span::Span for Span<'s> {
210 type Context = (&'s str, &'s str);
211
212 type Offset = usize;
213
214 fn new(context: Self::Context, range: std::ops::Range<Self::Offset>) -> Self {
215 Self {
216 start: range.start,
217 end: range.end,
218 path_and_source: context,
219 }
220 }
221
222 fn context(&self) -> Self::Context {
223 self.path_and_source
224 }
225
226 fn start(&self) -> Self::Offset {
227 self.start
228 }
229
230 fn end(&self) -> Self::Offset {
231 self.end
232 }
233}
234
235impl<'s> ariadne::Span for Span<'s> {
236 type SourceId = (&'s str, &'s str);
237
238 fn source(&self) -> &Self::SourceId {
239 &self.path_and_source
240 }
241
242 fn start(&self) -> usize {
243 self.start
244 }
245
246 fn end(&self) -> usize {
247 self.end
248 }
249}
250
251pub enum Error<'s> {
253 Parse(ParseError<'s>),
255 TypeChecking(Box<type_checking::Error<'s>>),
257}
258
259struct AriadneCache<'s>(HashMap<&'s str, Source<&'s str>>);
260
261impl<'s> Cache<(&'s str, &'s str)> for AriadneCache<'s> {
262 type Storage = &'s str;
263
264 fn fetch(
265 &mut self,
266 id: &(&'s str, &'s str),
267 ) -> Result<&Source<Self::Storage>, impl fmt::Debug> {
268 Ok::<_, Box<dyn Debug>>(self.0.entry(id.0).or_insert_with(|| Source::from(id.1)))
269 }
270
271 fn display<'a>(&self, id: &'a (&'s str, &'s str)) -> Option<impl fmt::Display + 'a> {
272 Some(Box::new(id.0))
273 }
274}
275
276impl Debug for Error<'_> {
277 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278 f.write_str("\n")?;
279 <Self as Display>::fmt(self, f)
280 }
281}
282
283impl Display for Error<'_> {
284 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285 match self {
286 Error::Parse(err) => {
287 if f.alternate() {
288 write!(f, "{err:#}")
289 } else {
290 write!(f, "{err}")
291 }
292 }
293 Error::TypeChecking(err) => {
294 if f.alternate() {
295 write!(f, "{err:#}")
296 } else {
297 write!(f, "{err}")
298 }
299 }
300 }
301 }
302}
303
304impl std::error::Error for Error<'_> {}
305
306pub struct TemplatesBuilder<
313 's,
314 Ctx = StandardContextualizer,
315 Esc = StandardEscaper,
316 Wrk = StandardContextWorker,
317> {
318 tmpls: Vec<Template<'s>>,
319 type_definitions: Vec<(TypeDefinition<'s>, Span<'s>)>,
320 funcs: HashMap<&'s str, Rc<dyn_maybe_send_sync!(Function)>>,
321 ctxer: Ctx,
322 _esc: PhantomData<Esc>,
323 _wrk: PhantomData<Wrk>,
324}
325
326impl Debug for TemplatesBuilder<'_> {
327 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328 f.debug_struct("TemplatesBuilder")
329 .field("tmpls", &self.tmpls)
330 .field("funcs", &self.funcs.keys())
331 .finish()
332 }
333}
334
335impl<'s, Ctx, Esc, Wrk> TemplatesBuilder<'s, Ctx, Esc, Wrk> {
336 pub fn with_source_file(
341 mut self,
342 path: &'s str,
343 source: &'s str,
344 ) -> Result<TemplatesBuilder<'s, Ctx, Esc, Wrk>, ParseError<'s>> {
345 let (tmpls, ty_defs) = SourceFile::parse(path, source)?.into_templates_and_type_defs();
346 self.tmpls.extend(tmpls);
347 self.type_definitions.extend(ty_defs);
348
349 Ok(self)
350 }
351
352 pub fn with_source_files<I: IntoIterator<Item = (&'s str, &'s str)>>(
357 self,
358 sources: I,
359 ) -> Result<TemplatesBuilder<'s, Ctx, Esc, Wrk>, ParseError<'s>> {
360 let mut res = Ok(self);
361
362 for (path, source) in sources {
363 let inter_res =
364 SourceFile::parse(path, source).map(SourceFile::into_templates_and_type_defs);
365
366 res = match (res, inter_res) {
367 (Ok(mut builder), Ok((templates, type_definitions))) => {
368 builder.tmpls.extend(templates);
369 builder.type_definitions.extend(type_definitions);
370 Ok(builder)
371 }
372 (Ok(_), Err(errors)) => Err(errors),
373 (res @ Err(_), Ok(_)) => res,
374 (Err(mut prev_error), Err(next_error)) => {
375 if let ParseError::Multiple(ref mut prev) = prev_error {
376 prev.push(next_error);
377 return Err(prev_error);
378 }
379
380 return Err(ParseError::Multiple(vec![prev_error, next_error]));
381 }
382 }
383 }
384
385 res
386 }
387
388 #[must_use]
393 pub fn with_function(
394 mut self,
395 name: &'s str,
396 func: impl Function + 'static + maybe_sync::MaybeSync + maybe_sync::MaybeSend,
397 ) -> TemplatesBuilder<'s, Ctx, Esc, Wrk> {
398 self.funcs.insert(name, Rc::new(func));
399
400 self
401 }
402
403 #[must_use]
408 pub fn with_functions(
409 mut self,
410 funcs: impl IntoIterator<Item = (&'s str, Rc<dyn_maybe_send_sync!(Function)>)>,
411 ) -> TemplatesBuilder<'s, Ctx, Esc, Wrk> {
412 self.funcs.extend(funcs);
413
414 self
415 }
416
417 pub fn build(self) -> Result<Templates<Esc>, Error<'s>>
426 where
427 Esc: Escaper,
428 Ctx: Contextualizer<Esc, Wrk>,
429 Wrk: ContextWorker<Esc>,
430 {
431 let (tmpls, tmpl_names) = type_checking::type_check_templates(
432 self.tmpls,
433 builtins::functions()
436 .into_iter()
437 .chain(self.funcs.into_iter())
438 .collect(),
439 self.type_definitions,
440 &self.ctxer,
441 )
442 .map_err(Box::new)
443 .map_err(Error::TypeChecking)?;
444
445 Ok(Templates { tmpl_names, tmpls })
446 }
447
448 #[must_use]
452 pub fn template_names<'a>(&'a self) -> impl ExactSizeIterator<Item = &'s str> + 'a {
453 self.tmpls.iter().map(|tmpl| tmpl.name.0)
454 }
455}
456
457impl<Ctx: Default, Esc, Wrk> TemplatesBuilder<'_, Ctx, Esc, Wrk> {
458 #[must_use]
462 pub fn new() -> Self {
463 TemplatesBuilder {
464 tmpls: Vec::new(),
465 type_definitions: Vec::new(),
466 funcs: builtins::functions().into_iter().collect(),
467 ctxer: Ctx::default(),
468 _esc: PhantomData,
469 _wrk: PhantomData,
470 }
471 }
472}
473
474impl<Ctx, Esc, Wrk> TemplatesBuilder<'_, Ctx, Esc, Wrk> {
475 #[must_use]
477 pub fn new_with_ctxer(ctxer: Ctx) -> Self {
478 TemplatesBuilder {
479 tmpls: Vec::new(),
480 type_definitions: Vec::new(),
481 funcs: builtins::functions().into_iter().collect(),
482 ctxer,
483 _esc: PhantomData,
484 _wrk: PhantomData,
485 }
486 }
487}
488
489impl<Ctx> Default for TemplatesBuilder<'_, Ctx>
490where
491 Ctx: Default,
492{
493 fn default() -> Self {
494 Self::new()
495 }
496}
497
498#[cfg(feature = "serde")]
499#[derive(Debug)]
501pub enum SerdeRenderError {
502 Serde(serde_values::Error),
504 Rendertime(rendertime::Error),
506}
507
508#[cfg(feature = "serde")]
509impl Display for SerdeRenderError {
510 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
511 match self {
512 Self::Serde(err) => Display::fmt(&err, f),
513 Self::Rendertime(err) => Display::fmt(&err, f),
514 }
515 }
516}
517
518#[cfg(feature = "serde")]
519impl std::error::Error for SerdeRenderError {}
520
521impl<'s, Esc: Escaper> Templates<Esc> {
522 pub fn new<'f, Ctx, Wrk>(
528 source_files: impl IntoIterator<Item = (&'s str, &'s str)>,
529 functions: HashMap<&'s str, Rc<dyn_maybe_send_sync!(Function)>>,
530 ) -> Result<Templates<Esc>, Error<'s>>
531 where
532 Ctx: Contextualizer<Esc, Wrk> + Default,
533 Esc: Escaper,
534 Wrk: ContextWorker<Esc>,
535 {
536 TemplatesBuilder::<Ctx, Esc, Wrk>::new()
537 .with_source_files(source_files)
538 .map_err(Error::Parse)?
539 .with_functions(functions)
540 .build()
541 }
542
543 #[must_use]
545 pub fn builder<Ctx, Wrk>() -> TemplatesBuilder<'s, Esc, Ctx, Wrk>
546 where
547 Esc: Default,
548 {
549 TemplatesBuilder::new()
550 }
551
552 pub fn new_with_ctxer<'f, Ctx, Wrk>(
559 source_files: impl IntoIterator<Item = (&'s str, &'s str)>,
560 functions: HashMap<&'s str, Rc<dyn_maybe_send_sync!(Function)>>,
561 ctxer: Ctx,
562 ) -> Result<Templates<Esc>, Error<'s>>
563 where
564 Ctx: Contextualizer<Esc, Wrk> + Default,
565 Esc: Escaper,
566 Wrk: ContextWorker<Esc>,
567 {
568 TemplatesBuilder::<Ctx, Esc, Wrk>::new_with_ctxer(ctxer)
569 .with_source_files(source_files)
570 .map_err(Error::Parse)?
571 .with_functions(functions)
572 .build()
573 }
574
575 pub fn render<V: Into<Value>>(&self, name: &str, args: V) -> Result<String, RendertimeError> {
585 let Value::Struct(args) = args.into() else {
586 return Err(rendertime::Error::NonStructRootValue);
587 };
588 rendertime::tmpl_output(self, self.tmpl_names[name], args)
589 }
590
591 pub fn render_fmt<V: Into<Value>>(
603 &self,
604 name: &str,
605 args: V,
606 writer: impl fmt::Write,
607 ) -> Result<(), rendertime::Error> {
608 let Value::Struct(args) = args.into() else {
609 return Err(rendertime::Error::NonStructRootValue);
610 };
611 rendertime::write_tmpl(writer, self, self.tmpl_names[name], args)
612 }
613
614 #[cfg(feature = "serde")]
624 pub fn render_serde(
625 &self,
626 name: &str,
627 args: impl serde::ser::Serialize,
628 ) -> Result<String, SerdeRenderError> {
629 self.render(
630 name,
631 args.serialize(serde_values::ValueSerializer)
632 .map_err(SerdeRenderError::Serde)?,
633 )
634 .map_err(SerdeRenderError::Rendertime)
635 }
636
637 #[cfg(feature = "serde")]
638 pub fn render_serde_fmt(
648 &self,
649 name: &str,
650 args: impl serde::ser::Serialize,
651 writer: impl fmt::Write,
652 ) -> Result<(), SerdeRenderError> {
653 self.render_fmt(
654 name,
655 args.serialize(serde_values::ValueSerializer)
656 .map_err(SerdeRenderError::Serde)?,
657 writer,
658 )
659 .map_err(SerdeRenderError::Rendertime)
660 }
661}