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:
17624 files changed
+4564928
-38
No files matched your search
Vendored
+1694
File diff suppressed because it is too large.
Load diff
Vendored
+54
@@ -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 {}
|
||||
Vendored
+144
@@ -0,0 +1,144 @@
|
||||
//! [![github]](https://github.com/dtolnay/basic-toml) [![crates-io]](https://crates.io/crates/basic-toml) [![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;
|
||||
Vendored
+833
@@ -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())
|
||||
}
|
||||
}
|
||||
Vendored
+545
@@ -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",
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user