summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Buland <mike@xagasoft.com>2026-06-08 15:07:12 -0700
committerMike Buland <mike@xagasoft.com>2026-06-08 15:07:12 -0700
commita9172970548805db45188a352b2aafbc0e7b32e3 (patch)
treecf674f3ea9ebc023a6795f7a8e12a8157efd5c8f
parent3e60b2746bb95cfcd9b7fa4b7ed9e1c72259fb23 (diff)
downloadcrimtag-a9172970548805db45188a352b2aafbc0e7b32e3.tar.gz
crimtag-a9172970548805db45188a352b2aafbc0e7b32e3.tar.bz2
crimtag-a9172970548805db45188a352b2aafbc0e7b32e3.tar.xz
crimtag-a9172970548805db45188a352b2aafbc0e7b32e3.zip
It works! Lots to do, but it works.
We have a lot of cleanup to do, remove debugging prints, and make it easier to use other structures and complex variable references, etc.
-rw-r--r--src/lexer.rs4
-rw-r--r--src/lib.rs200
-rw-r--r--src/parser.rs31
3 files changed, 197 insertions, 38 deletions
diff --git a/src/lexer.rs b/src/lexer.rs
index 0667d07..5ce7929 100644
--- a/src/lexer.rs
+++ b/src/lexer.rs
@@ -16,7 +16,7 @@ pub enum SymbolType<'a> {
16 StartPoint, 16 StartPoint,
17 EndFlat, 17 EndFlat,
18 EndPoint, 18 EndPoint,
19 Fragment, 19 View,
20 Output, 20 Output,
21 Show, 21 Show,
22 Equals, 22 Equals,
@@ -228,7 +228,7 @@ impl<'a> Lexer<'a> {
228 None 228 None
229 } else { 229 } else {
230 Some(Symbol::new( match s { 230 Some(Symbol::new( match s {
231 "fragment" => SymbolType::Fragment, 231 "view" => SymbolType::View,
232 "show" => SymbolType::Show, 232 "show" => SymbolType::Show,
233 "output" => SymbolType::Output, 233 "output" => SymbolType::Output,
234 _ => SymbolType::Token(s) 234 _ => SymbolType::Token(s)
diff --git a/src/lib.rs b/src/lib.rs
index dec06a5..e07f85b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,6 +6,8 @@ use std::time::SystemTime;
6mod lexer; 6mod lexer;
7mod parser; 7mod parser;
8 8
9pub use parser::ParseError;
10
9#[derive(Debug,Copy,Clone)] 11#[derive(Debug,Copy,Clone)]
10pub struct Position { 12pub struct Position {
11 pub line: u32, 13 pub line: u32,
@@ -20,13 +22,14 @@ impl Position {
20 } 22 }
21} 23}
22 24
25#[derive(Debug)]
23pub struct Crimtag { 26pub struct Crimtag {
24 fragments: HashMap<String, Fragment>, 27 views: HashMap<String, View>,
25 sources: Vec<FragmentSource>, 28 sources: Vec<ViewSource>,
26} 29}
27 30
28#[derive(PartialEq,Eq,Debug,Clone)] 31#[derive(PartialEq,Eq,Debug,Clone)]
29enum FragmentSource { 32enum ViewSource {
30 File { 33 File {
31 path: PathBuf, 34 path: PathBuf,
32 loaded: SystemTime, 35 loaded: SystemTime,
@@ -37,12 +40,29 @@ enum FragmentSource {
37 }, 40 },
38} 41}
39 42
40struct Fragment { 43#[derive(Debug)]
41 name: String, 44struct View {
42 source: usize, 45 source: usize,
43 sections: HashMap<String, Output>, 46 outputs: Vec<Output>,
47// theme: Option<String>
48 layout: Option<String>,
44} 49}
45 50
51impl View {
52 fn process(&self, input: &Value) -> Result<Value, ParseError> {
53 let mut out_vars = HashMap::new();
54 for output in &self.outputs {
55 if let Token::Output(_,_,tokens) = &output.ast_root {
56 let mut buf = String::new();
57 exec( input, tokens, &mut buf )?;
58 out_vars.insert( output.name.clone(), Value::String(buf) );
59 }
60 }
61 Ok(Value::Dictionary(out_vars))
62 }
63}
64
65#[derive(Debug)]
46struct Output { 66struct Output {
47 name: String, 67 name: String,
48 ast_root: Token, 68 ast_root: Token,
@@ -53,7 +73,7 @@ type Properties = HashMap<String, String>;
53#[derive(Debug)] 73#[derive(Debug)]
54enum Token { 74enum Token {
55 Root(Vec<Token>), 75 Root(Vec<Token>),
56 Fragment(String,Properties,Vec<Token>), 76 View(String,Properties,Vec<Token>),
57 Output(String,Properties,Vec<Token>), 77 Output(String,Properties,Vec<Token>),
58 Show(String,Properties), 78 Show(String,Properties),
59 Text(String), 79 Text(String),
@@ -67,18 +87,24 @@ enum Token {
67 },*/ 87 },*/
68} 88}
69 89
70struct State { 90#[derive(Debug)]
91pub enum Value {
92 Dictionary(HashMap<String,Value>),
93 List(Vec<Value>),
94 String(String),
95 Int(i64),
96 Float(f64),
71} 97}
72 98
73impl Crimtag { 99impl Crimtag {
74 pub fn new() -> Self { 100 pub fn new() -> Self {
75 Self { 101 Self {
76 fragments: HashMap::new(), 102 views: HashMap::new(),
77 sources: vec![FragmentSource::Static], 103 sources: vec![ViewSource::Static],
78 } 104 }
79 } 105 }
80 106
81 fn id_source(&mut self, src: &FragmentSource ) -> usize { 107 fn id_source(&mut self, src: &ViewSource ) -> usize {
82 for idx in 0..self.sources.len() { 108 for idx in 0..self.sources.len() {
83 if self.sources[idx] == *src { 109 if self.sources[idx] == *src {
84 return idx; 110 return idx;
@@ -104,23 +130,133 @@ impl Crimtag {
104 SystemTime::now() 130 SystemTime::now()
105 }; 131 };
106 132
107 let mut p = parser::Parser::new(); 133 let p = parser::Parser::new();
108 p.parse( &String::from_utf8( std::fs::read( path )? )? ); 134 self.register_views(
109 135 p.parse( &String::from_utf8( std::fs::read( path )? )? )?,
136 ViewSource::File {
137 path: path.to_path_buf(),
138 loaded: time,
139 })?;
110 Ok(()) 140 Ok(())
111 } 141 }
112 142
113 pub fn load_static(&mut self, data: &str) -> Result<(),Box::<dyn Error>> { 143 pub fn load_static(&mut self, data: &str) -> Result<(),ParseError> {
114 let mut p = parser::Parser::new(); 144 let p = parser::Parser::new();
115 println!("Parsed token tree: {:?}", p.parse( &data )? ); 145 self.register_views( p.parse( &data )?, ViewSource::Static )
146 }
116 147
117 Ok(()) 148 pub fn load_external(&mut self, key: &str, data: &str) -> Result<(),ParseError> {
149 let p = parser::Parser::new();
150 self.register_views(
151 p.parse( &data )?,
152 ViewSource::External{ key: key.to_string()}
153 )
118 } 154 }
119 155
120 pub fn load_external(&mut self, key: &str, data: &str) -> Result<(),Box::<dyn Error>> { 156 fn register_views(&mut self, token: Token, source: ViewSource) -> Result<(),ParseError> {
121 Ok(()) 157 let source_id = self.id_source( &source );
158 if let Token::Root(views) = token {
159 for token in views {
160 if let Token::View(name,props,output_tokens) = token {
161 let mut outputs = Vec::new();
162 for ot in output_tokens {
163 let output_name = if let Token::Output(on,..) = &ot {
164 on.clone()
165 } else {
166 return Err(ParseError::new(
167 Position::new(0,0),
168 "Broken parser? Item in view is not an output."
169 ));
170 };
171 outputs.push(
172 Output {
173 name: output_name,
174 ast_root: ot,
175 }
176 );
177 }
178
179 let layout = if let Some(l) = props.get("layout") {
180 Some(l.clone())
181 } else {
182 None
183 };
184
185 self.views.insert( name, View {
186 source: source_id,
187 outputs: outputs,
188 layout: layout,
189 });
190 } else {
191 return Err(ParseError::new(
192 Position::new(0,0),
193 "Broken parser? Item in root is not a view."
194 ));
195 }
196 }
197 Ok(())
198 } else {
199 Err(ParseError::new(
200 Position::new(0,0),
201 "Broken parser? Root is not a root token."
202 ))
203 }
204 }
205
206 pub fn view_partial(&self, view: &str, input: &Value ) -> Result<Value,ParseError> {
207 if let Some(view) = self.views.get(view) {
208 return view.process( input )
209 } else {
210 return Err(ParseError::new(Position::new(0,0),"No such view found"));
211 }
212 }
213
214 pub fn view(&self, view: &str, input: &Value ) -> Result<String,ParseError> {
215 if let Some(view) = self.views.get( view ) {
216 let mut output = view.process( input )?;
217 if let Some(l) = &view.layout {
218 println!("Layout: {}", l );
219 self.view( l, &output )
220 } else {
221 println!("No layout, at the top");
222 if let Value::Dictionary(mut d) = output &&
223 let Some(content) = d.remove("content") &&
224 let Value::String(s) = content {
225
226 Ok(s)
227 } else {
228 Err(ParseError::new(Position::new(0,0),"No content found in root layout."))
229 }
230 }
231
232 } else {
233 Err(ParseError::new(Position::new(0,0),"No such view found"))
234 }
122 } 235 }
123} 236}
237fn exec(input: &Value, tokens: &Vec<Token>, buf: &mut String) -> Result<(),ParseError> {
238 for token in tokens {
239 match token {
240 Token::Root(..) | Token::View(..) | Token::Output(..) => {
241 println!("Too high!?");
242 // Error?
243 }
244 Token::Show(s,_) => {
245 if let Value::Dictionary(d) = input &&
246 let Some(Value::String(s)) = d.get(s) {
247 buf.push_str(s);
248 }
249 }
250 Token::Text(s) => {
251 buf.push_str( s );
252 }
253 Token::Literal(..) => {
254 println!("Literal!?");
255 }
256 }
257 }
258 Ok(())
259}
124 260
125#[cfg(test)] 261#[cfg(test)]
126mod tests { 262mod tests {
@@ -130,7 +266,7 @@ mod tests {
130 266
131 #[test] 267 #[test]
132 fn lexing() { 268 fn lexing() {
133 let data = r#"Leading comment: [|fragment "basic" something="yeup"|>Hello there <|fragment|] Trailing text"#; 269 let data = r#"Leading comment: [|view "basic" something="yeup"|>Hello there <|view|] Trailing text"#;
134 let ll = lexer::Lexer::new( &data ); 270 let ll = lexer::Lexer::new( &data );
135 for sym in ll { 271 for sym in ll {
136 if let SymbolType::Error{what} = sym.symbol() { 272 if let SymbolType::Error{what} = sym.symbol() {
@@ -146,11 +282,13 @@ mod tests {
146 fn parsing() { 282 fn parsing() {
147 let mut ct = Crimtag::new(); 283 let mut ct = Crimtag::new();
148 if let Err(e) = ct.load_static(r#"Here is a sample 284 if let Err(e) = ct.load_static(r#"Here is a sample
149[|fragment "index" theme="standard"|><html><body>Hi</body></html><|fragment|] 285[|view "index" theme="standard"|><html><body>Hi</body></html><|view|]
150That was fun! now a placeholder: [|fragment "placeholder"|] and now one with an implicit [|fragment "simple"|>Body [|show "content"|]<|fragment|] aoeu 286That was fun! now a placeholder: [|view "placeholder"|] and now one with an implicit [|view "simple"|>Body [|show "content"|] and end<|view|] aoeu
287
288[|view "index" layout="simple"|>Whatever man!<|view|]
151 289
152Now with explicit outputs: 290Now with explicit outputs:
153[|fragment "complex"|> 291[|view "complex"|>
154 [|output "content"|> 292 [|output "content"|>
155 Here's a [|show "name"|]. 293 Here's a [|show "name"|].
156 <|output|] 294 <|output|]
@@ -160,10 +298,22 @@ Now with explicit outputs:
160 [|output "footer"|> 298 [|output "footer"|>
161 Same, I guess! 299 Same, I guess!
162 <|output|] 300 <|output|]
163<|fragment|]"#) { 301<|view|]"#) {
164 println!("Error: {:?}", e ); 302 println!("Error: {:?}", e );
165 } else { 303 } else {
166 println!("It finished"); 304 println!("It finished");
305 if let Ok(v) = ct.view(
306 "index",
307 &Value::Dictionary(
308 HashMap::from([
309 ("hi".to_string(),Value::String("hi".to_string()))
310 ])
311 )
312 ) {
313 println!("View result: {:?}", v );
314 } else {
315 println!("Error?");
316 }
167 } 317 }
168 } 318 }
169} 319}
diff --git a/src/parser.rs b/src/parser.rs
index 1591c8b..aff64f6 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -10,6 +10,15 @@ pub struct ParseError {
10 what: String, 10 what: String,
11} 11}
12 12
13impl ParseError {
14 pub fn new( position: Position, what: &str ) -> Self {
15 ParseError {
16 position,
17 what: what.to_string(),
18 }
19 }
20}
21
13impl fmt::Display for ParseError { 22impl fmt::Display for ParseError {
14 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
15 write!(f, "Error parsing input: {}", "yeah") 24 write!(f, "Error parsing input: {}", "yeah")
@@ -85,7 +94,7 @@ impl<'a> SymbolHelper for Option<Symbol<'a>> {
85 if let Some(st) = self { 94 if let Some(st) = self {
86 Ok(match st.symbol() { 95 Ok(match st.symbol() {
87 SymbolType::Token(_) | 96 SymbolType::Token(_) |
88 SymbolType::Fragment | 97 SymbolType::View |
89 SymbolType::Output | 98 SymbolType::Output |
90 SymbolType::Show => true, 99 SymbolType::Show => true,
91 _ => false 100 _ => false
@@ -127,7 +136,7 @@ pub struct Parser {
127 * close_tag: '<|' token '|]' 136 * close_tag: '<|' token '|]'
128 * ; 137 * ;
129 * 138 *
130 * tag_guts: 'fragment' literal props 139 * tag_guts: 'view' literal props
131 * | 'section' literal 140 * | 'section' literal
132 * | 'output' literal 141 * | 'output' literal
133 * ; 142 * ;
@@ -167,7 +176,7 @@ pub struct Parser {
167 * close_tag: '<|' token '|]' 176 * close_tag: '<|' token '|]'
168 * ; 177 * ;
169 * 178 *
170 * tag_guts: 'fragment' literal props 179 * tag_guts: 'view' literal props
171 * | 'section' literal 180 * | 'section' literal
172 * | 'output' literal 181 * | 'output' literal
173 * ; 182 * ;
@@ -216,7 +225,7 @@ impl Parser {
216 match ctx.cur().unwrap().symbol() { 225 match ctx.cur().unwrap().symbol() {
217 SymbolType::Text(_) => { /* Skip top level text */ } 226 SymbolType::Text(_) => { /* Skip top level text */ }
218 SymbolType::StartFlat => { 227 SymbolType::StartFlat => {
219 children.push( self.parse_tag_fragment( ctx )? ); 228 children.push( self.parse_tag_view( ctx )? );
220 } 229 }
221 SymbolType::Error{..} => { 230 SymbolType::Error{..} => {
222 return self.lex_error( &ctx.cur().unwrap() ); 231 return self.lex_error( &ctx.cur().unwrap() );
@@ -230,9 +239,9 @@ impl Parser {
230 Ok(Token::Root(children)) 239 Ok(Token::Root(children))
231 } 240 }
232 241
233 fn parse_tag_fragment(&self, ctx: &mut Context) -> ParseResult<Token> { 242 fn parse_tag_view(&self, ctx: &mut Context) -> ParseResult<Token> {
234 let mut tb = self.parse_open_tag( ctx )?; 243 let mut tb = self.parse_open_tag( ctx )?;
235 if !tb.is_name( &SymbolType::Fragment ) { 244 if !tb.is_name( &SymbolType::View ) {
236 return Err(ParseError{ 245 return Err(ParseError{
237 position: tb.start_pos(), 246 position: tb.start_pos(),
238 what: "Unexpected tag type, only frament allowed at root".to_string(), 247 what: "Unexpected tag type, only frament allowed at root".to_string(),
@@ -244,7 +253,7 @@ impl Parser {
244 } 253 }
245 let mut children = Vec::new(); 254 let mut children = Vec::new();
246 255
247 println!("--parse-tag-fragment-- tag parsed, next token: {:?}", ctx.cur() ); 256 println!("--parse-tag-view-- tag parsed, next token: {:?}", ctx.cur() );
248 loop { 257 loop {
249 if ctx.cur().is_none() { 258 if ctx.cur().is_none() {
250 return Err(ParseError{ 259 return Err(ParseError{
@@ -328,7 +337,7 @@ impl Parser {
328 337
329 if ctx.next().is_some() { 338 if ctx.next().is_some() {
330 match ctx.cur().unwrap().symbol() { 339 match ctx.cur().unwrap().symbol() {
331 SymbolType::Fragment | SymbolType::Show | 340 SymbolType::View | SymbolType::Show |
332 SymbolType::Output => { 341 SymbolType::Output => {
333 let name_sym = ctx.cur().unwrap(); 342 let name_sym = ctx.cur().unwrap();
334 ctx.next(); 343 ctx.next();
@@ -532,7 +541,7 @@ impl<'a> TagBuilder<'a> {
532 } 541 }
533 } 542 }
534 543
535 pub fn name(&self) -> Option<Symbol> { 544 pub fn name(&self) -> Option<Symbol<'a>> {
536 self.name 545 self.name
537 } 546 }
538 547
@@ -570,8 +579,8 @@ impl<'a> TagBuilder<'a> {
570 pub fn build(mut self) -> ParseResult<Token> { 579 pub fn build(mut self) -> ParseResult<Token> {
571 if let Some(sym) = self.name { 580 if let Some(sym) = self.name {
572 match sym.symbol() { 581 match sym.symbol() {
573 SymbolType::Fragment => { 582 SymbolType::View => {
574 Ok(Token::Fragment(self.params.swap_remove(0), self.props, self.children)) 583 Ok(Token::View(self.params.swap_remove(0), self.props, self.children))
575 } 584 }
576 SymbolType::Output => { 585 SymbolType::Output => {
577 Ok(Token::Output(self.params.swap_remove(0), self.props, self.children)) 586 Ok(Token::Output(self.params.swap_remove(0), self.props, self.children))