Skip to main content

intuicio_core/
script.rs

1//! The data a frontend produces and a backend consumes.
2//!
3//! This is the interface that keeps frontends and backends independent. A
4//! frontend emits a [`ScriptPackage`], whatever its input looks like. A backend
5//! turns the functions inside it into callable [`crate::function::Function`]
6//! values. Neither has to know the other.
7//!
8//! # Shape of the data
9//!
10//! ```text
11//! ScriptPackage
12//!   ScriptModule           name
13//!     ScriptStruct         name, fields
14//!     ScriptEnum           name, variants
15//!     ScriptFunction       signature + Script
16//!       ScriptOperation    the actual instructions
17//! ```
18//!
19//! # Operations
20//!
21//! [`ScriptOperation`] is deliberately small: define and move registers, call a
22//! function, branch, loop, return, suspend. That is the common set every
23//! frontend and backend can share. Anything more specific, such as pushing a
24//! literal, goes into [`ScriptOperation::Expression`] and the
25//! [`ScriptExpression`] type a frontend defines for itself.
26//!
27//! # Loading
28//!
29//! [`ScriptContentProvider`] is the other half. It fetches source by path, so a
30//! frontend can follow imports without knowing where they live.
31use crate::{
32    Visibility,
33    context::Context,
34    function::{Function, FunctionBody, FunctionParameter, FunctionQuery, FunctionSignature},
35    meta::Meta,
36    registry::Registry,
37    types::{
38        TypeQuery,
39        enum_type::{EnumVariant, RuntimeEnumBuilder},
40        struct_type::{RuntimeStructBuilder, StructField},
41    },
42};
43use std::{
44    collections::HashMap,
45    error::Error,
46    path::{Path, PathBuf},
47    sync::Arc,
48};
49
50/// A shared, finished [`Script`].
51pub type ScriptHandle<'a, SE> = Arc<Script<'a, SE>>;
52/// A straight list of operations, run from first to last.
53pub type Script<'a, SE> = Vec<ScriptOperation<'a, SE>>;
54
55/// The extra operations a frontend adds on top of the built-in set.
56///
57/// The built-in operations move data around but never create it, since the
58/// platform has no opinion about what data is. Pushing a literal, dropping a
59/// value, anything else specific to one language, goes here.
60///
61/// ```
62/// # use intuicio_core::{context::Context, registry::Registry, script::ScriptExpression};
63/// enum MyExpression {
64///     Literal(i32),
65///     Drop,
66/// }
67///
68/// impl ScriptExpression for MyExpression {
69///     fn evaluate(&self, context: &mut Context, _: &Registry) {
70///         match self {
71///             Self::Literal(value) => { context.stack().push(*value); }
72///             Self::Drop => { context.stack().drop(); }
73///         }
74///     }
75/// }
76/// ```
77pub trait ScriptExpression: Send + Sync {
78    /// Runs this expression against the running context.
79    fn evaluate(&self, context: &mut Context, registry: &Registry);
80}
81
82impl ScriptExpression for () {
83    fn evaluate(&self, _: &mut Context, _: &Registry) {}
84}
85
86/// A ready-made [`ScriptExpression`] built from a closure.
87///
88/// Saves defining an expression type when a script is assembled in Rust
89/// rather than parsed.
90#[allow(clippy::type_complexity)]
91pub struct InlineExpression(Arc<dyn Fn(&mut Context, &Registry) + Send + Sync>);
92
93impl InlineExpression {
94    /// An expression that pushes a copy of `value` every time it runs.
95    pub fn copied<T: Copy + Send + Sync + 'static>(value: T) -> Self {
96        Self(Arc::new(move |context, _| {
97            context.stack().push(value);
98        }))
99    }
100
101    /// An expression that pushes a clone of `value` every time it runs.
102    pub fn cloned<T: Clone + Send + Sync + 'static>(value: T) -> Self {
103        Self(Arc::new(move |context, _| {
104            context.stack().push(value.clone());
105        }))
106    }
107
108    /// An expression that runs an arbitrary closure.
109    pub fn closure<F: Fn(&mut Context, &Registry) + Send + Sync + 'static>(f: F) -> Self {
110        Self(Arc::new(f))
111    }
112}
113
114impl ScriptExpression for InlineExpression {
115    fn evaluate(&self, context: &mut Context, registry: &Registry) {
116        (self.0)(context, registry);
117    }
118}
119
120/// One instruction of a script.
121///
122/// See the [module docs](self) for why the set is this small.
123#[derive(Debug)]
124pub enum ScriptOperation<'a, SE: ScriptExpression> {
125    /// Does nothing.
126    None,
127    /// Runs a frontend-defined operation. See [`ScriptExpression`].
128    Expression { expression: SE },
129    /// Allocates a register of the queried type.
130    ///
131    /// A register has to be defined before it is used, and is dropped when the
132    /// scope that defined it ends.
133    DefineRegister { query: TypeQuery<'a> },
134    /// Destroys the value in a register, leaving it empty.
135    DropRegister { index: usize },
136    /// Moves a register's value onto the stack, leaving the register empty.
137    PushFromRegister { index: usize },
138    /// Moves the top stack value into a register.
139    PopToRegister { index: usize },
140    /// Moves a value from one register to another.
141    MoveRegister { from: usize, to: usize },
142    /// Finds a function in the registry and invokes it.
143    CallFunction { query: FunctionQuery<'a> },
144    /// Takes a `bool` off the stack and runs one of two scopes.
145    ///
146    /// The failure scope is optional, in which case a `false` does nothing.
147    BranchScope {
148        scope_success: ScriptHandle<'a, SE>,
149        scope_failure: Option<ScriptHandle<'a, SE>>,
150    },
151    /// Repeats a scope while a `bool` taken off the stack is `true`.
152    ///
153    /// The scope must leave a fresh `bool` on the stack before it ends,
154    /// otherwise the next round reads whatever happens to be there.
155    LoopScope { scope: ScriptHandle<'a, SE> },
156    /// Runs a nested scope and comes back when it ends.
157    PushScope { scope: ScriptHandle<'a, SE> },
158    /// Ends the current scope early, the way `return` does.
159    PopScope,
160    /// Takes a `bool` off the stack and ends the current scope when it is
161    /// `false`.
162    ContinueScopeConditionally,
163    /// Stops stepping and reports back to the caller without finishing.
164    ///
165    /// For frontends building coroutines or futures. Anything to yield has to
166    /// be left on the stack for the host to take.
167    Suspend,
168}
169
170impl<SE: ScriptExpression> ScriptOperation<'_, SE> {
171    /// Returns the operation's name, for debugging and tooling.
172    pub fn label(&self) -> &str {
173        match self {
174            Self::None => "None",
175            Self::Expression { .. } => "Expression",
176            Self::DefineRegister { .. } => "DefineRegister",
177            Self::DropRegister { .. } => "DropRegister",
178            Self::PushFromRegister { .. } => "PushFromRegister",
179            Self::PopToRegister { .. } => "PopToRegister",
180            Self::MoveRegister { .. } => "MoveRegister",
181            Self::CallFunction { .. } => "CallFunction",
182            Self::BranchScope { .. } => "BranchScope",
183            Self::LoopScope { .. } => "LoopScope",
184            Self::PushScope { .. } => "PushScope",
185            Self::PopScope => "PopScope",
186            Self::ContinueScopeConditionally => "ContinueScopeConditionally",
187            Self::Suspend => "Suspend",
188        }
189    }
190}
191
192/// Assembles a [`Script`] one operation at a time.
193///
194/// ```ignore
195/// let script = ScriptBuilder::<MyExpression>::default()
196///     .expression(MyExpression::Literal(40))
197///     .expression(MyExpression::Literal(2))
198///     .call_function(FunctionQuery { name: Some("add".into()), ..Default::default() })
199///     .build();
200/// ```
201pub struct ScriptBuilder<'a, SE: ScriptExpression>(Script<'a, SE>);
202
203impl<SE: ScriptExpression> Default for ScriptBuilder<'_, SE> {
204    fn default() -> Self {
205        Self(vec![])
206    }
207}
208
209impl<'a, SE: ScriptExpression> ScriptBuilder<'a, SE> {
210    /// Finishes the script and shares it.
211    pub fn build(self) -> ScriptHandle<'a, SE> {
212        ScriptHandle::new(self.0)
213    }
214
215    /// Appends [`ScriptOperation::Expression`].
216    pub fn expression(mut self, expression: SE) -> Self {
217        self.0.push(ScriptOperation::Expression { expression });
218        self
219    }
220
221    /// Appends [`ScriptOperation::DefineRegister`].
222    pub fn define_register(mut self, query: TypeQuery<'a>) -> Self {
223        self.0.push(ScriptOperation::DefineRegister { query });
224        self
225    }
226
227    /// Appends [`ScriptOperation::DropRegister`].
228    pub fn drop_register(mut self, index: usize) -> Self {
229        self.0.push(ScriptOperation::DropRegister { index });
230        self
231    }
232
233    /// Appends [`ScriptOperation::PushFromRegister`].
234    pub fn push_from_register(mut self, index: usize) -> Self {
235        self.0.push(ScriptOperation::PushFromRegister { index });
236        self
237    }
238
239    /// Appends [`ScriptOperation::PopToRegister`].
240    pub fn pop_to_register(mut self, index: usize) -> Self {
241        self.0.push(ScriptOperation::PopToRegister { index });
242        self
243    }
244
245    /// Appends [`ScriptOperation::MoveRegister`].
246    pub fn move_register(mut self, from: usize, to: usize) -> Self {
247        self.0.push(ScriptOperation::MoveRegister { from, to });
248        self
249    }
250
251    /// Appends [`ScriptOperation::CallFunction`].
252    pub fn call_function(mut self, query: FunctionQuery<'a>) -> Self {
253        self.0.push(ScriptOperation::CallFunction { query });
254        self
255    }
256
257    /// Appends [`ScriptOperation::BranchScope`].
258    pub fn branch_scope(
259        mut self,
260        scope_success: ScriptHandle<'a, SE>,
261        scope_failure: Option<ScriptHandle<'a, SE>>,
262    ) -> Self {
263        self.0.push(ScriptOperation::BranchScope {
264            scope_success,
265            scope_failure,
266        });
267        self
268    }
269
270    /// Appends [`ScriptOperation::LoopScope`].
271    pub fn loop_scope(mut self, scope: ScriptHandle<'a, SE>) -> Self {
272        self.0.push(ScriptOperation::LoopScope { scope });
273        self
274    }
275
276    /// Appends [`ScriptOperation::PushScope`].
277    pub fn push_scope(mut self, scope: ScriptHandle<'a, SE>) -> Self {
278        self.0.push(ScriptOperation::PushScope { scope });
279        self
280    }
281
282    /// Appends [`ScriptOperation::PopScope`].
283    pub fn pop_scope(mut self) -> Self {
284        self.0.push(ScriptOperation::PopScope);
285        self
286    }
287
288    /// Appends [`ScriptOperation::ContinueScopeConditionally`].
289    pub fn continue_scope_conditionally(mut self) -> Self {
290        self.0.push(ScriptOperation::ContinueScopeConditionally);
291        self
292    }
293
294    /// Appends [`ScriptOperation::Suspend`].
295    pub fn suspend(mut self) -> Self {
296        self.0.push(ScriptOperation::Suspend);
297        self
298    }
299}
300
301/// A function parameter as a frontend describes it, by type query rather than
302/// by resolved type.
303#[derive(Debug)]
304pub struct ScriptFunctionParameter<'a> {
305    /// Metadata attached to this parameter.
306    pub meta: Option<Meta>,
307    /// Parameter name.
308    pub name: String,
309    /// Query that picks the parameter type.
310    pub type_query: TypeQuery<'a>,
311}
312
313impl ScriptFunctionParameter<'_> {
314    /// Resolves the type query against the registry.
315    ///
316    /// # Panics
317    ///
318    /// Panics when no registered type matches.
319    pub fn build(&self, registry: &Registry) -> FunctionParameter {
320        FunctionParameter {
321            meta: self.meta.to_owned(),
322            name: self.name.to_owned(),
323            type_handle: registry
324                .types()
325                .find(|type_| self.type_query.is_valid(type_))
326                .unwrap()
327                .clone(),
328        }
329    }
330}
331
332/// A function signature as a frontend describes it, with types still
333/// unresolved.
334#[derive(Debug)]
335pub struct ScriptFunctionSignature<'a> {
336    /// Metadata attached to this function.
337    pub meta: Option<Meta>,
338    /// Name to register the function under.
339    pub name: String,
340    /// Module the function belongs to.
341    pub module_name: Option<String>,
342    /// Query that picks the type this function is a method of.
343    pub type_query: Option<TypeQuery<'a>>,
344    /// How widely the function is visible.
345    pub visibility: Visibility,
346    /// Arguments, in declaration order.
347    pub inputs: Vec<ScriptFunctionParameter<'a>>,
348    /// Results, in declaration order.
349    pub outputs: Vec<ScriptFunctionParameter<'a>>,
350}
351
352impl ScriptFunctionSignature<'_> {
353    /// Resolves every type query against the registry.
354    ///
355    /// # Panics
356    ///
357    /// Panics when a type query matches nothing.
358    pub fn build(&self, registry: &Registry) -> FunctionSignature {
359        FunctionSignature {
360            meta: self.meta.to_owned(),
361            name: self.name.to_owned(),
362            module_name: self.module_name.to_owned(),
363            type_handle: self.type_query.as_ref().map(|type_query| {
364                registry
365                    .types()
366                    .find(|type_| type_query.is_valid(type_))
367                    .unwrap()
368                    .clone()
369            }),
370            visibility: self.visibility,
371            inputs: self
372                .inputs
373                .iter()
374                .map(|parameter| parameter.build(registry))
375                .collect(),
376            outputs: self
377                .outputs
378                .iter()
379                .map(|parameter| parameter.build(registry))
380                .collect(),
381        }
382    }
383}
384
385/// A function a frontend produced: an unresolved signature and its
386/// operations.
387#[derive(Debug)]
388pub struct ScriptFunction<'a, SE: ScriptExpression> {
389    /// Signature, with its types still unresolved.
390    pub signature: ScriptFunctionSignature<'a>,
391    /// Operations that make up the body.
392    pub script: ScriptHandle<'a, SE>,
393}
394
395impl<SE: ScriptExpression> ScriptFunction<'static, SE> {
396    /// Turns this into a real function with the given backend and registers it.
397    ///
398    /// Returns whatever the backend produced alongside the function, or
399    /// [`None`] when the backend declined.
400    pub fn install<SFG: ScriptFunctionGenerator<SE>>(
401        &self,
402        registry: &mut Registry,
403        input: SFG::Input,
404    ) -> Option<SFG::Output> {
405        let (function, output) = SFG::generate_function(self, registry, input)?;
406        registry.add_function(function);
407        Some(output)
408    }
409}
410
411/// A backend: turns script operations into a runnable function body.
412///
413/// A virtual machine implements this by keeping the operations and stepping
414/// through them. A transpiler could emit code instead. `Input` is whatever the
415/// backend needs to be given, `Output` whatever it wants to hand back.
416pub trait ScriptFunctionGenerator<SE: ScriptExpression> {
417    /// Configuration the backend needs, passed through at install time.
418    type Input;
419    /// Anything the backend wants to return alongside the function.
420    type Output;
421
422    /// Builds a body from a script, or returns [`None`] when it cannot.
423    fn generate_function_body(
424        script: ScriptHandle<'static, SE>,
425        input: Self::Input,
426    ) -> Option<(FunctionBody, Self::Output)>;
427
428    /// Builds a whole function, resolving the signature against the registry.
429    fn generate_function(
430        function: &ScriptFunction<'static, SE>,
431        registry: &Registry,
432        input: Self::Input,
433    ) -> Option<(Function, Self::Output)> {
434        let (body, output) = Self::generate_function_body(function.script.clone(), input)?;
435        Some((
436            Function::new(function.signature.build(registry), body),
437            output,
438        ))
439    }
440}
441
442/// A struct field as a frontend describes it, by type query rather than by
443/// resolved type.
444#[derive(Debug)]
445pub struct ScriptStructField<'a> {
446    /// Metadata attached to this field.
447    pub meta: Option<Meta>,
448    /// Field name.
449    pub name: String,
450    /// How widely the field is visible.
451    pub visibility: Visibility,
452    /// Query that picks the field type.
453    pub type_query: TypeQuery<'a>,
454}
455
456impl ScriptStructField<'_> {
457    /// Resolves the type query against the registry.
458    ///
459    /// # Panics
460    ///
461    /// Panics when no registered type matches.
462    pub fn build(&self, registry: &Registry) -> StructField {
463        let mut result = StructField::new(
464            &self.name,
465            registry
466                .types()
467                .find(|type_| self.type_query.is_valid(type_))
468                .unwrap()
469                .clone(),
470        )
471        .with_visibility(self.visibility);
472        result.meta.clone_from(&self.meta);
473        result
474    }
475}
476
477/// A struct a frontend produced.
478///
479/// Installing takes two passes, [`ScriptStruct::declare`] then
480/// [`ScriptStruct::define`], so that types can refer to each other.
481#[derive(Debug)]
482pub struct ScriptStruct<'a> {
483    /// Metadata attached to this type.
484    pub meta: Option<Meta>,
485    /// Name to register the type under.
486    pub name: String,
487    /// Module the type belongs to.
488    pub module_name: Option<String>,
489    /// How widely the type is visible.
490    pub visibility: Visibility,
491    /// Fields, in declaration order.
492    pub fields: Vec<ScriptStructField<'a>>,
493}
494
495impl ScriptStruct<'_> {
496    /// Registers the type with no fields yet, so other types can name it.
497    pub fn declare(&self, registry: &mut Registry) {
498        let mut builder = RuntimeStructBuilder::new(&self.name);
499        builder = builder.visibility(self.visibility);
500        if let Some(module_name) = self.module_name.as_ref() {
501            builder = builder.module_name(module_name);
502        }
503        if let Some(meta) = self.meta.as_ref() {
504            builder = builder.meta(meta.to_owned());
505        }
506        registry.add_type(builder.build());
507    }
508
509    /// Fills in the fields of an already declared type, in place.
510    ///
511    /// Does nothing when the type was never declared.
512    pub fn define(&self, registry: &mut Registry) {
513        let query = TypeQuery {
514            name: Some(self.name.as_str().into()),
515            module_name: self
516                .module_name
517                .as_ref()
518                .map(|module_name| module_name.into()),
519            ..Default::default()
520        };
521        if let Some(handle) = registry.find_type(query) {
522            let mut builder = RuntimeStructBuilder::new(&self.name);
523            builder = builder.visibility(self.visibility);
524            if let Some(module_name) = self.module_name.as_ref() {
525                builder = builder.module_name(module_name);
526            }
527            if let Some(meta) = self.meta.as_ref() {
528                builder = builder.meta(meta.to_owned());
529            }
530            for field in &self.fields {
531                builder = builder.field(field.build(registry));
532            }
533            unsafe {
534                let type_ = Arc::as_ptr(&handle).cast_mut();
535                *type_ = builder.build().into();
536            }
537        }
538    }
539
540    /// Registers the type complete with fields, in one pass.
541    ///
542    /// Only works when every field type is already registered. Otherwise use
543    /// declare and define.
544    pub fn install(&self, registry: &mut Registry) {
545        let mut builder = RuntimeStructBuilder::new(&self.name);
546        builder = builder.visibility(self.visibility);
547        if let Some(module_name) = self.module_name.as_ref() {
548            builder = builder.module_name(module_name);
549        }
550        for field in &self.fields {
551            builder = builder.field(field.build(registry));
552        }
553        registry.add_type(builder.build());
554    }
555}
556
557/// An enum variant as a frontend describes it.
558#[derive(Debug)]
559pub struct ScriptEnumVariant<'a> {
560    /// Metadata attached to this variant.
561    pub meta: Option<Meta>,
562    /// Variant name.
563    pub name: String,
564    /// Fields the variant carries.
565    pub fields: Vec<ScriptStructField<'a>>,
566    /// Discriminant to give the variant. Counted from the previous one when
567    /// [`None`].
568    pub discriminant: Option<u8>,
569}
570
571impl ScriptEnumVariant<'_> {
572    /// Resolves the field type queries against the registry.
573    ///
574    /// # Panics
575    ///
576    /// Panics when a field type query matches nothing.
577    pub fn build(&self, registry: &Registry) -> EnumVariant {
578        let mut result = EnumVariant::new(&self.name);
579        result.fields = self
580            .fields
581            .iter()
582            .map(|field| field.build(registry))
583            .collect();
584        result.meta.clone_from(&self.meta);
585        result
586    }
587}
588
589/// An enum a frontend produced.
590///
591/// Installing takes two passes, [`ScriptEnum::declare`] then
592/// [`ScriptEnum::define`], so that types can refer to each other.
593#[derive(Debug)]
594pub struct ScriptEnum<'a> {
595    /// Metadata attached to this type.
596    pub meta: Option<Meta>,
597    /// Name to register the type under.
598    pub name: String,
599    /// Module the type belongs to.
600    pub module_name: Option<String>,
601    /// How widely the type is visible.
602    pub visibility: Visibility,
603    /// Variants, in declaration order.
604    pub variants: Vec<ScriptEnumVariant<'a>>,
605    /// Discriminant a default value holds.
606    pub default_variant: Option<u8>,
607}
608
609impl ScriptEnum<'_> {
610    /// Registers the type with no variants yet, so other types can name it.
611    pub fn declare(&self, registry: &mut Registry) {
612        let mut builder = RuntimeEnumBuilder::new(&self.name);
613        if let Some(discriminant) = self.default_variant {
614            builder = builder.set_default_variant(discriminant);
615        }
616        builder = builder.visibility(self.visibility);
617        if let Some(module_name) = self.module_name.as_ref() {
618            builder = builder.module_name(module_name);
619        }
620        if let Some(meta) = self.meta.as_ref() {
621            builder = builder.meta(meta.to_owned());
622        }
623        registry.add_type(builder.build());
624    }
625
626    /// Fills in the variants of an already declared type, in place.
627    ///
628    /// Does nothing when the type was never declared.
629    pub fn define(&self, registry: &mut Registry) {
630        let query = TypeQuery {
631            name: Some(self.name.as_str().into()),
632            module_name: self
633                .module_name
634                .as_ref()
635                .map(|module_name| module_name.into()),
636            ..Default::default()
637        };
638        if let Some(handle) = registry.find_type(query) {
639            let mut builder = RuntimeEnumBuilder::new(&self.name);
640            if let Some(discriminant) = self.default_variant {
641                builder = builder.set_default_variant(discriminant);
642            }
643            builder = builder.visibility(self.visibility);
644            if let Some(module_name) = self.module_name.as_ref() {
645                builder = builder.module_name(module_name);
646            }
647            if let Some(meta) = self.meta.as_ref() {
648                builder = builder.meta(meta.to_owned());
649            }
650            for variant in &self.variants {
651                if let Some(discriminant) = variant.discriminant {
652                    builder =
653                        builder.variant_with_discriminant(variant.build(registry), discriminant);
654                } else {
655                    builder = builder.variant(variant.build(registry));
656                }
657            }
658            unsafe {
659                let type_ = Arc::as_ptr(&handle).cast_mut();
660                *type_ = builder.build().into();
661            }
662        }
663    }
664
665    /// Registers the type complete with variants, in one pass.
666    ///
667    /// Only works when every field type is already registered. Otherwise use
668    /// declare and define.
669    pub fn install(&self, registry: &mut Registry) {
670        let mut builder = RuntimeEnumBuilder::new(&self.name);
671        if let Some(discriminant) = self.default_variant {
672            builder = builder.set_default_variant(discriminant);
673        }
674        builder = builder.visibility(self.visibility);
675        if let Some(module_name) = self.module_name.as_ref() {
676            builder = builder.module_name(module_name);
677        }
678        for variant in &self.variants {
679            if let Some(discriminant) = variant.discriminant {
680                builder = builder.variant_with_discriminant(variant.build(registry), discriminant);
681            } else {
682                builder = builder.variant(variant.build(registry));
683            }
684        }
685        registry.add_type(builder.build());
686    }
687}
688
689/// A named group of types and functions, as a frontend produced it.
690#[derive(Debug, Default)]
691pub struct ScriptModule<'a, SE: ScriptExpression> {
692    /// Module name, stamped onto everything inside it.
693    pub name: String,
694    /// Structs the module declares.
695    pub structs: Vec<ScriptStruct<'a>>,
696    /// Enums the module declares.
697    pub enums: Vec<ScriptEnum<'a>>,
698    /// Functions the module declares.
699    pub functions: Vec<ScriptFunction<'a, SE>>,
700}
701
702impl<SE: ScriptExpression> ScriptModule<'_, SE> {
703    /// Stamps this module's name onto every type and function inside it.
704    pub fn fix_module_names(&mut self) {
705        for type_ in &mut self.structs {
706            type_.module_name = Some(self.name.to_owned());
707        }
708        for type_ in &mut self.enums {
709            type_.module_name = Some(self.name.to_owned());
710        }
711        for function in &mut self.functions {
712            function.signature.module_name = Some(self.name.to_owned());
713        }
714    }
715
716    /// First pass: registers every type without its fields.
717    pub fn declare_types(&self, registry: &mut Registry) {
718        for type_ in &self.structs {
719            type_.declare(registry);
720        }
721        for type_ in &self.enums {
722            type_.declare(registry);
723        }
724    }
725
726    /// Second pass: fills in the fields of every declared type.
727    pub fn define_types(&self, registry: &mut Registry) {
728        for type_ in &self.structs {
729            type_.define(registry);
730        }
731        for type_ in &self.enums {
732            type_.define(registry);
733        }
734    }
735
736    /// Runs both type passes.
737    pub fn install_types(&self, registry: &mut Registry) {
738        self.declare_types(registry);
739        self.define_types(registry);
740    }
741}
742
743impl<SE: ScriptExpression> ScriptModule<'static, SE> {
744    /// Compiles every function with the given backend and registers it.
745    pub fn install_functions<SFG: ScriptFunctionGenerator<SE>>(
746        &self,
747        registry: &mut Registry,
748        input: SFG::Input,
749    ) where
750        SFG::Input: Clone,
751    {
752        for function in &self.functions {
753            function.install::<SFG>(registry, input.clone());
754        }
755    }
756}
757
758/// A whole compilation unit: the modules a frontend produced.
759#[derive(Debug, Default)]
760pub struct ScriptPackage<'a, SE: ScriptExpression> {
761    /// Modules this unit is made of.
762    pub modules: Vec<ScriptModule<'a, SE>>,
763}
764
765impl<SE: ScriptExpression> ScriptPackage<'static, SE> {
766    /// Installs everything into a registry.
767    ///
768    /// Every module's types go in first, across the whole package, so functions
769    /// and fields can refer to types from any module.
770    pub fn install<SFG: ScriptFunctionGenerator<SE>>(
771        &self,
772        registry: &mut Registry,
773        input: SFG::Input,
774    ) where
775        SFG::Input: Clone,
776    {
777        for module in &self.modules {
778            module.install_types(registry);
779        }
780        for module in &self.modules {
781            module.install_functions::<SFG>(registry, input.clone());
782        }
783    }
784}
785
786/// One loaded source unit, as [`ScriptContentProvider::unpack_load`] returns
787/// it.
788///
789/// `data` carries the load error rather than failing the whole batch, so one
790/// bad file does not hide the rest.
791pub struct ScriptContent<T> {
792    /// Path the content was loaded from.
793    pub path: String,
794    /// Name to refer to this unit by, often the same as the path.
795    pub name: String,
796    /// The parsed content, nothing to load, or the error that stopped it.
797    pub data: Result<Option<T>, Box<dyn Error>>,
798}
799
800/// Fetches script source by path.
801///
802/// A frontend follows imports through this trait. Where the source lives, on
803/// disk, in an archive or generated on the fly, is then not the frontend's
804/// problem.
805pub trait ScriptContentProvider<T> {
806    /// Loads and parses one unit, or returns [`None`] when there is nothing to
807    /// load.
808    fn load(&mut self, path: &str) -> Result<Option<T>, Box<dyn Error>>;
809
810    /// Loads a path that may hold several units, such as an archive.
811    ///
812    /// Defaults to a single [`ScriptContentProvider::load`].
813    fn unpack_load(&mut self, path: &str) -> Result<Vec<ScriptContent<T>>, Box<dyn Error>> {
814        Ok(vec![ScriptContent {
815            path: path.to_owned(),
816            name: path.to_owned(),
817            data: self.load(path),
818        }])
819    }
820
821    /// Turns a path into the canonical form used to recognise the same unit
822    /// twice.
823    ///
824    /// Defaults to leaving it alone.
825    fn sanitize_path(&self, path: &str) -> Result<String, Box<dyn Error>> {
826        Ok(path.to_owned())
827    }
828
829    /// Resolves an import path written inside `parent`.
830    fn join_paths(&self, parent: &str, relative: &str) -> Result<String, Box<dyn Error>>;
831}
832
833/// Dispatches to another provider based on the file extension.
834///
835/// Lets one package mix source formats, for example a text language
836/// importing a serialized module.
837pub struct ExtensionContentProvider<S> {
838    default_extension: Option<String>,
839    extension_providers: HashMap<String, Box<dyn ScriptContentProvider<S>>>,
840}
841
842impl<S> Default for ExtensionContentProvider<S> {
843    fn default() -> Self {
844        Self {
845            default_extension: None,
846            extension_providers: Default::default(),
847        }
848    }
849}
850
851impl<S> ExtensionContentProvider<S> {
852    /// Sets the extension to assume for paths that carry none.
853    pub fn default_extension(mut self, extension: impl ToString) -> Self {
854        self.default_extension = Some(extension.to_string());
855        self
856    }
857
858    /// Routes one extension to a provider.
859    pub fn extension(
860        mut self,
861        extension: &str,
862        content_provider: impl ScriptContentProvider<S> + 'static,
863    ) -> Self {
864        self.extension_providers
865            .insert(extension.to_owned(), Box::new(content_provider));
866        self
867    }
868}
869
870impl<S> ScriptContentProvider<S> for ExtensionContentProvider<S> {
871    fn load(&mut self, _: &str) -> Result<Option<S>, Box<dyn Error>> {
872        Ok(None)
873    }
874
875    fn unpack_load(&mut self, path: &str) -> Result<Vec<ScriptContent<S>>, Box<dyn Error>> {
876        let extension = match Path::new(path).extension() {
877            Some(extension) => extension.to_string_lossy().to_string(),
878            None => match &self.default_extension {
879                Some(extension) => extension.to_owned(),
880                None => return Err(Box::new(ExtensionContentProviderError::NoDefaultExtension)),
881            },
882        };
883        if let Some(content_provider) = self.extension_providers.get_mut(&extension) {
884            content_provider.unpack_load(path)
885        } else {
886            Err(Box::new(
887                ExtensionContentProviderError::ContentProviderForExtensionNotFound(extension),
888            ))
889        }
890    }
891
892    fn sanitize_path(&self, path: &str) -> Result<String, Box<dyn Error>> {
893        let extension = match Path::new(path).extension() {
894            Some(extension) => extension.to_string_lossy().to_string(),
895            None => match &self.default_extension {
896                Some(extension) => extension.to_owned(),
897                None => return Err(Box::new(ExtensionContentProviderError::NoDefaultExtension)),
898            },
899        };
900        if let Some(content_provider) = self.extension_providers.get(&extension) {
901            content_provider.sanitize_path(path)
902        } else {
903            Err(Box::new(
904                ExtensionContentProviderError::ContentProviderForExtensionNotFound(extension),
905            ))
906        }
907    }
908
909    fn join_paths(&self, parent: &str, relative: &str) -> Result<String, Box<dyn Error>> {
910        let extension = match Path::new(relative).extension() {
911            Some(extension) => extension.to_string_lossy().to_string(),
912            None => match &self.default_extension {
913                Some(extension) => extension.to_owned(),
914                None => return Err(Box::new(ExtensionContentProviderError::NoDefaultExtension)),
915            },
916        };
917        if let Some(content_provider) = self.extension_providers.get(&extension) {
918            content_provider.join_paths(parent, relative)
919        } else {
920            Err(Box::new(
921                ExtensionContentProviderError::ContentProviderForExtensionNotFound(extension),
922            ))
923        }
924    }
925}
926
927/// What can go wrong while routing by extension.
928#[derive(Debug)]
929pub enum ExtensionContentProviderError {
930    /// A path had no extension and no default was set.
931    NoDefaultExtension,
932    /// No provider is registered for that extension.
933    ContentProviderForExtensionNotFound(String),
934}
935
936impl std::fmt::Display for ExtensionContentProviderError {
937    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
938        match self {
939            ExtensionContentProviderError::NoDefaultExtension => {
940                write!(f, "No default extension set")
941            }
942            ExtensionContentProviderError::ContentProviderForExtensionNotFound(extension) => {
943                write!(
944                    f,
945                    "Could not find content provider for extension: `{extension}`"
946                )
947            }
948        }
949    }
950}
951
952impl Error for ExtensionContentProviderError {}
953
954/// A provider that loads nothing.
955///
956/// Useful for an extension that should be recognised but skipped.
957pub struct IgnoreContentProvider;
958
959impl<S> ScriptContentProvider<S> for IgnoreContentProvider {
960    fn load(&mut self, _: &str) -> Result<Option<S>, Box<dyn Error>> {
961        Ok(None)
962    }
963
964    fn join_paths(&self, parent: &str, relative: &str) -> Result<String, Box<dyn Error>> {
965        Ok(format!("{parent}/{relative}"))
966    }
967}
968
969/// Turns raw file bytes into whatever a frontend works with.
970pub trait BytesContentParser<T> {
971    /// Parses the bytes.
972    fn parse(&self, bytes: Vec<u8>) -> Result<T, Box<dyn Error>>;
973}
974
975/// Loads scripts from the file system.
976///
977/// Paths without an extension get the configured one, and are canonicalized
978/// so the same file reached by two paths is recognised as one.
979pub struct FileContentProvider<T> {
980    extension: String,
981    parser: Box<dyn BytesContentParser<T>>,
982}
983
984impl<T> FileContentProvider<T> {
985    /// Builds a provider for one extension and parser.
986    pub fn new(extension: impl ToString, parser: impl BytesContentParser<T> + 'static) -> Self {
987        Self {
988            extension: extension.to_string(),
989            parser: Box::new(parser),
990        }
991    }
992}
993
994impl<T> ScriptContentProvider<T> for FileContentProvider<T> {
995    fn load(&mut self, path: &str) -> Result<Option<T>, Box<dyn Error>> {
996        Ok(Some(self.parser.parse(std::fs::read(path)?)?))
997    }
998
999    fn sanitize_path(&self, path: &str) -> Result<String, Box<dyn Error>> {
1000        let mut result = PathBuf::from(path);
1001        if result.extension().is_none() {
1002            result.set_extension(&self.extension);
1003        }
1004        Ok(result.canonicalize()?.to_string_lossy().into_owned())
1005    }
1006
1007    fn join_paths(&self, parent: &str, relative: &str) -> Result<String, Box<dyn Error>> {
1008        let mut path = PathBuf::from(parent);
1009        path.pop();
1010        Ok(path.join(relative).to_string_lossy().into_owned())
1011    }
1012}