Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion scpi-contrib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ clap = {version = "4.0", features = ["derive"]}
[dev-dependencies]
csv = "1.1"
serde = { version = "1", features = ["derive"] }
rand = "0.8"
rand = "0.9"

[features]
default = []
Expand Down
2 changes: 1 addition & 1 deletion scpi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ features = [
]

[dependencies.uom]
version = "0.36.0"
version = "0.37.0"
default-features = false
optional = true
features = ["autoconvert", "f32", "f64", "si", "try-from"]
Expand Down
76 changes: 76 additions & 0 deletions scpi/examples/custom_formatter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use scpi::{
error::{self, Result},
tree::prelude::*,
};

use std::io::{self, BufRead, Write};

struct MyDevice;
impl Device for MyDevice {
fn handle_error(&mut self, err: Error) {
eprintln!("Error: {err}")
}
}

struct HelloWorldCommand;
impl Command<MyDevice> for HelloWorldCommand {
// Allow only queries
cmd_qonly!();

// Called when a query is made
fn query(
&self,
_device: &mut MyDevice,
_context: &mut Context,
mut params: Parameters,
mut resp: ResponseUnit,
) -> scpi::error::Result<()> {
let target: Option<&str> = params.next_optional_data()?;
if let Some(target) = target {
let greeting = format!("Hello {target}");
resp.data(greeting.as_bytes()).finish()
} else {
resp.data(b"Hello world".as_slice()).finish()
}
}
}

struct MyCustomFormatter;

impl Formatter for MyCustomFormatter {
fn push_str(&mut self, s: &[u8]) -> Result<()> {
std::io::stdout()
.write(s)
.map_err(|_| error::ErrorCode::ExecutionError)?;
Ok(())
}

fn push_byte(&mut self, b: u8) -> Result<()> {
std::io::stdout()
.write(&[b])
.map_err(|_| error::ErrorCode::ExecutionError)?;
Ok(())
}
}

const MYTREE: Node<MyDevice> = Root![
Leaf!(b"*COM" => &HelloWorldCommand),
Branch![ b"HELLo";
Leaf!(default b"WORLd" => &HelloWorldCommand)
]
];

fn main() {
let mut device = MyDevice;

let stdin = io::stdin();
for command in stdin.lock().lines() {
let command = command.unwrap().into_bytes();

// Prepare a context and buffer to put a response into
let mut context = Context::default();

// Execute command
let _res = MYTREE.run(&command, &mut device, &mut context, &mut MyCustomFormatter);
}
}
5 changes: 4 additions & 1 deletion scpi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@
/// Does output buffer contain data?
pub mav: bool,

pub output: bool,

/// User context data.
///
/// **Do not use this to pass application data!**
Expand All @@ -156,12 +158,13 @@
Context {
mav: false,
user: &(),
output: false,
}
}

// Create a new context with user data
pub fn new_with_user(user: &'a dyn Any) -> Self {
Context { mav: false, user }
Context { user, ..Default::default() }

Check warning on line 167 in scpi/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

scpi/src/lib.rs#L167

Added line #L167 was not covered by tests
}

/// Get user context data.
Expand Down
38 changes: 2 additions & 36 deletions scpi/src/parser/response/arrayformatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@ use arrayvec::ArrayVec;

use crate::error::{ErrorCode, Result};

use super::{
Formatter, ResponseUnit, RESPONSE_MESSAGE_TERMINATOR, RESPONSE_MESSAGE_UNIT_SEPARATOR,
};
use super::Formatter;

