From 062048ef6e3e060bcf841ea2ec92e026f09d8da0 Mon Sep 17 00:00:00 2001 From: eichlan Date: Tue, 23 Jun 2026 15:31:51 -0700 Subject: Roorganized into workspace and packages. This is to accomidate a new proc_macro package, which can't have anything else in it. --- crimtag/Cargo.lock | 7 + crimtag/Cargo.toml | 6 + crimtag/src/context.rs | 289 ++++++++++++++++++ crimtag/src/error.rs | 59 ++++ crimtag/src/lexer.rs | 368 +++++++++++++++++++++++ crimtag/src/lib.rs | 544 +++++++++++++++++++++++++++++++++ crimtag/src/parser.rs | 785 ++++++++++++++++++++++++++++++++++++++++++++++++ crimtag/src/position.rs | 96 ++++++ 8 files changed, 2154 insertions(+) create mode 100644 crimtag/Cargo.lock create mode 100644 crimtag/Cargo.toml create mode 100644 crimtag/src/context.rs create mode 100644 crimtag/src/error.rs create mode 100644 crimtag/src/lexer.rs create mode 100644 crimtag/src/lib.rs create mode 100644 crimtag/src/parser.rs create mode 100644 crimtag/src/position.rs (limited to 'crimtag') diff --git a/crimtag/Cargo.lock b/crimtag/Cargo.lock new file mode 100644 index 0000000..f7e6686 --- /dev/null +++ b/crimtag/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "crimtag" +version = "0.1.0" diff --git a/crimtag/Cargo.toml b/crimtag/Cargo.toml new file mode 100644 index 0000000..ee022ca --- /dev/null +++ b/crimtag/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "crimtag" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/crimtag/src/context.rs b/crimtag/src/context.rs new file mode 100644 index 0000000..6f8e04f --- /dev/null +++ b/crimtag/src/context.rs @@ -0,0 +1,289 @@ +use std::collections::HashMap; +use std::fmt; + +use crate::error::CrimResult; + +use crate::{Identifier,IdentifierValue}; + +pub type Context = HashMap; + +pub trait MappedStructure<'a> { + fn get_value(&'a self, id: &str) -> Option>; +} + +impl<'a> MappedStructure<'a> for Context { + fn get_value(&'a self, id: &str) -> Option> { + self.get(id).map(|v|Into::>::into(v)) + } +} + +pub trait MappedList<'a> { + fn get_index(&'a self, id: usize) -> Option>; + fn for_each(&'a self, func: &dyn Fn(&MappedValue<'a>, &mut String) -> CrimResult<()>) -> CrimResult; +} + +impl<'a, T: MappedStructure<'a>> MappedList<'a> for Vec { + fn get_index(&'a self, id: usize) -> Option> { + if let Some(x) = self.get(id) { + Some(MappedValue::<'a>::Struct(x)) + } else { + None + } + } + + fn for_each(&'a self, func: &dyn Fn(&MappedValue<'a>, &mut String) -> CrimResult<()>) -> CrimResult { + let mut buf = String::new(); + for i in self { + func( &MappedValue::Struct(i), &mut buf )?; + } + Ok(buf) + } +} + +impl<'a> MappedList<'a> for Vec { + fn get_index(&'a self, id: usize) -> Option> { + if let Some(x) = self.get(id) { + Some(x.into()) + } else { + None + } + } + + fn for_each(&'a self, func: &dyn Fn(&MappedValue<'a>, &mut String) -> CrimResult<()>) -> CrimResult { + let mut buf = String::new(); + for i in self { + func( &Into::>::into( i ), &mut buf )?; + } + Ok(buf) + } +} + +impl<'a> MappedList<'a> for Vec> { + fn get_index(&'a self, id: usize) -> Option> { + if let Some(x) = self.get(id) { + Some(*x) + } else { + None + } + } + + fn for_each(&'a self, func: &dyn Fn(&MappedValue<'a>, &mut String) -> CrimResult<()>) -> CrimResult { + let mut buf = String::new(); + for i in self { + func( i, &mut buf )?; + } + Ok(buf) + } +} + +pub type MappedHash<'a> = HashMap>; + +impl<'a> MappedStructure<'a> for MappedHash<'a> { + fn get_value(&'a self, id: &str) -> Option> { + self.get(id).copied() + } +} + +#[derive(Copy,Clone)] +pub enum MappedValue<'a> { + Struct(&'a dyn MappedStructure<'a>), + List(&'a dyn MappedList<'a>), + + Str(&'a str), + String(&'a String), + Int8(i8), + Int16(i16), + Int32(i32), + Int64(i64), + UInt8(u8), + UInt16(u16), + UInt32(u32), + UInt64(u64), + Float32(f32), + Float64(f64), + Bool(bool), +} + +impl<'a> fmt::Debug for MappedValue<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + MappedValue::<'a>::Struct(_) => write!(f, "MappedValue"), + MappedValue::<'a>::List(_) => write!(f, "MappedList"), + MappedValue::<'a>::Str(v) => write!(f, "{:?}", v), + MappedValue::<'a>::String(v) => write!(f, "{:?}", v), + MappedValue::<'a>::Int8(v) => write!(f, "{:?}", v), + MappedValue::<'a>::Int16(v) => write!(f, "{:?}", v), + MappedValue::<'a>::Int32(v) => write!(f, "{:?}", v), + MappedValue::<'a>::Int64(v) => write!(f, "{:?}", v), + MappedValue::<'a>::UInt8(v) => write!(f, "{:?}", v), + MappedValue::<'a>::UInt16(v) => write!(f, "{:?}", v), + MappedValue::<'a>::UInt32(v) => write!(f, "{:?}", v), + MappedValue::<'a>::UInt64(v) => write!(f, "{:?}", v), + MappedValue::<'a>::Float32(v) => write!(f, "{:?}", v), + MappedValue::<'a>::Float64(v) => write!(f, "{:?}", v), + MappedValue::<'a>::Bool(v) => write!(f, "{:?}", v), + } + } + +} + +pub struct StatefulContext<'a> { + root: &'a dyn MappedStructure<'a>, + local: &'a dyn MappedStructure<'a>, +} + +impl<'a> StatefulContext<'a> { + pub fn root( root: &'a dyn MappedStructure<'a> ) -> Self { + Self { + root, local: root, + } + } + + pub fn local(&self, local: &'a dyn MappedStructure<'a> ) -> Self { + Self { + root: self.root, + local + } + } + + pub fn full( root: &'a dyn MappedStructure<'a>, local: &'a dyn MappedStructure<'a> ) -> Self { + Self { + root, local + } + } + + pub fn get_value(&self, id: &[IdentifierValue]) -> Option> { + let mut value : Option = + if id[0] == IdentifierValue::Root { + Some(MappedValue::Struct(self.root)) + } else { + Some(MappedValue::Struct(self.local)) + }; + for idpart in id { + match idpart { + IdentifierValue::Root => { + continue; + } + IdentifierValue::Name(name) => { + if let Some(MappedValue::Struct(s)) = value && + let Some(nv) = s.get_value( &name ) { + value.replace( nv ); + } else { + return None; + } + } + IdentifierValue::Index(_) => { + return None; + } + } + } + value + } +} + +impl<'a> From<&'a Value> for MappedValue<'a> { + fn from(value: &'a Value) -> Self { + match value { + Value::Dictionary(ctx) => MappedValue::Struct(ctx), + Value::List(list) => MappedValue::List(list), + Value::String(s) => MappedValue::String(s), + Value::Int(i) => MappedValue::Int64(*i), + Value::Float(f) => MappedValue::Float64(*f), + Value::Bool(b) => MappedValue::Bool(*b), + Value::Identifier(_identifier) => panic!("Identifier!?"), + } + } +} + +#[derive(Debug,Clone)] +pub enum Value { + Dictionary(Context), + List(Vec), + String(String), + Int(i64), + Float(f64), + Bool(bool), + 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: bool) -> Value { + Value::Bool(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/crimtag/src/error.rs b/crimtag/src/error.rs new file mode 100644 index 0000000..bbb5d6f --- /dev/null +++ b/crimtag/src/error.rs @@ -0,0 +1,59 @@ + +use core::error::Error; +use std::fmt; + +use crate::position::*; + +#[derive(Debug)] +pub struct CrimError { + position: Position, + what: String, +} + +impl 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 broken( position: Position, what: &str ) -> Self { + Self { + position, + what: format!("Broken parser? {}", what), + } + } + + pub fn eos( context: &str ) -> Self { + Self { + position: Position::none(), + what: format!("Premature end of stream while looking for {}", context), + } + } + + pub fn what(&self) -> &str { + &self.what + } + + pub fn start(&self) -> &Position { + &self.position + } +} + +impl fmt::Display for CrimError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Error parsing input: {}", "yeah") + } +} + +impl Error for CrimError { } + +pub type CrimResult = Result; diff --git a/crimtag/src/lexer.rs b/crimtag/src/lexer.rs new file mode 100644 index 0000000..d4d39e9 --- /dev/null +++ b/crimtag/src/lexer.rs @@ -0,0 +1,368 @@ +use std::iter::Iterator; +//use core::error::Error; +use std::str::CharIndices; +use std::fmt; + +use crate::Position; + +#[derive(Debug,Copy,Clone,PartialEq,Eq)] +pub enum ErrorType { + UnexpectedChar(char), +} + +#[derive(Debug,Copy,Clone,PartialEq,Eq)] +pub enum SymbolType<'a> { + StartFlat, + StartPoint, + EndFlat, + EndPoint, + + View, + Output, + Show, + Loop, + If, + ElIf, + Else, + + Sharp, + Equals, + Period, + Token(&'a str), + Literal(&'a str), + Text(&'a str), + Error{ what: ErrorType }, +} + +#[derive(Copy,Clone)] +pub struct Symbol<'a> { + symbol: SymbolType<'a>, + start: Position, + end: Position, +} + +impl<'a> fmt::Debug for Symbol<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?} @ {:?}-{:?}", self.symbol, self.start, self.end ) + } +} + +impl<'a> Symbol<'a> { + pub fn new( symbol: SymbolType<'a>, start: Position, end: Position ) -> Self { + Self { + symbol, start, end, + } + } + + pub fn check_type bool>(&self, f: T ) -> bool { + f( &self.symbol ) + } + + pub fn symbol(&self) -> &SymbolType<'a> { + &self.symbol + } + + pub fn start(&self) -> &Position { + &self.start + } + + /* + pub fn end(&self) -> &Position { + &self.end + } + */ +} + +#[derive(Debug)] +enum Mode { + Text, + InTag, +} + +pub struct Lexer<'a> { + data: &'a str, + chars: CharIndices<'a>, + cur: [Option<(usize, char)>;2], + icur: usize, + mode: Mode, + pos: Position, +} + +fn is_valid_token_char( c: char, first: bool ) -> bool { + if c.is_whitespace() { + return false; + } + if first { + match c { + 'a'..'z' | 'A'..'Z' | '_' => true, + _ => false + } + } else { + match c { + 'a'..'z' | 'A'..'Z' | '0'..'9' | '_' => true, + _ => false + } + } +} + +impl<'a> Lexer<'a> { + pub fn new( data: &'a str ) -> Lexer<'a> { + let mut chars = data.char_indices(); + let cur = chars.next(); + let cur2 = chars.next(); + //println!(" - cur: {:?}, peek: {:?}", cur, cur2 ); + Lexer { + data, + chars, + cur: [cur, cur2], + icur: 0, + mode: Mode::Text, + pos: Position::new(1,1), + } + } + + fn next(&mut self) -> Option { + self.cur[self.icur] = self.chars.next(); + self.icur = (self.icur+1)%2; + //println!(" - cur: {:?}, peek: {:?}", self.cur(), self.peek() ); + if let Some((_,chr)) = self.cur[self.icur] { + if chr == '\n' { + self.pos.column = 1; + self.pos.line += 1; + } else { + self.pos.column += 1; + } + Some(chr) + } else { + None + } + } + + fn cur(&self) -> Option { + if let Some((_, chr)) = self.cur[self.icur] { + Some(chr) + } else { + None + } + } + + fn cur_index(&self) -> usize { + if let Some((idx, _)) = self.cur[self.icur] { + idx + } else { + self.chars.offset() + } + } + + fn peek(&self) -> Option { + if let Some((_,chr)) = self.cur[(self.icur+1)%2] { + Some(chr) + } else { + None + } + } + + fn peek_index(&self) -> usize { + if let Some((idx, _)) = self.cur[(self.icur+1)%2] { + idx + } else { + self.chars.offset() + } + } + + fn error( &self, what: ErrorType ) -> Option> { + Some(Symbol::new( SymbolType::Error { + what: what + }, self.pos, self.pos )) + } + + fn skip_ws( &mut self ) { + while self.cur().is_some_and(|x|x.is_whitespace()) { + self.next(); + } + } + + fn next_symbol(&mut self) -> Option> { + // If we hit the end then we're already done. + if self.cur().is_none() { + return None; + } + + match self.mode { + Mode::Text => { + if let Some(sym) = self.parse_start_tag() { + Some(sym) + } else { + self.parse_text() + } + } + Mode::InTag => { + if let Some(sym) = self.parse_end_tag() { + Some(sym) + } else { + self.parse_token() + } + } + } + } + + fn parse_text(&mut self) -> Option> { + let start = self.cur_index(); + let start_pos = self.pos.clone(); + while self.next().is_some() && !self.is_start_tag() { } + let end = self.cur_index(); + let end_pos = self.pos.clone(); + let s = &self.data[start..end]; + //println!(" text: >>>{}<<<", s); + if start == end { + None + } else { + Some(Symbol::new( SymbolType::Text(s), start_pos, end_pos )) + } + } + + fn parse_token(&mut self) -> Option> { + self.skip_ws(); + match self.cur() { + Some('"') => { + return self.parse_literal_str(); + } + Some('#') => { + self.next(); + return Some(Symbol::new(SymbolType::Sharp, self.pos, self.pos)); + } + Some('=') => { + self.next(); + return Some(Symbol::new(SymbolType::Equals, self.pos, self.pos)); + } + Some('.') => { + self.next(); + return Some(Symbol::new(SymbolType::Period, self.pos, self.pos)); + } + _ => {} + } + let start = self.cur_index(); + let start_pos = self.pos.clone(); + + let mut first = true; + while self.next().is_some_and(|ch| is_valid_token_char(ch, first) ) && + !self.is_end_tag() { first = false; } + let end = self.cur_index(); + let end_pos = self.pos.clone(); + let s = &self.data[start..end]; + if start == end { + None + } else { + Some(Symbol::new( match s { + "view" => SymbolType::View, + "show" => SymbolType::Show, + "output" => SymbolType::Output, + "loop" => SymbolType::Loop, + "if" => SymbolType::If, + "elif" => SymbolType::ElIf, + "else" => SymbolType::Else, + _ => SymbolType::Token(s) + }, start_pos, end_pos )) + } + } + + fn parse_literal_str(&mut self) -> Option> { + if let Some(chr) = self.cur() && chr != '"' { + return self.error( ErrorType::UnexpectedChar(chr) ); + } + let start = self.peek_index(); + let start_pos = self.pos.clone(); + while self.next().is_some_and(|chr| chr != '"') { } + let end = self.cur_index(); + let end_pos = self.pos.clone(); + self.next(); + let s = &self.data[start..end]; + Some(Symbol::new(SymbolType::Literal(s), start_pos, end_pos )) + } + + fn is_start_tag(&mut self) -> bool { + if let Some(cur) = self.cur() && (cur == '[' || cur == '<') && + let Some(peek) = self.peek() && peek == '|' { + true + } else { + false + } + } + + fn parse_start_tag(&mut self) -> Option> { + let start_pos = self.pos.clone(); + match self.cur() { + Some('[') => { + if let Some(p) = self.peek() && p == '|' { + self.next(); + let end_pos = self.pos.clone(); + self.next(); + self.mode = Mode::InTag; + Some(Symbol::new( SymbolType::StartFlat, start_pos, end_pos ) ) + } else { + None + } + } + Some('<') => { + if let Some(p) = self.peek() && p == '|' { + self.next(); + let end_pos = self.pos.clone(); + self.next(); + self.mode = Mode::InTag; + Some(Symbol::new(SymbolType::StartPoint, start_pos, end_pos ) ) + } else { + None + } + } + _ => { + None + } + } + } + + fn is_end_tag(&mut self) -> bool { + if let Some(cur) = self.cur() && cur == '|' && + let Some(peek) = self.peek() && (peek == '>' || peek == ']') { + true + } else { + false + } + } + + fn parse_end_tag(&mut self) -> Option> { + self.skip_ws(); + let start_pos = self.pos.clone(); + if let Some(chr) = self.cur() && chr == '|' { + match self.peek() { + Some(']') => { + self.next(); + let end_pos = self.pos.clone(); + self.next(); + self.mode = Mode::Text; + Some(Symbol::new( SymbolType::EndFlat, start_pos, end_pos)) + } + Some('>') => { + self.next(); + let end_pos = self.pos.clone(); + self.next(); + self.mode = Mode::Text; + Some(Symbol::new( SymbolType::EndPoint, start_pos, end_pos)) + } + _ => { + None + } + } + } else { + None + } + } +} + +impl<'a> Iterator for Lexer<'a> { + type Item = Symbol<'a>; + + fn next(&mut self) -> Option { + self.next_symbol() + } +} + diff --git a/crimtag/src/lib.rs b/crimtag/src/lib.rs new file mode 100644 index 0000000..e8f9e9f --- /dev/null +++ b/crimtag/src/lib.rs @@ -0,0 +1,544 @@ +use std::path::{PathBuf,Path}; +use std::collections::HashMap; +use core::error::Error; +use std::time::SystemTime; + +mod lexer; +mod parser; +mod position; +mod error; +pub mod context; + +pub use error::{CrimError,CrimResult}; +pub use position::*; +pub use context::{Context,Value,MappedStructure,StatefulContext,MappedValue,MappedList,MappedHash}; + +#[derive(Debug)] +pub struct Crimtag { + views: HashMap, + sources: Vec, +} + +#[derive(PartialEq,Eq,Debug,Clone)] +pub enum ViewSource { + File { + path: PathBuf, + loaded: SystemTime, + }, + Static, + External { + key: String, + }, +} + +#[derive(Debug)] +struct View { + name: String, + source: usize, + outputs: Vec, +// theme: Option + layout: Option, +} + +impl View { + fn process(&self, input: &dyn for<'a> MappedStructure<'a>) -> CrimResult { + let mut out_vars = Context::new(); + for output in &self.outputs { + let mut buf = String::new(); + let sc = StatefulContext::root( input ); + exec( &sc, &output.code, &mut buf )?; + out_vars.insert( output.name.clone(), Value::String(buf) ); + } + Ok(out_vars) + } +} + +#[derive(Debug)] +struct Output { + name: String, + code: Vec, +} + +type Properties = HashMap; + +#[derive(Debug)] +enum Token { + Loop(Identifier, Vec), + Show(Identifier,Properties), + If(Identifier, Vec, Vec), + Text(String), +} + +#[derive(Debug,Clone,PartialEq)] +pub enum IdentifierValue { + Root, + Name(String), + Index(usize), +} + +pub type Identifier = Vec; + +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 source_id = self.id_source( + &ViewSource::File { + path: path.to_path_buf(), + loaded: time, + } + ); + let p = parser::Parser::new(); + self.register_views( + p.parse( + &String::from_utf8( std::fs::read( path )? )?, + source_id + )?, + )?; + Ok(()) + } + + 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) -> CrimResult<()> { + let source_id = self.id_source( + &ViewSource::External{ key: key.to_string()} + ); + + let p = parser::Parser::new(); + self.register_views( + p.parse( &data, source_id )?, + ) + } + + fn register_views(&mut self, views: Vec) -> CrimResult<()> { + for view in views { + self.views.insert( view.name.clone(), view ); + } + Ok(()) + } + + pub fn get_view_source(&self, view: &str) -> Option { + if let Some(view) = self.views.get(view) { + if view.source < self.sources.len() { + Some(self.sources[view.source].clone()) + } else { + None + } + } else { + None + } + } + + pub fn render_partial(&self, view: &str, input: &dyn for<'a> MappedStructure<'a> ) -> CrimResult { + if let Some(view) = self.views.get(view) { + return view.process( input ) + } else { + return Err(CrimError::other("No such view found".into())); + } + } + + pub fn render(&self, view: &str, input: &dyn for<'a> MappedStructure<'a> ) -> CrimResult { + if let Some(view) = self.views.get( view ) { + //println!("::Token tree::\n{:?}", view ); + let mut output = view.process( input )?; + if let Some(l) = &view.layout { + self.render( l, &output ) + } else { + if let Some(content) = output.remove("content") && + let Value::String(s) = content { + + Ok(s) + } else { + Err(CrimError::other("No content found in root layout.".into())) + } + } + + } else { + Err(CrimError::other("No such view found".into())) + } + } +} + +fn exec<'a>(input: &StatefulContext<'a>, tokens: &Vec, buf: &mut String) -> CrimResult<()> { + for token in tokens { + match token { + Token::Loop(ident,code) => { + //println!("!!! Loop over: {:?} {:?}", ident, input.get_value( &ident )); + if let Some(value) = input.get_value( &ident ) && + let MappedValue::List(list) = value { + let output = + &list.for_each(&|rec: &MappedValue, buf: &mut String| { + if let MappedValue::Struct(d) = rec { + exec( &input.local(*d), code, buf )?; + } + Ok(()) + })?; + //println!("!!! Output from loop: {}", output ); + buf.push_str( &output ); + } + } + Token::If(ident,code,other) => { + if let Some(value) = input.get_value( &ident ) && + let MappedValue::Bool(b) = value && b { + exec(input, code, buf)? + } else { + exec(input, other, buf)? + } + } + Token::Show(ident,p) => { + let format = if let Some(s) = p.get("format") { + s + } else { + &"".to_string() + }; + if let Some(value) = input.get_value( &ident ) { + match value { + MappedValue::Struct(_) => { + buf.push_str("Struct"); + } + MappedValue::List(_) => { + buf.push_str("List"); + } + MappedValue::Bool(b) => { + if b { + buf.push_str("true"); + } else { + buf.push_str("false"); + } + } + MappedValue::Str(s) => { + buf.push_str(s); + } + MappedValue::String(s) => { + buf.push_str(s.as_str()); + } + MappedValue::Int8(i) => { + buf.push_str(&i.to_string()); + } + MappedValue::Int16(i) => { + buf.push_str(&i.to_string()); + } + MappedValue::Int32(i) => { + buf.push_str(&i.to_string()); + } + MappedValue::Int64(i) => { + buf.push_str(&i.to_string()); + } + MappedValue::UInt8(i) => { + buf.push_str(&i.to_string()); + } + MappedValue::UInt16(i) => { + buf.push_str(&i.to_string()); + } + MappedValue::UInt32(i) => { + buf.push_str(&i.to_string()); + } + MappedValue::UInt64(i) => { + buf.push_str(&i.to_string()); + } + MappedValue::Float32(f) => { + if format == "" { + buf.push_str(&f.to_string()); + } else { + buf.push_str(&f.to_string()); + } + } + MappedValue::Float64(f) => { + if format == "" { + buf.push_str(&f.to_string()); + } else { + buf.push_str(&f.to_string()); + } + } + } + } + } + Token::Text(s) => { + buf.push_str( s ); + } + //_ => {} + } + } + 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 "person" layout="simple"|>Welcome [|show person.last_name|], [|show person.first_name|]! It's lovely to see you again.<|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.render( + "person", + &Context::from([ + ("hi".to_string(),Value::String("hi".to_string())), + ("person".to_string(),Value::Dictionary(HashMap::from([ + ("first_name".to_string(), Value::String("Bob".to_string())), + ("last_name".to_string(), Value::String("Smith".to_string())), + ]))), + ]) + ) { + println!("View result: {:?}", v ); + } else { + println!("Error?"); + } + } + } + + #[test] + fn loops() -> Result<(),CrimError> { + let mut ct = Crimtag::new(); + ct.load_static(r#"Looping code: +[|view "index"|>We will enumerate people here:[|loop people|> + - [|show last_name|], [|show first_name|][|if show_title |>: [|show title|] <|else|> **title redacted** <|if|] ::>[|loop tags|> [|show tag|]<|loop|] <:: <|loop|] +<|view|] +"#)?; + + let ctx = Context::from([ + ("people".to_string(), vec![ + [ + ("first_name".into(), "Joe".into()), + ("last_name".into(), "Smith".into()), + ("show_title".into(), true.into()), + ("title".into(), "CEO".into()), + ("tags".into(), vec![ + Context::from([("tag".into(), "jerk".into()),]).into(), + Context::from([("tag".into(), "ugly".into()),]).into(), + ].into()), + ].into(), + [ + ("first_name".into(), "Chris".into()), + ("last_name".into(), "Perkens".into()), + ("show_title".into(), false.into()), + ("title".into(), "Baconeer".into()), + ("tags".into(), vec![ + Context::from([("tag".into(), "jerk".into()),]).into(), + Context::from([("tag".into(), "ugly".into()),]).into(), + ].into()), + ].into(), + [ + ("first_name".into(), "Will".into()), + ("last_name".into(), "Power".into()), + ("show_title".into(), false.into()), + ("title".into(), "CFO".into()), + ("tags".into(), vec![ + Context::from([("tag".into(), "jerk".into()),]).into(), + Context::from([("tag".into(), "ugly".into()),]).into(), + ].into()), + ].into(), + [ + ("first_name".into(), "Justin".into()), + ("last_name".into(), "Time".into()), + ("show_title".into(), true.into()), + ("title".into(), "CIO".into()), + ("tags".into(), vec![ + Context::from([("tag".into(), "jerk".into()),]).into(), + Context::from([("tag".into(), "ugly".into()),]).into(), + ].into()), + ].into(), + ].into()), + ]); + + println!("View result: {}", ct.render("index", &ctx)? ); + + Ok(()) + } + + #[test] + fn conditional() -> Result<(),CrimError> { + let mut ct = Crimtag::new(); + ct.load_static(r#"Hi there +[|view "index"|> + Color: [|if is_red|> red <|elif is_blue |> blue <|else|> green <|if|] +<|view|]"#)?; + + let ctx = Context::from([ + ("is_red".into(), false.into()), + ("is_blue".into(), false.into()), + ("color".into(), "purple".into()), + ]); + + println!("View result: {}", ct.render("index", &ctx)? ); + + Ok(()) + } + + struct Item { + pub id: i32, + pub title: String, + pub body: String, + } + + struct Person { + pub id: i32, + pub username: String, + pub name: String, + } + + struct Page { + pub user: Person, + pub total_items: i32, + pub total_pages: i32, + pub cur_page: i32, + pub items: Vec, + } + + impl Page { + fn new() -> Page { + Page { + user: Person { + id: 443, + username: "eichlan".into(), + name: "Mike".into(), + }, + total_items: 4000, + total_pages: 400, + cur_page: 35, + items: vec![ + Item{ + id: 123, + title: "hi".into(), + body: "body".into(), + }, + Item{ + id: 124, + title: "bye".into(), + body: "wooo".into(), + }, + Item{ + id: 125, + title: "ciao".into(), + body: "whatever".into(), + } + ], + } + } + } + + impl<'a> MappedStructure<'a> for Page { + fn get_value(&'a self, id: &str) -> Option> { + match id { + "user" => Some(MappedValue::Struct(&self.user)), + "total_items" => Some(MappedValue::Int32(self.total_items)), + "total_pages" => Some(MappedValue::Int32(self.total_pages)), + "cur_page" => Some(MappedValue::Int32(self.cur_page)), + "items" => Some(MappedValue::<'a>::List(&self.items)), + _ => None, + } + } + } + + impl<'a> MappedStructure<'a> for Person { + fn get_value(&'a self, id: &str) -> Option> { + match id { + "id" => Some(MappedValue::Int32(self.id)), + "username" => Some(MappedValue::String(&self.username)), + "name" => Some(MappedValue::String(&self.name)), + _ => None, + } + } + } + + impl<'a> MappedStructure<'a> for Item { + fn get_value(&'a self, id: &str) -> Option> { + match id { + "id" => Some(MappedValue::Int32(self.id)), + "title" => Some(MappedValue::String(&self.title)), + "body" => Some(MappedValue::String(&self.body)), + _ => None, + } + } + } + + #[test] + fn custom() -> Result<(), CrimError> { + let page = Page::new(); + + println!("{:?}", + page.get_value(&"total_pages") + ); + + if let Some(MappedValue::List(l)) = page.get_value(&"items") { + l.for_each(&|x: &MappedValue, buf: &mut String| { + if let MappedValue::Struct(s) = x { + println!(" - {:?}", s.get_value(&"title")); + buf.push_str(&format!("{:?}",s.get_value(&"title"))); + } + Ok(()) + }).expect("loop"); + } + Ok(()) + } +} diff --git a/crimtag/src/parser.rs b/crimtag/src/parser.rs new file mode 100644 index 0000000..4f7ff6a --- /dev/null +++ b/crimtag/src/parser.rs @@ -0,0 +1,785 @@ +use crate::lexer::*; +use crate::*; + +struct Context<'a> { + cur: [Option>;2], + icur: usize, + ll: Lexer<'a>, + source: usize, +} + +impl<'a> Context<'a> { + pub fn new( mut ll: Lexer<'a>, source: usize ) -> Self { + let cur = [ll.next(), ll.next()]; + //println!(" - cur: {:?}, peek: {:?}", cur[0], cur[1] ); + Self { + cur, + icur: 0, + ll, + source, + } + } + + pub fn next(&mut self) -> Option> { + self.cur[self.icur] = self.ll.next(); + self.icur = (self.icur+1)%2; + //println!(" - cur: {:?}, peek: {:?}", self.cur(), self.peek() ); + self.cur[self.icur] + } + + pub fn cur(&self) -> Option> { + self.cur[self.icur] + } + + pub fn peek(&self) -> Option> { + self.cur[(self.icur+1)%2] + } + + /* + pub fn check_unwrapped bool>(&self, context: &str, f: T) -> CrimResult { + if self.cur().is_none() || self.peek().is_none() { + Err(CrimError::eos( context )) + } else { + Ok(f( self.cur().unwrap().symbol(), self.peek().unwrap().symbol())) + } + } + */ + + pub fn source(&self) -> usize { + self.source + } +} + +trait SymbolHelper { + fn is bool>(&self, context: &str, f: T ) -> CrimResult; + #[allow(dead_code)] + fn is_valid_tag_name(&self) -> CrimResult; +} + +impl<'a> SymbolHelper for Option> { + fn is bool>(&self, context: &str, f: T ) -> CrimResult { + if let Some(st) = self { + Ok(f( &st.symbol() )) + } else { + Err(CrimError::eos( context )) + } + } + + fn is_valid_tag_name(&self) -> CrimResult { + if let Some(st) = self { + Ok(match st.symbol() { + SymbolType::Token(_) | + SymbolType::View | + SymbolType::Output | + SymbolType::Loop | + SymbolType::If | + SymbolType::ElIf | + SymbolType::Else | + SymbolType::Show => true, + _ => false + }) + } else { + Err(CrimError::parse( + Position::none(), + "Unexpeceted end of stream.".to_string() + )) + } + } +} + +pub struct Parser { +} + +/** + * input: input complete_tag + * | input text + * | + * ; + * + * tag: unary_tag + * | multinary_open_tag + * | multinary_mid_tag + * | multinary_close_tag + * ; + * + * unary_tag: '[|' tag_guts '|]' + * ; + * + * | '[|' tag_guts '|>' + * | '<|' tag_guts '|>' + * | '<|' tag_guts '|]' + * ; + * + * tag_pair: open_tag tag_body close_tag + * ; + * + * tag_body: tag_body tag + * | tag_body tag_pair + * | tag_body text + * | + * ; + * + * open_tag: '[|' tag_guts '|>' + * ; + * + * close_tag: '<|' token '|]' + * ; + * + * tag_guts: 'view' literal props + * | 'section' literal + * | 'output' literal + * ; + * + * literal: '"' [^"]* '"' + * ; + * + * props: props token '=' literal + * | + * ; + * + * disambiguate (tm): + * + * input: input tag + * | input text + * | + * ; + * + * tag: open_tag_base unary_tag + * | open_tag_base binary_tag + * ; + * + * open_tag_base: '[|' tag_guts + * ; + * + * unary_tag: '|]' + * ; + * + * binary_tag: '|>' tag_body close_tag + * ; + * + * tag_body: tag_body tag + * | tag_body text + * | + * ; + * + * close_tag: '<|' token '|]' + * ; + * + * tag_guts: 'view' literal props + * | 'section' literal + * | 'output' literal + * ; + * + * literal: '"' [^"]* '"' + * ; + * + * props: props token '=' literal + * | + * ; + */ +impl Parser { + pub fn new() -> Self { + Self { + } + } + + fn lex_error(&self, error: &Symbol) -> CrimResult { + if let SymbolType::Error{what} = error.symbol() { + Err(CrimError::parse( *error.start(), + format!("What: {:?}", *what) + )) + } else { + Err(CrimError::parse( Position::none(), "Not an error?".into())) + } + } + + pub fn parse(&self, src: &str, source: usize ) -> CrimResult> { + let ll = Lexer::new( src ); + let mut ctx = Context::new( ll, source ); + + // Parse the root of the file, the input context + self.p_input( &mut ctx ) + } + + fn p_input(&self, ctx: &mut Context ) -> CrimResult> { + let mut tags = Vec::new(); + loop { + if ctx.cur().is_none() { + break; + } + match ctx.cur().unwrap().symbol() { + SymbolType::Text(_) => { /* Skip top level text */ } + SymbolType::StartFlat => { + let tb = self.parse_tag( ctx )?; + let tb = self.parse_tag_set( ctx, tb )?; + tags.push( tb ); + } + SymbolType::Error{..} => { + self.lex_error( &ctx.cur().unwrap() )?; + } + _ => { + return Err(CrimError::parse( + *ctx.cur().unwrap().start(), + format!("Unexpected symbol {:?} found looking for tags.", ctx.cur().unwrap().symbol() ) + )); + } + } + ctx.next(); + } + + let mut views = Vec::new(); + for tag in tags { + let start_pos = tag.start_pos(); + let name = tag.name().clone(); + if tag.is_name(&SymbolType::View) && + let BuildResult::View(view) = tag.build( ctx )? { + views.push( view ); + } else { + return Err(CrimError::parse( + start_pos, + format!("Expected view at root, found {:?}", name ) + )); + } + } + Ok(views) + } + + fn parse_tag<'a>(&self, ctx: &mut Context<'a>) -> CrimResult> { + let start_sym = ctx.cur().unwrap(); + let mut tb = TagBuilder::new(&start_sym); + + if ctx.next().is_some() { + match ctx.cur().unwrap().symbol() { + SymbolType::View | SymbolType::Show | SymbolType::Loop | + SymbolType::If | SymbolType::ElIf | SymbolType::Else | + SymbolType::Output => { + let name_sym = ctx.cur().unwrap(); + ctx.next(); + tb.set_name( name_sym ); + } + _ => { + return Err(CrimError::parse( + *ctx.cur().unwrap().start(), + "Unexpected symbol".to_string() + )); + } + } + } else { + return Err(CrimError::eos("tag type")); + } + + if tb.can_have_params() || tb.can_have_expr() { + self.parse_tag_params( ctx, &mut tb )?; + } + if tb.can_have_props() { + self.parse_tag_props( ctx, &mut tb )?; + } + + if let Some(end_sym) = ctx.cur() { + match end_sym.symbol() { + SymbolType::EndPoint | SymbolType::EndFlat => { + tb.set_end( &end_sym )?; + } + _ => { + return Err(CrimError::parse( + *end_sym.start(), + format!("Unexpected symbol {:?} looking for end of tag.", end_sym.symbol()) + )); + } + } + } + + ctx.next(); + + Ok(tb) + } + + fn parse_tag_set<'a>(&self, ctx: &mut Context<'a>, mut base: TagBuilder<'a>) -> CrimResult> { + if base.is_unary() { + return Ok(base); + } + + loop { + if ctx.cur().is_none() { + return Err(CrimError::eos(format!("close tag for {:?}", base.name()).as_str())); + } + match ctx.cur().unwrap().symbol() { + SymbolType::Text(s) => { + base.add_child(Entry::Token(Token::Text(s.to_string()))); + ctx.next(); + } + SymbolType::StartFlat | SymbolType::StartPoint => { + let tb = self.parse_tag( ctx )?; + + match tb.tag_type() { + TagType::MultinaryOpen => { + base.add_child(Entry::TagBuilder(self.parse_tag_set( ctx, tb )?)); + } + TagType::MultinaryMid => { + base.add_chain(Entry::TagBuilder(self.parse_tag_set( ctx, tb )?)); + return Ok(base); + } + TagType::MultinaryClose => { + if (tb.is_name(&SymbolType::If) && + (base.is_name(&SymbolType::ElIf) || + base.is_name(&SymbolType::Else))) || + tb.is_name(base.name().unwrap().symbol()) { + return Ok(base); + } else { + return Err(CrimError::parse( + *tb.name().unwrap().start(), + format!("Found {:?} looking for close of {:?}", tb.name().unwrap().symbol(), base.name().unwrap().symbol()) + )); + } + } + TagType::Unary => { + base.add_child(Entry::TagBuilder(tb)); + } + TagType::Unknown => { + return Err(CrimError::broken(base.start_pos(), "Found unknown tag when looking for something else.")); + } + } + } + SymbolType::Error{..} => { + return self.lex_error( &ctx.cur().unwrap() ); + } + _ => { + return Err(CrimError::parse( + *ctx.cur().unwrap().start(), + format!("Unexpected token: {:?}", ctx.cur().unwrap().symbol()), + )); + } + } + } + } + + fn parse_tag_params(&self, ctx: &mut Context, tb: &mut TagBuilder ) -> CrimResult<()> { + loop { + if ctx.cur().is_none() { + return Err(CrimError::eos("tag parameters")); + } + match ctx.cur().unwrap().symbol() { + SymbolType::Literal(s) => { + tb.add_param( ParamValue::Literal(s.to_string()) ); + ctx.next(); + } + SymbolType::Token(_) | SymbolType::Sharp => { + if ctx.peek().is("tag data", |s| *s == SymbolType::Equals)? { + break; + } + tb.add_param( self.parse_identifier( ctx )? ); + } + _ => { + break; + } + } + } + Ok(()) + } + + fn parse_identifier(&self, ctx: &mut Context ) -> CrimResult { + let mut id = Identifier::new(); + + if ctx.cur().is("identifier",|s| matches!( s, SymbolType::Sharp))? { + id.push( IdentifierValue::Root ); + ctx.next(); + } + loop { + if ctx.cur().is("identifier",|s| matches!( s, SymbolType::Token(_) ))? { + if let SymbolType::Token(s) = ctx.cur().unwrap().symbol() { + id.push( IdentifierValue::Name(s.to_string()) ); + } + } else { + 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 )? { + break; + } + ctx.next(); + } + + Ok(ParamValue::Identifier(id)) + } + + fn parse_tag_props(&self, ctx: &mut Context, tb: &mut TagBuilder ) -> CrimResult<()> { + loop { + if ctx.cur().is_none() { + return Err(CrimError::eos("tag properties")); + } + if let SymbolType::Token(s) = ctx.cur().unwrap().symbol() { + if ctx.peek().is_some_and(|s|s.check_type(|t| matches!(t,SymbolType::Equals))) { + ctx.next(); + if let Some(sym) = ctx.next() && + let SymbolType::Literal(lv) = sym.symbol() { + tb.add_prop( s.to_string(), lv.to_string() ); + } else { + return Err(CrimError::parse(Position::none(),"Expected quoted literal string".to_string())); + } + } else { + break; + } + } else { + break; + } + ctx.next(); + } + Ok(()) + } +} + +#[derive(PartialEq,Copy,Clone,Debug)] +enum TagType { + Unknown, + Unary, + MultinaryOpen, + MultinaryMid, + MultinaryClose, +} + +#[derive(Debug)] +enum Entry<'a>{ + Token(Token), + TagBuilder(TagBuilder<'a>), +} + +type EntryList<'a> = Vec>; + +trait EntryListConverter { + fn has_outputs(&self) -> bool; + fn to_outputs(&mut self, ctx: &mut Context ) -> CrimResult>; + fn to_tokens(&mut self, ctx: &mut Context) -> CrimResult>; +} + +impl<'a> EntryListConverter for EntryList<'a> { + fn has_outputs(&self) -> bool { + self.iter().any(|e| + if let Entry::TagBuilder(tb) = e + && tb.is_name(&SymbolType::Output) { + true + } else { + false + } + ) + } + + fn to_outputs(&mut self, ctx: &mut Context) -> CrimResult> { + let mut outputs = Vec::new(); + + if self.has_outputs() { + // We have an explicit output, we cant have anything else + for e in self.drain(..) { + let tb = if let Entry::TagBuilder(tb) = e { + tb + } else { + continue; + }; + if let BuildResult::Output(out) = tb.build(ctx)? { + outputs.push( out ); + } + } + } else { + // No outputs, so we create one implicit output + outputs.push(Output { + name: "content".into(), + code: self.to_tokens(ctx)? + }); + } + + Ok(outputs) + } + + fn to_tokens(&mut self, ctx: &mut Context) -> CrimResult> { + let mut tokens : Vec = Vec::new(); + for e in self.drain(..) { + match e { + Entry::Token(token) => { + tokens.push( token ); + } + Entry::TagBuilder(tb) => { + if let BuildResult::Token(token) = tb.build(ctx)? { + tokens.push( token ); + } else { + return Err(CrimError::broken( Position::none(), "Non-token result built.")); + } + } + } + } + Ok(tokens) + } +} + +#[derive(Debug)] +struct TagBuilder<'a> { + name: Option>, + params: Vec, + props: Properties, + children: Vec>, + tag_type: TagType, + start: Symbol<'a>, + chain: Vec>, +} + +#[derive(Debug)] +enum ParamValue { + Literal(String), + Identifier(Identifier), +} + +enum BuildResult { + View(View), + Output(Output), + Token(Token), +} + +impl<'a> TagBuilder<'a> { + pub fn new(start: &Symbol<'a>) -> TagBuilder<'a> { + TagBuilder { + name: None, + params: Vec::new(), + props: Properties::new(), + children: Vec::new(), + tag_type: TagType::Unknown, + start: start.clone(), + chain: Vec::new(), + } + } + + pub fn set_end(&mut self, end: &Symbol) -> CrimResult<()> { + if *self.start.symbol() == SymbolType::StartFlat { + if *end.symbol() == SymbolType::EndFlat { + self.tag_type = TagType::Unary; + } else if *end.symbol() == SymbolType::EndPoint { + self.tag_type = TagType::MultinaryOpen; + } else { + return Err(CrimError::broken( *end.start(), "Invalid bracket token type.")); + } + } else if *self.start.symbol() == SymbolType::StartPoint { + if *end.symbol() == SymbolType::EndFlat { + self.tag_type = TagType::MultinaryClose; + } else if *end.symbol() == SymbolType::EndPoint { + self.tag_type = TagType::MultinaryMid; + } else { + return Err(CrimError::broken( *end.start(), "Invalid bracket token type.")); + } + } else { + return Err(CrimError::broken( *self.start.start(), "Invalid bracket token type.")); + } + Ok(()) + } + + pub fn start_pos(&self) -> Position { + *self.start.start() + } + + pub fn is_unary(&self) -> bool { + self.tag_type == TagType::Unary + } +/* + pub fn is_multinary_open(&self) -> bool { + self.tag_type == TagType::MultinaryOpen + } +*/ + pub fn can_have_params(&self) -> bool { + match self.name.unwrap().symbol() { + SymbolType::View | SymbolType::Output | SymbolType::Loop => true, + SymbolType::Show | SymbolType::If | SymbolType::ElIf | + SymbolType::Else => false, + _ => false, + } + } + + pub fn can_have_expr(&self) -> bool { + match self.name.unwrap().symbol() { + SymbolType::View | SymbolType::Output | SymbolType::Loop => false, + SymbolType::Show | SymbolType::If | SymbolType::ElIf | + SymbolType::Else => true, + _ => false, + } + } + + pub fn can_have_props(&self) -> bool { + true + } +/* + pub fn can_have_children(&self) -> bool { + if self.tag_type == TagType::MultinaryOpen || + self.tag_type == TagType::MultinaryMid { + true + } else { + false + } + } +*/ + pub fn is_name(&self, name: &SymbolType<'a>) -> bool { + if let Some(a) = self.name { + a.symbol() == name + } else { + false + } + } + + pub fn name(&self) -> Option> { + self.name + } +/* + pub fn params(&self) -> &Vec { + &self.params + } +*/ + pub fn set_name(&mut self, name: Symbol<'a>) { + self.name = Some(name); + } + + pub fn add_param(&mut self, param: ParamValue) { + self.params.push( param ); + } + + pub fn add_prop(&mut self, key: String, value: String) { + self.props.insert( key, value ); + } + + pub fn add_child(&mut self, tb: Entry<'a>) { + self.children.push( tb ); + } +/* + pub fn append_children(&mut self, children: &mut Vec::>) { + self.children.append( children ); + } +*/ + pub fn add_chain(&mut self, tb: Entry<'a>) { + self.chain.push( tb ); + } +/* + pub fn set_type(&mut self, tag_type: TagType) { + self.tag_type = tag_type; + } +*/ + pub fn tag_type(&self) -> TagType { + self.tag_type + } + + pub fn build(mut self, ctx: &mut Context ) -> CrimResult { + if let Some(sym) = self.name { + match sym.symbol() { + SymbolType::View => { + let name = if let ParamValue::Literal(s) = self.params.swap_remove(0) { + s.to_string() + } else { + return Err(CrimError::parse(Position::none(), "Expected string literal for view name.".to_string())); + }; + Ok(BuildResult::View(View { + name: name, + source: ctx.source(), + outputs: self.children.to_outputs(ctx)?, + //theme: Option + layout: self.props.get("layout").cloned(), + })) + } + SymbolType::Output => { + let name = if let ParamValue::Literal(s) = self.params.swap_remove(0) { + s.to_string() + } else { + return Err(CrimError::parse(Position::none(), "Expected string literal for output name.".into())); + }; + Ok(BuildResult::Output(Output { + name: name, + code: self.children.to_tokens(ctx)?, + })) + } + SymbolType::Loop => { + let id = if let ParamValue::Identifier(id) + = self.params.swap_remove(0) { + id + } else { + return Err(CrimError::parse( + Position::none(), + "Identifier for loop variable name.".into()) + ); + }; + Ok(BuildResult::Token( + Token::Loop(id, self.children.to_tokens(ctx)?) + )) + } + SymbolType::Show => { + let id = if let ParamValue::Identifier(id) + = self.params.swap_remove(0) { + id + } else { + return Err(CrimError::parse( + Position::none(), + "Identifier for show variable name.".into()) + ); + }; + Ok(BuildResult::Token(Token::Show(id, self.props))) + } + SymbolType::If => { + let id = if let ParamValue::Identifier(id) + = self.params.swap_remove(0) { + id + } else { + return Err(CrimError::parse( + Position::none(), + "Identifier for if variable name.".into()) + ); + }; + + let is_else = if self.chain.len() == 1 && + let Entry::TagBuilder(tb) = &self.chain[0] && + tb.is_name(&SymbolType::Else) && + tb.params.len() == 0 { + true + } else { + false + }; + + if is_else && + let Entry::TagBuilder(mut tb) = self.chain.swap_remove(0) { + self.chain.clear(); + self.chain.append(&mut tb.children); + } + Ok(BuildResult::Token(Token::If(id, self.children.to_tokens(ctx)?, self.chain.to_tokens(ctx)?))) + } + SymbolType::ElIf => { + let id = if let ParamValue::Identifier(id) = self.params.swap_remove(0) { + id + } else { + return Err(CrimError::parse( + Position::none(), + "Identifier for elif variable name.".into()) + ); + }; + + // this is copied from if, since they're the same this + // should be encapsulated and moved into a function... + // ...but I can't do that right now. + let is_else = if self.chain.len() == 1 && + let Entry::TagBuilder(tb) = &self.chain[0] && + tb.is_name(&SymbolType::Else) && + tb.params.len() == 0 { + true + } else { + false + }; + + if is_else && + let Entry::TagBuilder(mut tb) = self.chain.swap_remove(0) { + self.chain.clear(); + self.chain.append(&mut tb.children); + } + + + Ok(BuildResult::Token(Token::If(id, self.children.to_tokens(ctx)?, self.chain.to_tokens(ctx)?))) + } + SymbolType::Else => { + Ok(BuildResult::Token(Token::If(Identifier::new(), self.children.to_tokens(ctx)?, self.chain.to_tokens(ctx)?))) + } + _ => { + Err(CrimError::parse( self.start_pos(), "Bad tag type".into())) + } + } + } else { + Err(CrimError::parse(self.start_pos(), "Bad tag type".into())) + } + } +} diff --git a/crimtag/src/position.rs b/crimtag/src/position.rs new file mode 100644 index 0000000..24f5309 --- /dev/null +++ b/crimtag/src/position.rs @@ -0,0 +1,96 @@ +use std::fmt; +use std::cmp::Ordering; + +#[derive(Copy,Clone)] +pub struct Position { + pub line: u32, + pub column: u32, +} + +impl Position { + pub fn new( line: u32, column: u32 ) -> Self { + Self { + line, column, + } + } + + pub fn none() -> Self { + Self { + line: 0, + column: 0, + } + } + + pub fn is_none(&self) -> bool { + self.line == 0 || self.column == 0 + } +} + +impl fmt::Debug for Position { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.is_none() { + write!(f, "none") + } else { + write!(f, "{}:{}", self.line, self.column ) + } + } +} + +impl PartialOrd for Position { + fn partial_cmp(&self, other: &Self) -> Option { + let ord = self.line.partial_cmp( &other.line ); + if Some(Ordering::Equal) == ord { + self.column.partial_cmp( &other.column ) + } else { + ord + } + } +} + +impl Ord for Position { + fn cmp(&self, other: &Self) -> Ordering { + let ord = self.line.cmp( &other.line ); + if Ordering::Equal == ord { + self.column.cmp( &other.column ) + } else { + ord + } + } +} + +impl PartialEq for Position { + fn eq(&self, other: &Self) -> bool { + self.line == other.line && self.column == other.column + } +} + +impl Eq for Position {} + +#[derive(Copy,Clone)] +pub struct Range { + start: Position, + end: Position, +} + +impl Range { + pub fn new( start: Position, end: Position ) -> Self { + Self { + start, end, + } + } + + pub fn include(&mut self, pos: &Position ) { + if *pos < self.start { + self.start = pos.clone(); + } + if *pos > self.end { + self.end = pos.clone(); + } + } +} + +impl fmt::Debug for Range { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}-{:?}", self.start, self.end ) + } +} -- cgit v1.2.3