Skip to content

Commit

Permalink
feat: split core mod (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
cvng authored Jan 24, 2024
1 parent d6b9224 commit ef56f4c
Show file tree
Hide file tree
Showing 4 changed files with 346 additions and 304 deletions.
2 changes: 1 addition & 1 deletion derive/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ impl TemplateArgs {
if args.source.is_some() {
return Err("must specify 'source' or 'path', not both".into());
}
args.source = Some(Source::Source(crate::rewriter::rewrite_path(s.value())));
args.source = Some(Source::Source(crate::rewriter::transform_path(s.value())));
args.ext = Some("html".into());
} else {
return Err("template path must be string literal".into());
Expand Down
2 changes: 2 additions & 0 deletions derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ mod heritage;
use heritage::{Context, Heritage};
mod input;
use input::{Print, TemplateArgs, TemplateInput};

mod node;
mod rewriter;

#[proc_macro_derive(Template, attributes(template))]
Expand Down
315 changes: 315 additions & 0 deletions derive/src/node.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
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::char;
use nom::character::complete::space1;
use nom::combinator::map;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::sequence::tuple;
use parser::ParseError;

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

pub(crate) struct Parsed {
pub(crate) ast: Ast<'static>,
#[allow(dead_code)]
pub(crate) source: String,
}

impl Parsed {
pub(crate) fn new(source: String) -> Result<Self, ParseError> {
let src = unsafe { std::mem::transmute::<&str, &'static str>(source.as_str()) };
let ast = Ast::from_str(src)?;

Ok(Self { ast, source })
}

pub(crate) fn nodes(&self) -> &[Node<'_>] {
&self.ast.nodes
}
}

#[derive(Debug)]
pub(crate) struct Ast<'a> {
nodes: Vec<Node<'a>>,
}

impl<'a> Ast<'a> {
fn from_str(src: &'a str) -> Result<Self, ParseError> {
let parse = |i: &'a str| Node::many(i);

match parse(src) {
Ok(("", nodes)) => Ok(Self { nodes }),
err => panic!("{:#?}", err),
}
}
}

#[derive(Debug, PartialEq)]
pub(crate) enum Node<'a> {
Lit(Lit<'a>),
JsxStart(JsxStart<'a>),
JsxClose(JsxClose<'a>),
MacroArgs(MacroArgs<'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))
}

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

#[derive(Debug, PartialEq)]
pub(crate) struct Lit<'a> {
pub(crate) val: &'a str,
}

impl<'a> Lit<'a> {
fn parse(i: &'a str) -> ParseResult<'a, Self> {
let p = take_while(|c| c != '<' && c != '{');

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

Ok((i, Lit { val }))
}
}

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

impl<'a> JsxStart<'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('>'),
));

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

let args = args
.map(|s| s.trim())
.unwrap_or("")
.split_ascii_whitespace()
.filter(|s| !s.is_empty())
.collect::<Vec<_>>();

let self_closing = args.last().map(|s| s.ends_with('/')).unwrap_or(false);

let args = args
.iter()
.filter(|s| !s.ends_with('/'))
.copied()
.collect::<Vec<&str>>();

Ok((
i,
JsxStart {
name,
args,
self_closing,
},
))
}
}

#[derive(Debug, PartialEq)]
pub(crate) struct JsxClose<'a> {
pub(crate) name: &'a str,
}

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 (i, (_, name, _)) = p(i)?;

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

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

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

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

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

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

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

#[test]
fn test_jsx_start() {
assert_eq!(
JsxStart::parse("<Hello name rest=\"rest\" />"),
Ok((
"",
JsxStart {
name: "Hello",
args: vec!["name", "rest=\"rest\""],
self_closing: true,
}
))
);

assert_eq!(
JsxStart::parse("<Hello>"),
Ok((
"",
JsxStart {
name: "Hello",
args: vec![],
self_closing: false,
}
))
);
}

#[test]
fn test_jsx_close() {
assert_eq!(
JsxClose::parse("</Hello>"),
Ok(("", JsxClose { name: "Hello" }))
);
}

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

#[test]
fn test_lit() {
assert_eq!(Lit::parse("Test"), Ok(("", Lit { val: "Test" })));
}

#[test]
fn test_node() {
assert_eq!(Node::many(""), Ok(("", vec![])));

assert_eq!(
Node::many("<Hello />"),
Ok((
"",
vec![Node::JsxStart(JsxStart {
name: "Hello",
args: vec![],
self_closing: true,
})]
))
);

assert_eq!(
Node::many("<Hello />\nTest"),
Ok((
"",
vec![
Node::JsxStart(JsxStart {
name: "Hello",
args: vec![],
self_closing: true,
}),
Node::Lit(Lit { val: "\nTest" })
]
))
);

assert_eq!(
Node::many("Test\n<Hello />"),
Ok((
"",
vec![
Node::Lit(Lit { val: "Test\n" }),
Node::JsxStart(JsxStart {
name: "Hello",
args: vec![],
self_closing: true,
})
],
))
);

assert_eq!(
Node::many("</Hello>"),
Ok(("", vec![Node::JsxClose(JsxClose { name: "Hello" })]))
);

assert_eq!(
Node::many("</Hello>\nTest"),
Ok((
"",
vec![
Node::JsxClose(JsxClose { name: "Hello" }),
Node::Lit(Lit { val: "\nTest" })
]
))
);

assert_eq!(
Node::many("Test\n</Hello>"),
Ok((
"",
vec![
Node::Lit(Lit { val: "Test\n" }),
Node::JsxClose(JsxClose { name: "Hello" })
]
))
);

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

0 comments on commit ef56f4c

Please sign in to comment.