use std::path::{PathBuf,Path}; use std::collections::HashMap; use core::error::Error; use std::time::SystemTime; mod lexer; mod parser; pub use parser::ParseError; #[derive(Debug,Copy,Clone)] pub struct Position { pub line: u32, pub column: u32, } impl Position { pub fn new( line: u32, column: u32 ) -> Self { Self { line, column, } } } #[derive(Debug)] pub struct Crimtag { views: HashMap, sources: Vec, } #[derive(PartialEq,Eq,Debug,Clone)] enum ViewSource { File { path: PathBuf, loaded: SystemTime, }, Static, External { key: String, }, } #[derive(Debug)] struct View { source: usize, outputs: Vec, // theme: Option layout: Option, } impl View { fn process(&self, input: &Value) -> Result { let mut out_vars = HashMap::new(); for output in &self.outputs { if let Token::Output(_,_,tokens) = &output.ast_root { let mut buf = String::new(); exec( input, tokens, &mut buf )?; out_vars.insert( output.name.clone(), Value::String(buf) ); } } Ok(Value::Dictionary(out_vars)) } } #[derive(Debug)] struct Output { name: String, ast_root: Token, } type Properties = HashMap; #[derive(Debug)] enum Token { Root(Vec), View(String,Properties,Vec), Output(String,Properties,Vec), Show(String,Properties), Text(String), Literal(String), /* Tag{ name: String, params: Vec, props: Properties, children: Vec },*/ } #[derive(Debug)] pub enum Value { Dictionary(HashMap), List(Vec), String(String), Int(i64), Float(f64), } impl Crimtag { pub fn new() -> Self { Self { views: HashMap::new(), sources: vec![ViewSource::Static], } } fn id_source(&mut self, src: &ViewSource ) -> usize { for idx in 0..self.sources.len() { if self.sources[idx] == *src { return idx; } } // Nothing found, add a new one let idx = self.sources.len(); self.sources.push( src.clone() ); idx } pub fn load_file(&mut self, path: &Path) -> Result<(),Box::> { let time = if let Ok(meta) = path.metadata() { if let Ok(t) = meta.modified() { t } else if let Ok(t) = meta.created() { t } else { SystemTime::now() } } else { SystemTime::now() }; let p = parser::Parser::new(); self.register_views( p.parse( &String::from_utf8( std::fs::read( path )? )? )?, ViewSource::File { path: path.to_path_buf(), loaded: time, })?; Ok(()) } pub fn load_static(&mut self, data: &str) -> Result<(),ParseError> { let p = parser::Parser::new(); self.register_views( p.parse( &data )?, ViewSource::Static ) } pub fn load_external(&mut self, key: &str, data: &str) -> Result<(),ParseError> { let p = parser::Parser::new(); self.register_views( p.parse( &data )?, ViewSource::External{ key: key.to_string()} ) } fn register_views(&mut self, token: Token, source: ViewSource) -> Result<(),ParseError> { let source_id = self.id_source( &source ); if let Token::Root(views) = token { for token in views { if let Token::View(name,props,output_tokens) = token { let mut outputs = Vec::new(); for ot in output_tokens { let output_name = if let Token::Output(on,..) = &ot { on.clone() } else { return Err(ParseError::new( Position::new(0,0), "Broken parser? Item in view is not an output." )); }; outputs.push( Output { name: output_name, ast_root: ot, } ); } let layout = if let Some(l) = props.get("layout") { Some(l.clone()) } else { None }; self.views.insert( name, View { source: source_id, outputs: outputs, layout: layout, }); } else { return Err(ParseError::new( Position::new(0,0), "Broken parser? Item in root is not a view." )); } } Ok(()) } else { Err(ParseError::new( Position::new(0,0), "Broken parser? Root is not a root token." )) } } pub fn view_partial(&self, view: &str, input: &Value ) -> Result { if let Some(view) = self.views.get(view) { return view.process( input ) } else { return Err(ParseError::new(Position::new(0,0),"No such view found")); } } pub fn view(&self, view: &str, input: &Value ) -> Result { if let Some(view) = self.views.get( view ) { let mut output = view.process( input )?; if let Some(l) = &view.layout { println!("Layout: {}", l ); self.view( l, &output ) } else { println!("No layout, at the top"); if let Value::Dictionary(mut d) = output && let Some(content) = d.remove("content") && let Value::String(s) = content { Ok(s) } else { Err(ParseError::new(Position::new(0,0),"No content found in root layout.")) } } } else { Err(ParseError::new(Position::new(0,0),"No such view found")) } } } fn exec(input: &Value, tokens: &Vec, buf: &mut String) -> Result<(),ParseError> { for token in tokens { match token { Token::Root(..) | Token::View(..) | Token::Output(..) => { println!("Too high!?"); // Error? } Token::Show(s,_) => { if let Value::Dictionary(d) = input && let Some(Value::String(s)) = d.get(s) { buf.push_str(s); } } Token::Text(s) => { buf.push_str( s ); } Token::Literal(..) => { println!("Literal!?"); } } } Ok(()) } #[cfg(test)] mod tests { use super::*; use crate::lexer::*; #[test] fn lexing() { let data = r#"Leading comment: [|view "basic" something="yeup"|>Hello there <|view|] Trailing text"#; let ll = lexer::Lexer::new( &data ); for sym in ll { if let SymbolType::Error{what} = sym.symbol() { println!("Error {}:{}: {:?}", sym.start().line, sym.start().column, what ); break; } else { println!("Symbol: {:?}", sym ); } } } #[test] fn parsing() { let mut ct = Crimtag::new(); if let Err(e) = ct.load_static(r#"Here is a sample [|view "index" theme="standard"|>Hi<|view|] That was fun! now a placeholder: [|view "placeholder"|] and now one with an implicit [|view "simple"|>Body [|show "content"|] and end<|view|] aoeu [|view "index" layout="simple"|>Whatever man!<|view|] Now with explicit outputs: [|view "complex"|> [|output "content"|> Here's a [|show "name"|]. <|output|] [|output "sidebar"|> What's up world? <|output|] [|output "footer"|> Same, I guess! <|output|] <|view|]"#) { println!("Error: {:?}", e ); } else { println!("It finished"); if let Ok(v) = ct.view( "index", &Value::Dictionary( HashMap::from([ ("hi".to_string(),Value::String("hi".to_string())) ]) ) ) { println!("View result: {:?}", v ); } else { println!("Error?"); } } } }