feat: add display UI with splash screen, dashboard, and RSSI sparkline

Implement full SSD1306 OLED display support:
- Splash screen on boot (1.5s) with project name, version, board
- Dashboard showing radio state, config, counters, RSSI/SNR
- RSSI history sparkline (64-sample ring buffer, 2px-wide bars)
- DisplayOn/DisplayOff host commands routed via dedicated channel
- Pure render functions against generic DrawTarget (no hardware coupling)
- Display on/off control bypasses radio task entirely

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
swaitsandClaude Opus 4.6 committed 2026-03-29 20:42:54 -06:00
1 parent 7064110293
commit 3102fc747a
17624 files changed
+4564928 -38

No files matched your search

+1694
View File
File diff suppressed because it is too large. Load diff
+54
View File
@@ -0,0 +1,54 @@
use std::fmt::{self, Debug, Display};
/// Errors that can occur when serializing or deserializing TOML.
pub struct Error(Box<ErrorInner>);
pub(crate) enum ErrorInner {
Ser(crate::ser::Error),
De(crate::de::Error),
}
impl Error {
/// Produces a (line, column) pair of the position of the error if
/// available.
///
/// All indexes are 0-based.
pub fn line_col(&self) -> Option<(usize, usize)> {
match &*self.0 {
ErrorInner::Ser(_) => None,
ErrorInner::De(error) => error.line_col(),
}
}
}
impl From<crate::ser::Error> for Error {
fn from(error: crate::ser::Error) -> Self {
Error(Box::new(ErrorInner::Ser(error)))
}
}
impl From<crate::de::Error> for Error {
fn from(error: crate::de::Error) -> Self {
Error(Box::new(ErrorInner::De(error)))
}
}
impl Display for Error {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
match &*self.0 {
ErrorInner::Ser(error) => Display::fmt(error, formatter),
ErrorInner::De(error) => Display::fmt(error, formatter),
}
}
}
impl Debug for Error {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
match &*self.0 {
ErrorInner::Ser(error) => Debug::fmt(error, formatter),
ErrorInner::De(error) => Debug::fmt(error, formatter),
}
}
}
impl std::error::Error for Error {}
+144
View File
@@ -0,0 +1,144 @@
//! [![github]](https://github.com/dtolnay/basic-toml)&ensp;[![crates-io]](https://crates.io/crates/basic-toml)&ensp;[![docs-rs]](https://docs.rs/basic-toml)
//!
//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
//!
//! <br>
//!
//! A library for parsing and producing data in [TOML] format using [Serde].
//!
//! TOML is designed to be "a config file format for humans": minimal and easy
//! to read due to obvious semantics.
//!
//! ```toml
//! [package]
//! name = "basic-toml"
#![doc = concat!("version = \"", env!("CARGO_PKG_VERSION_MAJOR"), ".", env!("CARGO_PKG_VERSION_MINOR"), ".", env!("CARGO_PKG_VERSION_PATCH"), "\"")]
//! authors = ["Alex Crichton <alex@alexcrichton.com>"]
//!
//! [dependencies]
//! serde = "1.0"
//! ```
//!
//! The TOML format is widely used throughout the Rust community for
//! configuration, notably being used by [Cargo], Rust's package manager.
//!
//! [TOML]: https://toml.io
//! [Serde]: https://serde.rs
//! [Cargo]: https://crates.io
//!
//! # Deserialization
//!
//! ```
//! use semver::{Version, VersionReq};
//! use serde_derive::Deserialize;
//! use std::collections::BTreeMap as Map;
//!
//! #[derive(Deserialize)]
//! struct Manifest {
//! package: Package,
//! #[serde(default)]
//! dependencies: Map<String, VersionReq>,
//! }
//!
//! #[derive(Deserialize)]
//! struct Package {
//! name: String,
//! version: Version,
//! #[serde(default)]
//! authors: Vec<String>,
//! }
//!
//! fn main() {
//! let manifest: Manifest = basic_toml::from_str(r#"
//! [package]
//! name = "basic-toml"
#![doc = concat!(" version = \"", env!("CARGO_PKG_VERSION_MAJOR"), ".", env!("CARGO_PKG_VERSION_MINOR"), ".", env!("CARGO_PKG_VERSION_PATCH"), "\"")]
//! authors = ["Alex Crichton <alex@alexcrichton.com>"]
//!
//! [dependencies]
//! serde = "^1.0"
//! "#).unwrap();
//!
//! assert_eq!(manifest.package.name, "basic-toml");
#![doc = concat!(" assert_eq!(manifest.package.version, Version::new(", env!("CARGO_PKG_VERSION_MAJOR"), ", ", env!("CARGO_PKG_VERSION_MINOR"), ", ", env!("CARGO_PKG_VERSION_PATCH"), "));")]
//! assert_eq!(manifest.package.authors, ["Alex Crichton <alex@alexcrichton.com>"]);
//! assert_eq!(manifest.dependencies["serde"].to_string(), "^1.0");
//! }
//! ```
//!
//! # Serialization
//!
//! ```
//! use semver::{Version, VersionReq};
//! use serde_derive::Serialize;
//! use std::collections::BTreeMap as Map;
//!
//! #[derive(Serialize)]
//! struct Manifest {
//! package: Package,
//! dependencies: Map<String, VersionReq>,
//! }
//!
//! #[derive(Serialize)]
//! struct Package {
//! name: String,
//! version: Version,
//! authors: Vec<String>,
//! }
//!
//! fn main() {
//! let manifest = Manifest {
//! package: Package {
//! name: "basic-toml".to_owned(),
#![doc = concat!(" version: Version::new(", env!("CARGO_PKG_VERSION_MAJOR"), ", ", env!("CARGO_PKG_VERSION_MINOR"), ", ", env!("CARGO_PKG_VERSION_PATCH"), "),")]
//! authors: vec!["Alex Crichton <alex@alexcrichton.com>".to_owned()],
//! },
//! dependencies: {
//! let mut dependencies = Map::new();
//! dependencies.insert("serde".to_owned(), "^1.0".parse().unwrap());
//! dependencies
//! },
//! };
//!
//! let toml = basic_toml::to_string(&manifest).unwrap();
//! print!("{}", toml);
//! }
//! ```
//!
//! # Spec compatibility
//!
//! TOML v0.5.0.
//!
//! TOML's date and time syntax are not supported.
#![doc(html_root_url = "https://docs.rs/basic-toml/0.1.10")]
#![deny(missing_docs)]
#![allow(
clippy::bool_to_int_with_if,
clippy::elidable_lifetime_names,
clippy::let_underscore_untyped,
clippy::manual_let_else,
clippy::manual_range_contains,
clippy::match_like_matches_macro,
clippy::missing_errors_doc,
clippy::must_use_candidate,
clippy::needless_doctest_main,
clippy::needless_lifetimes,
clippy::needless_pass_by_value,
clippy::similar_names,
clippy::type_complexity,
clippy::uninlined_format_args,
clippy::unnecessary_box_returns,
clippy::unwrap_or_default
)]
mod de;
mod error;
mod ser;
mod tokens;
pub use crate::de::{from_slice, from_str};
pub use crate::error::Error;
pub use crate::ser::to_string;
+833
View File
@@ -0,0 +1,833 @@
use serde::ser::{self, Serialize};
use std::cell::Cell;
use std::error;
use std::fmt::{self, Display, Write};
/// Serialize the given data structure as a String of TOML.
///
/// Serialization can fail if `T`'s implementation of `Serialize` decides to
/// fail, if `T` contains a map with non-string keys, or if `T` attempts to
/// serialize an unsupported datatype such as an enum, tuple, or tuple struct.
pub fn to_string<T>(value: &T) -> Result<String, crate::Error>
where
T: ?Sized + Serialize,
{
let mut dst = String::with_capacity(128);
value.serialize(&mut Serializer::new(&mut dst))?;
Ok(dst)
}
#[derive(Debug)]
pub(crate) enum Error {
/// Indicates that a Rust type was requested to be serialized but it was not
/// supported.
///
/// Currently the TOML format does not support serializing types such as
/// enums, tuples and tuple structs.
UnsupportedType,
/// The key of all TOML maps must be strings, but serialization was
/// attempted where the key of a map was not a string.
KeyNotString,
/// All values in a TOML table must be emitted before further tables are
/// emitted. If a value is emitted *after* a table then this error is
/// generated.
ValueAfterTable,
/// None was attempted to be serialized, but it's not supported.
UnsupportedNone,
/// A custom error which could be generated when serializing a particular
/// type.
Custom(String),
}
struct Serializer<'a> {
dst: &'a mut String,
state: State<'a>,
}
#[derive(Debug, Copy, Clone)]
enum ArrayState {
Started,
StartedAsATable,
}
#[derive(Debug, Clone)]
enum State<'a> {
Table {
key: &'a str,
parent: &'a State<'a>,
first: &'a Cell<bool>,
table_emitted: &'a Cell<bool>,
},
Array {
parent: &'a State<'a>,
first: &'a Cell<bool>,
type_: &'a Cell<Option<ArrayState>>,
len: Option<usize>,
},
End,
}
struct SerializeSeq<'a, 'b> {
ser: &'b mut Serializer<'a>,
first: Cell<bool>,
type_: Cell<Option<ArrayState>>,
len: Option<usize>,
}
struct SerializeTable<'a, 'b> {
ser: &'b mut Serializer<'a>,
key: String,
first: Cell<bool>,
table_emitted: Cell<bool>,
}
impl<'a> Serializer<'a> {
fn new(dst: &'a mut String) -> Serializer<'a> {
Serializer {
dst,
state: State::End,
}
}
fn display<T: Display>(&mut self, t: T, type_: ArrayState) -> Result<(), Error> {
self.emit_key(type_)?;
write!(self.dst, "{}", t).map_err(ser::Error::custom)?;
if let State::Table { .. } = self.state {
self.dst.push('\n');
}
Ok(())
}
fn emit_key(&mut self, type_: ArrayState) -> Result<(), Error> {
self.array_type(type_);
let state = self.state.clone();
self.do_emit_key(&state)
}
// recursive implementation of `emit_key` above
fn do_emit_key(&mut self, state: &State) -> Result<(), Error> {
match *state {
State::End => Ok(()),
State::Array {
parent,
first,
type_,
len,
} => {
assert!(type_.get().is_some());
if first.get() {
self.do_emit_key(parent)?;
}
self.emit_array(first, len);
Ok(())
}
State::Table {
parent,
first,
table_emitted,
key,
} => {
if table_emitted.get() {
return Err(Error::ValueAfterTable);
}
if first.get() {
self.emit_table_header(parent)?;
first.set(false);
}
self.escape_key(key)?;
self.dst.push_str(" = ");
Ok(())
}
}
}
fn emit_array(&mut self, first: &Cell<bool>, _len: Option<usize>) {
if first.get() {
self.dst.push('[');
} else {
self.dst.push_str(", ");
}
}
fn array_type(&mut self, type_: ArrayState) {
let prev = match self.state {
State::Array { type_, .. } => type_,
_ => return,
};
if prev.get().is_none() {
prev.set(Some(type_));
}
}
fn escape_key(&mut self, key: &str) -> Result<(), Error> {
let ok = !key.is_empty()
&& key.chars().all(|c| match c {
'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' => true,
_ => false,
});
if ok {
write!(self.dst, "{}", key).map_err(ser::Error::custom)?;
} else {
self.emit_str(key)?;
}
Ok(())
}
fn emit_str(&mut self, value: &str) -> Result<(), Error> {
self.dst.push('"');
for ch in value.chars() {
match ch {
'\u{8}' => self.dst.push_str("\\b"),
'\u{9}' => self.dst.push_str("\\t"),
'\u{a}' => self.dst.push_str("\\n"),
'\u{c}' => self.dst.push_str("\\f"),
'\u{d}' => self.dst.push_str("\\r"),
'\u{22}' => self.dst.push_str("\\\""),
'\u{5c}' => self.dst.push_str("\\\\"),
c if c <= '\u{1f}' || c == '\u{7f}' => {
write!(self.dst, "\\u{:04X}", ch as u32).map_err(ser::Error::custom)?;
}
ch => self.dst.push(ch),
}
}
self.dst.push('"');
Ok(())
}
fn emit_table_header(&mut self, state: &State) -> Result<(), Error> {
let array_of_tables = match *state {
State::End => return Ok(()),
State::Array { .. } => true,
State::Table { .. } => false,
};
// Unlike [..]s, we can't omit [[..]] ancestors, so be sure to emit
// table headers for them.
let mut p = state;
if let State::Array { first, parent, .. } = *state {
if first.get() {
p = parent;
}
}
while let State::Table { first, parent, .. } = *p {
p = parent;
if !first.get() {
break;
}
if let State::Array {
parent: &State::Table { .. },
..
} = *parent
{
self.emit_table_header(parent)?;
break;
}
}
match *state {
State::Table { first, .. } => {
if !first.get() {
// Newline if we are a table that is not the first table in
// the document.
self.dst.push('\n');
}
}
State::Array { parent, first, .. } => {
if !first.get() {
// Always newline if we are not the first item in the
// table-array
self.dst.push('\n');
} else if let State::Table { first, .. } = *parent {
if !first.get() {
// Newline if we are not the first item in the document
self.dst.push('\n');
}
}
}
State::End => {}
}
self.dst.push('[');
if array_of_tables {
self.dst.push('[');
}
self.emit_key_part(state)?;
if array_of_tables {
self.dst.push(']');
}
self.dst.push_str("]\n");
Ok(())
}
fn emit_key_part(&mut self, key: &State) -> Result<bool, Error> {
match *key {
State::Array { parent, .. } => self.emit_key_part(parent),
State::End => Ok(true),
State::Table {
key,
parent,
table_emitted,
..
} => {
table_emitted.set(true);
let first = self.emit_key_part(parent)?;
if !first {
self.dst.push('.');
}
self.escape_key(key)?;
Ok(false)
}
}
}
}
macro_rules! serialize_float {
($this:expr, $v:expr) => {{
$this.emit_key(ArrayState::Started)?;
match ($v.is_sign_negative(), $v.is_nan(), $v == 0.0) {
(_, true, _) => write!($this.dst, "nan"),
(true, false, true) => write!($this.dst, "-0.0"),
(false, false, true) => write!($this.dst, "0.0"),
(_, false, false) => write!($this.dst, "{}", $v).and_then(|()| {
if $v % 1.0 == 0.0 {
write!($this.dst, ".0")
} else {
Ok(())
}
}),
}
.map_err(ser::Error::custom)?;
if let State::Table { .. } = $this.state {
$this.dst.push_str("\n");
}
return Ok(());
}};
}
impl<'a, 'b> ser::Serializer for &'b mut Serializer<'a> {
type Ok = ();
type Error = Error;
type SerializeSeq = SerializeSeq<'a, 'b>;
type SerializeTuple = SerializeSeq<'a, 'b>;
type SerializeTupleStruct = SerializeSeq<'a, 'b>;
type SerializeTupleVariant = ser::Impossible<(), Error>;
type SerializeMap = SerializeTable<'a, 'b>;
type SerializeStruct = SerializeTable<'a, 'b>;
type SerializeStructVariant = ser::Impossible<(), Error>;
fn serialize_bool(self, v: bool) -> Result<(), Self::Error> {
self.display(v, ArrayState::Started)
}
fn serialize_i8(self, v: i8) -> Result<(), Self::Error> {
self.display(v, ArrayState::Started)
}
fn serialize_i16(self, v: i16) -> Result<(), Self::Error> {
self.display(v, ArrayState::Started)
}
fn serialize_i32(self, v: i32) -> Result<(), Self::Error> {
self.display(v, ArrayState::Started)
}
fn serialize_i64(self, v: i64) -> Result<(), Self::Error> {
self.display(v, ArrayState::Started)
}
fn serialize_u8(self, v: u8) -> Result<(), Self::Error> {
self.display(v, ArrayState::Started)
}
fn serialize_u16(self, v: u16) -> Result<(), Self::Error> {
self.display(v, ArrayState::Started)
}
fn serialize_u32(self, v: u32) -> Result<(), Self::Error> {
self.display(v, ArrayState::Started)
}
fn serialize_u64(self, v: u64) -> Result<(), Self::Error> {
self.display(v, ArrayState::Started)
}
fn serialize_f32(self, v: f32) -> Result<(), Self::Error> {
serialize_float!(self, v)
}
fn serialize_f64(self, v: f64) -> Result<(), Self::Error> {
serialize_float!(self, v)
}
fn serialize_char(self, v: char) -> Result<(), Self::Error> {
let mut buf = [0; 4];
self.serialize_str(v.encode_utf8(&mut buf))
}
fn serialize_str(self, value: &str) -> Result<(), Self::Error> {
self.emit_key(ArrayState::Started)?;
self.emit_str(value)?;
if let State::Table { .. } = self.state {
self.dst.push('\n');
}
Ok(())
}
fn serialize_bytes(self, value: &[u8]) -> Result<(), Self::Error> {
value.serialize(self)
}
fn serialize_none(self) -> Result<(), Self::Error> {
Err(Error::UnsupportedNone)
}
fn serialize_some<T>(self, value: &T) -> Result<(), Self::Error>
where
T: ?Sized + Serialize,
{
value.serialize(self)
}
fn serialize_unit(self) -> Result<(), Self::Error> {
Err(Error::UnsupportedType)
}
fn serialize_unit_struct(self, _name: &'static str) -> Result<(), Self::Error> {
Err(Error::UnsupportedType)
}
fn serialize_unit_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
) -> Result<(), Self::Error> {
self.serialize_str(variant)
}
fn serialize_newtype_struct<T>(self, _name: &'static str, value: &T) -> Result<(), Self::Error>
where
T: ?Sized + Serialize,
{
value.serialize(self)
}
fn serialize_newtype_variant<T>(
self,
_name: &'static str,
_variant_index: u32,
_variant: &'static str,
_value: &T,
) -> Result<(), Self::Error>
where
T: ?Sized + Serialize,
{
Err(Error::UnsupportedType)
}
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
self.array_type(ArrayState::Started);
Ok(SerializeSeq {
ser: self,
first: Cell::new(true),
type_: Cell::new(None),
len,
})
}
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, Self::Error> {
self.serialize_seq(Some(len))
}
fn serialize_tuple_struct(
self,
_name: &'static str,
len: usize,
) -> Result<Self::SerializeTupleStruct, Self::Error> {
self.serialize_seq(Some(len))
}
fn serialize_tuple_variant(
self,
_name: &'static str,
_variant_index: u32,
_variant: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleVariant, Self::Error> {
Err(Error::UnsupportedType)
}
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
self.array_type(ArrayState::StartedAsATable);
Ok(SerializeTable {
ser: self,
key: String::new(),
first: Cell::new(true),
table_emitted: Cell::new(false),
})
}
fn serialize_struct(
self,
_name: &'static str,
_len: usize,
) -> Result<Self::SerializeStruct, Self::Error> {
self.array_type(ArrayState::StartedAsATable);
Ok(SerializeTable {
ser: self,
key: String::new(),
first: Cell::new(true),
table_emitted: Cell::new(false),
})
}
fn serialize_struct_variant(
self,
_name: &'static str,
_variant_index: u32,
_variant: &'static str,
_len: usize,
) -> Result<Self::SerializeStructVariant, Self::Error> {
Err(Error::UnsupportedType)
}
}
impl<'a, 'b> ser::SerializeSeq for SerializeSeq<'a, 'b> {
type Ok = ();
type Error = Error;
fn serialize_element<T>(&mut self, value: &T) -> Result<(), Error>
where
T: ?Sized + Serialize,
{
value.serialize(&mut Serializer {
dst: &mut *self.ser.dst,
state: State::Array {
parent: &self.ser.state,
first: &self.first,
type_: &self.type_,
len: self.len,
},
})?;
self.first.set(false);
Ok(())
}
fn end(self) -> Result<(), Error> {
match self.type_.get() {
Some(ArrayState::StartedAsATable) => return Ok(()),
Some(ArrayState::Started) => self.ser.dst.push(']'),
None => {
assert!(self.first.get());
self.ser.emit_key(ArrayState::Started)?;
self.ser.dst.push_str("[]");
}
}
if let State::Table { .. } = self.ser.state {
self.ser.dst.push('\n');
}
Ok(())
}
}
impl<'a, 'b> ser::SerializeTuple for SerializeSeq<'a, 'b> {
type Ok = ();
type Error = Error;
fn serialize_element<T>(&mut self, value: &T) -> Result<(), Error>
where
T: ?Sized + Serialize,
{
ser::SerializeSeq::serialize_element(self, value)
}
fn end(self) -> Result<(), Error> {
ser::SerializeSeq::end(self)
}
}
impl<'a, 'b> ser::SerializeTupleStruct for SerializeSeq<'a, 'b> {
type Ok = ();
type Error = Error;
fn serialize_field<T>(&mut self, value: &T) -> Result<(), Error>
where
T: ?Sized + Serialize,
{
ser::SerializeSeq::serialize_element(self, value)
}
fn end(self) -> Result<(), Error> {
ser::SerializeSeq::end(self)
}
}
impl<'a, 'b> ser::SerializeMap for SerializeTable<'a, 'b> {
type Ok = ();
type Error = Error;
fn serialize_key<T>(&mut self, input: &T) -> Result<(), Error>
where
T: ?Sized + Serialize,
{
self.key = input.serialize(StringExtractor)?;
Ok(())
}
fn serialize_value<T>(&mut self, value: &T) -> Result<(), Error>
where
T: ?Sized + Serialize,
{
let res = value.serialize(&mut Serializer {
dst: &mut *self.ser.dst,
state: State::Table {
key: &self.key,
parent: &self.ser.state,
first: &self.first,
table_emitted: &self.table_emitted,
},
});
match res {
Ok(()) => self.first.set(false),
Err(Error::UnsupportedNone) => {}
Err(e) => return Err(e),
}
Ok(())
}
fn end(self) -> Result<(), Error> {
if self.first.get() {
let state = self.ser.state.clone();
self.ser.emit_table_header(&state)?;
}
Ok(())
}
}
impl<'a, 'b> ser::SerializeStruct for SerializeTable<'a, 'b> {
type Ok = ();
type Error = Error;
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<(), Error>
where
T: ?Sized + Serialize,
{
let res = value.serialize(&mut Serializer {
dst: &mut *self.ser.dst,
state: State::Table {
key,
parent: &self.ser.state,
first: &self.first,
table_emitted: &self.table_emitted,
},
});
match res {
Ok(()) => self.first.set(false),
Err(Error::UnsupportedNone) => {}
Err(e) => return Err(e),
}
Ok(())
}
fn end(self) -> Result<(), Error> {
if self.first.get() {
let state = self.ser.state.clone();
self.ser.emit_table_header(&state)?;
}
Ok(())
}
}
struct StringExtractor;
impl ser::Serializer for StringExtractor {
type Ok = String;
type Error = Error;
type SerializeSeq = ser::Impossible<String, Error>;
type SerializeTuple = ser::Impossible<String, Error>;
type SerializeTupleStruct = ser::Impossible<String, Error>;
type SerializeTupleVariant = ser::Impossible<String, Error>;
type SerializeMap = ser::Impossible<String, Error>;
type SerializeStruct = ser::Impossible<String, Error>;
type SerializeStructVariant = ser::Impossible<String, Error>;
fn serialize_bool(self, _v: bool) -> Result<String, Self::Error> {
Err(Error::KeyNotString)
}
fn serialize_i8(self, _v: i8) -> Result<String, Self::Error> {
Err(Error::KeyNotString)
}
fn serialize_i16(self, _v: i16) -> Result<String, Self::Error> {
Err(Error::KeyNotString)
}
fn serialize_i32(self, _v: i32) -> Result<String, Self::Error> {
Err(Error::KeyNotString)
}
fn serialize_i64(self, _v: i64) -> Result<String, Self::Error> {
Err(Error::KeyNotString)
}
fn serialize_u8(self, _v: u8) -> Result<String, Self::Error> {
Err(Error::KeyNotString)
}
fn serialize_u16(self, _v: u16) -> Result<String, Self::Error> {
Err(Error::KeyNotString)
}
fn serialize_u32(self, _v: u32) -> Result<String, Self::Error> {
Err(Error::KeyNotString)
}
fn serialize_u64(self, _v: u64) -> Result<String, Self::Error> {
Err(Error::KeyNotString)
}
fn serialize_f32(self, _v: f32) -> Result<String, Self::Error> {
Err(Error::KeyNotString)
}
fn serialize_f64(self, _v: f64) -> Result<String, Self::Error> {
Err(Error::KeyNotString)
}
fn serialize_char(self, _v: char) -> Result<String, Self::Error> {
Err(Error::KeyNotString)
}
fn serialize_str(self, value: &str) -> Result<String, Self::Error> {
Ok(value.to_string())
}
fn serialize_bytes(self, _value: &[u8]) -> Result<String, Self::Error> {
Err(Error::KeyNotString)
}
fn serialize_none(self) -> Result<String, Self::Error> {
Err(Error::KeyNotString)
}
fn serialize_some<T>(self, _value: &T) -> Result<String, Self::Error>
where
T: ?Sized + Serialize,
{
Err(Error::KeyNotString)
}
fn serialize_unit(self) -> Result<String, Self::Error> {
Err(Error::KeyNotString)
}
fn serialize_unit_struct(self, _name: &'static str) -> Result<String, Self::Error> {
Err(Error::KeyNotString)
}
fn serialize_unit_variant(
self,
_name: &'static str,
_variant_index: u32,
_variant: &'static str,
) -> Result<String, Self::Error> {
Err(Error::KeyNotString)
}
fn serialize_newtype_struct<T>(
self,
_name: &'static str,
value: &T,
) -> Result<String, Self::Error>
where
T: ?Sized + Serialize,
{
value.serialize(self)
}
fn serialize_newtype_variant<T>(
self,
_name: &'static str,
_variant_index: u32,
_variant: &'static str,
_value: &T,
) -> Result<String, Self::Error>
where
T: ?Sized + Serialize,
{
Err(Error::KeyNotString)
}
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
Err(Error::KeyNotString)
}
fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, Self::Error> {
Err(Error::KeyNotString)
}
fn serialize_tuple_struct(
self,
_name: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleStruct, Self::Error> {
Err(Error::KeyNotString)
}
fn serialize_tuple_variant(
self,
_name: &'static str,
_variant_index: u32,
_variant: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleVariant, Self::Error> {
Err(Error::KeyNotString)
}
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
Err(Error::KeyNotString)
}
fn serialize_struct(
self,
_name: &'static str,
_len: usize,
) -> Result<Self::SerializeStruct, Self::Error> {
Err(Error::KeyNotString)
}
fn serialize_struct_variant(
self,
_name: &'static str,
_variant_index: u32,
_variant: &'static str,
_len: usize,
) -> Result<Self::SerializeStructVariant, Self::Error> {
Err(Error::KeyNotString)
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::UnsupportedType => "unsupported Rust type".fmt(f),
Error::KeyNotString => "map key was not a string".fmt(f),
Error::ValueAfterTable => "values must be emitted before tables".fmt(f),
Error::UnsupportedNone => "unsupported None value".fmt(f),
Error::Custom(ref s) => s.fmt(f),
}
}
}
impl error::Error for Error {}
impl ser::Error for Error {
fn custom<T: Display>(msg: T) -> Error {
Error::Custom(msg.to_string())
}
}
+545
View File
@@ -0,0 +1,545 @@
use std::borrow::Cow;
use std::char;
use std::str;
/// A span, designating a range of bytes where a token is located.
#[derive(Eq, PartialEq, Debug, Clone, Copy)]
pub struct Span {
/// The start of the range.
pub start: usize,
/// The end of the range (exclusive).
pub end: usize,
}
impl From<Span> for (usize, usize) {
fn from(Span { start, end }: Span) -> (usize, usize) {
(start, end)
}
}
#[derive(Eq, PartialEq, Debug)]
pub enum Token<'a> {
Whitespace(&'a str),
Newline,
Comment(&'a str),
Equals,
Period,
Comma,
Colon,
Plus,
LeftBrace,
RightBrace,
LeftBracket,
RightBracket,
Keylike(&'a str),
String {
src: &'a str,
val: Cow<'a, str>,
multiline: bool,
},
}
#[derive(Eq, PartialEq, Debug)]
pub enum Error {
InvalidCharInString(usize, char),
InvalidEscape(usize, char),
InvalidHexEscape(usize, char),
InvalidEscapeValue(usize, u32),
NewlineInString(usize),
Unexpected(usize, char),
UnterminatedString(usize),
NewlineInTableKey(usize),
MultilineStringKey(usize),
Wanted {
at: usize,
expected: &'static str,
found: &'static str,
},
}
#[derive(Clone)]
pub struct Tokenizer<'a> {
input: &'a str,
chars: CrlfFold<'a>,
}
#[derive(Clone)]
struct CrlfFold<'a> {
chars: str::CharIndices<'a>,
}
#[derive(Debug)]
enum MaybeString {
NotEscaped(usize),
Owned(String),
}
impl<'a> Tokenizer<'a> {
pub fn new(input: &'a str) -> Tokenizer<'a> {
let mut t = Tokenizer {
input,
chars: CrlfFold {
chars: input.char_indices(),
},
};
// Eat utf-8 BOM
t.eatc('\u{feff}');
t
}
pub fn next(&mut self) -> Result<Option<(Span, Token<'a>)>, Error> {
let (start, token) = match self.one() {
Some((start, '\n')) => (start, Token::Newline),
Some((start, ' ' | '\t')) => (start, self.whitespace_token(start)),
Some((start, '#')) => (start, self.comment_token(start)),
Some((start, '=')) => (start, Token::Equals),
Some((start, '.')) => (start, Token::Period),
Some((start, ',')) => (start, Token::Comma),
Some((start, ':')) => (start, Token::Colon),
Some((start, '+')) => (start, Token::Plus),
Some((start, '{')) => (start, Token::LeftBrace),
Some((start, '}')) => (start, Token::RightBrace),
Some((start, '[')) => (start, Token::LeftBracket),
Some((start, ']')) => (start, Token::RightBracket),
Some((start, '\'')) => {
return self
.literal_string(start)
.map(|t| Some((self.step_span(start), t)))
}
Some((start, '"')) => {
return self
.basic_string(start)
.map(|t| Some((self.step_span(start), t)))
}
Some((start, ch)) if is_keylike(ch) => (start, self.keylike(start)),
Some((start, ch)) => return Err(Error::Unexpected(start, ch)),
None => return Ok(None),
};
let span = self.step_span(start);
Ok(Some((span, token)))
}
pub fn peek(&mut self) -> Result<Option<(Span, Token<'a>)>, Error> {
self.clone().next()
}
pub fn eat(&mut self, expected: Token<'a>) -> Result<bool, Error> {
self.eat_spanned(expected).map(|s| s.is_some())
}
/// Eat a value, returning it's span if it was consumed.
pub fn eat_spanned(&mut self, expected: Token<'a>) -> Result<Option<Span>, Error> {
let span = match self.peek()? {
Some((span, ref found)) if expected == *found => span,
Some(_) | None => return Ok(None),
};
drop(self.next());
Ok(Some(span))
}
pub fn expect(&mut self, expected: Token<'a>) -> Result<(), Error> {
// ignore span
let _ = self.expect_spanned(expected)?;
Ok(())
}
/// Expect the given token returning its span.
pub fn expect_spanned(&mut self, expected: Token<'a>) -> Result<Span, Error> {
let current = self.current();
match self.next()? {
Some((span, found)) => {
if expected == found {
Ok(span)
} else {
Err(Error::Wanted {
at: current,
expected: expected.describe(),
found: found.describe(),
})
}
}
None => Err(Error::Wanted {
at: self.input.len(),
expected: expected.describe(),
found: "eof",
}),
}
}
pub fn table_key(&mut self) -> Result<(Span, Cow<'a, str>), Error> {
let current = self.current();
match self.next()? {
Some((span, Token::Keylike(k))) => Ok((span, k.into())),
Some((
span,
Token::String {
src,
val,
multiline,
},
)) => {
let offset = self.substr_offset(src);
if multiline {
return Err(Error::MultilineStringKey(offset));
}
match src.find('\n') {
None => Ok((span, val)),
Some(i) => Err(Error::NewlineInTableKey(offset + i)),
}
}
Some((_, other)) => Err(Error::Wanted {
at: current,
expected: "a table key",
found: other.describe(),
}),
None => Err(Error::Wanted {
at: self.input.len(),
expected: "a table key",
found: "eof",
}),
}
}
pub fn eat_whitespace(&mut self) {
while self.eatc(' ') || self.eatc('\t') {
// ...
}
}
pub fn eat_comment(&mut self) -> Result<bool, Error> {
if !self.eatc('#') {
return Ok(false);
}
drop(self.comment_token(0));
self.eat_newline_or_eof().map(|()| true)
}
pub fn eat_newline_or_eof(&mut self) -> Result<(), Error> {
let current = self.current();
match self.next()? {
None | Some((_, Token::Newline)) => Ok(()),
Some((_, other)) => Err(Error::Wanted {
at: current,
expected: "newline",
found: other.describe(),
}),
}
}
pub fn skip_to_newline(&mut self) {
loop {
match self.one() {
Some((_, '\n')) | None => break,
_ => {}
}
}
}
fn eatc(&mut self, ch: char) -> bool {
match self.chars.clone().next() {
Some((_, ch2)) if ch == ch2 => {
self.one();
true
}
_ => false,
}
}
pub fn current(&mut self) -> usize {
match self.chars.clone().next() {
Some(i) => i.0,
None => self.input.len(),
}
}
fn whitespace_token(&mut self, start: usize) -> Token<'a> {
while self.eatc(' ') || self.eatc('\t') {
// ...
}
Token::Whitespace(&self.input[start..self.current()])
}
fn comment_token(&mut self, start: usize) -> Token<'a> {
while let Some((_, ch)) = self.chars.clone().next() {
if ch != '\t' && (ch < '\u{20}' || ch > '\u{10ffff}') {
break;
}
self.one();
}
Token::Comment(&self.input[start..self.current()])
}
fn read_string(
&mut self,
delim: char,
start: usize,
new_ch: &mut dyn FnMut(
&mut Tokenizer,
&mut MaybeString,
bool,
usize,
char,
) -> Result<(), Error>,
) -> Result<Token<'a>, Error> {
let mut multiline = false;
if self.eatc(delim) {
if self.eatc(delim) {
multiline = true;
} else {
return Ok(Token::String {
src: &self.input[start..start + 2],
val: Cow::Borrowed(""),
multiline: false,
});
}
}
let mut val = MaybeString::NotEscaped(self.current());
let mut n = 0;
loop {
n += 1;
match self.one() {
Some((i, '\n')) => {
if multiline {
if self.input.as_bytes()[i] == b'\r' {
val.make_owned(&self.input[..i]);
}
if n == 1 {
val = MaybeString::NotEscaped(self.current());
} else {
val.push('\n');
}
} else {
return Err(Error::NewlineInString(i));
}
}
Some((mut i, ch)) if ch == delim => {
if multiline {
if !self.eatc(delim) {
val.push(delim);
continue;
}
if !self.eatc(delim) {
val.push(delim);
val.push(delim);
continue;
}
if self.eatc(delim) {
val.push(delim);
i += 1;
}
if self.eatc(delim) {
val.push(delim);
i += 1;
}
}
return Ok(Token::String {
src: &self.input[start..self.current()],
val: val.into_cow(&self.input[..i]),
multiline,
});
}
Some((i, c)) => new_ch(self, &mut val, multiline, i, c)?,
None => return Err(Error::UnterminatedString(start)),
}
}
}
fn literal_string(&mut self, start: usize) -> Result<Token<'a>, Error> {
self.read_string('\'', start, &mut |_me, val, _multi, i, ch| {
if ch == '\u{09}' || ('\u{20}' <= ch && ch <= '\u{10ffff}' && ch != '\u{7f}') {
val.push(ch);
Ok(())
} else {
Err(Error::InvalidCharInString(i, ch))
}
})
}
fn basic_string(&mut self, start: usize) -> Result<Token<'a>, Error> {
self.read_string('"', start, &mut |me, val, multi, i, ch| match ch {
'\\' => {
val.make_owned(&me.input[..i]);
match me.chars.next() {
Some((_, '"')) => val.push('"'),
Some((_, '\\')) => val.push('\\'),
Some((_, 'b')) => val.push('\u{8}'),
Some((_, 'f')) => val.push('\u{c}'),
Some((_, 'n')) => val.push('\n'),
Some((_, 'r')) => val.push('\r'),
Some((_, 't')) => val.push('\t'),
Some((i, c @ ('u' | 'U'))) => {
let len = if c == 'u' { 4 } else { 8 };
val.push(me.hex(start, i, len)?);
}
Some((i, c @ (' ' | '\t' | '\n'))) if multi => {
if c != '\n' {
while let Some((_, ch)) = me.chars.clone().next() {
match ch {
' ' | '\t' => {
me.chars.next();
}
'\n' => {
me.chars.next();
break;
}
_ => return Err(Error::InvalidEscape(i, c)),
}
}
}
while let Some((_, ch)) = me.chars.clone().next() {
match ch {
' ' | '\t' | '\n' => {
me.chars.next();
}
_ => break,
}
}
}
Some((i, c)) => return Err(Error::InvalidEscape(i, c)),
None => return Err(Error::UnterminatedString(start)),
}
Ok(())
}
ch if ch == '\u{09}' || ('\u{20}' <= ch && ch <= '\u{10ffff}' && ch != '\u{7f}') => {
val.push(ch);
Ok(())
}
_ => Err(Error::InvalidCharInString(i, ch)),
})
}
fn hex(&mut self, start: usize, i: usize, len: usize) -> Result<char, Error> {
let mut buf = String::with_capacity(len);
for _ in 0..len {
match self.one() {
Some((_, ch)) if ch as u32 <= 0x7F && ch.is_ascii_hexdigit() => buf.push(ch),
Some((i, ch)) => return Err(Error::InvalidHexEscape(i, ch)),
None => return Err(Error::UnterminatedString(start)),
}
}
let val = u32::from_str_radix(&buf, 16).unwrap();
match char::from_u32(val) {
Some(ch) => Ok(ch),
None => Err(Error::InvalidEscapeValue(i, val)),
}
}
fn keylike(&mut self, start: usize) -> Token<'a> {
while let Some((_, ch)) = self.peek_one() {
if !is_keylike(ch) {
break;
}
self.one();
}
Token::Keylike(&self.input[start..self.current()])
}
pub fn substr_offset(&self, s: &'a str) -> usize {
assert!(s.len() <= self.input.len());
let a = self.input.as_ptr() as usize;
let b = s.as_ptr() as usize;
assert!(a <= b);
b - a
}
/// Calculate the span of a single character.
fn step_span(&mut self, start: usize) -> Span {
let end = match self.peek_one() {
Some(t) => t.0,
None => self.input.len(),
};
Span { start, end }
}
/// Peek one char without consuming it.
fn peek_one(&mut self) -> Option<(usize, char)> {
self.chars.clone().next()
}
/// Take one char.
pub fn one(&mut self) -> Option<(usize, char)> {
self.chars.next()
}
}
impl<'a> Iterator for CrlfFold<'a> {
type Item = (usize, char);
fn next(&mut self) -> Option<(usize, char)> {
self.chars.next().map(|(i, c)| {
if c == '\r' {
let mut attempt = self.chars.clone();
if let Some((_, '\n')) = attempt.next() {
self.chars = attempt;
return (i, '\n');
}
}
(i, c)
})
}
}
impl MaybeString {
fn push(&mut self, ch: char) {
match *self {
MaybeString::NotEscaped(..) => {}
MaybeString::Owned(ref mut s) => s.push(ch),
}
}
fn make_owned(&mut self, input: &str) {
match *self {
MaybeString::NotEscaped(start) => {
*self = MaybeString::Owned(input[start..].to_owned());
}
MaybeString::Owned(..) => {}
}
}
fn into_cow(self, input: &str) -> Cow<str> {
match self {
MaybeString::NotEscaped(start) => Cow::Borrowed(&input[start..]),
MaybeString::Owned(s) => Cow::Owned(s),
}
}
}
fn is_keylike(ch: char) -> bool {
('A' <= ch && ch <= 'Z')
|| ('a' <= ch && ch <= 'z')
|| ('0' <= ch && ch <= '9')
|| ch == '-'
|| ch == '_'
}
impl<'a> Token<'a> {
pub fn describe(&self) -> &'static str {
match *self {
Token::Keylike(_) => "an identifier",
Token::Equals => "an equals",
Token::Period => "a period",
Token::Comment(_) => "a comment",
Token::Newline => "a newline",
Token::Whitespace(_) => "whitespace",
Token::Comma => "a comma",
Token::RightBrace => "a right brace",
Token::LeftBrace => "a left brace",
Token::RightBracket => "a right bracket",
Token::LeftBracket => "a left bracket",
Token::String { multiline, .. } => {
if multiline {
"a multiline string"
} else {
"a string"
}
}
Token::Colon => "a colon",
Token::Plus => "a plus",
}
}
}