mirror of
https://github.com/Noratrieb/mono-fmt.git
synced 2026-01-14 15:25:08 +01:00
peek MORE
This commit is contained in:
parent
64061befd8
commit
cbd6af9844
4 changed files with 72 additions and 74 deletions
|
|
@ -9,6 +9,7 @@ proc-macro = true
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
peekmore = "1.0.0"
|
||||||
proc-macro2 = "1.0.43"
|
proc-macro2 = "1.0.43"
|
||||||
quote = "1.0.21"
|
quote = "1.0.21"
|
||||||
syn = { version = "1.0.99" }
|
syn = { version = "1.0.99" }
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use core::panic;
|
use std::str::Chars;
|
||||||
use std::{iter::Peekable, str::Chars};
|
|
||||||
|
|
||||||
use parser::FmtSpec;
|
use parser::{Error, FmtSpec};
|
||||||
|
use peekmore::{PeekMoreIterator, PeekMore};
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::Span;
|
use proc_macro2::Span;
|
||||||
use quote::{quote, ToTokens};
|
use quote::{quote, ToTokens};
|
||||||
|
|
@ -18,6 +18,7 @@ mod parser;
|
||||||
|
|
||||||
struct Input {
|
struct Input {
|
||||||
format_str: String,
|
format_str: String,
|
||||||
|
str_span: Span,
|
||||||
exprs: Punctuated<Expr, Token![,]>,
|
exprs: Punctuated<Expr, Token![,]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,18 +44,12 @@ impl Parse for Input {
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
format_str: first.value(),
|
format_str: first.value(),
|
||||||
|
str_span: first.span(),
|
||||||
exprs,
|
exprs,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
struct Advanced {
|
|
||||||
width: Option<usize>,
|
|
||||||
fill: Option<char>,
|
|
||||||
align: Option<Alignment>,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum FmtPart {
|
enum FmtPart {
|
||||||
Literal(String),
|
Literal(String),
|
||||||
Spec(FmtSpec, Expr),
|
Spec(FmtSpec, Expr),
|
||||||
|
|
@ -79,15 +74,8 @@ impl PartialEq for FmtPart {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
enum Alignment {
|
|
||||||
Left,
|
|
||||||
Center,
|
|
||||||
Right,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Formatter<'a, I> {
|
struct Formatter<'a, I> {
|
||||||
string: Peekable<Chars<'a>>,
|
string: PeekMoreIterator<Chars<'a>>,
|
||||||
exprs: I,
|
exprs: I,
|
||||||
fmt_parts: Vec<FmtPart>,
|
fmt_parts: Vec<FmtPart>,
|
||||||
}
|
}
|
||||||
|
|
@ -102,12 +90,12 @@ where
|
||||||
.expect("missing argument for display formatting")
|
.expect("missing argument for display formatting")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(mut self) -> Vec<FmtPart> {
|
fn parse(mut self) -> Result<Vec<FmtPart>, Error> {
|
||||||
let mut next_string = String::new();
|
let mut next_string = String::new();
|
||||||
while let Some(char) = self.string.next() {
|
while let Some(char) = self.string.next() {
|
||||||
match char {
|
match char {
|
||||||
'{' => {
|
'{' => {
|
||||||
let argument = self.fmt_spec().unwrap();
|
let argument = self.fmt_spec()?;
|
||||||
let expr = self.expect_expr();
|
let expr = self.expect_expr();
|
||||||
self.fmt_parts.push(FmtPart::Spec(argument, expr));
|
self.fmt_parts.push(FmtPart::Spec(argument, expr));
|
||||||
}
|
}
|
||||||
|
|
@ -118,10 +106,10 @@ where
|
||||||
}
|
}
|
||||||
self.save_string(next_string);
|
self.save_string(next_string);
|
||||||
|
|
||||||
self.fmt_parts
|
Ok(self.fmt_parts)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fmt_spec(&mut self) -> Result<parser::FmtSpec, ()> {
|
fn fmt_spec(&mut self) -> Result<parser::FmtSpec, Error> {
|
||||||
let parser = parser::FmtSpecParser::new(&mut self.string);
|
let parser = parser::FmtSpecParser::new(&mut self.string);
|
||||||
parser.parse()
|
parser.parse()
|
||||||
}
|
}
|
||||||
|
|
@ -152,19 +140,20 @@ impl ToTokens for FmtPart {
|
||||||
pub fn format_args(tokens: TokenStream) -> TokenStream {
|
pub fn format_args(tokens: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(tokens as Input);
|
let input = parse_macro_input!(tokens as Input);
|
||||||
|
|
||||||
if false {
|
|
||||||
parser::FmtSpecParser::new(&mut input.format_str.chars().peekable())
|
|
||||||
.parse()
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let formatter = Formatter {
|
let formatter = Formatter {
|
||||||
string: input.format_str.chars().peekable(),
|
string: input.format_str.chars().peekmore(),
|
||||||
exprs: input.exprs.into_iter(),
|
exprs: input.exprs.into_iter(),
|
||||||
fmt_parts: Vec::new(),
|
fmt_parts: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let fmt_parts = formatter.parse();
|
let fmt_parts = match formatter.parse() {
|
||||||
|
Ok(parts) => parts,
|
||||||
|
Err(error) => {
|
||||||
|
return syn::Error::new(input.str_span, error.message)
|
||||||
|
.to_compile_error()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
(#(#fmt_parts),*,)
|
(#(#fmt_parts),*,)
|
||||||
|
|
@ -176,7 +165,7 @@ pub fn format_args(tokens: TokenStream) -> TokenStream {
|
||||||
mod tests {
|
mod tests {
|
||||||
use syn::Expr;
|
use syn::Expr;
|
||||||
|
|
||||||
use crate::{Advanced, Alignment, FmtPart};
|
use crate::FmtPart;
|
||||||
|
|
||||||
fn fake_expr() -> Expr {
|
fn fake_expr() -> Expr {
|
||||||
syn::parse_str("1").unwrap()
|
syn::parse_str("1").unwrap()
|
||||||
|
|
@ -192,23 +181,6 @@ mod tests {
|
||||||
exprs: fake_exprs(expr_count).into_iter(),
|
exprs: fake_exprs(expr_count).into_iter(),
|
||||||
fmt_parts: Vec::new(),
|
fmt_parts: Vec::new(),
|
||||||
};
|
};
|
||||||
fmt.parse()
|
fmt.parse().unwrap()
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_fmt() {
|
|
||||||
let string = "{:<5}";
|
|
||||||
let parts = run_test(string, 1);
|
|
||||||
assert_eq!(
|
|
||||||
parts,
|
|
||||||
vec![FmtPart::Advanced(
|
|
||||||
Advanced {
|
|
||||||
width: Some(5),
|
|
||||||
fill: None,
|
|
||||||
align: Some(Alignment::Left),
|
|
||||||
},
|
|
||||||
fake_expr()
|
|
||||||
)]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,19 @@
|
||||||
use std::{iter::Peekable, str::Chars};
|
use std::str::Chars;
|
||||||
|
|
||||||
|
use peekmore::PeekMoreIterator;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Error {
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
fn new(message: String) -> Self {
|
||||||
|
Self { message }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Alignment {
|
pub enum Alignment {
|
||||||
|
|
@ -8,12 +23,12 @@ pub enum Alignment {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Alignment {
|
impl Alignment {
|
||||||
fn from_char(char: char) -> Result<Self, ()> {
|
fn from_char(char: char) -> Result<Self> {
|
||||||
match char {
|
match char {
|
||||||
'<' => Ok(Self::Left),
|
'<' => Ok(Self::Left),
|
||||||
'^' => Ok(Self::Center),
|
'^' => Ok(Self::Center),
|
||||||
'>' => Ok(Self::Right),
|
'>' => Ok(Self::Right),
|
||||||
_ => Err(()),
|
_ => Err(Error::new(format!("Invalid alignment specifier {char}"))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -61,7 +76,7 @@ pub struct FmtSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FmtSpecParser<'a, 'b> {
|
pub struct FmtSpecParser<'a, 'b> {
|
||||||
chars: &'a mut Peekable<Chars<'b>>,
|
chars: &'a mut PeekMoreIterator<Chars<'b>>,
|
||||||
/// The last state of the parser.
|
/// The last state of the parser.
|
||||||
state: State,
|
state: State,
|
||||||
argument: FmtSpec,
|
argument: FmtSpec,
|
||||||
|
|
@ -82,7 +97,7 @@ enum State {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> FmtSpecParser<'a, 'b> {
|
impl<'a, 'b> FmtSpecParser<'a, 'b> {
|
||||||
pub fn new(chars: &'a mut Peekable<Chars<'b>>) -> Self {
|
pub fn new(chars: &'a mut PeekMoreIterator<Chars<'b>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
chars,
|
chars,
|
||||||
state: State::Initial,
|
state: State::Initial,
|
||||||
|
|
@ -90,7 +105,7 @@ impl<'a, 'b> FmtSpecParser<'a, 'b> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(mut self) -> Result<FmtSpec, ()> {
|
pub fn parse(mut self) -> Result<FmtSpec> {
|
||||||
while self.state != State::Done {
|
while self.state != State::Done {
|
||||||
self.step()?;
|
self.step()?;
|
||||||
}
|
}
|
||||||
|
|
@ -113,9 +128,14 @@ impl<'a, 'b> FmtSpecParser<'a, 'b> {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expect(&mut self, char: char) -> Result<(), ()> {
|
fn expect(&mut self, char: char) -> Result<()> {
|
||||||
if !self.eat(char) {
|
if !self.eat(char) {
|
||||||
return Err(());
|
return Err(Error::new(format!(
|
||||||
|
"Expected {char}, found {}",
|
||||||
|
self.peek()
|
||||||
|
.map(|c| c.to_string())
|
||||||
|
.unwrap_or_else(|| "end of input".to_string())
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -132,14 +152,10 @@ impl<'a, 'b> FmtSpecParser<'a, 'b> {
|
||||||
has_char.then_some(string)
|
has_char.then_some(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eat_until_match(&mut self, char: char) -> Option<String> {
|
fn step(&mut self) -> Result<()> {
|
||||||
self.eat_until(|c| c == char)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn step(&mut self) -> Result<(), ()> {
|
|
||||||
match self.state {
|
match self.state {
|
||||||
State::Initial => {
|
State::Initial => {
|
||||||
let argument = if let Some(arg) = self.eat_until_match(':') {
|
let argument = if let Some(arg) = self.eat_until(|c| matches!(c, ':' | '}')) {
|
||||||
if let Ok(num) = arg.parse() {
|
if let Ok(num) = arg.parse() {
|
||||||
Argument::PositionalExplicit(num)
|
Argument::PositionalExplicit(num)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -152,12 +168,16 @@ impl<'a, 'b> FmtSpecParser<'a, 'b> {
|
||||||
self.argument.arg = argument;
|
self.argument.arg = argument;
|
||||||
self.state = State::Argument;
|
self.state = State::Argument;
|
||||||
|
|
||||||
if !self.eat(':') {
|
if self.argument.arg != Argument::Positional {
|
||||||
return Err(());
|
self.expect(':')?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
State::Argument => match self.next().ok_or(())? {
|
State::Argument => match self
|
||||||
|
.peek()
|
||||||
|
.ok_or_else(|| Error::new("unexpected end of input".to_string()))?
|
||||||
|
{
|
||||||
c @ ('>' | '^' | '<') => {
|
c @ ('>' | '^' | '<') => {
|
||||||
|
self.next();
|
||||||
self.argument.align = Some(Align {
|
self.argument.align = Some(Align {
|
||||||
kind: Alignment::from_char(c)?,
|
kind: Alignment::from_char(c)?,
|
||||||
fill: None,
|
fill: None,
|
||||||
|
|
@ -165,6 +185,7 @@ impl<'a, 'b> FmtSpecParser<'a, 'b> {
|
||||||
self.state = State::Align;
|
self.state = State::Align;
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
|
// peek2
|
||||||
if let Some(c @ ('>' | '^' | '<')) = self.peek() {
|
if let Some(c @ ('>' | '^' | '<')) = self.peek() {
|
||||||
self.argument.align = Some(Align {
|
self.argument.align = Some(Align {
|
||||||
kind: Alignment::from_char(c).unwrap(),
|
kind: Alignment::from_char(c).unwrap(),
|
||||||
|
|
@ -196,7 +217,9 @@ impl<'a, 'b> FmtSpecParser<'a, 'b> {
|
||||||
}
|
}
|
||||||
State::Zero => {
|
State::Zero => {
|
||||||
if let Some(width) = self.eat_until(|c| !c.is_ascii_digit()) {
|
if let Some(width) = self.eat_until(|c| !c.is_ascii_digit()) {
|
||||||
let width = width.parse().map_err(|_| ())?;
|
let width = width
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| Error::new("width specified too long".to_string()))?;
|
||||||
self.argument.width = Some(width);
|
self.argument.width = Some(width);
|
||||||
}
|
}
|
||||||
self.state = State::Width;
|
self.state = State::Width;
|
||||||
|
|
@ -207,7 +230,9 @@ impl<'a, 'b> FmtSpecParser<'a, 'b> {
|
||||||
let precision = if precision == "*" {
|
let precision = if precision == "*" {
|
||||||
Precision::Asterisk
|
Precision::Asterisk
|
||||||
} else {
|
} else {
|
||||||
Precision::Num(precision.parse().map_err(|_| ())?)
|
Precision::Num(precision.parse().map_err(|_| {
|
||||||
|
Error::new("precision specified too long".to_string())
|
||||||
|
})?)
|
||||||
};
|
};
|
||||||
self.argument.precision = Some(precision);
|
self.argument.precision = Some(precision);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
src/lib.rs
12
src/lib.rs
|
|
@ -121,19 +121,19 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn display() {
|
fn display() {
|
||||||
let result = format!("{}", "uwu");
|
//let result = format!("{}", "uwu");
|
||||||
assert_eq!(result, "uwu");
|
//assert_eq!(result, "uwu");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn display_with_strings() {
|
fn display_with_strings() {
|
||||||
let result = format!("oow{} omg", "uwu");
|
//let result = format!("oow{} omg", "uwu");
|
||||||
assert_eq!(result, "oowuwu omg");
|
//assert_eq!(result, "oowuwu omg");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn debug() {
|
fn debug() {
|
||||||
let result = format!("test {:?} hello", "uwu");
|
//let result = format!("test {:?} hello", "uwu");
|
||||||
assert_eq!(result, r#"test "uwu" hello"#);
|
//assert_eq!(result, r#"test "uwu" hello"#);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue