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 - [new]: initializes the plugin
29 - [name]: sets the name of the plugin in the format selection list
30 - [translates]: allows the plugin to opt in or out of translating certain signals
31 - [variable_info]: specifies the hierarchical structure of the signal
32 - [translate]: does the actual translation of bit vectors to new values
33
34 In addition, there are a few [optional] functions that can be implemented for additional
35 functionality
36 - [reload]: Called when Surfer reloads the waveform
37 - [set_wave_source]: Called when the current waveform changes
38
39 ## Accessing Files
40
41 Surfer plugins are sandboxed and are in general not allowed _any_ access to the external
42 world. Translators may need to read the file system however, and for that, "host functions"
43 are provided. To use them, define them in your plugin using
44
45 ```rust
46 use extism_pdk::host_fn;
47
48 #[host_fn]
49 extern "ExtismHost" {
50 pub fn read_file(filename: String) -> Vec<u8>;
51 pub fn file_exists(filename: String) -> bool;
52 }
53 ```
54
55 ## Maintaining State
56
57 Plugins may need to maintain state between calls. This can be done by
58 simply using static variables in the plugin.
59 ```
60 static STATE: Mutex<bool> = Mutex::new(false)
61 ```
62
63 > NOTE: The static variables are shared between all "instances" of the
64 > translator, i.e. if you want to maintain different state for different
65 > variables, this must currently be handled on the plugin side.
66
67 ## Testing and Installation
68
69 To build your plugin, call
70 ```bash
71 cargo build --debug --target wasm32-unknown-unknown
72 ```
73 which will create `target/debug/cool_surfer_translator.wasm`
74
75 This file can then be copied to the local or global plugin translator directories in order to be found and automatically loaded by Surfer
76
77 Local:
78 ```
79 .surfer/translators/
80 ```
81
82 Global
83 | Os | Path |
84 |---------|-----------------------------------------------------------------------|
85 | Linux | `~/.config/surfer/translators`. |
86 | Windows | `C:\Users\<Name>\AppData\Roaming\surfer-project\surfer\config\translators`. |
87 | macOS | `/Users/<Name>/Library/Application Support/org.surfer-project.surfer/translators` |
88*/
89
90use extism_pdk::FnResult;
91use surfer_translation_types::plugin_types::TranslateParams;
92use surfer_translation_types::{
93 TranslationPreference, TranslationResult, ValueKind, VariableInfo, VariableMeta,
94};
95
96/// The new function is used to initialize a plugin. It is called once when the
97/// plugin is loaded
98pub fn new() -> FnResult<()> {
99 Ok(())
100}
101
102/// Returns the name of the plugin as shown to the user. This needs to be unique, so
103/// do not set it to a translator name that is already present in Surfer.
104///
105/// While it is possible to change the name between calls, doing so will cause
106/// unexpected behaviour.
107pub fn name() -> FnResult<&'static str> {
108 Ok("Docs Plugin")
109}
110
111/// Returns a translation preference for the specified variable, which allows
112/// the translator to opt out of translating certain signals which it does not
113/// support.
114///
115/// For example, a translator which translates 32 bit floating point values should return
116/// [TranslationPreference::Yes] for bit variables with `num_bits == 32` and
117/// [TranslationPreference::No] for other signals.
118///
119/// Translators also have the option of returning [TranslationPreference::Prefer] to
120/// not only allow their use on a signal, but make it the _default_ translator for that signal.
121/// This should be used with caution and only in cases where the translator is _sure_ that the
122/// translator is a sane default. A prototypical example is translators for custom HDLs where
123/// it is known that the signal came from the custom HDL.
124pub fn translates(_variable: VariableMeta<(), ()>) -> FnResult<TranslationPreference> {
125 Ok(TranslationPreference::Yes)
126}
127
128/// Returns information about the hierarchical structure of the signal. For translators
129/// which simply want to do bit vector to string and/or color translation, returning
130/// [VariableInfo::Bits] is sufficient.
131///
132/// For compound signals, [VariableInfo::Compound] is used, which allows the user to
133/// expand the signal into its available subfields. If subfields specified here
134/// are omitted by the [translate] function, they will be left empty during the corresponding
135/// clock cycles.
136pub fn variable_info(variable: VariableMeta<(), ()>) -> FnResult<VariableInfo> {
137 Ok(VariableInfo::Compound {
138 subfields: (0..(variable.num_bits.unwrap_or_default() / 4 + 1))
139 .map(|i| (format!("[{i}]"), VariableInfo::Bits))
140 .collect(),
141 })
142}
143
144/// Gets called once for every value of every signal being rendered, and
145/// returns the corresponding translated value.
146///
147/// For non-hierarchical values, returning
148/// ```notest
149/// Ok(TranslationResult {
150/// val: surfer_translation_types::ValueRepr::String(/* value here */),
151/// kind: ValueKind::Normal,
152/// subfields: vec![],
153/// })
154/// ```
155/// works, for hierarchical values, see [TranslationResult]
156///
157/// It is often helpful to destructure the params like this to not have to perform field
158/// access on the values
159/// ```notest
160/// pub fn translate(
161/// TranslateParams { variable, value }: TranslateParams,
162/// ) -> FnResult<TranslationResult> {}
163/// ```
164///
165pub fn translate(
166 TranslateParams {
167 variable: _,
168 value: _,
169 }: TranslateParams,
170) -> FnResult<TranslationResult> {
171 Ok(TranslationResult {
172 val: surfer_translation_types::ValueRepr::Tuple,
173 kind: ValueKind::Normal,
174 subfields: vec![],
175 })
176}
177
178/// Documentation for functions which are not necessary for a basic translator but can do more
179/// advanced things.
180pub mod optional {
181 use extism_pdk::Json;
182
183 use super::*;
184
185 /// Called every time Surfer reloads the waveform. This can be used to
186 /// re-run any initialization that depends on which waveform is loaded.
187 ///
188 /// Note that `set_wave_source` is also called when reloading, so if the state
189 /// depends on the currently loaded waveform, `reload` is not necessary.
190 pub fn reload() -> FnResult<()> {
191 Ok(())
192 }
193
194 /// This is called whenever the wave source changes and can be used by the plugin to change
195 /// its behaviour depending on the currently loaded waveform.
196 pub fn set_wave_source(
197 Json(_wave_source): Json<Option<surfer_translation_types::WaveSource>>,
198 ) -> FnResult<()> {
199 Ok(())
200 }
201}
202
203#[doc(hidden)]
204pub use optional::*;