use proc_macro::{TokenStream, TokenTree};
use std::collections::{HashMap, VecDeque};
fn parse_key(input: &mut VecDeque<TokenTree>) -> Option<String> {
let mut key = String::new();
loop {
match input.pop_front() {
Some(TokenTree::Punct(p)) => {
if p.as_char() == '=' {
return Some(key);
}
return Some("".to_string());
}
Some(TokenTree::Ident(i)) => {
key.push_str(&i.to_string());
}
Some(TokenTree::Literal(l)) => {
key.push_str(&l.to_string());
}
Some(TokenTree::Group(g)) => {
key.push_str(&g.to_string());
}
None => {
return None;
}
}
}
}
fn parse_value(input: &mut VecDeque<TokenTree>) -> String {
let mut value = String::new();
loop {
match input.pop_front() {
Some(TokenTree::Punct(p)) => {
if p.as_char() == ',' {
return value;
}
value.push_str(&p.to_string());
}
Some(TokenTree::Ident(i)) => {
value.push_str(&i.to_string());
}
Some(TokenTree::Literal(l)) => {
value.push_str(&l.to_string());
}
Some(TokenTree::Group(g)) => {
value.push_str(&g.to_string());
}
None => {
return value;
}
}
}
}
fn build_kvs(input: &mut VecDeque<TokenTree>) -> Result<HashMap<String, String>, TokenStream> {
let mut kvs = HashMap::new();
if input.is_empty() {
return Ok(kvs);
}
match input.pop_front() {
Some(TokenTree::Punct(p)) => {
if p.as_char() != ',' {
return Err(r#"compile_error!("Expected ','")"#.parse().unwrap());
}
}
_ => {
return Err(r#"compile_error!("Expected ','")"#.parse().unwrap());
}
}
loop {
let key = match parse_key(input) {
Some(k) => k,
None => {
return Ok(kvs);
}
};
let value = parse_value(input);
kvs.insert(key, value);
}
}
pub struct LFormatResult {
pub output: TokenStream,
pub name: String,
}
pub fn lformat_impl(collect: &mut VecDeque<TokenTree>, logger: String) -> LFormatResult {
let some_input = match collect.remove(0) {
Some(i) => i,
None => {
return LFormatResult {
output: r#"compile_error!("lformat!() must be called with a string literal")"#
.parse()
.unwrap(),
name: "".to_string(),
}
}
};
let format_string = match some_input {
TokenTree::Literal(l) => {
let out = l.to_string();
if !out.starts_with('"') || !out.ends_with('"') {
return LFormatResult {
output: r#"compile_error!("lformat!() must be called with a string literal")"#
.parse()
.unwrap(),
name: "".to_string(),
};
}
out[1..out.len() - 1].to_string()
}
_ => {
return LFormatResult {
output: r#"compile_error!("lformat!() must be called with a string literal")"#
.parse()
.unwrap(),
name: "".to_string(),
};
}
};
let k = match build_kvs(collect) {
Ok(kvs) => kvs,
Err(e) => {
return LFormatResult {
output: e,
name: "".to_string(),
};
}
};
let mut source = String::new();
enum Mode {
Literal(String),
Key(String),
}
let mut mode = Mode::Literal(String::new());
for (c, char) in format_string.chars().enumerate() {
match mode {
Mode::Literal(mut literal) => {
if char == '{' {
if format_string.chars().nth(c + 1) == Some('{') {
literal.push(char);
mode = Mode::Literal(literal);
} else if !literal.is_empty() {
source.push_str(&logger);
source.push_str(".write_literal(\"");
source.push_str(&literal);
source.push_str("\");\n");
mode = Mode::Key(String::new());
} else {
mode = Mode::Key(String::new());
}
} else {
literal.push(char);
mode = Mode::Literal(literal);
}
}
Mode::Key(mut key) => {
if char == '}' {
source.push_str(&logger);
source.push_str(".write_val(");
let value = match k.get(&key) {
Some(l) => l.to_string(),
None => {
return LFormatResult {
output: format!(r#"compile_error!("Key {} not found")"#, key)
.parse()
.unwrap(),
name: "".to_string(),
};
}
};
source.push_str(&value);
source.push_str(");\n");
mode = Mode::Literal(String::new());
} else {
key.push(char);
mode = Mode::Key(key);
}
}
}
}
match mode {
Mode::Literal(l) => {
if !l.is_empty() {
source.push_str(&logger.to_string());
source.push_str(".write_literal(\"");
source.push_str(&l);
source.push_str("\");\n");
}
}
Mode::Key(_) => {
return LFormatResult {
output: r#"compile_error!("Expected '}'")"#.parse().unwrap(),
name: "".to_string(),
};
}
}
return LFormatResult {
output: source.parse().unwrap(),
name: format_string,
};
}