Skip to main content

brep_app/
scripting.rs

1//! Scripts in the CAD app: the CAD-side host for the interpreter the PLM's
2//! administrator hooks run on (`BREP_script_core`, plm-cad-integration-todo
3//! §3 S11).
4//!
5//! The core evaluates one file and calls one global function with one JSON
6//! argument; this module decides what a script running inside the CAD app can
7//! reach. Today that is:
8//!
9//! | Global | What it is |
10//! | --- | --- |
11//! | `console.log / info / warn / error` | lines kept with the outcome (the core's own binding) |
12//! | `cad.version` | this app's version |
13//! | `cad.store` | `"file"` natively, `"browser"` in the wasm build |
14//! | `cad.plm` | `false`: no PLM is connected |
15//! | `ui.notify(text)` | a message for the user, kept with the outcome for the host to show |
16//!
17//! There are NO `plm.*` bindings yet. They arrive with the PLM transport (S1),
18//! and every one of them will need a signed-in session; [`PLM_BINDINGS`] names
19//! them so a script can be told which of its calls need a server. Until then a
20//! script that reaches for `plm` finds it undefined, which is also exactly
21//! what it finds with no server connected — the file-only case is the only
22//! case, and it runs the same natively and in the browser.
23
24use std::cell::RefCell;
25use std::rc::Rc;
26
27use brep_script_core::boa_engine::object::ObjectInitializer;
28use brep_script_core::boa_engine::property::Attribute;
29// Keys are spelled `JsString::from`, not `js_string!`: that macro expands to
30// `::boa_engine`, which this crate reaches only through BREP_script_core.
31use brep_script_core::boa_engine::{Context, JsResult, JsString, JsValue, NativeFunction};
32pub use brep_script_core::{Failure, Success};
33use serde_json::Value;
34
35/// The `plm.*` bindings a CAD script can call, each needing a connected PLM.
36/// Empty until S1 lands the transport.
37pub const PLM_BINDINGS: &[&str] = &[];
38
39/// Where the app keeps its documents, as `cad.store` reports it.
40pub fn store_kind() -> &'static str {
41    if cfg!(target_arch = "wasm32") {
42        "browser"
43    } else {
44        "file"
45    }
46}
47
48/// A finished call: the core's result plus what the script asked the UI to
49/// show.
50#[derive(Debug, Clone)]
51pub struct Outcome<T> {
52    pub result: T,
53    pub notices: Vec<String>,
54}
55
56/// Evaluate `source` (named `file` in messages) with the CAD host installed,
57/// then call its global `function` with `input`.
58pub fn call(source: &str, file: &str, function: &str, input: &Value) -> Result<Outcome<Success>, Outcome<Failure>> {
59    let notices: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));
60    let installed = notices.clone();
61    let result = brep_script_core::call(source, file, function, input, &move |context, _| {
62        install(context, &installed)
63    });
64    let notices = notices.borrow().clone();
65    match result {
66        Ok(result) => Ok(Outcome { result, notices }),
67        Err(result) => Err(Outcome { result, notices }),
68    }
69}
70
71/// `cad` and `ui`.
72fn install(context: &mut Context, notices: &Rc<RefCell<Vec<String>>>) -> JsResult<()> {
73    let cad = ObjectInitializer::new(context)
74        .property(JsString::from("version"), JsString::from(env!("CARGO_PKG_VERSION")), Attribute::READONLY)
75        .property(JsString::from("store"), JsString::from(store_kind()), Attribute::READONLY)
76        .property(JsString::from("plm"), false, Attribute::READONLY)
77        .build();
78    context.register_global_property(JsString::from("cad"), cad, Attribute::all())?;
79
80    let notices = notices.clone();
81    // SAFETY: the closure captures an `Rc<RefCell<Vec<String>>>`, which holds
82    // no garbage-collected value, so the collector has nothing in it to trace.
83    let notify = unsafe {
84        NativeFunction::from_closure(move |_, args, context| {
85            let line = args
86                .iter()
87                .map(|value| brep_script_core::text_of(value, context))
88                .collect::<Vec<_>>()
89                .join(" ");
90            notices.borrow_mut().push(line);
91            Ok(JsValue::undefined())
92        })
93    };
94    let ui = ObjectInitializer::new(context).function(notify, JsString::from("notify"), 1).build();
95    context.register_global_property(JsString::from("ui"), ui, Attribute::all())
96}
97