From cffacc4dc4a46b525250772e87fd69a2f1c64dc2 Mon Sep 17 00:00:00 2001 From: Mike Buland Date: Thu, 11 Jun 2026 11:35:24 -0700 Subject: Split things out, made things easier to use. CrimError now has several constructors for different cases, so it's easier to use, using constructors is normalized now. Value has a load of From implementations now so it's much, much, much easier to use in practice. --- src/context.rs | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/error.rs | 13 ++++-- src/lib.rs | 110 ++++++++++++++++---------------------------------- src/parser.rs | 60 ++++++++++++++-------------- 4 files changed, 199 insertions(+), 108 deletions(-) create mode 100644 src/context.rs diff --git a/src/context.rs b/src/context.rs new file mode 100644 index 0000000..78a7940 --- /dev/null +++ b/src/context.rs @@ -0,0 +1,124 @@ +use std::collections::HashMap; + +use crate::{Identifier,IdentifierValue}; + +pub type Context = HashMap; + +pub trait MappedStructure { + fn get_value(&self, id: &Identifier) -> Option; +} + +impl MappedStructure for Context { + fn get_value(&self, id: &Identifier) -> Option { + if id.len() == 0 { + return None; + } + let mut cur_val : Option<&Value> = if let IdentifierValue::Name(s) = &id[0] { + self.get(s) + } else { + return None; + }; + if cur_val.is_none() { + return None; + } + + for idx in 1..id.len() { + if let IdentifierValue::Name(s) = &id[idx] && + let Some(Value::Dictionary(d)) = &cur_val { + cur_val.replace( if let Some(v) = d.get(s) { + v + } else { + return None; + }); + } + } + + return cur_val.cloned(); + } +} + +#[derive(Debug,Clone)] +pub enum Value { + Dictionary(Context), + List(Vec), + String(String), + Int(i64), + Float(f64), + Identifier(Identifier), +} + +impl From<&str> for Value { + fn from(val: &str ) -> Value { + Value::String(val.into()) + } +} + +impl From for Value { + fn from(val: String) -> Value { + Value::String(val) + } +} + +impl From for Value { + fn from(val: i64) -> Value { + Value::Int(val) + } +} + +impl From for Value { + fn from(val: f64) -> Value { + Value::Float(val) + } +} + +impl From> for Value { + fn from(val: Vec) -> Value { + Value::List(val) + } +} + +impl From<&[Value]> for Value { + fn from(val: &[Value]) -> Value { + Value::List(Vec::from(val)) + } +} + +impl From for Value { + fn from(val: Context) -> Value { + Value::Dictionary(val) + } +} + +impl From<[(String,Value); N]> for Value { + fn from(val: [(String,Value); N]) -> Value { + Value::Dictionary(HashMap::from(val)) + } +} + +/* +impl MappedStructure for Value { + fn get_value(&self, id: &Identifier) -> Option { + if id.len() == 0 { + return Some(self); + } + match Value { + Value::Dictionary(dict) => { + } + } + let mut cur_val : Option<&Value> = Some(&self); + + for idx in 0..id.len() { + if let IdentifierValue::Name(s) = &id[idx] && + let Some(Value::Dictionary(d)) = &cur_val { + cur_val.replace( if let Some(v) = d.get(s) { + v + } else { + return None; + }); + } + } + + return cur_val.cloned(); + } +} +*/ diff --git a/src/error.rs b/src/error.rs index a2b6d96..a2349ee 100644 --- a/src/error.rs +++ b/src/error.rs @@ -11,15 +11,22 @@ pub struct CrimError { } impl CrimError { - pub fn new( position: Position, what: String ) -> Self { - CrimError { + pub fn parse( position: Position, what: String ) -> Self { + Self { position, what, } } + pub fn other(what: String) -> Self { + Self { + position: Position::none(), + what, + } + } + pub fn eos( context: &str ) -> Self { - CrimError { + Self { position: Position::none(), what: format!("Premature end of stream while looking for {}", context), } diff --git a/src/lib.rs b/src/lib.rs index 9b838f3..b0bc688 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,9 +7,11 @@ mod lexer; mod parser; mod position; mod error; +mod context; pub use error::{CrimError,CrimResult}; pub use position::*; +pub use context::{Context,Value,MappedStructure}; #[derive(Debug)] pub struct Crimtag { @@ -39,7 +41,7 @@ struct View { } impl View { - fn process(&self, input: &impl MappedStructure) -> Result { + fn process(&self, input: &impl MappedStructure) -> CrimResult { let mut out_vars = Context::new(); for output in &self.outputs { let mut buf = String::new(); @@ -68,16 +70,6 @@ enum Token { Text(String), } -#[derive(Debug,Clone)] -pub enum Value { - Dictionary(Context), - List(Vec), - String(String), - Int(i64), - Float(f64), - Identifier(Identifier), -} - #[derive(Debug,Clone)] pub enum IdentifierValue { Name(String), @@ -85,40 +77,6 @@ pub enum IdentifierValue { } pub type Identifier = Vec; -pub type Context = HashMap; - -pub trait MappedStructure { - fn get_value(&self, id: &Identifier) -> Option; -} - -impl MappedStructure for Context { - fn get_value(&self, id: &Identifier) -> Option { - if id.len() == 0 { - return None; - } - let mut cur_val : Option<&Value> = if let IdentifierValue::Name(s) = &id[0] { - self.get(s) - } else { - return None; - }; - if cur_val.is_none() { - return None; - } - - for idx in 1..id.len() { - if let IdentifierValue::Name(s) = &id[idx] && - let Some(Value::Dictionary(d)) = &cur_val { - cur_val.replace( if let Some(v) = d.get(s) { - v - } else { - return None; - }); - } - } - - return cur_val.cloned(); - } -} impl Crimtag { pub fn new() -> Self { @@ -169,13 +127,13 @@ impl Crimtag { Ok(()) } - pub fn load_static(&mut self, data: &str) -> Result<(),CrimError> { + pub fn load_static(&mut self, data: &str) -> CrimResult<()> { let source_id = self.id_source( &ViewSource::Static ); let p = parser::Parser::new(); self.register_views( p.parse( &data, source_id )? ) } - pub fn load_external(&mut self, key: &str, data: &str) -> Result<(),CrimError> { + pub fn load_external(&mut self, key: &str, data: &str) -> CrimResult<()> { let source_id = self.id_source( &ViewSource::External{ key: key.to_string()} ); @@ -186,7 +144,7 @@ impl Crimtag { ) } - fn register_views(&mut self, views: Vec) -> Result<(),CrimError> { + fn register_views(&mut self, views: Vec) -> CrimResult<()> { for view in views { self.views.insert( view.name.clone(), view ); } @@ -205,15 +163,15 @@ impl Crimtag { } } - pub fn render_partial(&self, view: &str, input: &impl MappedStructure ) -> Result { + pub fn render_partial(&self, view: &str, input: &impl MappedStructure ) -> CrimResult { if let Some(view) = self.views.get(view) { return view.process( input ) } else { - return Err(CrimError::new(Position::none(),"No such view found".into())); + return Err(CrimError::other("No such view found".into())); } } - pub fn render(&self, view: &str, input: &impl MappedStructure ) -> Result { + pub fn render(&self, view: &str, input: &impl MappedStructure ) -> CrimResult { if let Some(view) = self.views.get( view ) { let mut output = view.process( input )?; if let Some(l) = &view.layout { @@ -224,17 +182,17 @@ impl Crimtag { Ok(s) } else { - Err(CrimError::new(Position::none(), "No content found in root layout.".into())) + Err(CrimError::other("No content found in root layout.".into())) } } } else { - Err(CrimError::new(Position::none(),"No such view found".into())) + Err(CrimError::other("No such view found".into())) } } } -fn exec(input: &impl MappedStructure, tokens: &Vec, buf: &mut String) -> Result<(),CrimError> { +fn exec(input: &impl MappedStructure, tokens: &Vec, buf: &mut String) -> CrimResult<()> { for token in tokens { match token { Token::Loop(ident,code) => { @@ -363,28 +321,28 @@ Now with explicit outputs: } let ctx = Context::from([ - ("people".to_string(), crate::Value::List(vec![ - crate::Value::Dictionary(Context::from([ - ("first_name".to_string(), Value::String("Joe".to_string())), - ("last_name".to_string(), Value::String("Smith".to_string())), - ("title".to_string(), Value::String("CEO".to_string())), - ])), - crate::Value::Dictionary(Context::from([ - ("first_name".to_string(), Value::String("Chris".to_string())), - ("last_name".to_string(), Value::String("Perkens".to_string())), - ("title".to_string(), Value::String("Baconeer".to_string())), - ])), - crate::Value::Dictionary(Context::from([ - ("first_name".to_string(), Value::String("Will".to_string())), - ("last_name".to_string(), Value::String("Power".to_string())), - ("title".to_string(), Value::String("CFO".to_string())), - ])), - crate::Value::Dictionary(Context::from([ - ("first_name".to_string(), Value::String("Justin".to_string())), - ("last_name".to_string(), Value::String("Time".to_string())), - ("title".to_string(), Value::String("CIO".to_string())), - ])), - ])), + ("people".to_string(), vec![ + [ + ("first_name".into(), "Joe".into()), + ("last_name".into(), "Smith".into()), + ("title".into(), "CEO".into()), + ].into(), + [ + ("first_name".into(), "Chris".into()), + ("last_name".into(), "Perkens".into()), + ("title".into(), "Baconeer".into()), + ].into(), + [ + ("first_name".into(), "Will".into()), + ("last_name".into(), "Power".into()), + ("title".into(), "CFO".into()), + ].into(), + [ + ("first_name".into(), "Justin".into()), + ("last_name".into(), "Time".into()), + ("title".into(), "CIO".into()), + ].into(), + ].into()), ]); match ct.render("index", &ctx) { Ok(s) => println!("View result: {}", s ), diff --git a/src/parser.rs b/src/parser.rs index 83144fb..be5492c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -76,7 +76,7 @@ impl<'a> SymbolHelper for Option> { _ => false }) } else { - Err(CrimError::new( + Err(CrimError::parse( Position::none(), "Unexpeceted end of stream.".to_string() )) @@ -172,11 +172,11 @@ impl Parser { fn lex_error(&self, error: &Symbol) -> CrimResult { if let SymbolType::Error{what} = error.symbol() { - Err(CrimError::new( *error.start(), + Err(CrimError::parse( *error.start(), format!("What: {:?}", *what) )) } else { - Err(CrimError::new( Position::none(), "Not an error?".into())) + Err(CrimError::parse( Position::none(), "Not an error?".into())) } } @@ -214,7 +214,7 @@ impl Parser { fn parse_tag_view(&self, ctx: &mut Context) -> CrimResult { let tb = self.parse_open_tag( ctx )?; if !tb.is_name( &SymbolType::View ) { - return Err(CrimError::new(tb.start_pos(), + return Err(CrimError::parse(tb.start_pos(), "Unexpected tag type, only frament allowed at root".into() )); } @@ -260,7 +260,7 @@ impl Parser { }); } else { if !all_text { - return Err(CrimError::new( + return Err(CrimError::parse( *end.start(), "You cannot mix non-output and output tags in a view.".to_string() )); @@ -269,7 +269,7 @@ impl Parser { return tb.build_view(ctx,outputs); } else { // They don't match, complain. - return Err(CrimError::new( + return Err(CrimError::parse( *end.start(), "Mismatched open and closing tags.".to_string() )); @@ -279,7 +279,7 @@ impl Parser { return self.lex_error( &ctx.cur().unwrap() ); } _ => { - return Err(CrimError::new( + return Err(CrimError::parse( *ctx.cur().unwrap().start(), format!("Unexpected tag in view root: {:?}", ctx.cur().unwrap().symbol()) )); @@ -302,7 +302,7 @@ impl Parser { tb.set_name( name_sym ); } _ => { - return Err(CrimError::new( + return Err(CrimError::parse( *ctx.cur().unwrap().start(), "Unexpected symbol".to_string() )); @@ -318,13 +318,13 @@ impl Parser { if let Some(end_sym) = ctx.cur() { match end_sym.symbol() { SymbolType::EndPoint => { - tb.set_type( TagType::BinaryOpen ); + tb.set_type( TagType::MultinaryOpen ); } SymbolType::EndFlat => { tb.set_type( TagType::Unary ); } _ => { - return Err(CrimError::new( + return Err(CrimError::parse( *end_sym.start(), format!("Unexpected symbol {:?} looking for end of tag.", end_sym.symbol()) )); @@ -339,20 +339,20 @@ impl Parser { fn parse_end_tag<'a>(&self, ctx: &mut Context<'a> ) -> CrimResult> { if !ctx.cur().is("end tag",|s| *s == SymbolType::StartPoint )? { - return Err(CrimError::new( + return Err(CrimError::parse( *ctx.cur().unwrap().start(), "Invalid end tag?".to_string() )); } if !ctx.next().is_valid_tag_name()? { - return Err(CrimError::new( + return Err(CrimError::parse( *ctx.cur().unwrap().start(), "Invalid tag name".to_string() )); } let name = ctx.cur().unwrap(); if !ctx.next().is("end of end tag", |s| *s == SymbolType::EndFlat )? { - return Err(CrimError::new( + return Err(CrimError::parse( *ctx.cur().unwrap().start(), "Tag should be <| |] style end tag.".to_string(), )); @@ -386,7 +386,7 @@ impl Parser { return Ok(tb); } else { // They don't match, complain. - return Err(CrimError::new( + return Err(CrimError::parse( *end.start(), "Mismatched open and closing tags.".to_string() )); @@ -396,7 +396,7 @@ impl Parser { return self.lex_error( &ctx.cur().unwrap() ); } _ => { - return Err(CrimError::new( + return Err(CrimError::parse( *ctx.cur().unwrap().start(), format!("Unexpected token: {:?}", ctx.cur().unwrap().symbol()), )); @@ -438,7 +438,7 @@ impl Parser { id.push( IdentifierValue::Name(s.to_string()) ); } } else { - return Err(CrimError::new( ctx.cur().unwrap().start().clone(), format!("Expeceted identifier, found {:?}", ctx.cur().unwrap().symbol() ) ) ); + return Err(CrimError::parse( ctx.cur().unwrap().start().clone(), format!("Expeceted identifier, found {:?}", ctx.cur().unwrap().symbol() ) ) ); } if !ctx.next().is("identifier seperator",|s| *s == SymbolType::Period )? { @@ -462,7 +462,7 @@ impl Parser { let SymbolType::Literal(lv) = sym.symbol() { tb.add_prop( s.to_string(), lv.to_string() ); } else { - return Err(CrimError::new(Position::none(),"Expected quoted literal string".to_string())); + return Err(CrimError::parse(Position::none(),"Expected quoted literal string".to_string())); } } else { break; @@ -480,7 +480,8 @@ impl Parser { enum TagType { Unknown, Unary, - BinaryOpen, + MultinaryOpen, + MultinaryMid, } struct TagBuilder<'a> { @@ -523,7 +524,8 @@ impl<'a> TagBuilder<'a> { } pub fn can_have_children(&self) -> bool { - if self.tag_type == TagType::BinaryOpen { + if self.tag_type == TagType::MultinaryOpen || + self.tag_type == TagType::MultinaryMid { true } else { false @@ -580,7 +582,7 @@ impl<'a> TagBuilder<'a> { let name = if let ParamValue::Literal(s) = self.params.swap_remove(0) { s.to_string() } else { - return Err(CrimError::new(Position::none(), "Expected string literal for view name.".to_string())); + return Err(CrimError::parse(Position::none(), "Expected string literal for view name.".to_string())); }; Ok(View { name: name, @@ -590,10 +592,10 @@ impl<'a> TagBuilder<'a> { layout: self.props.get("layout").cloned(), }) } else { - Err(CrimError::new(self.start_pos, format!("Expected tag type view, found {:?}", sym.symbol()))) + Err(CrimError::parse(self.start_pos, format!("Expected tag type view, found {:?}", sym.symbol()))) } } else { - Err(CrimError::new(self.start_pos, "Broken Parser? No tag type found when building view.".to_string())) + Err(CrimError::parse(self.start_pos, "Broken Parser? No tag type found when building view.".to_string())) } } @@ -603,17 +605,17 @@ impl<'a> TagBuilder<'a> { let name = if let ParamValue::Literal(s) = self.params.swap_remove(0) { s.to_string() } else { - return Err(CrimError::new(Position::none(), "Expected string literal for output name.".into())); + return Err(CrimError::parse(Position::none(), "Expected string literal for output name.".into())); }; Ok(Output { name: name, code: self.children, }) } else { - Err(CrimError::new(self.start_pos, format!("Expected tag type view, found {:?}", sym.symbol()))) + Err(CrimError::parse(self.start_pos, format!("Expected tag type view, found {:?}", sym.symbol()))) } } else { - Err(CrimError::new(self.start_pos, "Broken Parser? No tag type found when building view.".into())) + Err(CrimError::parse(self.start_pos, "Broken Parser? No tag type found when building view.".into())) } } @@ -624,7 +626,7 @@ impl<'a> TagBuilder<'a> { let id = if let ParamValue::Identifier(id) = self.params.swap_remove(0) { id } else { - return Err(CrimError::new(Position::none(), "Identifier for loop variable name.".into())); + return Err(CrimError::parse(Position::none(), "Identifier for loop variable name.".into())); }; Ok(Token::Loop(id, self.children)) } @@ -632,16 +634,16 @@ impl<'a> TagBuilder<'a> { let id = if let ParamValue::Identifier(id) = self.params.swap_remove(0) { id } else { - return Err(CrimError::new(Position::none(), "Identifier for show variable name.".into())); + return Err(CrimError::parse(Position::none(), "Identifier for show variable name.".into())); }; Ok(Token::Show(id, self.props)) } _ => { - Err(CrimError::new( self.start_pos, "Bad tag type".into())) + Err(CrimError::parse( self.start_pos, "Bad tag type".into())) } } } else { - Err(CrimError::new(self.start_pos, "Bad tag type".into())) + Err(CrimError::parse(self.start_pos, "Bad tag type".into())) } } } -- cgit v1.2.3