summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Buland <mike@xagasoft.com>2026-08-19 13:05:01 -0700
committerMike Buland <mike@xagasoft.com>2026-08-19 13:05:01 -0700
commitd1e78b4a3454984db74ebf2a302a64b985ce6de4 (patch)
treed3953cd8aad144a1be131ff3c38688f93cbccbb5
parent5602585e7afe431f25e68f30ac54761db2a710e5 (diff)
downloaddeepcrow-d1e78b4a3454984db74ebf2a302a64b985ce6de4.tar.gz
deepcrow-d1e78b4a3454984db74ebf2a302a64b985ce6de4.tar.bz2
deepcrow-d1e78b4a3454984db74ebf2a302a64b985ce6de4.tar.xz
deepcrow-d1e78b4a3454984db74ebf2a302a64b985ce6de4.zip
Rearranged and renamed some bits.
Diffstat (limited to '')
-rw-r--r--deepcrow/src/lexer.rs48
-rw-r--r--deepcrow/src/lib.rs246
-rw-r--r--deepcrow/src/parser.rs118
-rw-r--r--deepcrow/tests/basic.rs246
4 files changed, 329 insertions, 329 deletions
diff --git a/deepcrow/src/lexer.rs b/deepcrow/src/lexer.rs
index d4d39e9..2528cb6 100644
--- a/deepcrow/src/lexer.rs
+++ b/deepcrow/src/lexer.rs
@@ -11,7 +11,7 @@ pub enum ErrorType {
11} 11}
12 12
13#[derive(Debug,Copy,Clone,PartialEq,Eq)] 13#[derive(Debug,Copy,Clone,PartialEq,Eq)]
14pub enum SymbolType<'a> { 14pub enum TokenType<'a> {
15 StartFlat, 15 StartFlat,
16 StartPoint, 16 StartPoint,
17 EndFlat, 17 EndFlat,
@@ -28,7 +28,7 @@ pub enum SymbolType<'a> {
28 Sharp, 28 Sharp,
29 Equals, 29 Equals,
30 Period, 30 Period,
31 Token(&'a str), 31 Identifier(&'a str),
32 Literal(&'a str), 32 Literal(&'a str),
33 Text(&'a str), 33 Text(&'a str),
34 Error{ what: ErrorType }, 34 Error{ what: ErrorType },
@@ -36,7 +36,7 @@ pub enum SymbolType<'a> {
36 36
37#[derive(Copy,Clone)] 37#[derive(Copy,Clone)]
38pub struct Symbol<'a> { 38pub struct Symbol<'a> {
39 symbol: SymbolType<'a>, 39 symbol: TokenType<'a>,
40 start: Position, 40 start: Position,
41 end: Position, 41 end: Position,
42} 42}
@@ -48,17 +48,17 @@ impl<'a> fmt::Debug for Symbol<'a> {
48} 48}
49 49
50impl<'a> Symbol<'a> { 50impl<'a> Symbol<'a> {
51 pub fn new( symbol: SymbolType<'a>, start: Position, end: Position ) -> Self { 51 pub fn new( symbol: TokenType<'a>, start: Position, end: Position ) -> Self {
52 Self { 52 Self {
53 symbol, start, end, 53 symbol, start, end,
54 } 54 }
55 } 55 }
56 56
57 pub fn check_type<T: Fn( &SymbolType ) -> bool>(&self, f: T ) -> bool { 57 pub fn check_type<T: Fn( &TokenType ) -> bool>(&self, f: T ) -> bool {
58 f( &self.symbol ) 58 f( &self.symbol )
59 } 59 }
60 60
61 pub fn symbol(&self) -> &SymbolType<'a> { 61 pub fn symbol(&self) -> &TokenType<'a> {
62 &self.symbol 62 &self.symbol
63 } 63 }
64 64
@@ -171,7 +171,7 @@ impl<'a> Lexer<'a> {
171 } 171 }
172 172
173 fn error( &self, what: ErrorType ) -> Option<Symbol<'a>> { 173 fn error( &self, what: ErrorType ) -> Option<Symbol<'a>> {
174 Some(Symbol::new( SymbolType::Error { 174 Some(Symbol::new( TokenType::Error {
175 what: what 175 what: what
176 }, self.pos, self.pos )) 176 }, self.pos, self.pos ))
177 } 177 }
@@ -217,7 +217,7 @@ impl<'a> Lexer<'a> {
217 if start == end { 217 if start == end {
218 None 218 None
219 } else { 219 } else {
220 Some(Symbol::new( SymbolType::Text(s), start_pos, end_pos )) 220 Some(Symbol::new( TokenType::Text(s), start_pos, end_pos ))
221 } 221 }
222 } 222 }
223 223
@@ -229,15 +229,15 @@ impl<'a> Lexer<'a> {
229 } 229 }
230 Some('#') => { 230 Some('#') => {
231 self.next(); 231 self.next();
232 return Some(Symbol::new(SymbolType::Sharp, self.pos, self.pos)); 232 return Some(Symbol::new(TokenType::Sharp, self.pos, self.pos));
233 } 233 }
234 Some('=') => { 234 Some('=') => {
235 self.next(); 235 self.next();
236 return Some(Symbol::new(SymbolType::Equals, self.pos, self.pos)); 236 return Some(Symbol::new(TokenType::Equals, self.pos, self.pos));
237 } 237 }
238 Some('.') => { 238 Some('.') => {
239 self.next(); 239 self.next();
240 return Some(Symbol::new(SymbolType::Period, self.pos, self.pos)); 240 return Some(Symbol::new(TokenType::Period, self.pos, self.pos));
241 } 241 }
242 _ => {} 242 _ => {}
243 } 243 }
@@ -254,14 +254,14 @@ impl<'a> Lexer<'a> {
254 None 254 None
255 } else { 255 } else {
256 Some(Symbol::new( match s { 256 Some(Symbol::new( match s {
257 "view" => SymbolType::View, 257 "view" => TokenType::View,
258 "show" => SymbolType::Show, 258 "show" => TokenType::Show,
259 "output" => SymbolType::Output, 259 "output" => TokenType::Output,
260 "loop" => SymbolType::Loop, 260 "loop" => TokenType::Loop,
261 "if" => SymbolType::If, 261 "if" => TokenType::If,
262 "elif" => SymbolType::ElIf, 262 "elif" => TokenType::ElIf,
263 "else" => SymbolType::Else, 263 "else" => TokenType::Else,
264 _ => SymbolType::Token(s) 264 _ => TokenType::Identifier(s)
265 }, start_pos, end_pos )) 265 }, start_pos, end_pos ))
266 } 266 }
267 } 267 }
@@ -277,7 +277,7 @@ impl<'a> Lexer<'a> {
277 let end_pos = self.pos.clone(); 277 let end_pos = self.pos.clone();
278 self.next(); 278 self.next();
279 let s = &self.data[start..end]; 279 let s = &self.data[start..end];
280 Some(Symbol::new(SymbolType::Literal(s), start_pos, end_pos )) 280 Some(Symbol::new(TokenType::Literal(s), start_pos, end_pos ))
281 } 281 }
282 282
283 fn is_start_tag(&mut self) -> bool { 283 fn is_start_tag(&mut self) -> bool {
@@ -298,7 +298,7 @@ impl<'a> Lexer<'a> {
298 let end_pos = self.pos.clone(); 298 let end_pos = self.pos.clone();
299 self.next(); 299 self.next();
300 self.mode = Mode::InTag; 300 self.mode = Mode::InTag;
301 Some(Symbol::new( SymbolType::StartFlat, start_pos, end_pos ) ) 301 Some(Symbol::new( TokenType::StartFlat, start_pos, end_pos ) )
302 } else { 302 } else {
303 None 303 None
304 } 304 }
@@ -309,7 +309,7 @@ impl<'a> Lexer<'a> {
309 let end_pos = self.pos.clone(); 309 let end_pos = self.pos.clone();
310 self.next(); 310 self.next();
311 self.mode = Mode::InTag; 311 self.mode = Mode::InTag;
312 Some(Symbol::new(SymbolType::StartPoint, start_pos, end_pos ) ) 312 Some(Symbol::new(TokenType::StartPoint, start_pos, end_pos ) )
313 } else { 313 } else {
314 None 314 None
315 } 315 }
@@ -339,14 +339,14 @@ impl<'a> Lexer<'a> {
339 let end_pos = self.pos.clone(); 339 let end_pos = self.pos.clone();
340 self.next(); 340 self.next();
341 self.mode = Mode::Text; 341 self.mode = Mode::Text;
342 Some(Symbol::new( SymbolType::EndFlat, start_pos, end_pos)) 342 Some(Symbol::new( TokenType::EndFlat, start_pos, end_pos))
343 } 343 }
344 Some('>') => { 344 Some('>') => {
345 self.next(); 345 self.next();
346 let end_pos = self.pos.clone(); 346 let end_pos = self.pos.clone();
347 self.next(); 347 self.next();
348 self.mode = Mode::Text; 348 self.mode = Mode::Text;
349 Some(Symbol::new( SymbolType::EndPoint, start_pos, end_pos)) 349 Some(Symbol::new( TokenType::EndPoint, start_pos, end_pos))
350 } 350 }
351 _ => { 351 _ => {
352 None 352 None
diff --git a/deepcrow/src/lib.rs b/deepcrow/src/lib.rs
index b10cd03..b9a4ffe 100644
--- a/deepcrow/src/lib.rs
+++ b/deepcrow/src/lib.rs
@@ -298,249 +298,3 @@ fn exec<'a>(input: &StatefulContext<'a>, tokens: &Vec<Token>, buf: &mut String)
298 Ok(()) 298 Ok(())
299} 299}
300 300
301#[cfg(test)]
302mod tests {
303 use super::*;
304
305 use crate::lexer::*;
306
307 #[test]
308 fn lexing() {
309 let data = r#"Leading comment: [|view "basic" something="yeup"|>Hello there <|view|] Trailing text"#;
310 let ll = lexer::Lexer::new( &data );
311 for sym in ll {
312 if let SymbolType::Error{what} = sym.symbol() {
313 println!("Error {}:{}: {:?}", sym.start().line, sym.start().column, what );
314 break;
315 } else {
316 println!("Symbol: {:?}", sym );
317 }
318 }
319 }
320
321 #[test]
322 fn parsing() {
323 let mut ct = DeepCrow::new();
324 if let Err(e) = ct.load_static(r#"Here is a sample
325[|view "index" theme="standard"|><html><body>Hi</body></html><|view|]
326That was fun! now a placeholder: [|view "placeholder"|] and now one with an implicit [|view "simple"|>Body -> [|show content|] <- and end<|view|] aoeu
327
328[|view "person" layout="simple"|>Welcome [|show person.last_name|], [|show person.first_name|]! It's lovely to see you again.<|view|]
329
330Now with explicit outputs:
331[|view "complex"|>
332 [|output "content"|>
333 Here's a [|show name|].
334 <|output|]
335 [|output "sidebar"|>
336 What's up world?
337 <|output|]
338 [|output "footer"|>
339 Same, I guess!
340 <|output|]
341<|view|]"#) {
342 println!("Error: {:?}", e );
343 } else {
344 println!("It finished");
345 if let Ok(v) = ct.render(
346 "person",
347 &Context::from([
348 ("hi".to_string(),Value::String("hi".to_string())),
349 ("person".to_string(),Value::Dictionary(HashMap::from([
350 ("first_name".to_string(), Value::String("Bob".to_string())),
351 ("last_name".to_string(), Value::String("Smith".to_string())),
352 ]))),
353 ])
354 ) {
355 println!("View result: {:?}", v );
356 } else {
357 println!("Error?");
358 }
359 }
360 }
361
362 #[test]
363 fn loops() -> Result<()> {
364 let mut ct = DeepCrow::new();
365 ct.load_static(r#"Looping code:
366[|view "index"|>We will enumerate people here:[|loop people|>
367 - [|show last_name|], [|show first_name|][|if show_title |>: [|show title|] <|else|> **title redacted** <|if|] ::>[|loop tags|> [|show tag|]<|loop|] <:: <|loop|]
368<|view|]
369"#)?;
370
371 let ctx = Context::from([
372 ("people".to_string(), vec![
373 [
374 ("first_name".into(), "Joe".into()),
375 ("last_name".into(), "Smith".into()),
376 ("show_title".into(), true.into()),
377 ("title".into(), "CEO".into()),
378 ("tags".into(), vec![
379 Context::from([("tag".into(), "jerk".into()),]).into(),
380 Context::from([("tag".into(), "ugly".into()),]).into(),
381 ].into()),
382 ].into(),
383 [
384 ("first_name".into(), "Chris".into()),
385 ("last_name".into(), "Perkens".into()),
386 ("show_title".into(), false.into()),
387 ("title".into(), "Baconeer".into()),
388 ("tags".into(), vec![
389 Context::from([("tag".into(), "jerk".into()),]).into(),
390 Context::from([("tag".into(), "ugly".into()),]).into(),
391 ].into()),
392 ].into(),
393 [
394 ("first_name".into(), "Will".into()),
395 ("last_name".into(), "Power".into()),
396 ("show_title".into(), false.into()),
397 ("title".into(), "CFO".into()),
398 ("tags".into(), vec![
399 Context::from([("tag".into(), "jerk".into()),]).into(),
400 Context::from([("tag".into(), "ugly".into()),]).into(),
401 ].into()),
402 ].into(),
403 [
404 ("first_name".into(), "Justin".into()),
405 ("last_name".into(), "Time".into()),
406 ("show_title".into(), true.into()),
407 ("title".into(), "CIO".into()),
408 ("tags".into(), vec![
409 Context::from([("tag".into(), "jerk".into()),]).into(),
410 Context::from([("tag".into(), "ugly".into()),]).into(),
411 ].into()),
412 ].into(),
413 ].into()),
414 ]);
415
416 println!("View result: {}", ct.render("index", &ctx)? );
417
418 Ok(())
419 }
420
421 #[test]
422 fn conditional() -> Result<()> {
423 let mut ct = DeepCrow::new();
424 ct.load_static(r#"Hi there
425[|view "index"|>
426 Color: [|if is_red|> red <|elif is_blue |> blue <|else|> green <|if|]
427<|view|]"#)?;
428
429 let ctx = Context::from([
430 ("is_red".into(), false.into()),
431 ("is_blue".into(), false.into()),
432 ("color".into(), "purple".into()),
433 ]);
434
435 println!("View result: {}", ct.render("index", &ctx)? );
436
437 Ok(())
438 }
439
440 struct Item {
441 pub id: i32,
442 pub title: String,
443 pub body: String,
444 }
445
446 struct Person {
447 pub id: i32,
448 pub username: String,
449 pub name: String,
450 }
451
452 struct Page {
453 pub user: Person,
454 pub total_items: i32,
455 pub total_pages: i32,
456 pub cur_page: i32,
457 pub items: Vec<Item>,
458 }
459
460 impl Page {
461 fn new() -> Page {
462 Page {
463 user: Person {
464 id: 443,
465 username: "eichlan".into(),
466 name: "Mike".into(),
467 },
468 total_items: 4000,
469 total_pages: 400,
470 cur_page: 35,
471 items: vec![
472 Item{
473 id: 123,
474 title: "hi".into(),
475 body: "body".into(),
476 },
477 Item{
478 id: 124,
479 title: "bye".into(),
480 body: "wooo".into(),
481 },
482 Item{
483 id: 125,
484 title: "ciao".into(),
485 body: "whatever".into(),
486 }
487 ],
488 }
489 }
490 }
491
492 impl<'a> MappedStructure<'a> for Page {
493 fn get_value(&'a self, id: &str) -> Option<MappedValue<'a>> {
494 match id {
495 "user" => Some(MappedValue::Struct(&self.user)),
496 "total_items" => Some(MappedValue::Int32(self.total_items)),
497 "total_pages" => Some(MappedValue::Int32(self.total_pages)),
498 "cur_page" => Some(MappedValue::Int32(self.cur_page)),
499 "items" => Some(MappedValue::<'a>::List(&self.items)),
500 _ => None,
501 }
502 }
503 }
504
505 impl<'a> MappedStructure<'a> for Person {
506 fn get_value(&'a self, id: &str) -> Option<MappedValue<'a>> {
507 match id {
508 "id" => Some(MappedValue::Int32(self.id)),
509 "username" => Some(MappedValue::String(&self.username)),
510 "name" => Some(MappedValue::String(&self.name)),
511 _ => None,
512 }
513 }
514 }
515
516 impl<'a> MappedStructure<'a> for Item {
517 fn get_value(&'a self, id: &str) -> Option<MappedValue<'a>> {
518 match id {
519 "id" => Some(MappedValue::Int32(self.id)),
520 "title" => Some(MappedValue::String(&self.title)),
521 "body" => Some(MappedValue::String(&self.body)),
522 _ => None,
523 }
524 }
525 }
526
527 #[test]
528 fn custom() -> Result<()> {
529 let page = Page::new();
530
531 println!("{:?}",
532 page.get_value(&"total_pages")
533 );
534
535 if let Some(MappedValue::List(l)) = page.get_value(&"items") {
536 l.for_each(&|x: &MappedValue, buf: &mut String| {
537 if let MappedValue::Struct(s) = x {
538 println!(" - {:?}", s.get_value(&"title"));
539 buf.push_str(&format!("{:?}",s.get_value(&"title")));
540 }
541 Ok(())
542 }).expect("loop");
543 }
544 Ok(())
545 }
546}
diff --git a/deepcrow/src/parser.rs b/deepcrow/src/parser.rs
index fbfc44e..529c58e 100644
--- a/deepcrow/src/parser.rs
+++ b/deepcrow/src/parser.rs
@@ -36,7 +36,7 @@ impl<'a> Context<'a> {
36 } 36 }
37 37
38 /* 38 /*
39 pub fn check_unwrapped<T: Fn( &SymbolType, &SymbolType ) -> bool>(&self, context: &str, f: T) -> Result<bool> { 39 pub fn check_unwrapped<T: Fn( &TokenType, &TokenType ) -> bool>(&self, context: &str, f: T) -> Result<bool> {
40 if self.cur().is_none() || self.peek().is_none() { 40 if self.cur().is_none() || self.peek().is_none() {
41 Err(DeepCrowError::eos( context )) 41 Err(DeepCrowError::eos( context ))
42 } else { 42 } else {
@@ -51,13 +51,13 @@ impl<'a> Context<'a> {
51} 51}
52 52
53trait SymbolHelper { 53trait SymbolHelper {
54 fn is<T: Fn( &SymbolType ) -> bool>(&self, context: &str, f: T ) -> Result<bool>; 54 fn is<T: Fn( &TokenType ) -> bool>(&self, context: &str, f: T ) -> Result<bool>;
55 #[allow(dead_code)] 55 #[allow(dead_code)]
56 fn is_valid_tag_name(&self) -> Result<bool>; 56 fn is_valid_tag_name(&self) -> Result<bool>;
57} 57}
58 58
59impl<'a> SymbolHelper for Option<Symbol<'a>> { 59impl<'a> SymbolHelper for Option<Symbol<'a>> {
60 fn is<T: Fn( &SymbolType ) -> bool>(&self, context: &str, f: T ) -> Result<bool> { 60 fn is<T: Fn( &TokenType ) -> bool>(&self, context: &str, f: T ) -> Result<bool> {
61 if let Some(st) = self { 61 if let Some(st) = self {
62 Ok(f( &st.symbol() )) 62 Ok(f( &st.symbol() ))
63 } else { 63 } else {
@@ -68,14 +68,14 @@ impl<'a> SymbolHelper for Option<Symbol<'a>> {
68 fn is_valid_tag_name(&self) -> Result<bool> { 68 fn is_valid_tag_name(&self) -> Result<bool> {
69 if let Some(st) = self { 69 if let Some(st) = self {
70 Ok(match st.symbol() { 70 Ok(match st.symbol() {
71 SymbolType::Token(_) | 71 TokenType::Identifier(_) |
72 SymbolType::View | 72 TokenType::View |
73 SymbolType::Output | 73 TokenType::Output |
74 SymbolType::Loop | 74 TokenType::Loop |
75 SymbolType::If | 75 TokenType::If |
76 SymbolType::ElIf | 76 TokenType::ElIf |
77 SymbolType::Else | 77 TokenType::Else |
78 SymbolType::Show => true, 78 TokenType::Show => true,
79 _ => false 79 _ => false
80 }) 80 })
81 } else { 81 } else {
@@ -184,7 +184,7 @@ impl Parser {
184 } 184 }
185 185
186 fn lex_error<T>(&self, error: &Symbol) -> Result<T> { 186 fn lex_error<T>(&self, error: &Symbol) -> Result<T> {
187 if let SymbolType::Error{what} = error.symbol() { 187 if let TokenType::Error{what} = error.symbol() {
188 Err(DeepCrowError::parse( *error.start(), 188 Err(DeepCrowError::parse( *error.start(),
189 format!("What: {:?}", *what) 189 format!("What: {:?}", *what)
190 )) 190 ))
@@ -208,13 +208,13 @@ impl Parser {
208 break; 208 break;
209 } 209 }
210 match ctx.cur().unwrap().symbol() { 210 match ctx.cur().unwrap().symbol() {
211 SymbolType::Text(_) => { /* Skip top level text */ } 211 TokenType::Text(_) => { /* Skip top level text */ }
212 SymbolType::StartFlat => { 212 TokenType::StartFlat => {
213 let tb = self.parse_tag( ctx )?; 213 let tb = self.parse_tag( ctx )?;
214 let tb = self.parse_tag_set( ctx, tb )?; 214 let tb = self.parse_tag_set( ctx, tb )?;
215 tags.push( tb ); 215 tags.push( tb );
216 } 216 }
217 SymbolType::Error{..} => { 217 TokenType::Error{..} => {
218 self.lex_error( &ctx.cur().unwrap() )?; 218 self.lex_error( &ctx.cur().unwrap() )?;
219 } 219 }
220 _ => { 220 _ => {
@@ -231,7 +231,7 @@ impl Parser {
231 for tag in tags { 231 for tag in tags {
232 let start_pos = tag.start_pos(); 232 let start_pos = tag.start_pos();
233 let name = tag.name().clone(); 233 let name = tag.name().clone();
234 if tag.is_name(&SymbolType::View) && 234 if tag.is_name(&TokenType::View) &&
235 let BuildResult::View(view) = tag.build( ctx )? { 235 let BuildResult::View(view) = tag.build( ctx )? {
236 views.push( view ); 236 views.push( view );
237 } else { 237 } else {
@@ -250,9 +250,9 @@ impl Parser {
250 250
251 if ctx.next().is_some() { 251 if ctx.next().is_some() {
252 match ctx.cur().unwrap().symbol() { 252 match ctx.cur().unwrap().symbol() {
253 SymbolType::View | SymbolType::Show | SymbolType::Loop | 253 TokenType::View | TokenType::Show | TokenType::Loop |
254 SymbolType::If | SymbolType::ElIf | SymbolType::Else | 254 TokenType::If | TokenType::ElIf | TokenType::Else |
255 SymbolType::Output => { 255 TokenType::Output => {
256 let name_sym = ctx.cur().unwrap(); 256 let name_sym = ctx.cur().unwrap();
257 ctx.next(); 257 ctx.next();
258 tb.set_name( name_sym ); 258 tb.set_name( name_sym );
@@ -277,7 +277,7 @@ impl Parser {
277 277
278 if let Some(end_sym) = ctx.cur() { 278 if let Some(end_sym) = ctx.cur() {
279 match end_sym.symbol() { 279 match end_sym.symbol() {
280 SymbolType::EndPoint | SymbolType::EndFlat => { 280 TokenType::EndPoint | TokenType::EndFlat => {
281 tb.set_end( &end_sym )?; 281 tb.set_end( &end_sym )?;
282 } 282 }
283 _ => { 283 _ => {
@@ -304,11 +304,11 @@ impl Parser {
304 return Err(DeepCrowError::eos(format!("close tag for {:?}", base.name()).as_str())); 304 return Err(DeepCrowError::eos(format!("close tag for {:?}", base.name()).as_str()));
305 } 305 }
306 match ctx.cur().unwrap().symbol() { 306 match ctx.cur().unwrap().symbol() {
307 SymbolType::Text(s) => { 307 TokenType::Text(s) => {
308 base.add_child(Entry::Token(Token::Text(s.to_string()))); 308 base.add_child(Entry::Token(Token::Text(s.to_string())));
309 ctx.next(); 309 ctx.next();
310 } 310 }
311 SymbolType::StartFlat | SymbolType::StartPoint => { 311 TokenType::StartFlat | TokenType::StartPoint => {
312 let tb = self.parse_tag( ctx )?; 312 let tb = self.parse_tag( ctx )?;
313 313
314 match tb.tag_type() { 314 match tb.tag_type() {
@@ -320,9 +320,9 @@ impl Parser {
320 return Ok(base); 320 return Ok(base);
321 } 321 }
322 TagType::MultinaryClose => { 322 TagType::MultinaryClose => {
323 if (tb.is_name(&SymbolType::If) && 323 if (tb.is_name(&TokenType::If) &&
324 (base.is_name(&SymbolType::ElIf) || 324 (base.is_name(&TokenType::ElIf) ||
325 base.is_name(&SymbolType::Else))) || 325 base.is_name(&TokenType::Else))) ||
326 tb.is_name(base.name().unwrap().symbol()) { 326 tb.is_name(base.name().unwrap().symbol()) {
327 return Ok(base); 327 return Ok(base);
328 } else { 328 } else {
@@ -340,7 +340,7 @@ impl Parser {
340 } 340 }
341 } 341 }
342 } 342 }
343 SymbolType::Error{..} => { 343 TokenType::Error{..} => {
344 return self.lex_error( &ctx.cur().unwrap() ); 344 return self.lex_error( &ctx.cur().unwrap() );
345 } 345 }
346 _ => { 346 _ => {
@@ -359,12 +359,12 @@ impl Parser {
359 return Err(DeepCrowError::eos("tag parameters")); 359 return Err(DeepCrowError::eos("tag parameters"));
360 } 360 }
361 match ctx.cur().unwrap().symbol() { 361 match ctx.cur().unwrap().symbol() {
362 SymbolType::Literal(s) => { 362 TokenType::Literal(s) => {
363 tb.add_param( ParamValue::Literal(s.to_string()) ); 363 tb.add_param( ParamValue::Literal(s.to_string()) );
364 ctx.next(); 364 ctx.next();
365 } 365 }
366 SymbolType::Token(_) | SymbolType::Sharp => { 366 TokenType::Identifier(_) | TokenType::Sharp => {
367 if ctx.peek().is("tag data", |s| *s == SymbolType::Equals)? { 367 if ctx.peek().is("tag data", |s| *s == TokenType::Equals)? {
368 break; 368 break;
369 } 369 }
370 tb.add_param( self.parse_identifier( ctx )? ); 370 tb.add_param( self.parse_identifier( ctx )? );
@@ -380,20 +380,20 @@ impl Parser {
380 fn parse_identifier(&self, ctx: &mut Context ) -> Result<ParamValue> { 380 fn parse_identifier(&self, ctx: &mut Context ) -> Result<ParamValue> {
381 let mut id = Identifier::new(); 381 let mut id = Identifier::new();
382 382
383 if ctx.cur().is("identifier",|s| matches!( s, SymbolType::Sharp))? { 383 if ctx.cur().is("identifier",|s| matches!( s, TokenType::Sharp))? {
384 id.push( IdentifierValue::Root ); 384 id.push( IdentifierValue::Root );
385 ctx.next(); 385 ctx.next();
386 } 386 }
387 loop { 387 loop {
388 if ctx.cur().is("identifier",|s| matches!( s, SymbolType::Token(_) ))? { 388 if ctx.cur().is("identifier",|s| matches!( s, TokenType::Identifier(_) ))? {
389 if let SymbolType::Token(s) = ctx.cur().unwrap().symbol() { 389 if let TokenType::Identifier(s) = ctx.cur().unwrap().symbol() {
390 id.push( IdentifierValue::Name(s.to_string()) ); 390 id.push( IdentifierValue::Name(s.to_string()) );
391 } 391 }
392 } else { 392 } else {
393 return Err(DeepCrowError::parse( ctx.cur().unwrap().start().clone(), format!("Expeceted identifier, found {:?}", ctx.cur().unwrap().symbol() ) ) ); 393 return Err(DeepCrowError::parse( ctx.cur().unwrap().start().clone(), format!("Expeceted identifier, found {:?}", ctx.cur().unwrap().symbol() ) ) );
394 } 394 }
395 395
396 if !ctx.next().is("identifier seperator",|s| *s == SymbolType::Period )? { 396 if !ctx.next().is("identifier seperator",|s| *s == TokenType::Period )? {
397 break; 397 break;
398 } 398 }
399 ctx.next(); 399 ctx.next();
@@ -407,11 +407,11 @@ impl Parser {
407 if ctx.cur().is_none() { 407 if ctx.cur().is_none() {
408 return Err(DeepCrowError::eos("tag properties")); 408 return Err(DeepCrowError::eos("tag properties"));
409 } 409 }
410 if let SymbolType::Token(s) = ctx.cur().unwrap().symbol() { 410 if let TokenType::Identifier(s) = ctx.cur().unwrap().symbol() {
411 if ctx.peek().is_some_and(|s|s.check_type(|t| matches!(t,SymbolType::Equals))) { 411 if ctx.peek().is_some_and(|s|s.check_type(|t| matches!(t,TokenType::Equals))) {
412 ctx.next(); 412 ctx.next();
413 if let Some(sym) = ctx.next() && 413 if let Some(sym) = ctx.next() &&
414 let SymbolType::Literal(lv) = sym.symbol() { 414 let TokenType::Literal(lv) = sym.symbol() {
415 tb.add_prop( s.to_string(), lv.to_string() ); 415 tb.add_prop( s.to_string(), lv.to_string() );
416 } else { 416 } else {
417 return Err(DeepCrowError::parse(Position::none(),"Expected quoted literal string".to_string())); 417 return Err(DeepCrowError::parse(Position::none(),"Expected quoted literal string".to_string()));
@@ -455,7 +455,7 @@ impl<'a> EntryListConverter for EntryList<'a> {
455 fn has_outputs(&self) -> bool { 455 fn has_outputs(&self) -> bool {
456 self.iter().any(|e| 456 self.iter().any(|e|
457 if let Entry::TagBuilder(tb) = e 457 if let Entry::TagBuilder(tb) = e
458 && tb.is_name(&SymbolType::Output) { 458 && tb.is_name(&TokenType::Output) {
459 true 459 true
460 } else { 460 } else {
461 false 461 false
@@ -546,18 +546,18 @@ impl<'a> TagBuilder<'a> {
546 } 546 }
547 547
548 pub fn set_end(&mut self, end: &Symbol) -> Result<()> { 548 pub fn set_end(&mut self, end: &Symbol) -> Result<()> {
549 if *self.start.symbol() == SymbolType::StartFlat { 549 if *self.start.symbol() == TokenType::StartFlat {
550 if *end.symbol() == SymbolType::EndFlat { 550 if *end.symbol() == TokenType::EndFlat {
551 self.tag_type = TagType::Unary; 551 self.tag_type = TagType::Unary;
552 } else if *end.symbol() == SymbolType::EndPoint { 552 } else if *end.symbol() == TokenType::EndPoint {
553 self.tag_type = TagType::MultinaryOpen; 553 self.tag_type = TagType::MultinaryOpen;
554 } else { 554 } else {
555 return Err(DeepCrowError::broken( *end.start(), "Invalid bracket token type.")); 555 return Err(DeepCrowError::broken( *end.start(), "Invalid bracket token type."));
556 } 556 }
557 } else if *self.start.symbol() == SymbolType::StartPoint { 557 } else if *self.start.symbol() == TokenType::StartPoint {
558 if *end.symbol() == SymbolType::EndFlat { 558 if *end.symbol() == TokenType::EndFlat {
559 self.tag_type = TagType::MultinaryClose; 559 self.tag_type = TagType::MultinaryClose;
560 } else if *end.symbol() == SymbolType::EndPoint { 560 } else if *end.symbol() == TokenType::EndPoint {
561 self.tag_type = TagType::MultinaryMid; 561 self.tag_type = TagType::MultinaryMid;
562 } else { 562 } else {
563 return Err(DeepCrowError::broken( *end.start(), "Invalid bracket token type.")); 563 return Err(DeepCrowError::broken( *end.start(), "Invalid bracket token type."));
@@ -582,18 +582,18 @@ impl<'a> TagBuilder<'a> {
582*/ 582*/
583 pub fn can_have_params(&self) -> bool { 583 pub fn can_have_params(&self) -> bool {
584 match self.name.unwrap().symbol() { 584 match self.name.unwrap().symbol() {
585 SymbolType::View | SymbolType::Output | SymbolType::Loop => true, 585 TokenType::View | TokenType::Output | TokenType::Loop => true,
586 SymbolType::Show | SymbolType::If | SymbolType::ElIf | 586 TokenType::Show | TokenType::If | TokenType::ElIf |
587 SymbolType::Else => false, 587 TokenType::Else => false,
588 _ => false, 588 _ => false,
589 } 589 }
590 } 590 }
591 591
592 pub fn can_have_expr(&self) -> bool { 592 pub fn can_have_expr(&self) -> bool {
593 match self.name.unwrap().symbol() { 593 match self.name.unwrap().symbol() {
594 SymbolType::View | SymbolType::Output | SymbolType::Loop => false, 594 TokenType::View | TokenType::Output | TokenType::Loop => false,
595 SymbolType::Show | SymbolType::If | SymbolType::ElIf | 595 TokenType::Show | TokenType::If | TokenType::ElIf |
596 SymbolType::Else => true, 596 TokenType::Else => true,
597 _ => false, 597 _ => false,
598 } 598 }
599 } 599 }
@@ -611,7 +611,7 @@ impl<'a> TagBuilder<'a> {
611 } 611 }
612 } 612 }
613*/ 613*/
614 pub fn is_name(&self, name: &SymbolType<'a>) -> bool { 614 pub fn is_name(&self, name: &TokenType<'a>) -> bool {
615 if let Some(a) = self.name { 615 if let Some(a) = self.name {
616 a.symbol() == name 616 a.symbol() == name
617 } else { 617 } else {
@@ -662,7 +662,7 @@ impl<'a> TagBuilder<'a> {
662 pub fn build(mut self, ctx: &mut Context ) -> Result<BuildResult> { 662 pub fn build(mut self, ctx: &mut Context ) -> Result<BuildResult> {
663 if let Some(sym) = self.name { 663 if let Some(sym) = self.name {
664 match sym.symbol() { 664 match sym.symbol() {
665 SymbolType::View => { 665 TokenType::View => {
666 let name = if let ParamValue::Literal(s) = self.params.swap_remove(0) { 666 let name = if let ParamValue::Literal(s) = self.params.swap_remove(0) {
667 s.to_string() 667 s.to_string()
668 } else { 668 } else {
@@ -676,7 +676,7 @@ impl<'a> TagBuilder<'a> {
676 layout: self.props.get("layout").cloned(), 676 layout: self.props.get("layout").cloned(),
677 })) 677 }))
678 } 678 }
679 SymbolType::Output => { 679 TokenType::Output => {
680 let name = if let ParamValue::Literal(s) = self.params.swap_remove(0) { 680 let name = if let ParamValue::Literal(s) = self.params.swap_remove(0) {
681 s.to_string() 681 s.to_string()
682 } else { 682 } else {
@@ -687,7 +687,7 @@ impl<'a> TagBuilder<'a> {
687 code: self.children.to_tokens(ctx)?, 687 code: self.children.to_tokens(ctx)?,
688 })) 688 }))
689 } 689 }
690 SymbolType::Loop => { 690 TokenType::Loop => {
691 let id = if let ParamValue::Identifier(id) 691 let id = if let ParamValue::Identifier(id)
692 = self.params.swap_remove(0) { 692 = self.params.swap_remove(0) {
693 id 693 id
@@ -701,7 +701,7 @@ impl<'a> TagBuilder<'a> {
701 Token::Loop(id, self.children.to_tokens(ctx)?) 701 Token::Loop(id, self.children.to_tokens(ctx)?)
702 )) 702 ))
703 } 703 }
704 SymbolType::Show => { 704 TokenType::Show => {
705 let id = if let ParamValue::Identifier(id) 705 let id = if let ParamValue::Identifier(id)
706 = self.params.swap_remove(0) { 706 = self.params.swap_remove(0) {
707 id 707 id
@@ -713,7 +713,7 @@ impl<'a> TagBuilder<'a> {
713 }; 713 };
714 Ok(BuildResult::Token(Token::Show(id, self.props))) 714 Ok(BuildResult::Token(Token::Show(id, self.props)))
715 } 715 }
716 SymbolType::If => { 716 TokenType::If => {
717 let id = if let ParamValue::Identifier(id) 717 let id = if let ParamValue::Identifier(id)
718 = self.params.swap_remove(0) { 718 = self.params.swap_remove(0) {
719 id 719 id
@@ -726,7 +726,7 @@ impl<'a> TagBuilder<'a> {
726 726
727 let is_else = if self.chain.len() == 1 && 727 let is_else = if self.chain.len() == 1 &&
728 let Entry::TagBuilder(tb) = &self.chain[0] && 728 let Entry::TagBuilder(tb) = &self.chain[0] &&
729 tb.is_name(&SymbolType::Else) && 729 tb.is_name(&TokenType::Else) &&
730 tb.params.len() == 0 { 730 tb.params.len() == 0 {
731 true 731 true
732 } else { 732 } else {
@@ -740,7 +740,7 @@ impl<'a> TagBuilder<'a> {
740 } 740 }
741 Ok(BuildResult::Token(Token::If(id, self.children.to_tokens(ctx)?, self.chain.to_tokens(ctx)?))) 741 Ok(BuildResult::Token(Token::If(id, self.children.to_tokens(ctx)?, self.chain.to_tokens(ctx)?)))
742 } 742 }
743 SymbolType::ElIf => { 743 TokenType::ElIf => {
744 let id = if let ParamValue::Identifier(id) = self.params.swap_remove(0) { 744 let id = if let ParamValue::Identifier(id) = self.params.swap_remove(0) {
745 id 745 id
746 } else { 746 } else {
@@ -755,7 +755,7 @@ impl<'a> TagBuilder<'a> {
755 // ...but I can't do that right now. 755 // ...but I can't do that right now.
756 let is_else = if self.chain.len() == 1 && 756 let is_else = if self.chain.len() == 1 &&
757 let Entry::TagBuilder(tb) = &self.chain[0] && 757 let Entry::TagBuilder(tb) = &self.chain[0] &&
758 tb.is_name(&SymbolType::Else) && 758 tb.is_name(&TokenType::Else) &&
759 tb.params.len() == 0 { 759 tb.params.len() == 0 {
760 true 760 true
761 } else { 761 } else {
@@ -771,7 +771,7 @@ impl<'a> TagBuilder<'a> {
771 771
772 Ok(BuildResult::Token(Token::If(id, self.children.to_tokens(ctx)?, self.chain.to_tokens(ctx)?))) 772 Ok(BuildResult::Token(Token::If(id, self.children.to_tokens(ctx)?, self.chain.to_tokens(ctx)?)))
773 } 773 }
774 SymbolType::Else => { 774 TokenType::Else => {
775 Ok(BuildResult::Token(Token::If(Identifier::new(), self.children.to_tokens(ctx)?, self.chain.to_tokens(ctx)?))) 775 Ok(BuildResult::Token(Token::If(Identifier::new(), self.children.to_tokens(ctx)?, self.chain.to_tokens(ctx)?)))
776 } 776 }
777 _ => { 777 _ => {
diff --git a/deepcrow/tests/basic.rs b/deepcrow/tests/basic.rs
new file mode 100644
index 0000000..37cc21f
--- /dev/null
+++ b/deepcrow/tests/basic.rs
@@ -0,0 +1,246 @@
1use std::collections::HashMap;
2
3use deepcrow::*;
4
5/*
6#[test]
7fn lexing() {
8 let data = r#"Leading comment: [|view "basic" something="yeup"|>Hello there <|view|] Trailing text"#;
9 let ll = Lexer::new( &data );
10 for sym in ll {
11 if let SymbolType::Error{what} = sym.symbol() {
12 println!("Error {}:{}: {:?}", sym.start().line, sym.start().column, what );
13 break;
14 } else {
15 println!("Symbol: {:?}", sym );
16 }
17 }
18}
19*/
20
21#[test]
22fn parsing() {
23 let mut ct = DeepCrow::new();
24 if let Err(e) = ct.load_static(r#"Here is a sample
25[|view "index" theme="standard"|><html><body>Hi</body></html><|view|]
26That was fun! now a placeholder: [|view "placeholder"|] and now one with an implicit [|view "simple"|>Body -> [|show content|] <- and end<|view|] aoeu
27
28[|view "person" layout="simple"|>Welcome [|show person.last_name|], [|show person.first_name|]! It's lovely to see you again.<|view|]
29
30Now with explicit outputs:
31[|view "complex"|>
32[|output "content"|>
33 Here's a [|show name|].
34<|output|]
35[|output "sidebar"|>
36 What's up world?
37<|output|]
38[|output "footer"|>
39 Same, I guess!
40<|output|]
41<|view|]"#) {
42 println!("Error: {:?}", e );
43 } else {
44 println!("It finished");
45 if let Ok(v) = ct.render(
46 "person",
47 &Context::from([
48 ("hi".to_string(),Value::String("hi".to_string())),
49 ("person".to_string(),Value::Dictionary(HashMap::from([
50 ("first_name".to_string(), Value::String("Bob".to_string())),
51 ("last_name".to_string(), Value::String("Smith".to_string())),
52 ]))),
53 ])
54 ) {
55 println!("View result: {:?}", v );
56 } else {
57 println!("Error?");
58 }
59 }
60}
61
62#[test]
63fn loops() -> Result<()> {
64 let mut ct = DeepCrow::new();
65 ct.load_static(r#"Looping code:
66[|view "index"|>We will enumerate people here:[|loop people|>
67- [|show last_name|], [|show first_name|][|if show_title |>: [|show title|] <|else|> **title redacted** <|if|] ::>[|loop tags|> [|show tag|]<|loop|] <:: <|loop|]
68<|view|]
69"#)?;
70
71 let ctx = Context::from([
72 ("people".to_string(), vec![
73 [
74 ("first_name".into(), "Joe".into()),
75 ("last_name".into(), "Smith".into()),
76 ("show_title".into(), true.into()),
77 ("title".into(), "CEO".into()),
78 ("tags".into(), vec![
79 Context::from([("tag".into(), "jerk".into()),]).into(),
80 Context::from([("tag".into(), "ugly".into()),]).into(),
81 ].into()),
82 ].into(),
83 [
84 ("first_name".into(), "Chris".into()),
85 ("last_name".into(), "Perkens".into()),
86 ("show_title".into(), false.into()),
87 ("title".into(), "Baconeer".into()),
88 ("tags".into(), vec![
89 Context::from([("tag".into(), "jerk".into()),]).into(),
90 Context::from([("tag".into(), "ugly".into()),]).into(),
91 ].into()),
92 ].into(),
93 [
94 ("first_name".into(), "Will".into()),
95 ("last_name".into(), "Power".into()),
96 ("show_title".into(), false.into()),
97 ("title".into(), "CFO".into()),
98 ("tags".into(), vec![
99 Context::from([("tag".into(), "jerk".into()),]).into(),
100 Context::from([("tag".into(), "ugly".into()),]).into(),
101 ].into()),
102 ].into(),
103 [
104 ("first_name".into(), "Justin".into()),
105 ("last_name".into(), "Time".into()),
106 ("show_title".into(), true.into()),
107 ("title".into(), "CIO".into()),
108 ("tags".into(), vec![
109 Context::from([("tag".into(), "jerk".into()),]).into(),
110 Context::from([("tag".into(), "ugly".into()),]).into(),
111 ].into()),
112 ].into(),
113 ].into()),
114 ]);
115
116 println!("View result: {}", ct.render("index", &ctx)? );
117
118 Ok(())
119}
120
121#[test]
122fn conditional() -> Result<()> {
123 let mut ct = DeepCrow::new();
124 ct.load_static(r#"Hi there
125[|view "index"|>
126Color: [|if is_red|> red <|elif is_blue |> blue <|else|> green <|if|]
127<|view|]"#)?;
128
129 let ctx = Context::from([
130 ("is_red".into(), false.into()),
131 ("is_blue".into(), false.into()),
132 ("color".into(), "purple".into()),
133 ]);
134
135 println!("View result: {}", ct.render("index", &ctx)? );
136
137 Ok(())
138}
139
140struct Item {
141 pub id: i32,
142 pub title: String,
143 pub body: String,
144}
145
146struct Person {
147 pub id: i32,
148 pub username: String,
149 pub name: String,
150}
151
152struct Page {
153 pub user: Person,
154 pub total_items: i32,
155 pub total_pages: i32,
156 pub cur_page: i32,
157 pub items: Vec<Item>,
158}
159
160impl Page {
161 fn new() -> Page {
162 Page {
163 user: Person {
164 id: 443,
165 username: "eichlan".into(),
166 name: "Mike".into(),
167 },
168 total_items: 4000,
169 total_pages: 400,
170 cur_page: 35,
171 items: vec![
172 Item{
173 id: 123,
174 title: "hi".into(),
175 body: "body".into(),
176 },
177 Item{
178 id: 124,
179 title: "bye".into(),
180 body: "wooo".into(),
181 },
182 Item{
183 id: 125,
184 title: "ciao".into(),
185 body: "whatever".into(),
186 }
187 ],
188 }
189 }
190}
191
192impl<'a> MappedStructure<'a> for Page {
193 fn get_value(&'a self, id: &str) -> Option<MappedValue<'a>> {
194 match id {
195 "user" => Some(MappedValue::Struct(&self.user)),
196 "total_items" => Some(MappedValue::Int32(self.total_items)),
197 "total_pages" => Some(MappedValue::Int32(self.total_pages)),
198 "cur_page" => Some(MappedValue::Int32(self.cur_page)),
199 "items" => Some(MappedValue::<'a>::List(&self.items)),
200 _ => None,
201 }
202 }
203}
204
205impl<'a> MappedStructure<'a> for Person {
206 fn get_value(&'a self, id: &str) -> Option<MappedValue<'a>> {
207 match id {
208 "id" => Some(MappedValue::Int32(self.id)),
209 "username" => Some(MappedValue::String(&self.username)),
210 "name" => Some(MappedValue::String(&self.name)),
211 _ => None,
212 }
213 }
214}
215
216impl<'a> MappedStructure<'a> for Item {
217 fn get_value(&'a self, id: &str) -> Option<MappedValue<'a>> {
218 match id {
219 "id" => Some(MappedValue::Int32(self.id)),
220 "title" => Some(MappedValue::String(&self.title)),
221 "body" => Some(MappedValue::String(&self.body)),
222 _ => None,
223 }
224 }
225}
226
227#[test]
228fn custom() -> Result<()> {
229 let page = Page::new();
230
231 println!("{:?}",
232 page.get_value(&"total_pages")
233 );
234
235 if let Some(MappedValue::List(l)) = page.get_value(&"items") {
236 l.for_each(&|x: &MappedValue, buf: &mut String| {
237 if let MappedValue::Struct(s) = x {
238 println!(" - {:?}", s.get_value(&"title"));
239 buf.push_str(&format!("{:?}",s.get_value(&"title")));
240 }
241 Ok(())
242 }).expect("loop");
243 }
244 Ok(())
245}
246