impl<const CAP: usize> Formatter for ArrayVec<u8, CAP> {
/// Internal use
fn push_str(&mut self, s: &[u8]) -> Result<()> {
self.try_extend_from_slice(s)
.map_err(|_| ErrorCode::OutOfMemory.into())
Expand All @@ -16,38 +13,6 @@ impl<const CAP: usize> Formatter for ArrayVec<u8, CAP> {
fn push_byte(&mut self, b: u8) -> Result<()> {
self.try_push(b).map_err(|_| ErrorCode::OutOfMemory.into())
}

fn as_slice(&self) -> &[u8] {
self.as_slice()
}

fn clear(&mut self) {
self.clear();
}

fn len(&self) -> usize {
self.len()
}

fn message_start(&mut self) -> Result<()> {
Ok(())
}

fn message_end(&mut self) -> Result<()> {
self.push_byte(RESPONSE_MESSAGE_TERMINATOR)
}

fn response_unit(&mut self) -> Result<ResponseUnit> {
if !self.is_empty() {
self.push_byte(RESPONSE_MESSAGE_UNIT_SEPARATOR)?;
}
Ok(ResponseUnit {
fmt: self,
result: Ok(()),
has_header: false,
has_data: false,
})
}
}

#[cfg(test)]
Expand All @@ -68,6 +33,7 @@ mod tests {
.finish()
.unwrap();
// Second unit
array.message_unit_separator().unwrap();
array.response_unit().unwrap().data(42i16).finish().unwrap();
array.message_end().unwrap();
assert_eq!(array.as_slice(), b"\"potato\",0;42\n");
Expand Down
40 changes: 24 additions & 16 deletions scpi/src/parser/response/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,26 +241,17 @@ pub trait Formatter {
///Push single byte to output
fn push_byte(&mut self, b: u8) -> Result<()>;

/// Get underlying buffer as a byte slice
fn as_slice(&self) -> &[u8];

/// Clear buffer
fn clear(&mut self);

/// Returns length of buffer
fn len(&self) -> usize;

fn is_empty(&self) -> bool {
self.len() == 0
}

/* Control */

/// Start a response message
fn message_start(&mut self) -> Result<()>;
fn message_start(&mut self) -> Result<()> {
Ok(())
}

/// End a response message
fn message_end(&mut self) -> Result<()>;
fn message_end(&mut self) -> Result<()> {
self.push_byte(RESPONSE_MESSAGE_TERMINATOR)
}

/* Formatters */

Expand All @@ -274,7 +265,15 @@ pub trait Formatter {
self.push_byte(RESPONSE_HEADER_SEPARATOR)
}

fn response_unit(&mut self) -> Result<ResponseUnit>;
/// Insert a new [RESPONSE_MESSAGE_UNIT_SEPARATOR].
fn message_unit_separator(&mut self) -> Result<()> {
self.push_byte(RESPONSE_MESSAGE_UNIT_SEPARATOR)
}

/// Create a new [ResponseUnit].
fn response_unit(&mut self) -> Result<ResponseUnit> where Self: Sized {
Ok(ResponseUnit::new(self))
}
}

/// A response unit returned by a query
Expand All @@ -286,6 +285,15 @@ pub struct ResponseUnit<'a> {
}

impl<'a> ResponseUnit<'a> {
pub fn new(fmt: &'a mut dyn Formatter) -> Self {
ResponseUnit {
fmt,
result: Ok(()),
has_header: false,
has_data: false,
}
}

/// Response header
///
/// **Warning**: Panics if called after [`Self::data`]
Expand Down
36 changes: 1 addition & 35 deletions scpi/src/parser/response/vecformatter.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use crate::error::Result;

use super::{
Formatter, ResponseUnit, RESPONSE_MESSAGE_TERMINATOR, RESPONSE_MESSAGE_UNIT_SEPARATOR,
};
use super::Formatter;

impl Formatter for alloc::vec::Vec<u8> {
/// Internal use
Expand All @@ -15,36 +13,4 @@ impl Formatter for alloc::vec::Vec<u8> {
self.push(b);
Ok(())
}

fn as_slice(&self) -> &[u8] {
self.as_slice()
}

fn clear(&mut self) {
self.clear();
}

fn len(&self) -> usize {
self.len()
}

fn message_start(&mut self) -> Result<()> {
Ok(())
}

fn message_end(&mut self) -> Result<()> {
self.push_byte(RESPONSE_MESSAGE_TERMINATOR)
}

fn response_unit(&mut self) -> Result<ResponseUnit> {
if !self.is_empty() {
self.push_byte(RESPONSE_MESSAGE_UNIT_SEPARATOR)?;
}
Ok(ResponseUnit {
fmt: self,
result: Ok(()),
has_header: false,
has_data: false,
})
}
}
29 changes: 23 additions & 6 deletions scpi/src/tree/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ pub mod prelude {
response::{Formatter, ResponseData, ResponseUnit},
tokenizer::{Token, Tokenizer},
},
Context, Device,
Branch, Context, Device, Leaf, Root, cmd_qonly, cmd_nquery, cmd_both
};
}

Expand Down Expand Up @@ -266,7 +266,12 @@ where
where
FMT: Formatter,
{
// Start a new message
context.output = false;

// Tokenize string
let mut tokenizer = Tokenizer::new(command).peekable();
// Execute
let res = self.run_tokens(device, context, &mut tokenizer, response);
if let Err(err) = &res {
device.handle_error(*err);
Expand All @@ -288,7 +293,7 @@ where

//Start response message
response.message_start()?;
loop {
let res = loop {
// Execute header
match tokens.peek() {
// :header..
Expand Down Expand Up @@ -320,9 +325,6 @@ where
match tokens.next() {
// EOM
None => {
if !response.is_empty() {
response.message_end()?;
}
break Ok(());
}
// New unit
Expand All @@ -340,7 +342,14 @@ where
// Error
Some(Err(err)) => break Err(Error::new(err)),
}
};

// Terminate any partial message
if context.output {
response.message_end()?;
}

res
}

pub(crate) fn exec<FMT>(
Expand Down Expand Up @@ -383,8 +392,16 @@ where
// Consume header seperator
tokens.next_if(|t| matches!(t, Ok(Token::ProgramHeaderSeparator)));

// Start a new response unit with a seperator if not the first query.
let response_unit = if context.output {
response.message_unit_separator()?;
response.response_unit()?
} else {
context.output = true;
response.response_unit()?
};

// Execute handler
let response_unit = response.response_unit()?;
handler.query(device, context, Parameters::with(tokens), response_unit)
}
// This is a leaf node, cannot traverse further
Expand Down