#![allow(unused)]
mod delimiteds;
mod headings;
mod nestables;
mod scripts;
use std::ops::Not;
use super::{
Stream,
macros::{not, slice_till},
};
use crate::{
ast::{FmlColor, FmlList, FmlValue, FmlValues, ListItem},
parser::{
ESCAPABLE_TOKENS, State,
functions::{
macros::*,
styles::{
delimiteds::delimiteds,
headings::headings,
nestables::nestables,
scripts::{line_scripts, scripts},
},
},
},
};
use winnow::{
Result,
ascii::*,
combinator::{repeat, *},
error::{ContextError, ParserError, StrContext},
prelude::*,
token::*,
};
#[cfg(not(windows))]
const LINE_ENDING: &'static str = "\n";
#[cfg(windows)]
const LINE_ENDING: &'static str = "\r\n";
fn inner_fml(scope: &str, state: State) -> Result<FmlValues> {
let mut stream = Stream {
input: scope,
state,
};
fml_values(stream)
}
fn wrap_in_colors(mut fml: FmlValue, fg: Option<String>, bg: Option<String>) -> FmlValue {
if let Some(color) = fg {
fml = FmlValue::ColorFg(FmlColor {
color,
body: vec![fml].into(),
})
}
if let Some(color) = bg {
fml = FmlValue::ColorBg(FmlColor {
color,
body: vec![fml].into(),
})
}
fml
}
fn line_spanning<'i>(input: &mut Stream<'i>) -> Result<FmlValue> {
let is_in_heading = input.state.is_in_heading;
let is_in_script = input.state.is_in_script;
alt((
nestables,
cond(!is_in_heading, headings).verify_map(|x| x),
cond(!is_in_script, line_scripts).verify_map(|x| x),
))
.parse_next(input)
}
pub fn code_block<'i>(input: &mut Stream<'i>) -> Result<FmlValue> {
let end = || preceded(multispace0, "```");
let lang_name_end = || (multispace1, not(end()));
let lang_name = slice_till!(1.., not!(lang_name_end())).map(ToString::to_string);
let body_inner = alt((
r"\```".value("```"),
slice_till!(1.., not!(alt((r"\```", end())))),
));
let body = repeat_till(1.., body_inner, peek(end())).map(|(acc, _term)| acc);
(
delimited(
"```",
opt(terminated(lang_name, multispace1)),
opt(repeat(0.., (space0, line_ending)).map(|()| ())),
),
terminated(body, end()),
)
.map(|(lang, body)| FmlValue::CodeBlock { lang, body })
.parse_next(input)
}
pub fn fml_values<'i>(input: Stream<'i>) -> Result<FmlValues> {
repeat(1.., alt((styled_text, plain_text)))
.map(FmlValues)
.parse(input)
.map_err(|e| e.into_inner())
}
fn styled_text<'i>(input: &mut Stream<'i>) -> Result<FmlValue> {
let is_sol = input.state.is_sol;
let is_in_script = input.state.is_in_script;
alt((
code_block,
delimiteds,
cond(is_sol, line_spanning).verify_map(|x| x),
cond(!is_in_script, scripts).verify_map(|x| x),
))
.parse_next(input)
}
fn plain_text<'i>(input: &mut Stream<'i>) -> Result<FmlValue> {
fn line_ending_stateful<'i>(input: &mut Stream<'i>) -> Result<&'i str> {
let res = (line_ending, opt(peek(alt((headings, nestables))))).parse_next(input);
if res.is_ok() {
input.state.is_sol = true;
}
res.map(|(nl, peek)| if peek.is_some() { "" } else { nl })
}
fn normal_char<'i>(input: &mut Stream<'i>) -> Result<&'i str> {
let res = not!(
esc_identity!(one_of(ESCAPABLE_TOKENS)),
line_ending,
styled_text
)
.parse_next(input);
let is_sol = &mut input.state.is_sol;
if res.is_ok() && *is_sol {
*is_sol = false;
}
res
}
let pt = alt((
normal_char,
esc_identity!(one_of(ESCAPABLE_TOKENS)),
line_ending_stateful,
));
(
any,
repeat(0.., pt).fold(String::new, |mut acc, new| {
acc.push_str(new);
acc
}),
)
.map(|(ch, mut s): (char, String)| {
s.insert(0, ch);
FmlValue::Text(s)
})
.parse_next(input)
}
#[cfg(test)]
mod tests {
use super::*;
fn str2stream<'i>(input: &'i str) -> Stream<'i> {
Stream {
input,
state: State::default(),
}
}
#[cfg(test)]
mod delimited {
use super::*;
#[test]
fn normal() {
let mut inp = r"*styled text*";
let res: Result<String> = delimited_esc!("*").parse_next(&mut inp);
let exp = "styled text".to_string();
assert_eq!(res.unwrap(), exp);
}
#[test]
fn escaped() {
let mut inp = r"*styled \* line \of t\\ext*";
let res: Result<String> = delimited_esc!("*").parse_next(&mut inp);
let exp = r"styled * line \of t\ext".to_string();
assert_eq!(res.unwrap(), exp);
}
#[test]
fn unescaped() {
let mut inp = r"*styled text\\*";
let res: Result<String> = delimited_esc!("*").parse_next(&mut inp);
let exp = r"styled text\".to_string();
assert_eq!(res.unwrap(), exp);
}
#[cfg(test)]
mod fails {
use super::*;
#[test]
fn empty() {
let mut inp = r"**";
let res: Result<String> = delimited_esc!("*").parse_next(&mut inp);
assert!(res.is_err());
}
#[test]
fn blank() {
let mut inp = r"* *";
let res: Result<String> = delimited_esc!("*").parse_next(&mut inp);
assert!(res.is_err());
}
#[test]
fn blank_multiline() {
let mut inp = r"*
*";
let res: Result<String> = delimited_esc!("*").parse_next(&mut inp);
assert!(res.is_err());
}
#[test]
fn escape_start() {
let mut inp = r"\*styled text*";
let res: Result<String> = delimited_esc!("*").parse_next(&mut inp);
assert!(res.is_err());
}
#[test]
fn escape_end() {
let mut inp = r"*styled text\*";
let res: Result<String> = delimited_esc!("*").parse_next(&mut inp);
assert!(res.is_err());
}
}
}
#[cfg(test)]
mod code_block {
use super::*;
#[test]
fn one_line() {
let inp = r"```javax.util.AbstractShortestJavaClassPath.MandatoryGlobalState.doSomething()```";
let res = code_block.parse(str2stream(inp));
let exp = FmlValue::CodeBlock {
lang: None,
body:
r"javax.util.AbstractShortestJavaClassPath.MandatoryGlobalState.doSomething()"
.to_string(),
};
assert_eq!(res.unwrap(), exp);
}
#[test]
fn one_line_lang() {
let inp = r"```java javax.util.AbstractShortestJavaClassPath.MandatoryGlobalState.doSomething()```";
let res = code_block.parse(str2stream(inp));
let exp = FmlValue::CodeBlock {
lang: Some("java".to_string()),
body:
r"javax.util.AbstractShortestJavaClassPath.MandatoryGlobalState.doSomething()"
.to_string(),
};
assert_eq!(res.unwrap(), exp);
}
#[test]
fn multiline() {
let inp = r#"```
fn main() {
println!("Hello, world!");
}
```"#;
let res = code_block.parse(str2stream(inp));
let exp = FmlValue::CodeBlock {
lang: None,
body: r#"fn main() {
println!("Hello, world!");
}"#
.to_string(),
};
assert_eq!(res.unwrap(), exp);
}
#[test]
fn multiline_lang() {
let inp = r#"```rust
fn main() {
println!("Hello, world!");
}
```"#;
let res = code_block.parse(str2stream(inp));
let exp = FmlValue::CodeBlock {
lang: Some("rust".to_string()),
body: r#"fn main() {
println!("Hello, world!");
}"#
.to_string(),
};
assert_eq!(res.unwrap(), exp);
}
mod fails {
use super::*;
#[test]
fn empty() {
let inp = "``````";
let res = code_block.parse(str2stream(inp));
assert!(res.is_err());
}
#[test]
fn blank() {
let inp = "``` ```";
let res = code_block.parse(str2stream(inp));
eprintln!("{res:#?}");
assert!(res.is_err());
}
#[test]
fn blank_multiline() {
let inp = r"```
```";
let res = code_block.parse(str2stream(inp));
eprintln!("{res:#?}");
assert!(res.is_err());
}
}
}
}