From 057bd8cb2d4a1539701d65489fe647b96a538e98 Mon Sep 17 00:00:00 2001 From: Mike Buland Date: Thu, 4 Jun 2026 15:41:42 -0700 Subject: Start of my new templating library. --- .gitignore | 3 + Cargo.lock | 7 ++ Cargo.toml | 6 ++ src/lexer.rs | 258 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 75 +++++++++++++++++ 5 files changed, 349 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/lexer.rs create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ffa72a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +.*.swp +.*.swo diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f7e6686 --- /dev/null +++ b/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/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ee022ca --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "crimtag" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/src/lexer.rs b/src/lexer.rs new file mode 100644 index 0000000..d6806d8 --- /dev/null +++ b/src/lexer.rs @@ -0,0 +1,258 @@ +use std::iter::Iterator; +//use core::error::Error; +use std::str::CharIndices; + +#[derive(Debug)] +pub enum Symbol<'a> { + StartFlat, + StartPoint, + EndFlat, + EndPoint, + Fragment, + Section, + Output, + Equals, + Token(&'a str), + Literal(&'a str), + Text(&'a str), + Error{ line: u32, row: u32, what: String }, + EOS, +} + +enum Mode { + Text, + InTag, +} + +pub struct Lexer<'a> { + data: &'a str, + chars: CharIndices<'a>, + cur: [Option<(usize, char)>;2], + icur: usize, + mode: Mode, + line: u32, + row: u32, +} + +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, + line: 0, + row: 0, + } + } + + 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] { + 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: String ) -> Option> { + Some(Symbol::Error { + line: self.line, + row: self.row, + what: what + }) + } + + 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(); + while self.next().is_some() && !self.is_start_tag() { } + let end = self.cur_index(); + let s = &self.data[start..end]; + //println!(" text: >>>{}<<<", s); + if start == end { + None + } else { + Some(Symbol::Text(s)) + } + } + + fn parse_token(&mut self) -> Option> { + self.skip_ws(); + if let Some(chr) = self.cur() && chr == '"' { + // Literal string + return self.parse_literal_str(); + } + let start = self.cur_index(); + + while self.next().is_some_and(|ch| !ch.is_whitespace() ) && + !self.is_end_tag() {} + let end = self.cur_index(); + let s = &self.data[start..end]; + if start == end { + None + } else { + Some(match s { + "fragment" => Symbol::Fragment, + "section" => Symbol::Section, + "output" => Symbol::Output, + _ => Symbol::Token(s) + }) + } + } + + fn parse_literal_str(&mut self) -> Option> { + if let Some(chr) = self.cur() && chr != '"' { + return self.error(format!("Expected '\"' but found '{}'", chr)); + } + let start = self.peek_index(); + while self.next().is_some_and(|chr| chr != '"') { } + let end = self.cur_index(); + self.next(); + let s = &self.data[start..end]; + Some(Symbol::Literal(s)) + } + + 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> { + match self.cur() { + Some('[') => { + if let Some(p) = self.peek() && p == '|' { + self.next(); self.next(); + self.mode = Mode::InTag; + Some(Symbol::StartFlat) + } else { + None + } + } + Some('<') => { + if let Some(p) = self.peek() && p == '|' { + self.next(); self.next(); + self.mode = Mode::InTag; + Some(Symbol::StartPoint) + } 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> { + if let Some(chr) = self.cur() && chr == '|' { + match self.peek() { + Some(']') => { + self.next(); self.next(); + self.mode = Mode::Text; + Some(Symbol::EndFlat) + } + Some('>') => { + self.next(); self.next(); + self.mode = Mode::Text; + Some(Symbol::EndPoint) + } + _ => { + 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 new file mode 100644 index 0000000..57f777e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,75 @@ +use std::path::{PathBuf,Path}; +use std::collections::HashMap; +use core::error::Error; + +mod lexer; + +pub struct Tx { + fragments: HashMap, +} + +enum FragmentSource { + File { + path: PathBuf, + loaded: usize, + } +} + +struct Fragment { + name: String, + source: FragmentSource, + sections: HashMap, +} + +struct Section { + name: String, + ast_root: Token, +} + +type Properties = HashMap; + +enum Token { + Root(Vec), + Fragment(String,Properties,Vec), + Section(String,Properties,Vec), + Text(String), + +} + +struct State { +} + +impl Tx { + pub fn new() -> Self { + Self { + fragments: HashMap::new(), + } + } + + pub fn load(path: &Path) -> Result<(),Box::> { + Ok(()) + } + + pub fn parse(data: String) -> Result<(),Box::> { + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn lexing() { + let data = r#"Leading comment: [|fragment "basic"|>Hello there <|fragment|] Trailing text"#; + let ll = lexer::Lexer::new( &data ); + for sym in ll { + if let lexer::Symbol::Error{line,row,what} = sym { + println!("Error {}:{}: {}", line, row, what ); + break; + } else { + println!("Symbol: {:?}", sym ); + } + } + } +} -- cgit v1.2.3