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. --- Cargo.lock | 7 + Cargo.toml | 11 +- 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 ++++++ derive/Cargo.toml | 10 + derive/src/lib.rs | 75 +++++ derive/tests/basic.rs | 15 + src/context.rs | 289 ------------------ src/error.rs | 59 ---- src/lexer.rs | 368 ----------------------- src/lib.rs | 544 --------------------------------- src/parser.rs | 785 ------------------------------------------------ src/position.rs | 96 ------ 19 files changed, 2267 insertions(+), 2146 deletions(-) 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 create mode 100644 derive/Cargo.toml create mode 100644 derive/src/lib.rs create mode 100644 derive/tests/basic.rs delete mode 100644 src/context.rs delete mode 100644 src/error.rs delete mode 100644 src/lexer.rs delete mode 100644 src/lib.rs delete mode 100644 src/parser.rs delete mode 100644 src/position.rs diff --git a/Cargo.lock b/Cargo.lock index f7e6686..842949d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,3 +5,10 @@ version = 4 [[package]] name = "crimtag" version = "0.1.0" + +[[package]] +name = "derive" +version = "0.1.0" +dependencies = [ + "crimtag", +] diff --git a/Cargo.toml b/Cargo.toml index ee022ca..a20cdc3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ -[package] -name = "crimtag" -version = "0.1.0" -edition = "2024" +[workspace] +members = ["derive","crimtag"] +default-members = ["derive","crimtag"] +resolver = "3" -[dependencies] +[workspace.dependencies] +crimtag = { path = "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 ) + } +} diff --git a/derive/Cargo.toml b/derive/Cargo.toml new file mode 100644 index 0000000..65f3a75 --- /dev/null +++ b/derive/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "derive" +version = "0.1.0" +edition = "2024" + +[lib] +proc-macro = true + +[dev-dependencies] +crimtag = { path = "../crimtag"} diff --git a/derive/src/lib.rs b/derive/src/lib.rs new file mode 100644 index 0000000..f9c9180 --- /dev/null +++ b/derive/src/lib.rs @@ -0,0 +1,75 @@ +extern crate proc_macro; +use proc_macro::{TokenStream,TokenTree,Ident,Delimiter}; + +#[proc_macro_derive(CrimtagMappable)] +pub fn derive_crimtag_mappable(s: TokenStream) -> TokenStream { + let mut i = s.into_iter(); + while let Some(t) = i.next() { + if let TokenTree::Ident(ident) = t && + ident.to_string() == "struct" { + break; + } + } + let name = if let Some(TokenTree::Ident(ident)) = i.next() { + ident.to_string() + } else { + panic!("No name found in struct."); + }; + + // TODO: Expand this to include the generic parameters and the where? at + // least the parameters. + println!("struct name: {:?}", name); + + let mut newfunc = format!( +r#"impl<'a> crimtag::MappedStructure<'a> for {} {{ + fn get_value(&'a self, id: &str) -> Option> {{ + match id {{ +"#, name); + + // We're going to cheat. Every field name is followed directly by a colon, + // so all we really need to do is hunt for colons and then take the + // previous item. + + while let Some(n) = i.next() { + if let TokenTree::Group(g) = &n && + g.delimiter() == Delimiter::Brace { + // Operate on the struct members. + let mut gi = g.stream().into_iter(); + let mut cur = 0usize; + let mut tok = [gi.next(), gi.next()]; + loop { + if let Some(TokenTree::Punct(ch)) = &tok[(cur+1)%2] && + ch.as_char() == ':' && + let Some(TokenTree::Ident(id)) = &tok[cur] { + println!("!!!!!!!!!! -> {}", id); + newfunc.push_str(&format!( +r#" "{}" => Some(self.{}.into()), +"#, id, id)); + } + println!(" - {:?} {:?}", tok[cur], tok[(cur+1)%2] ); + // Update next. + if let Some(ni) = gi.next() { + tok[cur].replace( ni ); + cur = (cur+1)%2; + } else { + break; + } + } + } + } + + newfunc.push_str( +r#" _ => None, + } + } +}"#); + + println!(); + println!(); + println!("{}", newfunc); + println!(); + println!(); + + newfunc.parse().unwrap() +} + diff --git a/derive/tests/basic.rs b/derive/tests/basic.rs new file mode 100644 index 0000000..18fc366 --- /dev/null +++ b/derive/tests/basic.rs @@ -0,0 +1,15 @@ +use derive::*; +use crimtag::*; + +#[derive(CrimtagMappable)] +pub struct Test { + number: i32, + something: f64, + inner_thing: String, +} + + + +#[test] +fn it_works() { +} diff --git a/src/context.rs b/src/context.rs deleted file mode 100644 index 6f8e04f..0000000 --- a/src/context.rs +++ /dev/null @@ -1,289 +0,0 @@ -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/src/error.rs b/src/error.rs deleted file mode 100644 index bbb5d6f..0000000 --- a/src/error.rs +++ /dev/null @@ -1,59 +0,0 @@ - -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/src/lexer.rs b/src/lexer.rs deleted file mode 100644 index d4d39e9..0000000 --- a/src/lexer.rs +++ /dev/null @@ -1,368 +0,0 @@ -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/src/lib.rs b/src/lib.rs deleted file mode 100644 index e8f9e9f..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,544 +0,0 @@ -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/src/parser.rs b/src/parser.rs deleted file mode 100644 index 4f7ff6a..0000000 --- a/src/parser.rs +++ /dev/null @@ -1,785 +0,0 @@ -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/src/position.rs b/src/position.rs deleted file mode 100644 index 24f5309..0000000 --- a/src/position.rs +++ /dev/null @@ -1,96 +0,0 @@ -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