parser kind of works

This commit is contained in:
nora 2022-09-13 10:20:16 +02:00
parent cbd6af9844
commit 5d24f703fa
3 changed files with 108 additions and 66 deletions

View file

@ -1,7 +1,7 @@
use std::str::Chars; use std::str::Chars;
use parser::{Error, FmtSpec}; use parser::{Error, FmtSpec};
use peekmore::{PeekMoreIterator, PeekMore}; use peekmore::{PeekMore, PeekMoreIterator};
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::Span; use proc_macro2::Span;
use quote::{quote, ToTokens}; use quote::{quote, ToTokens};
@ -14,8 +14,6 @@ use syn::{
mod parser; mod parser;
// TODO: Rewrite using state machine please
struct Input { struct Input {
format_str: String, format_str: String,
str_span: Span, str_span: Span,
@ -163,9 +161,13 @@ pub fn format_args(tokens: TokenStream) -> TokenStream {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use peekmore::PeekMore;
use syn::Expr; use syn::Expr;
use crate::FmtPart; use crate::{
parser::{Align, Alignment, Argument, FmtSpec, FmtType},
FmtPart,
};
fn fake_expr() -> Expr { fn fake_expr() -> Expr {
syn::parse_str("1").unwrap() syn::parse_str("1").unwrap()
@ -177,10 +179,59 @@ mod tests {
fn run_test(string: &str, expr_count: usize) -> Vec<FmtPart> { fn run_test(string: &str, expr_count: usize) -> Vec<FmtPart> {
let fmt = super::Formatter { let fmt = super::Formatter {
string: string.chars().peekable(), string: string.chars().peekmore(),
exprs: fake_exprs(expr_count).into_iter(), exprs: fake_exprs(expr_count).into_iter(),
fmt_parts: Vec::new(), fmt_parts: Vec::new(),
}; };
fmt.parse().unwrap() fmt.parse().unwrap()
} }
#[test]
fn empty() {
let parts = run_test("{}", 1);
assert_eq!(
parts,
vec![FmtPart::Spec(
FmtSpec {
..FmtSpec::default()
},
fake_expr()
)]
);
}
#[test]
fn debug() {
let parts = run_test("{:?}", 1);
assert_eq!(
parts,
vec![FmtPart::Spec(
FmtSpec {
kind: FmtType::Debug,
..FmtSpec::default()
},
fake_expr()
)]
);
}
#[test]
fn many() {
let parts = run_test("{uwu:-<?}", 1);
assert_eq!(
parts,
vec![FmtPart::Spec(
FmtSpec {
arg: Argument::Keyword("uwu".to_string()),
align: Some(Align {
kind: Alignment::Left,
fill: Some('-'),
}),
kind: FmtType::Debug,
..FmtSpec::default()
},
fake_expr()
)]
);
}
} }

View file

@ -35,8 +35,8 @@ impl Alignment {
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct Align { pub struct Align {
kind: Alignment, pub kind: Alignment,
fill: Option<char>, pub fill: Option<char>,
} }
#[derive(Debug, PartialEq, Default)] #[derive(Debug, PartialEq, Default)]
@ -58,21 +58,21 @@ pub enum FmtType {
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
enum Precision { pub enum Precision {
Num(usize), Num(usize),
Asterisk, Asterisk,
} }
#[derive(Debug, PartialEq, Default)] #[derive(Debug, PartialEq, Default)]
pub struct FmtSpec { pub struct FmtSpec {
arg: Argument, pub arg: Argument,
align: Option<Align>, pub align: Option<Align>,
sign: Option<char>, pub sign: Option<char>,
alternate: bool, pub alternate: bool,
zero: bool, pub zero: bool,
width: Option<usize>, pub width: Option<usize>,
precision: Option<Precision>, pub precision: Option<Precision>,
kind: FmtType, pub kind: FmtType,
} }
pub struct FmtSpecParser<'a, 'b> { pub struct FmtSpecParser<'a, 'b> {
@ -88,11 +88,6 @@ enum State {
Argument, Argument,
// : here // : here
Align, Align,
Sign,
Alternate,
Zero,
Width,
Precision,
Done, Done,
} }
@ -171,6 +166,7 @@ impl<'a, 'b> FmtSpecParser<'a, 'b> {
if self.argument.arg != Argument::Positional { if self.argument.arg != Argument::Positional {
self.expect(':')?; self.expect(':')?;
} }
self.eat(':');
} }
State::Argument => match self State::Argument => match self
.peek() .peek()
@ -185,8 +181,9 @@ impl<'a, 'b> FmtSpecParser<'a, 'b> {
self.state = State::Align; self.state = State::Align;
} }
other => { other => {
// peek2 if let Some(c @ ('>' | '^' | '<')) = self.chars.peek_nth(1).copied() {
if let Some(c @ ('>' | '^' | '<')) = self.peek() { self.next(); // fill
self.next(); // align
self.argument.align = Some(Align { self.argument.align = Some(Align {
kind: Alignment::from_char(c).unwrap(), kind: Alignment::from_char(c).unwrap(),
fill: Some(other), fill: Some(other),
@ -201,30 +198,22 @@ impl<'a, 'b> FmtSpecParser<'a, 'b> {
self.next(); self.next();
self.argument.sign = Some(c); self.argument.sign = Some(c);
} }
self.state = State::Sign;
}
State::Sign => {
if self.eat('#') { if self.eat('#') {
self.argument.alternate = true; self.argument.alternate = true;
} }
self.state = State::Alternate;
}
State::Alternate => {
if self.eat('0') { if self.eat('0') {
self.argument.zero = true; self.argument.zero = true;
} }
self.state = 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 let width = width
.parse() .parse()
.map_err(|_| Error::new("width specified too long".to_string()))?; .map_err(|_| Error::new("width specified too long".to_string()))?;
self.argument.width = Some(width); self.argument.width = Some(width);
} }
self.state = State::Width;
}
State::Width => {
if self.eat('.') { if self.eat('.') {
if let Some(precision) = self.eat_until(|c| c != '*' && !c.is_ascii_digit()) { if let Some(precision) = self.eat_until(|c| c != '*' && !c.is_ascii_digit()) {
let precision = if precision == "*" { let precision = if precision == "*" {
@ -237,34 +226,36 @@ impl<'a, 'b> FmtSpecParser<'a, 'b> {
self.argument.precision = Some(precision); self.argument.precision = Some(precision);
} }
} }
self.state = State::Precision;
} match self.next() {
State::Precision => match self.next() { Some('?') => {
Some('?') => { self.argument.kind = FmtType::Debug;
self.argument.kind = FmtType::Debug;
self.expect('}')?;
}
Some('x') => {
self.expect('?')?;
self.argument.kind = FmtType::LowerHex;
self.expect('}')?;
}
Some('X') => {
self.expect('?')?;
self.argument.kind = FmtType::UpperHex;
self.expect('}')?;
}
Some('}') | None => {}
Some(other) => {
if let Some(kind) = self.eat_until(|c| c == '}') {
self.argument.kind = FmtType::Other(format!("{other}{kind}"));
self.expect('}')?;
} else {
self.argument.kind = FmtType::Default;
self.expect('}')?; self.expect('}')?;
} }
Some('x') => {
self.expect('?')?;
self.argument.kind = FmtType::LowerHex;
self.expect('}')?;
}
Some('X') => {
self.expect('?')?;
self.argument.kind = FmtType::UpperHex;
self.expect('}')?;
}
Some('}') | None => {}
Some(other) => {
if let Some(kind) = self.eat_until(|c| c == '}') {
self.argument.kind = FmtType::Other(format!("{other}{kind}"));
self.expect('}')?;
} else {
self.argument.kind = FmtType::Default;
self.expect('}')?;
}
}
} }
},
self.state = State::Done;
}
State::Done => unreachable!(), State::Done => unreachable!(),
} }

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