peek MORE

This commit is contained in:
nora 2022-09-13 10:04:56 +02:00
parent 64061befd8
commit cbd6af9844
4 changed files with 72 additions and 74 deletions

View file

@ -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" }

View file

@ -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()
)]
);
} }
} }

View file

@ -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);
} }

View file

@ -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"#);
} }
} }