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