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.
98///
99/// This needs to be unique, so do not set it to a translator name that is already present in Surfer.
100///
101/// While it is possible to change the name between calls, doing so will cause
102/// unexpected behaviour.
103pub fn name() -> FnResult<&'static str> {
104 Ok("Docs Plugin")
105}
106
107/// Returns a translation preference for the specified variable, which allows
108/// the translator to opt out of translating certain signals which it does not
109/// support.
110///
111/// For example, a translator which translates 32 bit floating point values should return
112/// [TranslationPreference::Yes] for bit variables with `num_bits == 32` and
113/// [TranslationPreference::No] for other signals.
114///
115/// Translators also have the option of returning [TranslationPreference::Prefer] to
116/// not only allow their use on a signal, but make it the _default_ translator for that signal.
117/// This should be used with caution and only in cases where the translator is _sure_ that the
118/// translator is a sane default. A prototypical example is translators for custom HDLs where
119/// it is known that the signal came from the custom HDL.
120pub fn translates(_variable: VariableMeta<(), ()>) -> FnResult<TranslationPreference> {
121 Ok(TranslationPreference::Yes)
122}
123
124/// Returns information about the hierarchical structure of the signal.
125///
126/// For translators
127/// which simply want to do bit vector to string and/or color translation, returning
128/// [VariableInfo::Bits] is sufficient.
129///
130/// For compound signals, [VariableInfo::Compound] is used, which allows the user to
131/// expand the signal into its available subfields. If subfields specified here
132/// are omitted by the [translate] function, they will be left empty during the corresponding
133/// clock cycles.
134pub fn variable_info(variable: VariableMeta<(), ()>) -> FnResult<VariableInfo> {
135 Ok(VariableInfo::Compound {
136 subfields: (0..(variable.num_bits.unwrap_or_default() / 4 + 1))
137 .map(|i| (format!("[{i}]"), VariableInfo::Bits))
138 .collect(),
139 })
140}
141
142/// Gets called once for every value of every signal being rendered, and
143/// returns the corresponding translated value.
144///
145/// For non-hierarchical values, returning
146/// ```notest
147/// Ok(TranslationResult {
148/// val: surfer_translation_types::ValueRepr::String(/* value here */),
149/// kind: ValueKind::Normal,
150/// subfields: vec![],
151/// })
152/// ```
153/// works, for hierarchical values, see [TranslationResult]
154///
155/// It is often helpful to destructure the params like this to not have to perform field
156/// access on the values
157/// ```notest
158/// pub fn translate(
159/// TranslateParams { variable, value }: TranslateParams,
160/// ) -> FnResult<TranslationResult> {}
161/// ```
162///
163pub fn translate(
164 TranslateParams {
165 variable: _,
166 value: _,
167 }: TranslateParams,
168) -> FnResult<TranslationResult> {
169 Ok(TranslationResult {
170 val: surfer_translation_types::ValueRepr::Tuple,
171 kind: ValueKind::Normal,
172 subfields: vec![],
173 })
174}
175
176/// Documentation for functions which are not necessary for a basic translator but can do more
177/// advanced things.
178pub mod optional {
179 use extism_pdk::Json;
180 use surfer_translation_types::translator::{TrueName, VariableNameInfo};
181
182 use super::*;
183
184 /// The new function is used to initialize a plugin.
185 ///
186 /// It is called once when the plugin is loaded
187 pub fn new() -> FnResult<()> {
188 Ok(())
189 }
190
191 /// Called every time Surfer reloads the waveform.
192 ///
193 /// This can be used to re-run any initialization that depends on which waveform is loaded.
194 ///
195 /// Note that `set_wave_source` is also called when reloading, so if the state
196 /// depends on the currently loaded waveform, `reload` is not necessary.
197 pub fn reload() -> FnResult<()> {
198 Ok(())
199 }
200
201 /// This is called whenever the wave source changes and can be used by the plugin to change
202 /// its behaviour depending on the currently loaded waveform.
203 pub fn set_wave_source(
204 Json(_wave_source): Json<Option<surfer_translation_types::WaveSource>>,
205 ) -> FnResult<()> {
206 Ok(())
207 }
208
209 /// Can be used to convert a variable name into a name that is more descriptive.
210 /// See [VariableNameInfo] and [TrueName] for details on the possible output.
211 ///
212 /// **NOTE** The user has no way to opt out of a translator that specifies a true name,
213 /// which means this feature should be used with caution and only on signals which
214 /// are likely to mean very little to the user in their original form. The original use
215 /// case for the feature is for translators for HDLs to translate temporary variables
216 /// into something more descriptive.
217 pub fn variable_name_info(
218 Json(_variable): Json<VariableMeta<(), ()>>,
219 ) -> FnResult<Option<VariableNameInfo>> {
220 let _ = TrueName::SourceCode {
221 line_number: 0,
222 before: String::new(),
223 this: String::new(),
224 after: String::new(),
225 };
226 Ok(None)
227 }
228}
229
230#[doc(hidden)]
231pub use optional::*;