Skip to content

Commit

Permalink
fix: parse lit (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
cvng authored Feb 3, 2024
1 parent ef56f4c commit 9854b18
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 74 deletions.
204 changes: 139 additions & 65 deletions derive/src/node.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::bytes::complete::take_till;
use nom::bytes::complete::take_while;
use nom::character::complete::alpha1;
use nom::character::complete::anychar;
use nom::character::complete::char;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::complete;
use nom::combinator::cut;
use nom::combinator::eof;
use nom::combinator::map;
use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::error::ErrorKind;
use nom::error_position;
use nom::multi::many0;
use nom::sequence::delimited;
use nom::sequence::terminated;
use nom::sequence::tuple;
use parser::ParseError;

const JSX_BLOCK_START: &str = "<";
const JSX_BLOCK_END: &str = ">";
const JSX_CLOSE_START: &str = "</";
const MACRO_DEF_START: &str = "{#def";
const MACRO_DEF_END: &str = "#}";

type ParseResult<'a, T = &'a str> = nom::IResult<&'a str, T>;

pub(crate) struct Parsed {
Expand Down Expand Up @@ -42,7 +58,7 @@ impl<'a> Ast<'a> {
fn from_str(src: &'a str) -> Result<Self, ParseError> {
let parse = |i: &'a str| Node::many(i);

match parse(src) {
match terminated(parse, cut(eof))(src) {
Ok(("", nodes)) => Ok(Self { nodes }),
err => panic!("{:#?}", err),
}
Expand All @@ -52,39 +68,29 @@ impl<'a> Ast<'a> {
#[derive(Debug, PartialEq)]
pub(crate) enum Node<'a> {
Lit(Lit<'a>),
JsxStart(JsxStart<'a>),
JsxBlock(JsxBlock<'a>),
JsxClose(JsxClose<'a>),
MacroArgs(MacroArgs<'a>),
MacroDef(MacroDef<'a>),
}

impl<'a> Node<'a> {
fn many(i: &'a str) -> ParseResult<'a, Vec<Self>> {
let mut nodes = vec![];

let mut i = i;

while !i.is_empty() {
let (i2, node) = Node::parse(i).unwrap(); // TODO: ?

i = i2;

nodes.push(node);
}

Ok((i, nodes))
complete(many0(alt((
map(Lit::parse, Self::Lit),
map(MacroDef::parse, Self::MacroDef),
Self::parse,
))))(i)
}

fn parse(i: &'a str) -> ParseResult<'a, Self> {
let mut p = alt((
map(JsxStart::parse, Node::JsxStart),
map(JsxClose::parse, Node::JsxClose),
map(MacroArgs::parse, Node::MacroArgs),
map(Lit::parse, Node::Lit),
));

let (i, node) = p(i)?;

Ok(dbg!(i, node))
delimited(
|i| tag(JSX_BLOCK_START)(i),
alt((
map(JsxBlock::parse, Self::JsxBlock),
map(JsxClose::parse, Self::JsxClose),
)),
cut(|i| tag(JSX_BLOCK_END)(i)),
)(i)
}
}

Expand All @@ -95,31 +101,38 @@ pub(crate) struct Lit<'a> {

impl<'a> Lit<'a> {
fn parse(i: &'a str) -> ParseResult<'a, Self> {
let p = take_while(|c| c != '<' && c != '{');
let p_start = alt((
tuple((tag(JSX_BLOCK_START), verify(alpha1, is_uppercase_first))),
tuple((tag(JSX_CLOSE_START), verify(alpha1, is_uppercase_first))),
tuple((tag(MACRO_DEF_START), verify(space0, |_: &str| true))),
));

let (i, val) = p(i)?;
let (i, _) = not(eof)(i)?;
let (i, content) = opt(recognize(skip_till(p_start)))(i)?;

Ok((i, Lit { val }))
match content {
Some("") => Err(nom::Err::Error(error_position!(i, ErrorKind::TakeUntil))),
Some(content) => Ok((i, Self { val: content })),
None => Ok(("", Self { val: i })),
}
}
}

#[derive(Debug, PartialEq)]
pub(crate) struct JsxStart<'a> {
pub(crate) struct JsxBlock<'a> {
pub(crate) name: &'a str,
pub(crate) args: Vec<&'a str>,
pub(crate) self_closing: bool,
}

impl<'a> JsxStart<'a> {
impl<'a> JsxBlock<'a> {
fn parse(i: &'a str) -> ParseResult<'a, Self> {
let mut p = tuple((
tag("<"),
recognize(verify(alpha1, is_uppercase_first)),
opt(take_till(|c| c == '>')),
char('>'),
opt(take_till(|c: char| c.to_string() == JSX_BLOCK_END)),
));

let (i, (_, name, args, _)) = p(i)?;
let (i, (name, args)) = p(i)?;

let args = args
.map(|s| s.trim())
Expand All @@ -138,7 +151,7 @@ impl<'a> JsxStart<'a> {

Ok((
i,
JsxStart {
Self {
name,
args,
self_closing,
Expand All @@ -154,46 +167,73 @@ pub(crate) struct JsxClose<'a> {

impl<'a> JsxClose<'a> {
fn parse(i: &'a str) -> ParseResult<'a, Self> {
let mut p = tuple((
tag("</"),
recognize(verify(alpha1, is_uppercase_first)),
char('>'),
));
let mut p = tuple((char('/'), recognize(verify(alpha1, is_uppercase_first))));

let (i, (_, name, _)) = p(i)?;
let (i, (_, name)) = p(i)?;

Ok((i, JsxClose { name }))
Ok((i, Self { name }))
}
}

#[derive(Debug, PartialEq)]
pub(crate) struct MacroArgs<'a> {
pub(crate) struct MacroDef<'a> {
pub(crate) args: Vec<&'a str>,
}

impl<'a> MacroArgs<'a> {
impl<'a> MacroDef<'a> {
fn parse(i: &'a str) -> ParseResult<'a, Self> {
let mut p = tuple((tag("{#def"), space1, recognize(alpha1), space1, tag("#}")));
let mut p = tuple((
tag(MACRO_DEF_START),
space1,
recognize(alpha1),
space1,
tag(MACRO_DEF_END),
));

let (i, (_, _, args, _, _)) = p(i)?;

let args = args.split_ascii_whitespace().collect();

Ok((i, MacroArgs { args }))
Ok((i, Self { args }))
}
}

fn is_uppercase_first(s: &str) -> bool {
s.chars().next().map(|s| s.is_uppercase()).unwrap_or(false)
s.chars()
.next()
.map(|c| c.is_ascii_uppercase())
.unwrap_or(false)
}

/// Skips input until `end` was found, but does not consume it.
/// Returns tuple that would be returned when parsing `end`.
fn skip_till<'a, O>(
end: impl FnMut(&'a str) -> ParseResult<'a, O>,
) -> impl FnMut(&'a str) -> ParseResult<'a, (&'a str, O)> {
enum Next<O> {
IsEnd(O),
NotEnd(char),
}
let mut next = alt((map(end, Next::IsEnd), map(anychar, Next::NotEnd)));
move |start: &'a str| {
let mut i = start;
loop {
let (j, is_end) = next(i)?;
match is_end {
Next::IsEnd(lookahead) => return Ok((i, (j, lookahead))),
Next::NotEnd(_) => i = j,
}
}
}
}

#[test]
fn test_jsx_start() {
fn test_jsx_block() {
assert_eq!(
JsxStart::parse("<Hello name rest=\"rest\" />"),
JsxBlock::parse("Hello name rest=\"rest\" /"),
Ok((
"",
JsxStart {
JsxBlock {
name: "Hello",
args: vec!["name", "rest=\"rest\""],
self_closing: true,
Expand All @@ -202,10 +242,10 @@ fn test_jsx_start() {
);

assert_eq!(
JsxStart::parse("<Hello>"),
JsxBlock::parse("Hello"),
Ok((
"",
JsxStart {
JsxBlock {
name: "Hello",
args: vec![],
self_closing: false,
Expand All @@ -217,16 +257,16 @@ fn test_jsx_start() {
#[test]
fn test_jsx_close() {
assert_eq!(
JsxClose::parse("</Hello>"),
JsxClose::parse("/Hello"),
Ok(("", JsxClose { name: "Hello" }))
);
}

#[test]
fn test_macro_args() {
fn test_macro_def() {
assert_eq!(
MacroArgs::parse("{#def name #}"),
Ok(("", MacroArgs { args: vec!["name"] }))
MacroDef::parse("{#def name #}"),
Ok(("", MacroDef { args: vec!["name"] }))
);
}

Expand All @@ -243,7 +283,7 @@ fn test_node() {
Node::many("<Hello />"),
Ok((
"",
vec![Node::JsxStart(JsxStart {
vec![Node::JsxBlock(JsxBlock {
name: "Hello",
args: vec![],
self_closing: true,
Expand All @@ -256,7 +296,7 @@ fn test_node() {
Ok((
"",
vec![
Node::JsxStart(JsxStart {
Node::JsxBlock(JsxBlock {
name: "Hello",
args: vec![],
self_closing: true,
Expand All @@ -272,7 +312,7 @@ fn test_node() {
"",
vec![
Node::Lit(Lit { val: "Test\n" }),
Node::JsxStart(JsxStart {
Node::JsxBlock(JsxBlock {
name: "Hello",
args: vec![],
self_closing: true,
Expand Down Expand Up @@ -308,8 +348,42 @@ fn test_node() {
))
);

// assert_eq!(
// Node::many("<"),
// Ok(("", vec![Node::Lit(Lit { val: "<".into() })]))
// );
assert_eq!(Node::many("<"), Ok(("", vec![Node::Lit(Lit { val: "<" })])));

assert_eq!(
Node::many("<i"),
Ok(("", vec![Node::Lit(Lit { val: "<i" })]))
);

assert_eq!(
Node::many("<i>"),
Ok(("", vec![Node::Lit(Lit { val: "<i>" })]))
);

assert_eq!(
Node::many("<i />"),
Ok(("", vec![Node::Lit(Lit { val: "<i />" })]))
);

assert_eq!(Node::many(">"), Ok(("", vec![Node::Lit(Lit { val: ">" })])));

assert_eq!(
Node::many("/>"),
Ok(("", vec![Node::Lit(Lit { val: "/>" })]))
);

assert_eq!(
Node::many("</"),
Ok(("", vec![Node::Lit(Lit { val: "</" })]))
);

assert_eq!(
Node::many("</i"),
Ok(("", vec![Node::Lit(Lit { val: "</i" })]))
);

assert_eq!(
Node::many("</i>"),
Ok(("", vec![Node::Lit(Lit { val: "</i>" })]))
);
}
Loading

0 comments on commit 9854b18

Please sign in to comment.