microcad_lang/builtin/
workpiece.rs

1// Copyright © 2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! Builtin function evaluation entity
5
6use custom_debug::Debug;
7use microcad_core::{Geometry2D, Geometry3D};
8use strum::Display;
9
10use crate::{
11    eval::*, model::*, render::RenderResult, resolve::Symbol, src_ref::SrcRef, syntax::*, value::*,
12};
13
14/// Builtin function type
15pub type BuiltinFn =
16    dyn Fn(Option<&ParameterValueList>, &ArgumentValueList, &mut Context) -> EvalResult<Value>;
17
18/// Builtin function struct
19#[derive(Clone)]
20pub struct Builtin {
21    /// Name of the builtin function
22    pub id: Identifier,
23
24    /// Optional parameter value list to check the builtin signature.
25    pub parameters: Option<ParameterValueList>,
26
27    /// Functor to evaluate this function
28    pub f: &'static BuiltinFn,
29}
30
31impl std::fmt::Debug for Builtin {
32    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
33        write!(f, "__builtin::{}", &self.id)
34    }
35}
36
37impl Builtin {
38    /// Return identifier
39    pub fn id(&self) -> Identifier {
40        self.id.clone()
41    }
42}
43
44impl CallTrait for Builtin {
45    /// Call builtin function with given parameter
46    /// # Arguments
47    /// - `args`: Function arguments
48    /// - `context`: Execution context
49    fn call(&self, args: &ArgumentValueList, context: &mut Context) -> EvalResult<Value> {
50        (self.f)(self.parameters.as_ref(), args, context)
51    }
52}
53
54/// The kind of the built-in workbench determines its output.
55#[derive(Debug, Clone, Display)]
56pub enum BuiltinWorkbenchKind {
57    /// A parametric 2D primitive.
58    Primitive2D,
59    /// A parametric 3D primitive.
60    Primitive3D,
61    /// An affine transformation.
62    Transform,
63    /// An operation on a model.
64    Operation,
65}
66
67/// The return value when calling a built-in workpiece.
68pub enum BuiltinWorkpieceOutput {
69    /// 2D geometry output.
70    Primitive2D(Geometry2D),
71    /// 3D geometry output.
72    Primitive3D(Geometry3D),
73    /// Transformation.
74    Transform(AffineTransform),
75    /// Operation.
76    Operation(Box<dyn Operation>),
77}
78
79/// Builtin sketch function type.
80pub type BuiltinWorkpieceFn = dyn Fn(&Tuple) -> RenderResult<BuiltinWorkpieceOutput>;
81
82/// The built-in workpiece.
83#[derive(Clone, Debug)]
84pub struct BuiltinWorkpiece {
85    /// Kind of the workpiece.
86    pub kind: BuiltinWorkbenchKind,
87    /// Output type
88    pub output_type: OutputType,
89    /// Creator symbol.
90    pub creator: Creator,
91    /// The function that will be called when the workpiece is rendered.
92    #[debug(skip)]
93    pub f: &'static BuiltinWorkpieceFn,
94}
95
96impl BuiltinWorkpiece {
97    /// Call the workpiece with its arguments.
98    pub fn call(&self) -> RenderResult<BuiltinWorkpieceOutput> {
99        (self.f)(&self.creator.arguments)
100    }
101}
102
103impl std::fmt::Display for BuiltinWorkpiece {
104    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105        writeln!(
106            f,
107            "{kind} {creator}",
108            kind = self.kind,
109            creator = self.creator,
110        )
111    }
112}
113
114/// Builtin part definition
115pub trait BuiltinWorkbenchDefinition {
116    /// Get id of the builtin part
117    fn id() -> &'static str;
118
119    /// The kind of the built-in workbench.
120    fn kind() -> BuiltinWorkbenchKind;
121
122    /// The expected output type.
123    fn output_type() -> OutputType {
124        OutputType::NotDetermined
125    }
126
127    /// The function that generates an output from the workpiece.
128    fn workpiece_function() -> &'static BuiltinWorkpieceFn;
129
130    /// Construct the workpiece from an argument tuple.
131    fn workpiece(creator: Creator) -> BuiltinWorkpiece {
132        BuiltinWorkpiece {
133            kind: Self::kind(),
134            output_type: Self::output_type(),
135            creator,
136            f: Self::workpiece_function(),
137        }
138    }
139
140    /// Create model from argument map
141    fn model(creator: Creator) -> Model {
142        ModelBuilder::new(
143            Element::BuiltinWorkpiece(Self::workpiece(creator)),
144            SrcRef(None),
145        )
146        .build()
147    }
148
149    /// Workbench function
150    fn function() -> &'static BuiltinFn {
151        &|params, args, context| {
152            log::trace!(
153                "Built-in workbench {call} {id:?}({args})",
154                call = crate::mark!(CALL),
155                id = Self::id()
156            );
157            Ok(Value::Model(
158                ArgumentMatch::find_multi_match(
159                    args,
160                    params.expect("A built-in part must have a parameter list"),
161                )?
162                .iter()
163                .map(|tuple| Self::model(Creator::new(context.current_symbol(), tuple.clone())))
164                .collect::<Models>()
165                .to_multiplicity(SrcRef(None)),
166            ))
167        }
168    }
169
170    /// Part initialization parameters
171    fn parameters() -> ParameterValueList {
172        ParameterValueList::default()
173    }
174
175    /// Create builtin symbol
176    fn symbol() -> Symbol {
177        Symbol::new_builtin(
178            Identifier::no_ref(Self::id()),
179            Some(Self::parameters()),
180            Self::function(),
181        )
182    }
183}