translator_docs/
lib.rs

1/*!
2    # Writing a Surfer Translator Plugin
3
4    Surfer translators are web-asssembly binaries that are loaded at runtime by Surfer.
5    They can be written in any language that has an `extism` plugin
6    development kit
7    [https://extism.org/docs/concepts/pdk/](https://extism.org/docs/concepts/pdk/).
8
9    For this example we will use Rust since that is what the rest of Surfer is written in, which allows us to reuse type definitions between Surfer itself and the plugin.
10
11    To create a plugin, create a new project
12    ```bash
13    cargo init --lib cool_surfer_translator
14    ```
15    then modify the `Cargo.toml` to set the library type to "cdylib", and add the `extism_pdk` and `surfer-translation-types` library as dependencies
16    ```toml
17    [lib]
18    crate-type = ["cdylib"]
19
20    [dependencies]
21    extism-pdk = "1.4.1"
22    surfer-translation-types.git = "https://gitlab.com/surfer-project/surfer.git"
23    ```
24
25    In your new project, you now need to define a few functions which must all
26    be annotated with `#[plugin_fn]` and have the right type signature. Click on each function to learn more
27
28    - [name]: sets the name of the plugin in the format selection list
29    - [translates]: allows the plugin to opt in or out of translating certain signals
30    - [variable_info]: specifies the hierarchical structure of the signal
31    - [translate]: does the actual translation of bit vectors to new values
32
33    In addition, there are a few [optional] functions that can be implemented for additional
34    functionality
35    - [new]: Called once on plugin load
36    - [reload]: Called when Surfer reloads the waveform
37    - [set_wave_source]: Called when the current waveform changes
38    - [variable_name_info]: Translate signal names
39
40    ## Accessing Files
41
42    Surfer plugins are sandboxed and are in general not allowed _any_ access to the external
43    world. Translators may need to read the file system however, and for that, "host functions"
44    are provided. To use them, define them in your plugin using
45
46    ```rust
47    use extism_pdk::host_fn;
48
49    #[host_fn]
50    extern "ExtismHost" {
51        pub fn read_file(filename: String) -> Vec<u8>;
52        pub fn file_exists(filename: String) -> bool;
53    }
54    ```
55
56    ## Maintaining State
57
58    Plugins may need to maintain state between calls. This can be done by
59    simply using static variables in the plugin.
60    ```
61    static STATE: Mutex<bool> = Mutex::new(false)
62    ```
63
64    > NOTE: The static variables are shared between all "instances" of the
65    > translator, i.e. if you want to maintain different state for different
66    > variables, this must currently be handled on the plugin side.
67
68    ## Testing and Installation
69
70    To build your plugin, call
71    ```bash
72    cargo build --debug --target wasm32-unknown-unknown
73    ```
74    which will create `target/debug/cool_surfer_translator.wasm`
75
76    This file can then be copied to the local or global plugin translator directories in order to be found and automatically loaded by Surfer
77
78    Local:
79    ```
80    .surfer/translators/
81    ```
82
83    Global
84    | Os      | Path                                                                  |
85    |---------|-----------------------------------------------------------------------|
86    | Linux   | `~/.config/surfer/translators`.                                       |
87    | Windows | `C:\Users\<Name>\AppData\Roaming\surfer-project\surfer\config\translators`.  |
88    | macOS   | `/Users/<Name>/Library/Application Support/org.surfer-project.surfer/translators` |
89*/
90
91use extism_pdk::FnResult;
92use surfer_translation_types::plugin_types::TranslateParams;
93use surfer_translation_types::{
94    TranslationPreference, TranslationResult, ValueKind, VariableInfo, VariableMeta,
95};
96
97/// Returns the name of the plugin as shown to the user. This needs to be unique, so
98/// do not set it to a translator name that is already present in Surfer.
99///
100/// While it is possible to change the name between calls, doing so will cause
101/// unexpected behaviour.
102pub fn name() -> FnResult<&'static str> {
103    Ok("Docs Plugin")
104}
105
106/// Returns a translation preference for the specified variable, which allows
107/// the translator to opt out of translating certain signals which it does not
108/// support.
109///
110/// For example, a translator which translates 32 bit floating point values should return
111/// [TranslationPreference::Yes] for bit variables with `num_bits == 32` and
112/// [TranslationPreference::No] for other signals.
113///
114/// Translators also have the option of returning [TranslationPreference::Prefer] to
115/// not only allow their use on a signal, but make it the _default_ translator for that signal.
116/// This should be used with caution and only in cases where the translator is _sure_ that the
117/// translator is a sane default. A prototypical example is translators for custom HDLs where
118/// it is known that the signal came from the custom HDL.
119pub fn translates(_variable: VariableMeta<(), ()>) -> FnResult<TranslationPreference> {
120    Ok(TranslationPreference::Yes)
121}
122
123/// Returns information about the hierarchical structure of the signal. For translators
124/// which simply want to do bit vector to string and/or color translation, returning
125/// [VariableInfo::Bits] is sufficient.
126///
127/// For compound signals, [VariableInfo::Compound] is used, which allows the user to
128/// expand the signal into its available subfields. If subfields specified here
129/// are omitted by the [translate] function, they will be left empty during the corresponding
130/// clock cycles.
131pub fn variable_info(variable: VariableMeta<(), ()>) -> FnResult<VariableInfo> {
132    Ok(VariableInfo::Compound {
133        subfields: (0..(variable.num_bits.unwrap_or_default() / 4 + 1))
134            .map(|i| (format!("[{i}]"), VariableInfo::Bits))
135            .collect(),
136    })
137}
138
139/// Gets called once for every value of every signal being rendered, and
140/// returns the corresponding translated value.
141///
142/// For non-hierarchical values, returning
143/// ```notest
144/// Ok(TranslationResult {
145///     val: surfer_translation_types::ValueRepr::String(/* value here */),
146///     kind: ValueKind::Normal,
147///     subfields: vec![],
148/// })
149/// ```
150/// works, for hierarchical values, see [TranslationResult]
151///
152/// It is often helpful to destructure the params like this to not have to perform field
153/// access on the values
154/// ```notest
155/// pub fn translate(
156///     TranslateParams { variable, value }: TranslateParams,
157/// ) -> FnResult<TranslationResult> {}
158/// ```
159///
160pub fn translate(
161    TranslateParams {
162        variable: _,
163        value: _,
164    }: TranslateParams,
165) -> FnResult<TranslationResult> {
166    Ok(TranslationResult {
167        val: surfer_translation_types::ValueRepr::Tuple,
168        kind: ValueKind::Normal,
169        subfields: vec![],
170    })
171}
172
173/// Documentation for functions which are not necessary for a basic translator but can do more
174/// advanced things.
175pub mod optional {
176    use extism_pdk::Json;
177    use surfer_translation_types::translator::{TrueName, VariableNameInfo};
178
179    use super::*;
180
181    /// The new function is used to initialize a plugin. It is called once when the
182    /// plugin is loaded
183    pub fn new() -> FnResult<()> {
184        Ok(())
185    }
186
187    /// Called every time Surfer reloads the waveform. This can be used to
188    /// re-run any initialization that depends on which waveform is loaded.
189    ///
190    /// Note that `set_wave_source` is also called when reloading, so if the state
191    /// depends on the currently loaded waveform, `reload` is not necessary.
192    pub fn reload() -> FnResult<()> {
193        Ok(())
194    }
195
196    /// This is called whenever the wave source changes and can be used by the plugin to change
197    /// its behaviour depending on the currently loaded waveform.
198    pub fn set_wave_source(
199        Json(_wave_source): Json<Option<surfer_translation_types::WaveSource>>,
200    ) -> FnResult<()> {
201        Ok(())
202    }
203
204    /// Can be used to convert a variable name into a name that is more descriptive.
205    /// See [VariableNameInfo] and [TrueName] for details on the possible output.
206    ///
207    /// **NOTE** The user has no way to opt out of a translator that specifies a true name,
208    /// which means this feature should be used with caution and only on signals which
209    /// are likely to mean very little to the user in their original form. The original use
210    /// case for the feature is for translators for HDLs to translate temporary variables
211    /// into something more descriptive.
212    pub fn variable_name_info(
213        Json(_variable): Json<VariableMeta<(), ()>>,
214    ) -> FnResult<Option<VariableNameInfo>> {
215        let _ = TrueName::SourceCode {
216            line_number: 0,
217            before: String::new(),
218            this: String::new(),
219            after: String::new(),
220        };
221        Ok(None)
222    }
223}
224
225#[doc(hidden)]
226pub use optional::*;