diff options
Diffstat (limited to 'crimtag/src')
| -rw-r--r-- | crimtag/src/context.rs | 289 | ||||
| -rw-r--r-- | crimtag/src/error.rs | 59 | ||||
| -rw-r--r-- | crimtag/src/lexer.rs | 368 | ||||
| -rw-r--r-- | crimtag/src/lib.rs | 544 | ||||
| -rw-r--r-- | crimtag/src/parser.rs | 785 | ||||
| -rw-r--r-- | crimtag/src/position.rs | 96 |
6 files changed, 2141 insertions, 0 deletions
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 @@ | |||
| 1 | use std::collections::HashMap; | ||
| 2 | use std::fmt; | ||
| 3 | |||
| 4 | use crate::error::CrimResult; | ||
| 5 | |||
| 6 | use crate::{Identifier,IdentifierValue}; | ||
| 7 | |||
| 8 | pub type Context = HashMap<String,Value>; | ||
| 9 | |||
| 10 | pub trait MappedStructure<'a> { | ||
| 11 | fn get_value(&'a self, id: &str) -> Option<MappedValue<'a>>; | ||
| 12 | } | ||
| 13 | |||
| 14 | impl<'a> MappedStructure<'a> for Context { | ||
| 15 | fn get_value(&'a self, id: &str) -> Option<MappedValue<'a>> { | ||
| 16 | self.get(id).map(|v|Into::<MappedValue<'a>>::into(v)) | ||
| 17 | } | ||
| 18 | } | ||
| 19 | |||
| 20 | pub trait MappedList<'a> { | ||
| 21 | fn get_index(&'a self, id: usize) -> Option<MappedValue<'a>>; | ||
| 22 | fn for_each(&'a self, func: &dyn Fn(&MappedValue<'a>, &mut String) -> CrimResult<()>) -> CrimResult<String>; | ||
| 23 | } | ||
| 24 | |||
| 25 | impl<'a, T: MappedStructure<'a>> MappedList<'a> for Vec<T> { | ||
| 26 | fn get_index(&'a self, id: usize) -> Option<MappedValue<'a>> { | ||
| 27 | if let Some(x) = self.get(id) { | ||
| 28 | Some(MappedValue::<'a>::Struct(x)) | ||
| 29 | } else { | ||
| 30 | None | ||
| 31 | } | ||
| 32 | } | ||
| 33 | |||
| 34 | fn for_each(&'a self, func: &dyn Fn(&MappedValue<'a>, &mut String) -> CrimResult<()>) -> CrimResult<String> { | ||
| 35 | let mut buf = String::new(); | ||
| 36 | for i in self { | ||
| 37 | func( &MappedValue::Struct(i), &mut buf )?; | ||
| 38 | } | ||
| 39 | Ok(buf) | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | impl<'a> MappedList<'a> for Vec<Value> { | ||
| 44 | fn get_index(&'a self, id: usize) -> Option<MappedValue<'a>> { | ||
| 45 | if let Some(x) = self.get(id) { | ||
| 46 | Some(x.into()) | ||
| 47 | } else { | ||
| 48 | None | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | fn for_each(&'a self, func: &dyn Fn(&MappedValue<'a>, &mut String) -> CrimResult<()>) -> CrimResult<String> { | ||
| 53 | let mut buf = String::new(); | ||
| 54 | for i in self { | ||
| 55 | func( &Into::<MappedValue<'a>>::into( i ), &mut buf )?; | ||
| 56 | } | ||
| 57 | Ok(buf) | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | impl<'a> MappedList<'a> for Vec<MappedValue<'a>> { | ||
| 62 | fn get_index(&'a self, id: usize) -> Option<MappedValue<'a>> { | ||
| 63 | if let Some(x) = self.get(id) { | ||
| 64 | Some(*x) | ||
| 65 | } else { | ||
| 66 | None | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | fn for_each(&'a self, func: &dyn Fn(&MappedValue<'a>, &mut String) -> CrimResult<()>) -> CrimResult<String> { | ||
| 71 | let mut buf = String::new(); | ||
| 72 | for i in self { | ||
| 73 | func( i, &mut buf )?; | ||
| 74 | } | ||
| 75 | Ok(buf) | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | pub type MappedHash<'a> = HashMap<String, MappedValue<'a>>; | ||
| 80 | |||
| 81 | impl<'a> MappedStructure<'a> for MappedHash<'a> { | ||
| 82 | fn get_value(&'a self, id: &str) -> Option<MappedValue<'a>> { | ||
| 83 | self.get(id).copied() | ||
| 84 | } | ||
| 85 | } | ||
| 86 | |||
| 87 | #[derive(Copy,Clone)] | ||
| 88 | pub enum MappedValue<'a> { | ||
| 89 | Struct(&'a dyn MappedStructure<'a>), | ||
| 90 | List(&'a dyn MappedList<'a>), | ||
| 91 | |||
| 92 | Str(&'a str), | ||
| 93 | String(&'a String), | ||
| 94 | Int8(i8), | ||
| 95 | Int16(i16), | ||
| 96 | Int32(i32), | ||
| 97 | Int64(i64), | ||
| 98 | UInt8(u8), | ||
| 99 | UInt16(u16), | ||
| 100 | UInt32(u32), | ||
| 101 | UInt64(u64), | ||
| 102 | Float32(f32), | ||
| 103 | Float64(f64), | ||
| 104 | Bool(bool), | ||
| 105 | } | ||
| 106 | |||
| 107 | impl<'a> fmt::Debug for MappedValue<'a> { | ||
| 108 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| 109 | match self { | ||
| 110 | MappedValue::<'a>::Struct(_) => write!(f, "MappedValue"), | ||
| 111 | MappedValue::<'a>::List(_) => write!(f, "MappedList"), | ||
| 112 | MappedValue::<'a>::Str(v) => write!(f, "{:?}", v), | ||
| 113 | MappedValue::<'a>::String(v) => write!(f, "{:?}", v), | ||
| 114 | MappedValue::<'a>::Int8(v) => write!(f, "{:?}", v), | ||
| 115 | MappedValue::<'a>::Int16(v) => write!(f, "{:?}", v), | ||
| 116 | MappedValue::<'a>::Int32(v) => write!(f, "{:?}", v), | ||
| 117 | MappedValue::<'a>::Int64(v) => write!(f, "{:?}", v), | ||
| 118 | MappedValue::<'a>::UInt8(v) => write!(f, "{:?}", v), | ||
| 119 | MappedValue::<'a>::UInt16(v) => write!(f, "{:?}", v), | ||
| 120 | MappedValue::<'a>::UInt32(v) => write!(f, "{:?}", v), | ||
| 121 | MappedValue::<'a>::UInt64(v) => write!(f, "{:?}", v), | ||
| 122 | MappedValue::<'a>::Float32(v) => write!(f, "{:?}", v), | ||
| 123 | MappedValue::<'a>::Float64(v) => write!(f, "{:?}", v), | ||
| 124 | MappedValue::<'a>::Bool(v) => write!(f, "{:?}", v), | ||
| 125 | } | ||
| 126 | } | ||
| 127 | |||
| 128 | } | ||
| 129 | |||
| 130 | pub struct StatefulContext<'a> { | ||
| 131 | root: &'a dyn MappedStructure<'a>, | ||
| 132 | local: &'a dyn MappedStructure<'a>, | ||
| 133 | } | ||
| 134 | |||
| 135 | impl<'a> StatefulContext<'a> { | ||
| 136 | pub fn root( root: &'a dyn MappedStructure<'a> ) -> Self { | ||
| 137 | Self { | ||
| 138 | root, local: root, | ||
| 139 | } | ||
| 140 | } | ||
| 141 | |||
| 142 | pub fn local(&self, local: &'a dyn MappedStructure<'a> ) -> Self { | ||
| 143 | Self { | ||
| 144 | root: self.root, | ||
| 145 | local | ||
| 146 | } | ||
| 147 | } | ||
| 148 | |||
| 149 | pub fn full( root: &'a dyn MappedStructure<'a>, local: &'a dyn MappedStructure<'a> ) -> Self { | ||
| 150 | Self { | ||
| 151 | root, local | ||
| 152 | } | ||
| 153 | } | ||
| 154 | |||
| 155 | pub fn get_value(&self, id: &[IdentifierValue]) -> Option<MappedValue<'a>> { | ||
| 156 | let mut value : Option<MappedValue> = | ||
| 157 | if id[0] == IdentifierValue::Root { | ||
| 158 | Some(MappedValue::Struct(self.root)) | ||
| 159 | } else { | ||
| 160 | Some(MappedValue::Struct(self.local)) | ||
| 161 | }; | ||
| 162 | for idpart in id { | ||
| 163 | match idpart { | ||
| 164 | IdentifierValue::Root => { | ||
| 165 | continue; | ||
| 166 | } | ||
| 167 | IdentifierValue::Name(name) => { | ||
| 168 | if let Some(MappedValue::Struct(s)) = value && | ||
| 169 | let Some(nv) = s.get_value( &name ) { | ||
| 170 | value.replace( nv ); | ||
| 171 | } else { | ||
| 172 | return None; | ||
| 173 | } | ||
| 174 | } | ||
| 175 | IdentifierValue::Index(_) => { | ||
| 176 | return None; | ||
| 177 | } | ||
| 178 | } | ||
| 179 | } | ||
| 180 | value | ||
| 181 | } | ||
| 182 | } | ||
| 183 | |||
| 184 | impl<'a> From<&'a Value> for MappedValue<'a> { | ||
| 185 | fn from(value: &'a Value) -> Self { | ||
| 186 | match value { | ||
| 187 | Value::Dictionary(ctx) => MappedValue::Struct(ctx), | ||
| 188 | Value::List(list) => MappedValue::List(list), | ||
| 189 | Value::String(s) => MappedValue::String(s), | ||
| 190 | Value::Int(i) => MappedValue::Int64(*i), | ||
| 191 | Value::Float(f) => MappedValue::Float64(*f), | ||
| 192 | Value::Bool(b) => MappedValue::Bool(*b), | ||
| 193 | Value::Identifier(_identifier) => panic!("Identifier!?"), | ||
| 194 | } | ||
| 195 | } | ||
| 196 | } | ||
| 197 | |||
| 198 | #[derive(Debug,Clone)] | ||
| 199 | pub enum Value { | ||
| 200 | Dictionary(Context), | ||
| 201 | List(Vec<Value>), | ||
| 202 | String(String), | ||
| 203 | Int(i64), | ||
| 204 | Float(f64), | ||
| 205 | Bool(bool), | ||
| 206 | Identifier(Identifier), | ||
| 207 | } | ||
| 208 | |||
| 209 | impl From<&str> for Value { | ||
| 210 | fn from(val: &str ) -> Value { | ||
| 211 | Value::String(val.into()) | ||
| 212 | } | ||
| 213 | } | ||
| 214 | |||
| 215 | impl From<String> for Value { | ||
| 216 | fn from(val: String) -> Value { | ||
| 217 | Value::String(val) | ||
| 218 | } | ||
| 219 | } | ||
| 220 | |||
| 221 | impl From<i64> for Value { | ||
| 222 | fn from(val: i64) -> Value { | ||
| 223 | Value::Int(val) | ||
| 224 | } | ||
| 225 | } | ||
| 226 | |||
| 227 | impl From<f64> for Value { | ||
| 228 | fn from(val: f64) -> Value { | ||
| 229 | Value::Float(val) | ||
| 230 | } | ||
| 231 | } | ||
| 232 | |||
| 233 | impl From<bool> for Value { | ||
| 234 | fn from(val: bool) -> Value { | ||
| 235 | Value::Bool(val) | ||
| 236 | } | ||
| 237 | } | ||
| 238 | |||
| 239 | impl From<Vec<Value>> for Value { | ||
| 240 | fn from(val: Vec<Value>) -> Value { | ||
| 241 | Value::List(val) | ||
| 242 | } | ||
| 243 | } | ||
| 244 | |||
| 245 | impl From<&[Value]> for Value { | ||
| 246 | fn from(val: &[Value]) -> Value { | ||
| 247 | Value::List(Vec::from(val)) | ||
| 248 | } | ||
| 249 | } | ||
| 250 | |||
| 251 | impl From<Context> for Value { | ||
| 252 | fn from(val: Context) -> Value { | ||
| 253 | Value::Dictionary(val) | ||
| 254 | } | ||
| 255 | } | ||
| 256 | |||
| 257 | impl<const N: usize> From<[(String,Value); N]> for Value { | ||
| 258 | fn from(val: [(String,Value); N]) -> Value { | ||
| 259 | Value::Dictionary(HashMap::from(val)) | ||
| 260 | } | ||
| 261 | } | ||
| 262 | |||
| 263 | /* | ||
| 264 | impl MappedStructure for Value { | ||
| 265 | fn get_value(&self, id: &Identifier) -> Option<Value> { | ||
| 266 | if id.len() == 0 { | ||
| 267 | return Some(self); | ||
| 268 | } | ||
| 269 | match Value { | ||
| 270 | Value::Dictionary(dict) => { | ||
| 271 | } | ||
| 272 | } | ||
| 273 | let mut cur_val : Option<&Value> = Some(&self); | ||
| 274 | |||
| 275 | for idx in 0..id.len() { | ||
| 276 | if let IdentifierValue::Name(s) = &id[idx] && | ||
| 277 | let Some(Value::Dictionary(d)) = &cur_val { | ||
| 278 | cur_val.replace( if let Some(v) = d.get(s) { | ||
| 279 | v | ||
| 280 | } else { | ||
| 281 | return None; | ||
| 282 | }); | ||
| 283 | } | ||
| 284 | } | ||
| 285 | |||
| 286 | return cur_val.cloned(); | ||
| 287 | } | ||
| 288 | } | ||
| 289 | */ | ||
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 @@ | |||
| 1 | |||
| 2 | use core::error::Error; | ||
| 3 | use std::fmt; | ||
| 4 | |||
| 5 | use crate::position::*; | ||
| 6 | |||
| 7 | #[derive(Debug)] | ||
| 8 | pub struct CrimError { | ||
| 9 | position: Position, | ||
| 10 | what: String, | ||
| 11 | } | ||
| 12 | |||
| 13 | impl CrimError { | ||
| 14 | pub fn parse( position: Position, what: String ) -> Self { | ||
| 15 | Self { | ||
| 16 | position, | ||
| 17 | what, | ||
| 18 | } | ||
| 19 | } | ||
| 20 | |||
| 21 | pub fn other(what: String) -> Self { | ||
| 22 | Self { | ||
| 23 | position: Position::none(), | ||
| 24 | what, | ||
| 25 | } | ||
| 26 | } | ||
| 27 | |||
| 28 | pub fn broken( position: Position, what: &str ) -> Self { | ||
| 29 | Self { | ||
| 30 | position, | ||
| 31 | what: format!("Broken parser? {}", what), | ||
| 32 | } | ||
| 33 | } | ||
| 34 | |||
| 35 | pub fn eos( context: &str ) -> Self { | ||
| 36 | Self { | ||
| 37 | position: Position::none(), | ||
| 38 | what: format!("Premature end of stream while looking for {}", context), | ||
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 42 | pub fn what(&self) -> &str { | ||
| 43 | &self.what | ||
| 44 | } | ||
| 45 | |||
| 46 | pub fn start(&self) -> &Position { | ||
| 47 | &self.position | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | impl fmt::Display for CrimError { | ||
| 52 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| 53 | write!(f, "Error parsing input: {}", "yeah") | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | impl Error for CrimError { } | ||
| 58 | |||
| 59 | pub type CrimResult<T> = Result<T, CrimError>; | ||
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 @@ | |||
| 1 | use std::iter::Iterator; | ||
| 2 | //use core::error::Error; | ||
| 3 | use std::str::CharIndices; | ||
| 4 | use std::fmt; | ||
| 5 | |||
| 6 | use crate::Position; | ||
| 7 | |||
| 8 | #[derive(Debug,Copy,Clone,PartialEq,Eq)] | ||
| 9 | pub enum ErrorType { | ||
| 10 | UnexpectedChar(char), | ||
| 11 | } | ||
| 12 | |||
| 13 | #[derive(Debug,Copy,Clone,PartialEq,Eq)] | ||
| 14 | pub enum SymbolType<'a> { | ||
| 15 | StartFlat, | ||
| 16 | StartPoint, | ||
| 17 | EndFlat, | ||
| 18 | EndPoint, | ||
| 19 | |||
| 20 | View, | ||
| 21 | Output, | ||
| 22 | Show, | ||
| 23 | Loop, | ||
| 24 | If, | ||
| 25 | ElIf, | ||
| 26 | Else, | ||
| 27 | |||
| 28 | Sharp, | ||
| 29 | Equals, | ||
| 30 | Period, | ||
| 31 | Token(&'a str), | ||
| 32 | Literal(&'a str), | ||
| 33 | Text(&'a str), | ||
| 34 | Error{ what: ErrorType }, | ||
| 35 | } | ||
| 36 | |||
| 37 | #[derive(Copy,Clone)] | ||
| 38 | pub struct Symbol<'a> { | ||
| 39 | symbol: SymbolType<'a>, | ||
| 40 | start: Position, | ||
| 41 | end: Position, | ||
| 42 | } | ||
| 43 | |||
| 44 | impl<'a> fmt::Debug for Symbol<'a> { | ||
| 45 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| 46 | write!(f, "{:?} @ {:?}-{:?}", self.symbol, self.start, self.end ) | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | impl<'a> Symbol<'a> { | ||
| 51 | pub fn new( symbol: SymbolType<'a>, start: Position, end: Position ) -> Self { | ||
| 52 | Self { | ||
| 53 | symbol, start, end, | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | pub fn check_type<T: Fn( &SymbolType ) -> bool>(&self, f: T ) -> bool { | ||
| 58 | f( &self.symbol ) | ||
| 59 | } | ||
| 60 | |||
| 61 | pub fn symbol(&self) -> &SymbolType<'a> { | ||
| 62 | &self.symbol | ||
| 63 | } | ||
| 64 | |||
| 65 | pub fn start(&self) -> &Position { | ||
| 66 | &self.start | ||
| 67 | } | ||
| 68 | |||
| 69 | /* | ||
| 70 | pub fn end(&self) -> &Position { | ||
| 71 | &self.end | ||
| 72 | } | ||
| 73 | */ | ||
| 74 | } | ||
| 75 | |||
| 76 | #[derive(Debug)] | ||
| 77 | enum Mode { | ||
| 78 | Text, | ||
| 79 | InTag, | ||
| 80 | } | ||
| 81 | |||
| 82 | pub struct Lexer<'a> { | ||
| 83 | data: &'a str, | ||
| 84 | chars: CharIndices<'a>, | ||
| 85 | cur: [Option<(usize, char)>;2], | ||
| 86 | icur: usize, | ||
| 87 | mode: Mode, | ||
| 88 | pos: Position, | ||
| 89 | } | ||
| 90 | |||
| 91 | fn is_valid_token_char( c: char, first: bool ) -> bool { | ||
| 92 | if c.is_whitespace() { | ||
| 93 | return false; | ||
| 94 | } | ||
| 95 | if first { | ||
| 96 | match c { | ||
| 97 | 'a'..'z' | 'A'..'Z' | '_' => true, | ||
| 98 | _ => false | ||
| 99 | } | ||
| 100 | } else { | ||
| 101 | match c { | ||
| 102 | 'a'..'z' | 'A'..'Z' | '0'..'9' | '_' => true, | ||
| 103 | _ => false | ||
| 104 | } | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | impl<'a> Lexer<'a> { | ||
| 109 | pub fn new( data: &'a str ) -> Lexer<'a> { | ||
| 110 | let mut chars = data.char_indices(); | ||
| 111 | let cur = chars.next(); | ||
| 112 | let cur2 = chars.next(); | ||
| 113 | //println!(" - cur: {:?}, peek: {:?}", cur, cur2 ); | ||
| 114 | Lexer { | ||
| 115 | data, | ||
| 116 | chars, | ||
| 117 | cur: [cur, cur2], | ||
| 118 | icur: 0, | ||
| 119 | mode: Mode::Text, | ||
| 120 | pos: Position::new(1,1), | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 124 | fn next(&mut self) -> Option<char> { | ||
| 125 | self.cur[self.icur] = self.chars.next(); | ||
| 126 | self.icur = (self.icur+1)%2; | ||
| 127 | //println!(" - cur: {:?}, peek: {:?}", self.cur(), self.peek() ); | ||
| 128 | if let Some((_,chr)) = self.cur[self.icur] { | ||
| 129 | if chr == '\n' { | ||
| 130 | self.pos.column = 1; | ||
| 131 | self.pos.line += 1; | ||
| 132 | } else { | ||
| 133 | self.pos.column += 1; | ||
| 134 | } | ||
| 135 | Some(chr) | ||
| 136 | } else { | ||
| 137 | None | ||
| 138 | } | ||
| 139 | } | ||
| 140 | |||
| 141 | fn cur(&self) -> Option<char> { | ||
| 142 | if let Some((_, chr)) = self.cur[self.icur] { | ||
| 143 | Some(chr) | ||
| 144 | } else { | ||
| 145 | None | ||
| 146 | } | ||
| 147 | } | ||
| 148 | |||
| 149 | fn cur_index(&self) -> usize { | ||
| 150 | if let Some((idx, _)) = self.cur[self.icur] { | ||
| 151 | idx | ||
| 152 | } else { | ||
| 153 | self.chars.offset() | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | fn peek(&self) -> Option<char> { | ||
| 158 | if let Some((_,chr)) = self.cur[(self.icur+1)%2] { | ||
| 159 | Some(chr) | ||
| 160 | } else { | ||
| 161 | None | ||
| 162 | } | ||
| 163 | } | ||
| 164 | |||
| 165 | fn peek_index(&self) -> usize { | ||
| 166 | if let Some((idx, _)) = self.cur[(self.icur+1)%2] { | ||
| 167 | idx | ||
| 168 | } else { | ||
| 169 | self.chars.offset() | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | fn error( &self, what: ErrorType ) -> Option<Symbol<'a>> { | ||
| 174 | Some(Symbol::new( SymbolType::Error { | ||
| 175 | what: what | ||
| 176 | }, self.pos, self.pos )) | ||
| 177 | } | ||
| 178 | |||
| 179 | fn skip_ws( &mut self ) { | ||
| 180 | while self.cur().is_some_and(|x|x.is_whitespace()) { | ||
| 181 | self.next(); | ||
| 182 | } | ||
| 183 | } | ||
| 184 | |||
| 185 | fn next_symbol(&mut self) -> Option<Symbol<'a>> { | ||
| 186 | // If we hit the end then we're already done. | ||
| 187 | if self.cur().is_none() { | ||
| 188 | return None; | ||
| 189 | } | ||
| 190 | |||
| 191 | match self.mode { | ||
| 192 | Mode::Text => { | ||
| 193 | if let Some(sym) = self.parse_start_tag() { | ||
| 194 | Some(sym) | ||
| 195 | } else { | ||
| 196 | self.parse_text() | ||
| 197 | } | ||
| 198 | } | ||
| 199 | Mode::InTag => { | ||
| 200 | if let Some(sym) = self.parse_end_tag() { | ||
| 201 | Some(sym) | ||
| 202 | } else { | ||
| 203 | self.parse_token() | ||
| 204 | } | ||
| 205 | } | ||
| 206 | } | ||
| 207 | } | ||
| 208 | |||
| 209 | fn parse_text(&mut self) -> Option<Symbol<'a>> { | ||
| 210 | let start = self.cur_index(); | ||
| 211 | let start_pos = self.pos.clone(); | ||
| 212 | while self.next().is_some() && !self.is_start_tag() { } | ||
| 213 | let end = self.cur_index(); | ||
| 214 | let end_pos = self.pos.clone(); | ||
| 215 | let s = &self.data[start..end]; | ||
| 216 | //println!(" text: >>>{}<<<", s); | ||
| 217 | if start == end { | ||
| 218 | None | ||
| 219 | } else { | ||
| 220 | Some(Symbol::new( SymbolType::Text(s), start_pos, end_pos )) | ||
| 221 | } | ||
| 222 | } | ||
| 223 | |||
| 224 | fn parse_token(&mut self) -> Option<Symbol<'a>> { | ||
| 225 | self.skip_ws(); | ||
| 226 | match self.cur() { | ||
| 227 | Some('"') => { | ||
| 228 | return self.parse_literal_str(); | ||
| 229 | } | ||
| 230 | Some('#') => { | ||
| 231 | self.next(); | ||
| 232 | return Some(Symbol::new(SymbolType::Sharp, self.pos, self.pos)); | ||
| 233 | } | ||
| 234 | Some('=') => { | ||
| 235 | self.next(); | ||
| 236 | return Some(Symbol::new(SymbolType::Equals, self.pos, self.pos)); | ||
| 237 | } | ||
| 238 | Some('.') => { | ||
| 239 | self.next(); | ||
| 240 | return Some(Symbol::new(SymbolType::Period, self.pos, self.pos)); | ||
| 241 | } | ||
| 242 | _ => {} | ||
| 243 | } | ||
| 244 | let start = self.cur_index(); | ||
| 245 | let start_pos = self.pos.clone(); | ||
| 246 | |||
| 247 | let mut first = true; | ||
| 248 | while self.next().is_some_and(|ch| is_valid_token_char(ch, first) ) && | ||
| 249 | !self.is_end_tag() { first = false; } | ||
| 250 | let end = self.cur_index(); | ||
| 251 | let end_pos = self.pos.clone(); | ||
| 252 | let s = &self.data[start..end]; | ||
| 253 | if start == end { | ||
| 254 | None | ||
| 255 | } else { | ||
| 256 | Some(Symbol::new( match s { | ||
| 257 | "view" => SymbolType::View, | ||
| 258 | "show" => SymbolType::Show, | ||
| 259 | "output" => SymbolType::Output, | ||
| 260 | "loop" => SymbolType::Loop, | ||
| 261 | "if" => SymbolType::If, | ||
| 262 | "elif" => SymbolType::ElIf, | ||
| 263 | "else" => SymbolType::Else, | ||
| 264 | _ => SymbolType::Token(s) | ||
| 265 | }, start_pos, end_pos )) | ||
| 266 | } | ||
| 267 | } | ||
| 268 | |||
| 269 | fn parse_literal_str(&mut self) -> Option<Symbol<'a>> { | ||
| 270 | if let Some(chr) = self.cur() && chr != '"' { | ||
| 271 | return self.error( ErrorType::UnexpectedChar(chr) ); | ||
| 272 | } | ||
| 273 | let start = self.peek_index(); | ||
| 274 | let start_pos = self.pos.clone(); | ||
| 275 | while self.next().is_some_and(|chr| chr != '"') { } | ||
| 276 | let end = self.cur_index(); | ||
| 277 | let end_pos = self.pos.clone(); | ||
| 278 | self.next(); | ||
| 279 | let s = &self.data[start..end]; | ||
| 280 | Some(Symbol::new(SymbolType::Literal(s), start_pos, end_pos )) | ||
| 281 | } | ||
| 282 | |||
| 283 | fn is_start_tag(&mut self) -> bool { | ||
| 284 | if let Some(cur) = self.cur() && (cur == '[' || cur == '<') && | ||
| 285 | let Some(peek) = self.peek() && peek == '|' { | ||
| 286 | true | ||
| 287 | } else { | ||
| 288 | false | ||
| 289 | } | ||
| 290 | } | ||
| 291 | |||
| 292 | fn parse_start_tag(&mut self) -> Option<Symbol<'a>> { | ||
| 293 | let start_pos = self.pos.clone(); | ||
| 294 | match self.cur() { | ||
| 295 | Some('[') => { | ||
| 296 | if let Some(p) = self.peek() && p == '|' { | ||
| 297 | self.next(); | ||
| 298 | let end_pos = self.pos.clone(); | ||
| 299 | self.next(); | ||
| 300 | self.mode = Mode::InTag; | ||
| 301 | Some(Symbol::new( SymbolType::StartFlat, start_pos, end_pos ) ) | ||
| 302 | } else { | ||
| 303 | None | ||
| 304 | } | ||
| 305 | } | ||
| 306 | Some('<') => { | ||
| 307 | if let Some(p) = self.peek() && p == '|' { | ||
| 308 | self.next(); | ||
| 309 | let end_pos = self.pos.clone(); | ||
| 310 | self.next(); | ||
| 311 | self.mode = Mode::InTag; | ||
| 312 | Some(Symbol::new(SymbolType::StartPoint, start_pos, end_pos ) ) | ||
| 313 | } else { | ||
| 314 | None | ||
| 315 | } | ||
| 316 | } | ||
| 317 | _ => { | ||
| 318 | None | ||
| 319 | } | ||
| 320 | } | ||
| 321 | } | ||
| 322 | |||
| 323 | fn is_end_tag(&mut self) -> bool { | ||
| 324 | if let Some(cur) = self.cur() && cur == '|' && | ||
| 325 | let Some(peek) = self.peek() && (peek == '>' || peek == ']') { | ||
| 326 | true | ||
| 327 | } else { | ||
| 328 | false | ||
| 329 | } | ||
| 330 | } | ||
| 331 | |||
| 332 | fn parse_end_tag(&mut self) -> Option<Symbol<'a>> { | ||
| 333 | self.skip_ws(); | ||
| 334 | let start_pos = self.pos.clone(); | ||
| 335 | if let Some(chr) = self.cur() && chr == '|' { | ||
| 336 | match self.peek() { | ||
| 337 | Some(']') => { | ||
| 338 | self.next(); | ||
| 339 | let end_pos = self.pos.clone(); | ||
| 340 | self.next(); | ||
| 341 | self.mode = Mode::Text; | ||
| 342 | Some(Symbol::new( SymbolType::EndFlat, start_pos, end_pos)) | ||
| 343 | } | ||
| 344 | Some('>') => { | ||
| 345 | self.next(); | ||
| 346 | let end_pos = self.pos.clone(); | ||
| 347 | self.next(); | ||
| 348 | self.mode = Mode::Text; | ||
| 349 | Some(Symbol::new( SymbolType::EndPoint, start_pos, end_pos)) | ||
| 350 | } | ||
| 351 | _ => { | ||
| 352 | None | ||
| 353 | } | ||
| 354 | } | ||
| 355 | } else { | ||
| 356 | None | ||
| 357 | } | ||
| 358 | } | ||
| 359 | } | ||
| 360 | |||
| 361 | impl<'a> Iterator for Lexer<'a> { | ||
| 362 | type Item = Symbol<'a>; | ||
| 363 | |||
| 364 | fn next(&mut self) -> Option<Self::Item> { | ||
| 365 | self.next_symbol() | ||
| 366 | } | ||
| 367 | } | ||
| 368 | |||
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 @@ | |||
| 1 | use std::path::{PathBuf,Path}; | ||
| 2 | use std::collections::HashMap; | ||
| 3 | use core::error::Error; | ||
| 4 | use std::time::SystemTime; | ||
| 5 | |||
| 6 | mod lexer; | ||
| 7 | mod parser; | ||
| 8 | mod position; | ||
| 9 | mod error; | ||
| 10 | pub mod context; | ||
| 11 | |||
| 12 | pub use error::{CrimError,CrimResult}; | ||
| 13 | pub use position::*; | ||
| 14 | pub use context::{Context,Value,MappedStructure,StatefulContext,MappedValue,MappedList,MappedHash}; | ||
| 15 | |||
| 16 | #[derive(Debug)] | ||
| 17 | pub struct Crimtag { | ||
| 18 | views: HashMap<String, View>, | ||
| 19 | sources: Vec<ViewSource>, | ||
| 20 | } | ||
| 21 | |||
| 22 | #[derive(PartialEq,Eq,Debug,Clone)] | ||
| 23 | pub enum ViewSource { | ||
| 24 | File { | ||
| 25 | path: PathBuf, | ||
| 26 | loaded: SystemTime, | ||
| 27 | }, | ||
| 28 | Static, | ||
| 29 | External { | ||
| 30 | key: String, | ||
| 31 | }, | ||
| 32 | } | ||
| 33 | |||
| 34 | #[derive(Debug)] | ||
| 35 | struct View { | ||
| 36 | name: String, | ||
| 37 | source: usize, | ||
| 38 | outputs: Vec<Output>, | ||
| 39 | // theme: Option<String> | ||
| 40 | layout: Option<String>, | ||
| 41 | } | ||
| 42 | |||
| 43 | impl View { | ||
| 44 | fn process(&self, input: &dyn for<'a> MappedStructure<'a>) -> CrimResult<Context> { | ||
| 45 | let mut out_vars = Context::new(); | ||
| 46 | for output in &self.outputs { | ||
| 47 | let mut buf = String::new(); | ||
| 48 | let sc = StatefulContext::root( input ); | ||
| 49 | exec( &sc, &output.code, &mut buf )?; | ||
| 50 | out_vars.insert( output.name.clone(), Value::String(buf) ); | ||
| 51 | } | ||
| 52 | Ok(out_vars) | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | #[derive(Debug)] | ||
| 57 | struct Output { | ||
| 58 | name: String, | ||
| 59 | code: Vec<Token>, | ||
| 60 | } | ||
| 61 | |||
| 62 | type Properties = HashMap<String, String>; | ||
| 63 | |||
| 64 | #[derive(Debug)] | ||
| 65 | enum Token { | ||
| 66 | Loop(Identifier, Vec<Token>), | ||
| 67 | Show(Identifier,Properties), | ||
| 68 | If(Identifier, Vec<Token>, Vec<Token>), | ||
| 69 | Text(String), | ||
| 70 | } | ||
| 71 | |||
| 72 | #[derive(Debug,Clone,PartialEq)] | ||
| 73 | pub enum IdentifierValue { | ||
| 74 | Root, | ||
| 75 | Name(String), | ||
| 76 | Index(usize), | ||
| 77 | } | ||
| 78 | |||
| 79 | pub type Identifier = Vec<IdentifierValue>; | ||
| 80 | |||
| 81 | impl Crimtag { | ||
| 82 | pub fn new() -> Self { | ||
| 83 | Self { | ||
| 84 | views: HashMap::new(), | ||
| 85 | sources: vec![ViewSource::Static], | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | fn id_source(&mut self, src: &ViewSource ) -> usize { | ||
| 90 | for idx in 0..self.sources.len() { | ||
| 91 | if self.sources[idx] == *src { | ||
| 92 | return idx; | ||
| 93 | } | ||
| 94 | } | ||
| 95 | |||
| 96 | // Nothing found, add a new one | ||
| 97 | let idx = self.sources.len(); | ||
| 98 | self.sources.push( src.clone() ); | ||
| 99 | idx | ||
| 100 | } | ||
| 101 | |||
| 102 | pub fn load_file(&mut self, path: &Path) -> Result<(),Box::<dyn Error>> { | ||
| 103 | let time = if let Ok(meta) = path.metadata() { | ||
| 104 | if let Ok(t) = meta.modified() { | ||
| 105 | t | ||
| 106 | } else if let Ok(t) = meta.created() { | ||
| 107 | t | ||
| 108 | } else { | ||
| 109 | SystemTime::now() | ||
| 110 | } | ||
| 111 | } else { | ||
| 112 | SystemTime::now() | ||
| 113 | }; | ||
| 114 | let source_id = self.id_source( | ||
| 115 | &ViewSource::File { | ||
| 116 | path: path.to_path_buf(), | ||
| 117 | loaded: time, | ||
| 118 | } | ||
| 119 | ); | ||
| 120 | let p = parser::Parser::new(); | ||
| 121 | self.register_views( | ||
| 122 | p.parse( | ||
| 123 | &String::from_utf8( std::fs::read( path )? )?, | ||
| 124 | source_id | ||
| 125 | )?, | ||
| 126 | )?; | ||
| 127 | Ok(()) | ||
| 128 | } | ||
| 129 | |||
| 130 | pub fn load_static(&mut self, data: &str) -> CrimResult<()> { | ||
| 131 | let source_id = self.id_source( &ViewSource::Static ); | ||
| 132 | let p = parser::Parser::new(); | ||
| 133 | self.register_views( p.parse( &data, source_id )? ) | ||
| 134 | } | ||
| 135 | |||
| 136 | pub fn load_external(&mut self, key: &str, data: &str) -> CrimResult<()> { | ||
| 137 | let source_id = self.id_source( | ||
| 138 | &ViewSource::External{ key: key.to_string()} | ||
| 139 | ); | ||
| 140 | |||
| 141 | let p = parser::Parser::new(); | ||
| 142 | self.register_views( | ||
| 143 | p.parse( &data, source_id )?, | ||
| 144 | ) | ||
| 145 | } | ||
| 146 | |||
| 147 | fn register_views(&mut self, views: Vec<View>) -> CrimResult<()> { | ||
| 148 | for view in views { | ||
| 149 | self.views.insert( view.name.clone(), view ); | ||
| 150 | } | ||
| 151 | Ok(()) | ||
| 152 | } | ||
| 153 | |||
| 154 | pub fn get_view_source(&self, view: &str) -> Option<ViewSource> { | ||
| 155 | if let Some(view) = self.views.get(view) { | ||
| 156 | if view.source < self.sources.len() { | ||
| 157 | Some(self.sources[view.source].clone()) | ||
| 158 | } else { | ||
| 159 | None | ||
| 160 | } | ||
| 161 | } else { | ||
| 162 | None | ||
| 163 | } | ||
| 164 | } | ||
| 165 | |||
| 166 | pub fn render_partial(&self, view: &str, input: &dyn for<'a> MappedStructure<'a> ) -> CrimResult<Context> { | ||
| 167 | if let Some(view) = self.views.get(view) { | ||
| 168 | return view.process( input ) | ||
| 169 | } else { | ||
| 170 | return Err(CrimError::other("No such view found".into())); | ||
| 171 | } | ||
| 172 | } | ||
| 173 | |||
| 174 | pub fn render(&self, view: &str, input: &dyn for<'a> MappedStructure<'a> ) -> CrimResult<String> { | ||
| 175 | if let Some(view) = self.views.get( view ) { | ||
| 176 | //println!("::Token tree::\n{:?}", view ); | ||
| 177 | let mut output = view.process( input )?; | ||
| 178 | if let Some(l) = &view.layout { | ||
| 179 | self.render( l, &output ) | ||
| 180 | } else { | ||
| 181 | if let Some(content) = output.remove("content") && | ||
| 182 | let Value::String(s) = content { | ||
| 183 | |||
| 184 | Ok(s) | ||
| 185 | } else { | ||
| 186 | Err(CrimError::other("No content found in root layout.".into())) | ||
| 187 | } | ||
| 188 | } | ||
| 189 | |||
| 190 | } else { | ||
| 191 | Err(CrimError::other("No such view found".into())) | ||
| 192 | } | ||
| 193 | } | ||
| 194 | } | ||
| 195 | |||
| 196 | fn exec<'a>(input: &StatefulContext<'a>, tokens: &Vec<Token>, buf: &mut String) -> CrimResult<()> { | ||
| 197 | for token in tokens { | ||
| 198 | match token { | ||
| 199 | Token::Loop(ident,code) => { | ||
| 200 | //println!("!!! Loop over: {:?} {:?}", ident, input.get_value( &ident )); | ||
| 201 | if let Some(value) = input.get_value( &ident ) && | ||
| 202 | let MappedValue::List(list) = value { | ||
| 203 | let output = | ||
| 204 | &list.for_each(&|rec: &MappedValue, buf: &mut String| { | ||
| 205 | if let MappedValue::Struct(d) = rec { | ||
| 206 | exec( &input.local(*d), code, buf )?; | ||
| 207 | } | ||
| 208 | Ok(()) | ||
| 209 | })?; | ||
| 210 | //println!("!!! Output from loop: {}", output ); | ||
| 211 | buf.push_str( &output ); | ||
| 212 | } | ||
| 213 | } | ||
| 214 | Token::If(ident,code,other) => { | ||
| 215 | if let Some(value) = input.get_value( &ident ) && | ||
| 216 | let MappedValue::Bool(b) = value && b { | ||
| 217 | exec(input, code, buf)? | ||
| 218 | } else { | ||
| 219 | exec(input, other, buf)? | ||
| 220 | } | ||
| 221 | } | ||
| 222 | Token::Show(ident,p) => { | ||
| 223 | let format = if let Some(s) = p.get("format") { | ||
| 224 | s | ||
| 225 | } else { | ||
| 226 | &"".to_string() | ||
| 227 | }; | ||
| 228 | if let Some(value) = input.get_value( &ident ) { | ||
| 229 | match value { | ||
| 230 | MappedValue::Struct(_) => { | ||
| 231 | buf.push_str("Struct"); | ||
| 232 | } | ||
| 233 | MappedValue::List(_) => { | ||
| 234 | buf.push_str("List"); | ||
| 235 | } | ||
| 236 | MappedValue::Bool(b) => { | ||
| 237 | if b { | ||
| 238 | buf.push_str("true"); | ||
| 239 | } else { | ||
| 240 | buf.push_str("false"); | ||
| 241 | } | ||
| 242 | } | ||
| 243 | MappedValue::Str(s) => { | ||
| 244 | buf.push_str(s); | ||
| 245 | } | ||
| 246 | MappedValue::String(s) => { | ||
| 247 | buf.push_str(s.as_str()); | ||
| 248 | } | ||
| 249 | MappedValue::Int8(i) => { | ||
| 250 | buf.push_str(&i.to_string()); | ||
| 251 | } | ||
| 252 | MappedValue::Int16(i) => { | ||
| 253 | buf.push_str(&i.to_string()); | ||
| 254 | } | ||
| 255 | MappedValue::Int32(i) => { | ||
| 256 | buf.push_str(&i.to_string()); | ||
| 257 | } | ||
| 258 | MappedValue::Int64(i) => { | ||
| 259 | buf.push_str(&i.to_string()); | ||
| 260 | } | ||
| 261 | MappedValue::UInt8(i) => { | ||
| 262 | buf.push_str(&i.to_string()); | ||
| 263 | } | ||
| 264 | MappedValue::UInt16(i) => { | ||
| 265 | buf.push_str(&i.to_string()); | ||
| 266 | } | ||
| 267 | MappedValue::UInt32(i) => { | ||
| 268 | buf.push_str(&i.to_string()); | ||
| 269 | } | ||
| 270 | MappedValue::UInt64(i) => { | ||
| 271 | buf.push_str(&i.to_string()); | ||
| 272 | } | ||
| 273 | MappedValue::Float32(f) => { | ||
| 274 | if format == "" { | ||
| 275 | buf.push_str(&f.to_string()); | ||
| 276 | } else { | ||
| 277 | buf.push_str(&f.to_string()); | ||
| 278 | } | ||
| 279 | } | ||
| 280 | MappedValue::Float64(f) => { | ||
| 281 | if format == "" { | ||
| 282 | buf.push_str(&f.to_string()); | ||
| 283 | } else { | ||
| 284 | buf.push_str(&f.to_string()); | ||
| 285 | } | ||
| 286 | } | ||
| 287 | } | ||
| 288 | } | ||
| 289 | } | ||
| 290 | Token::Text(s) => { | ||
| 291 | buf.push_str( s ); | ||
| 292 | } | ||
| 293 | //_ => {} | ||
| 294 | } | ||
| 295 | } | ||
| 296 | Ok(()) | ||
| 297 | } | ||
| 298 | |||
| 299 | #[cfg(test)] | ||
| 300 | mod tests { | ||
| 301 | use super::*; | ||
| 302 | |||
| 303 | use crate::lexer::*; | ||
| 304 | |||
| 305 | #[test] | ||
| 306 | fn lexing() { | ||
| 307 | let data = r#"Leading comment: [|view "basic" something="yeup"|>Hello there <|view|] Trailing text"#; | ||
| 308 | let ll = lexer::Lexer::new( &data ); | ||
| 309 | for sym in ll { | ||
| 310 | if let SymbolType::Error{what} = sym.symbol() { | ||
| 311 | println!("Error {}:{}: {:?}", sym.start().line, sym.start().column, what ); | ||
| 312 | break; | ||
| 313 | } else { | ||
| 314 | println!("Symbol: {:?}", sym ); | ||
| 315 | } | ||
| 316 | } | ||
| 317 | } | ||
| 318 | |||
| 319 | #[test] | ||
| 320 | fn parsing() { | ||
| 321 | let mut ct = Crimtag::new(); | ||
| 322 | if let Err(e) = ct.load_static(r#"Here is a sample | ||
| 323 | [|view "index" theme="standard"|><html><body>Hi</body></html><|view|] | ||
| 324 | That was fun! now a placeholder: [|view "placeholder"|] and now one with an implicit [|view "simple"|>Body -> [|show content|] <- and end<|view|] aoeu | ||
| 325 | |||
| 326 | [|view "person" layout="simple"|>Welcome [|show person.last_name|], [|show person.first_name|]! It's lovely to see you again.<|view|] | ||
| 327 | |||
| 328 | Now with explicit outputs: | ||
| 329 | [|view "complex"|> | ||
| 330 | [|output "content"|> | ||
| 331 | Here's a [|show name|]. | ||
| 332 | <|output|] | ||
| 333 | [|output "sidebar"|> | ||
| 334 | What's up world? | ||
| 335 | <|output|] | ||
| 336 | [|output "footer"|> | ||
| 337 | Same, I guess! | ||
| 338 | <|output|] | ||
| 339 | <|view|]"#) { | ||
| 340 | println!("Error: {:?}", e ); | ||
| 341 | } else { | ||
| 342 | println!("It finished"); | ||
| 343 | if let Ok(v) = ct.render( | ||
| 344 | "person", | ||
| 345 | &Context::from([ | ||
| 346 | ("hi".to_string(),Value::String("hi".to_string())), | ||
| 347 | ("person".to_string(),Value::Dictionary(HashMap::from([ | ||
| 348 | ("first_name".to_string(), Value::String("Bob".to_string())), | ||
| 349 | ("last_name".to_string(), Value::String("Smith".to_string())), | ||
| 350 | ]))), | ||
| 351 | ]) | ||
| 352 | ) { | ||
| 353 | println!("View result: {:?}", v ); | ||
| 354 | } else { | ||
| 355 | println!("Error?"); | ||
| 356 | } | ||
| 357 | } | ||
| 358 | } | ||
| 359 | |||
| 360 | #[test] | ||
| 361 | fn loops() -> Result<(),CrimError> { | ||
| 362 | let mut ct = Crimtag::new(); | ||
| 363 | ct.load_static(r#"Looping code: | ||
| 364 | [|view "index"|>We will enumerate people here:[|loop people|> | ||
| 365 | - [|show last_name|], [|show first_name|][|if show_title |>: [|show title|] <|else|> **title redacted** <|if|] ::>[|loop tags|> [|show tag|]<|loop|] <:: <|loop|] | ||
| 366 | <|view|] | ||
| 367 | "#)?; | ||
| 368 | |||
| 369 | let ctx = Context::from([ | ||
| 370 | ("people".to_string(), vec![ | ||
| 371 | [ | ||
| 372 | ("first_name".into(), "Joe".into()), | ||
| 373 | ("last_name".into(), "Smith".into()), | ||
| 374 | ("show_title".into(), true.into()), | ||
| 375 | ("title".into(), "CEO".into()), | ||
| 376 | ("tags".into(), vec![ | ||
| 377 | Context::from([("tag".into(), "jerk".into()),]).into(), | ||
| 378 | Context::from([("tag".into(), "ugly".into()),]).into(), | ||
| 379 | ].into()), | ||
| 380 | ].into(), | ||
| 381 | [ | ||
| 382 | ("first_name".into(), "Chris".into()), | ||
| 383 | ("last_name".into(), "Perkens".into()), | ||
| 384 | ("show_title".into(), false.into()), | ||
| 385 | ("title".into(), "Baconeer".into()), | ||
| 386 | ("tags".into(), vec![ | ||
| 387 | Context::from([("tag".into(), "jerk".into()),]).into(), | ||
| 388 | Context::from([("tag".into(), "ugly".into()),]).into(), | ||
| 389 | ].into()), | ||
| 390 | ].into(), | ||
| 391 | [ | ||
| 392 | ("first_name".into(), "Will".into()), | ||
| 393 | ("last_name".into(), "Power".into()), | ||
| 394 | ("show_title".into(), false.into()), | ||
| 395 | ("title".into(), "CFO".into()), | ||
| 396 | ("tags".into(), vec![ | ||
| 397 | Context::from([("tag".into(), "jerk".into()),]).into(), | ||
| 398 | Context::from([("tag".into(), "ugly".into()),]).into(), | ||
| 399 | ].into()), | ||
| 400 | ].into(), | ||
| 401 | [ | ||
| 402 | ("first_name".into(), "Justin".into()), | ||
| 403 | ("last_name".into(), "Time".into()), | ||
| 404 | ("show_title".into(), true.into()), | ||
| 405 | ("title".into(), "CIO".into()), | ||
| 406 | ("tags".into(), vec![ | ||
| 407 | Context::from([("tag".into(), "jerk".into()),]).into(), | ||
| 408 | Context::from([("tag".into(), "ugly".into()),]).into(), | ||
| 409 | ].into()), | ||
| 410 | ].into(), | ||
| 411 | ].into()), | ||
| 412 | ]); | ||
| 413 | |||
| 414 | println!("View result: {}", ct.render("index", &ctx)? ); | ||
| 415 | |||
| 416 | Ok(()) | ||
| 417 | } | ||
| 418 | |||
| 419 | #[test] | ||
| 420 | fn conditional() -> Result<(),CrimError> { | ||
| 421 | let mut ct = Crimtag::new(); | ||
| 422 | ct.load_static(r#"Hi there | ||
| 423 | [|view "index"|> | ||
| 424 | Color: [|if is_red|> red <|elif is_blue |> blue <|else|> green <|if|] | ||
| 425 | <|view|]"#)?; | ||
| 426 | |||
| 427 | let ctx = Context::from([ | ||
| 428 | ("is_red".into(), false.into()), | ||
| 429 | ("is_blue".into(), false.into()), | ||
| 430 | ("color".into(), "purple".into()), | ||
| 431 | ]); | ||
| 432 | |||
| 433 | println!("View result: {}", ct.render("index", &ctx)? ); | ||
| 434 | |||
| 435 | Ok(()) | ||
| 436 | } | ||
| 437 | |||
| 438 | struct Item { | ||
| 439 | pub id: i32, | ||
| 440 | pub title: String, | ||
| 441 | pub body: String, | ||
| 442 | } | ||
| 443 | |||
| 444 | struct Person { | ||
| 445 | pub id: i32, | ||
| 446 | pub username: String, | ||
| 447 | pub name: String, | ||
| 448 | } | ||
| 449 | |||
| 450 | struct Page { | ||
| 451 | pub user: Person, | ||
| 452 | pub total_items: i32, | ||
| 453 | pub total_pages: i32, | ||
| 454 | pub cur_page: i32, | ||
| 455 | pub items: Vec<Item>, | ||
| 456 | } | ||
| 457 | |||
| 458 | impl Page { | ||
| 459 | fn new() -> Page { | ||
| 460 | Page { | ||
| 461 | user: Person { | ||
| 462 | id: 443, | ||
| 463 | username: "eichlan".into(), | ||
| 464 | name: "Mike".into(), | ||
| 465 | }, | ||
| 466 | total_items: 4000, | ||
| 467 | total_pages: 400, | ||
| 468 | cur_page: 35, | ||
| 469 | items: vec![ | ||
| 470 | Item{ | ||
| 471 | id: 123, | ||
| 472 | title: "hi".into(), | ||
| 473 | body: "body".into(), | ||
| 474 | }, | ||
| 475 | Item{ | ||
| 476 | id: 124, | ||
| 477 | title: "bye".into(), | ||
| 478 | body: "wooo".into(), | ||
| 479 | }, | ||
| 480 | Item{ | ||
| 481 | id: 125, | ||
| 482 | title: "ciao".into(), | ||
| 483 | body: "whatever".into(), | ||
| 484 | } | ||
| 485 | ], | ||
| 486 | } | ||
| 487 | } | ||
| 488 | } | ||
| 489 | |||
| 490 | impl<'a> MappedStructure<'a> for Page { | ||
| 491 | fn get_value(&'a self, id: &str) -> Option<MappedValue<'a>> { | ||
| 492 | match id { | ||
| 493 | "user" => Some(MappedValue::Struct(&self.user)), | ||
| 494 | "total_items" => Some(MappedValue::Int32(self.total_items)), | ||
| 495 | "total_pages" => Some(MappedValue::Int32(self.total_pages)), | ||
| 496 | "cur_page" => Some(MappedValue::Int32(self.cur_page)), | ||
| 497 | "items" => Some(MappedValue::<'a>::List(&self.items)), | ||
| 498 | _ => None, | ||
| 499 | } | ||
| 500 | } | ||
| 501 | } | ||
| 502 | |||
| 503 | impl<'a> MappedStructure<'a> for Person { | ||
| 504 | fn get_value(&'a self, id: &str) -> Option<MappedValue<'a>> { | ||
| 505 | match id { | ||
| 506 | "id" => Some(MappedValue::Int32(self.id)), | ||
| 507 | "username" => Some(MappedValue::String(&self.username)), | ||
| 508 | "name" => Some(MappedValue::String(&self.name)), | ||
| 509 | _ => None, | ||
| 510 | } | ||
| 511 | } | ||
| 512 | } | ||
| 513 | |||
| 514 | impl<'a> MappedStructure<'a> for Item { | ||
| 515 | fn get_value(&'a self, id: &str) -> Option<MappedValue<'a>> { | ||
| 516 | match id { | ||
| 517 | "id" => Some(MappedValue::Int32(self.id)), | ||
| 518 | "title" => Some(MappedValue::String(&self.title)), | ||
| 519 | "body" => Some(MappedValue::String(&self.body)), | ||
| 520 | _ => None, | ||
| 521 | } | ||
| 522 | } | ||
| 523 | } | ||
| 524 | |||
| 525 | #[test] | ||
| 526 | fn custom() -> Result<(), CrimError> { | ||
| 527 | let page = Page::new(); | ||
| 528 | |||
| 529 | println!("{:?}", | ||
| 530 | page.get_value(&"total_pages") | ||
| 531 | ); | ||
| 532 | |||
| 533 | if let Some(MappedValue::List(l)) = page.get_value(&"items") { | ||
| 534 | l.for_each(&|x: &MappedValue, buf: &mut String| { | ||
| 535 | if let MappedValue::Struct(s) = x { | ||
| 536 | println!(" - {:?}", s.get_value(&"title")); | ||
| 537 | buf.push_str(&format!("{:?}",s.get_value(&"title"))); | ||
| 538 | } | ||
| 539 | Ok(()) | ||
| 540 | }).expect("loop"); | ||
| 541 | } | ||
| 542 | Ok(()) | ||
| 543 | } | ||
| 544 | } | ||
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 @@ | |||
| 1 | use crate::lexer::*; | ||
| 2 | use crate::*; | ||
| 3 | |||
| 4 | struct Context<'a> { | ||
| 5 | cur: [Option<Symbol<'a>>;2], | ||
| 6 | icur: usize, | ||
| 7 | ll: Lexer<'a>, | ||
| 8 | source: usize, | ||
| 9 | } | ||
| 10 | |||
| 11 | impl<'a> Context<'a> { | ||
| 12 | pub fn new( mut ll: Lexer<'a>, source: usize ) -> Self { | ||
| 13 | let cur = [ll.next(), ll.next()]; | ||
| 14 | //println!(" - cur: {:?}, peek: {:?}", cur[0], cur[1] ); | ||
| 15 | Self { | ||
| 16 | cur, | ||
| 17 | icur: 0, | ||
| 18 | ll, | ||
| 19 | source, | ||
| 20 | } | ||
| 21 | } | ||
| 22 | |||
| 23 | pub fn next(&mut self) -> Option<Symbol<'a>> { | ||
| 24 | self.cur[self.icur] = self.ll.next(); | ||
| 25 | self.icur = (self.icur+1)%2; | ||
| 26 | //println!(" - cur: {:?}, peek: {:?}", self.cur(), self.peek() ); | ||
| 27 | self.cur[self.icur] | ||
| 28 | } | ||
| 29 | |||
| 30 | pub fn cur(&self) -> Option<Symbol<'a>> { | ||
| 31 | self.cur[self.icur] | ||
| 32 | } | ||
| 33 | |||
| 34 | pub fn peek(&self) -> Option<Symbol<'a>> { | ||
| 35 | self.cur[(self.icur+1)%2] | ||
| 36 | } | ||
| 37 | |||
| 38 | /* | ||
| 39 | pub fn check_unwrapped<T: Fn( &SymbolType, &SymbolType ) -> bool>(&self, context: &str, f: T) -> CrimResult<bool> { | ||
| 40 | if self.cur().is_none() || self.peek().is_none() { | ||
| 41 | Err(CrimError::eos( context )) | ||
| 42 | } else { | ||
| 43 | Ok(f( self.cur().unwrap().symbol(), self.peek().unwrap().symbol())) | ||
| 44 | } | ||
| 45 | } | ||
| 46 | */ | ||
| 47 | |||
| 48 | pub fn source(&self) -> usize { | ||
| 49 | self.source | ||
| 50 | } | ||
| 51 | } | ||
| 52 | |||
| 53 | trait SymbolHelper { | ||
| 54 | fn is<T: Fn( &SymbolType ) -> bool>(&self, context: &str, f: T ) -> CrimResult<bool>; | ||
| 55 | #[allow(dead_code)] | ||
| 56 | fn is_valid_tag_name(&self) -> CrimResult<bool>; | ||
| 57 | } | ||
| 58 | |||
| 59 | impl<'a> SymbolHelper for Option<Symbol<'a>> { | ||
| 60 | fn is<T: Fn( &SymbolType ) -> bool>(&self, context: &str, f: T ) -> CrimResult<bool> { | ||
| 61 | if let Some(st) = self { | ||
| 62 | Ok(f( &st.symbol() )) | ||
| 63 | } else { | ||
| 64 | Err(CrimError::eos( context )) | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | fn is_valid_tag_name(&self) -> CrimResult<bool> { | ||
| 69 | if let Some(st) = self { | ||
| 70 | Ok(match st.symbol() { | ||
| 71 | SymbolType::Token(_) | | ||
| 72 | SymbolType::View | | ||
| 73 | SymbolType::Output | | ||
| 74 | SymbolType::Loop | | ||
| 75 | SymbolType::If | | ||
| 76 | SymbolType::ElIf | | ||
| 77 | SymbolType::Else | | ||
| 78 | SymbolType::Show => true, | ||
| 79 | _ => false | ||
| 80 | }) | ||
| 81 | } else { | ||
| 82 | Err(CrimError::parse( | ||
| 83 | Position::none(), | ||
| 84 | "Unexpeceted end of stream.".to_string() | ||
| 85 | )) | ||
| 86 | } | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | pub struct Parser { | ||
| 91 | } | ||
| 92 | |||
| 93 | /** | ||
| 94 | * input: input complete_tag | ||
| 95 | * | input text | ||
| 96 | * | | ||
| 97 | * ; | ||
| 98 | * | ||
| 99 | * tag: unary_tag | ||
| 100 | * | multinary_open_tag | ||
| 101 | * | multinary_mid_tag | ||
| 102 | * | multinary_close_tag | ||
| 103 | * ; | ||
| 104 | * | ||
| 105 | * unary_tag: '[|' tag_guts '|]' | ||
| 106 | * ; | ||
| 107 | * | ||
| 108 | * | '[|' tag_guts '|>' | ||
| 109 | * | '<|' tag_guts '|>' | ||
| 110 | * | '<|' tag_guts '|]' | ||
| 111 | * ; | ||
| 112 | * | ||
| 113 | * tag_pair: open_tag tag_body close_tag | ||
| 114 | * ; | ||
| 115 | * | ||
| 116 | * tag_body: tag_body tag | ||
| 117 | * | tag_body tag_pair | ||
| 118 | * | tag_body text | ||
| 119 | * | | ||
| 120 | * ; | ||
| 121 | * | ||
| 122 | * open_tag: '[|' tag_guts '|>' | ||
| 123 | * ; | ||
| 124 | * | ||
| 125 | * close_tag: '<|' token '|]' | ||
| 126 | * ; | ||
| 127 | * | ||
| 128 | * tag_guts: 'view' literal props | ||
| 129 | * | 'section' literal | ||
| 130 | * | 'output' literal | ||
| 131 | * ; | ||
| 132 | * | ||
| 133 | * literal: '"' [^"]* '"' | ||
| 134 | * ; | ||
| 135 | * | ||
| 136 | * props: props token '=' literal | ||
| 137 | * | | ||
| 138 | * ; | ||
| 139 | * | ||
| 140 | * disambiguate (tm): | ||
| 141 | * | ||
| 142 | * input: input tag | ||
| 143 | * | input text | ||
| 144 | * | | ||
| 145 | * ; | ||
| 146 | * | ||
| 147 | * tag: open_tag_base unary_tag | ||
| 148 | * | open_tag_base binary_tag | ||
| 149 | * ; | ||
| 150 | * | ||
| 151 | * open_tag_base: '[|' tag_guts | ||
| 152 | * ; | ||
| 153 | * | ||
| 154 | * unary_tag: '|]' | ||
| 155 | * ; | ||
| 156 | * | ||
| 157 | * binary_tag: '|>' tag_body close_tag | ||
| 158 | * ; | ||
| 159 | * | ||
| 160 | * tag_body: tag_body tag | ||
| 161 | * | tag_body text | ||
| 162 | * | | ||
| 163 | * ; | ||
| 164 | * | ||
| 165 | * close_tag: '<|' token '|]' | ||
| 166 | * ; | ||
| 167 | * | ||
| 168 | * tag_guts: 'view' literal props | ||
| 169 | * | 'section' literal | ||
| 170 | * | 'output' literal | ||
| 171 | * ; | ||
| 172 | * | ||
| 173 | * literal: '"' [^"]* '"' | ||
| 174 | * ; | ||
| 175 | * | ||
| 176 | * props: props token '=' literal | ||
| 177 | * | | ||
| 178 | * ; | ||
| 179 | */ | ||
| 180 | impl Parser { | ||
| 181 | pub fn new() -> Self { | ||
| 182 | Self { | ||
| 183 | } | ||
| 184 | } | ||
| 185 | |||
| 186 | fn lex_error<T>(&self, error: &Symbol) -> CrimResult<T> { | ||
| 187 | if let SymbolType::Error{what} = error.symbol() { | ||
| 188 | Err(CrimError::parse( *error.start(), | ||
| 189 | format!("What: {:?}", *what) | ||
| 190 | )) | ||
| 191 | } else { | ||
| 192 | Err(CrimError::parse( Position::none(), "Not an error?".into())) | ||
| 193 | } | ||
| 194 | } | ||
| 195 | |||
| 196 | pub fn parse(&self, src: &str, source: usize ) -> CrimResult<Vec<View>> { | ||
| 197 | let ll = Lexer::new( src ); | ||
| 198 | let mut ctx = Context::new( ll, source ); | ||
| 199 | |||
| 200 | // Parse the root of the file, the input context | ||
| 201 | self.p_input( &mut ctx ) | ||
| 202 | } | ||
| 203 | |||
| 204 | fn p_input(&self, ctx: &mut Context ) -> CrimResult<Vec<View>> { | ||
| 205 | let mut tags = Vec::new(); | ||
| 206 | loop { | ||
| 207 | if ctx.cur().is_none() { | ||
| 208 | break; | ||
| 209 | } | ||
| 210 | match ctx.cur().unwrap().symbol() { | ||
| 211 | SymbolType::Text(_) => { /* Skip top level text */ } | ||
| 212 | SymbolType::StartFlat => { | ||
| 213 | let tb = self.parse_tag( ctx )?; | ||
| 214 | let tb = self.parse_tag_set( ctx, tb )?; | ||
| 215 | tags.push( tb ); | ||
| 216 | } | ||
| 217 | SymbolType::Error{..} => { | ||
| 218 | self.lex_error( &ctx.cur().unwrap() )?; | ||
| 219 | } | ||
| 220 | _ => { | ||
| 221 | return Err(CrimError::parse( | ||
| 222 | *ctx.cur().unwrap().start(), | ||
| 223 | format!("Unexpected symbol {:?} found looking for tags.", ctx.cur().unwrap().symbol() ) | ||
| 224 | )); | ||
| 225 | } | ||
| 226 | } | ||
| 227 | ctx.next(); | ||
| 228 | } | ||
| 229 | |||
| 230 | let mut views = Vec::new(); | ||
| 231 | for tag in tags { | ||
| 232 | let start_pos = tag.start_pos(); | ||
| 233 | let name = tag.name().clone(); | ||
| 234 | if tag.is_name(&SymbolType::View) && | ||
| 235 | let BuildResult::View(view) = tag.build( ctx )? { | ||
| 236 | views.push( view ); | ||
| 237 | } else { | ||
| 238 | return Err(CrimError::parse( | ||
| 239 | start_pos, | ||
| 240 | format!("Expected view at root, found {:?}", name ) | ||
| 241 | )); | ||
| 242 | } | ||
| 243 | } | ||
| 244 | Ok(views) | ||
| 245 | } | ||
| 246 | |||
| 247 | fn parse_tag<'a>(&self, ctx: &mut Context<'a>) -> CrimResult<TagBuilder<'a>> { | ||
| 248 | let start_sym = ctx.cur().unwrap(); | ||
| 249 | let mut tb = TagBuilder::new(&start_sym); | ||
| 250 | |||
| 251 | if ctx.next().is_some() { | ||
| 252 | match ctx.cur().unwrap().symbol() { | ||
| 253 | SymbolType::View | SymbolType::Show | SymbolType::Loop | | ||
| 254 | SymbolType::If | SymbolType::ElIf | SymbolType::Else | | ||
| 255 | SymbolType::Output => { | ||
| 256 | let name_sym = ctx.cur().unwrap(); | ||
| 257 | ctx.next(); | ||
| 258 | tb.set_name( name_sym ); | ||
| 259 | } | ||
| 260 | _ => { | ||
| 261 | return Err(CrimError::parse( | ||
| 262 | *ctx.cur().unwrap().start(), | ||
| 263 | "Unexpected symbol".to_string() | ||
| 264 | )); | ||
| 265 | } | ||
| 266 | } | ||
| 267 | } else { | ||
| 268 | return Err(CrimError::eos("tag type")); | ||
| 269 | } | ||
| 270 | |||
| 271 | if tb.can_have_params() || tb.can_have_expr() { | ||
| 272 | self.parse_tag_params( ctx, &mut tb )?; | ||
| 273 | } | ||
| 274 | if tb.can_have_props() { | ||
| 275 | self.parse_tag_props( ctx, &mut tb )?; | ||
| 276 | } | ||
| 277 | |||
| 278 | if let Some(end_sym) = ctx.cur() { | ||
| 279 | match end_sym.symbol() { | ||
| 280 | SymbolType::EndPoint | SymbolType::EndFlat => { | ||
| 281 | tb.set_end( &end_sym )?; | ||
| 282 | } | ||
| 283 | _ => { | ||
| 284 | return Err(CrimError::parse( | ||
| 285 | *end_sym.start(), | ||
| 286 | format!("Unexpected symbol {:?} looking for end of tag.", end_sym.symbol()) | ||
| 287 | )); | ||
| 288 | } | ||
| 289 | } | ||
| 290 | } | ||
| 291 | |||
| 292 | ctx.next(); | ||
| 293 | |||
| 294 | Ok(tb) | ||
| 295 | } | ||
| 296 | |||
| 297 | fn parse_tag_set<'a>(&self, ctx: &mut Context<'a>, mut base: TagBuilder<'a>) -> CrimResult<TagBuilder<'a>> { | ||
| 298 | if base.is_unary() { | ||
| 299 | return Ok(base); | ||
| 300 | } | ||
| 301 | |||
| 302 | loop { | ||
| 303 | if ctx.cur().is_none() { | ||
| 304 | return Err(CrimError::eos(format!("close tag for {:?}", base.name()).as_str())); | ||
| 305 | } | ||
| 306 | match ctx.cur().unwrap().symbol() { | ||
| 307 | SymbolType::Text(s) => { | ||
| 308 | base.add_child(Entry::Token(Token::Text(s.to_string()))); | ||
| 309 | ctx.next(); | ||
| 310 | } | ||
| 311 | SymbolType::StartFlat | SymbolType::StartPoint => { | ||
| 312 | let tb = self.parse_tag( ctx )?; | ||
| 313 | |||
| 314 | match tb.tag_type() { | ||
| 315 | TagType::MultinaryOpen => { | ||
| 316 | base.add_child(Entry::TagBuilder(self.parse_tag_set( ctx, tb )?)); | ||
| 317 | } | ||
| 318 | TagType::MultinaryMid => { | ||
| 319 | base.add_chain(Entry::TagBuilder(self.parse_tag_set( ctx, tb )?)); | ||
| 320 | return Ok(base); | ||
| 321 | } | ||
| 322 | TagType::MultinaryClose => { | ||
| 323 | if (tb.is_name(&SymbolType::If) && | ||
| 324 | (base.is_name(&SymbolType::ElIf) || | ||
| 325 | base.is_name(&SymbolType::Else))) || | ||
| 326 | tb.is_name(base.name().unwrap().symbol()) { | ||
| 327 | return Ok(base); | ||
| 328 | } else { | ||
| 329 | return Err(CrimError::parse( | ||
| 330 | *tb.name().unwrap().start(), | ||
| 331 | format!("Found {:?} looking for close of {:?}", tb.name().unwrap().symbol(), base.name().unwrap().symbol()) | ||
| 332 | )); | ||
| 333 | } | ||
| 334 | } | ||
| 335 | TagType::Unary => { | ||
| 336 | base.add_child(Entry::TagBuilder(tb)); | ||
| 337 | } | ||
| 338 | TagType::Unknown => { | ||
| 339 | return Err(CrimError::broken(base.start_pos(), "Found unknown tag when looking for something else.")); | ||
| 340 | } | ||
| 341 | } | ||
| 342 | } | ||
| 343 | SymbolType::Error{..} => { | ||
| 344 | return self.lex_error( &ctx.cur().unwrap() ); | ||
| 345 | } | ||
| 346 | _ => { | ||
| 347 | return Err(CrimError::parse( | ||
| 348 | *ctx.cur().unwrap().start(), | ||
| 349 | format!("Unexpected token: {:?}", ctx.cur().unwrap().symbol()), | ||
| 350 | )); | ||
| 351 | } | ||
| 352 | } | ||
| 353 | } | ||
| 354 | } | ||
| 355 | |||
| 356 | fn parse_tag_params(&self, ctx: &mut Context, tb: &mut TagBuilder ) -> CrimResult<()> { | ||
| 357 | loop { | ||
| 358 | if ctx.cur().is_none() { | ||
| 359 | return Err(CrimError::eos("tag parameters")); | ||
| 360 | } | ||
| 361 | match ctx.cur().unwrap().symbol() { | ||
| 362 | SymbolType::Literal(s) => { | ||
| 363 | tb.add_param( ParamValue::Literal(s.to_string()) ); | ||
| 364 | ctx.next(); | ||
| 365 | } | ||
| 366 | SymbolType::Token(_) | SymbolType::Sharp => { | ||
| 367 | if ctx.peek().is("tag data", |s| *s == SymbolType::Equals)? { | ||
| 368 | break; | ||
| 369 | } | ||
| 370 | tb.add_param( self.parse_identifier( ctx )? ); | ||
| 371 | } | ||
| 372 | _ => { | ||
| 373 | break; | ||
| 374 | } | ||
| 375 | } | ||
| 376 | } | ||
| 377 | Ok(()) | ||
| 378 | } | ||
| 379 | |||
| 380 | fn parse_identifier(&self, ctx: &mut Context ) -> CrimResult<ParamValue> { | ||
| 381 | let mut id = Identifier::new(); | ||
| 382 | |||
| 383 | if ctx.cur().is("identifier",|s| matches!( s, SymbolType::Sharp))? { | ||
| 384 | id.push( IdentifierValue::Root ); | ||
| 385 | ctx.next(); | ||
| 386 | } | ||
| 387 | loop { | ||
| 388 | if ctx.cur().is("identifier",|s| matches!( s, SymbolType::Token(_) ))? { | ||
| 389 | if let SymbolType::Token(s) = ctx.cur().unwrap().symbol() { | ||
| 390 | id.push( IdentifierValue::Name(s.to_string()) ); | ||
| 391 | } | ||
| 392 | } else { | ||
| 393 | return Err(CrimError::parse( ctx.cur().unwrap().start().clone(), format!("Expeceted identifier, found {:?}", ctx.cur().unwrap().symbol() ) ) ); | ||
| 394 | } | ||
| 395 | |||
| 396 | if !ctx.next().is("identifier seperator",|s| *s == SymbolType::Period )? { | ||
| 397 | break; | ||
| 398 | } | ||
| 399 | ctx.next(); | ||
| 400 | } | ||
| 401 | |||
| 402 | Ok(ParamValue::Identifier(id)) | ||
| 403 | } | ||
| 404 | |||
| 405 | fn parse_tag_props(&self, ctx: &mut Context, tb: &mut TagBuilder ) -> CrimResult<()> { | ||
| 406 | loop { | ||
| 407 | if ctx.cur().is_none() { | ||
| 408 | return Err(CrimError::eos("tag properties")); | ||
| 409 | } | ||
| 410 | if let SymbolType::Token(s) = ctx.cur().unwrap().symbol() { | ||
| 411 | if ctx.peek().is_some_and(|s|s.check_type(|t| matches!(t,SymbolType::Equals))) { | ||
| 412 | ctx.next(); | ||
| 413 | if let Some(sym) = ctx.next() && | ||
| 414 | let SymbolType::Literal(lv) = sym.symbol() { | ||
| 415 | tb.add_prop( s.to_string(), lv.to_string() ); | ||
| 416 | } else { | ||
| 417 | return Err(CrimError::parse(Position::none(),"Expected quoted literal string".to_string())); | ||
| 418 | } | ||
| 419 | } else { | ||
| 420 | break; | ||
| 421 | } | ||
| 422 | } else { | ||
| 423 | break; | ||
| 424 | } | ||
| 425 | ctx.next(); | ||
| 426 | } | ||
| 427 | Ok(()) | ||
| 428 | } | ||
| 429 | } | ||
| 430 | |||
| 431 | #[derive(PartialEq,Copy,Clone,Debug)] | ||
| 432 | enum TagType { | ||
| 433 | Unknown, | ||
| 434 | Unary, | ||
| 435 | MultinaryOpen, | ||
| 436 | MultinaryMid, | ||
| 437 | MultinaryClose, | ||
| 438 | } | ||
| 439 | |||
| 440 | #[derive(Debug)] | ||
| 441 | enum Entry<'a>{ | ||
| 442 | Token(Token), | ||
| 443 | TagBuilder(TagBuilder<'a>), | ||
| 444 | } | ||
| 445 | |||
| 446 | type EntryList<'a> = Vec<Entry<'a>>; | ||
| 447 | |||
| 448 | trait EntryListConverter { | ||
| 449 | fn has_outputs(&self) -> bool; | ||
| 450 | fn to_outputs(&mut self, ctx: &mut Context ) -> CrimResult<Vec<Output>>; | ||
| 451 | fn to_tokens(&mut self, ctx: &mut Context) -> CrimResult<Vec<Token>>; | ||
| 452 | } | ||
| 453 | |||
| 454 | impl<'a> EntryListConverter for EntryList<'a> { | ||
| 455 | fn has_outputs(&self) -> bool { | ||
| 456 | self.iter().any(|e| | ||
| 457 | if let Entry::TagBuilder(tb) = e | ||
| 458 | && tb.is_name(&SymbolType::Output) { | ||
| 459 | true | ||
| 460 | } else { | ||
| 461 | false | ||
| 462 | } | ||
| 463 | ) | ||
| 464 | } | ||
| 465 | |||
| 466 | fn to_outputs(&mut self, ctx: &mut Context) -> CrimResult<Vec<Output>> { | ||
| 467 | let mut outputs = Vec::new(); | ||
| 468 | |||
| 469 | if self.has_outputs() { | ||
| 470 | // We have an explicit output, we cant have anything else | ||
| 471 | for e in self.drain(..) { | ||
| 472 | let tb = if let Entry::TagBuilder(tb) = e { | ||
| 473 | tb | ||
| 474 | } else { | ||
| 475 | continue; | ||
| 476 | }; | ||
| 477 | if let BuildResult::Output(out) = tb.build(ctx)? { | ||
| 478 | outputs.push( out ); | ||
| 479 | } | ||
| 480 | } | ||
| 481 | } else { | ||
| 482 | // No outputs, so we create one implicit output | ||
| 483 | outputs.push(Output { | ||
| 484 | name: "content".into(), | ||
| 485 | code: self.to_tokens(ctx)? | ||
| 486 | }); | ||
| 487 | } | ||
| 488 | |||
| 489 | Ok(outputs) | ||
| 490 | } | ||
| 491 | |||
| 492 | fn to_tokens(&mut self, ctx: &mut Context) -> CrimResult<Vec<Token>> { | ||
| 493 | let mut tokens : Vec<Token> = Vec::new(); | ||
| 494 | for e in self.drain(..) { | ||
| 495 | match e { | ||
| 496 | Entry::Token(token) => { | ||
| 497 | tokens.push( token ); | ||
| 498 | } | ||
| 499 | Entry::TagBuilder(tb) => { | ||
| 500 | if let BuildResult::Token(token) = tb.build(ctx)? { | ||
| 501 | tokens.push( token ); | ||
| 502 | } else { | ||
| 503 | return Err(CrimError::broken( Position::none(), "Non-token result built.")); | ||
| 504 | } | ||
| 505 | } | ||
| 506 | } | ||
| 507 | } | ||
| 508 | Ok(tokens) | ||
| 509 | } | ||
| 510 | } | ||
| 511 | |||
| 512 | #[derive(Debug)] | ||
| 513 | struct TagBuilder<'a> { | ||
| 514 | name: Option<Symbol<'a>>, | ||
| 515 | params: Vec<ParamValue>, | ||
| 516 | props: Properties, | ||
| 517 | children: Vec<Entry<'a>>, | ||
| 518 | tag_type: TagType, | ||
| 519 | start: Symbol<'a>, | ||
| 520 | chain: Vec<Entry<'a>>, | ||
| 521 | } | ||
| 522 | |||
| 523 | #[derive(Debug)] | ||
| 524 | enum ParamValue { | ||
| 525 | Literal(String), | ||
| 526 | Identifier(Identifier), | ||
| 527 | } | ||
| 528 | |||
| 529 | enum BuildResult { | ||
| 530 | View(View), | ||
| 531 | Output(Output), | ||
| 532 | Token(Token), | ||
| 533 | } | ||
| 534 | |||
| 535 | impl<'a> TagBuilder<'a> { | ||
| 536 | pub fn new(start: &Symbol<'a>) -> TagBuilder<'a> { | ||
| 537 | TagBuilder { | ||
| 538 | name: None, | ||
| 539 | params: Vec::new(), | ||
| 540 | props: Properties::new(), | ||
| 541 | children: Vec::new(), | ||
| 542 | tag_type: TagType::Unknown, | ||
| 543 | start: start.clone(), | ||
| 544 | chain: Vec::new(), | ||
| 545 | } | ||
| 546 | } | ||
| 547 | |||
| 548 | pub fn set_end(&mut self, end: &Symbol) -> CrimResult<()> { | ||
| 549 | if *self.start.symbol() == SymbolType::StartFlat { | ||
| 550 | if *end.symbol() == SymbolType::EndFlat { | ||
| 551 | self.tag_type = TagType::Unary; | ||
| 552 | } else if *end.symbol() == SymbolType::EndPoint { | ||
| 553 | self.tag_type = TagType::MultinaryOpen; | ||
| 554 | } else { | ||
| 555 | return Err(CrimError::broken( *end.start(), "Invalid bracket token type.")); | ||
| 556 | } | ||
| 557 | } else if *self.start.symbol() == SymbolType::StartPoint { | ||
| 558 | if *end.symbol() == SymbolType::EndFlat { | ||
| 559 | self.tag_type = TagType::MultinaryClose; | ||
| 560 | } else if *end.symbol() == SymbolType::EndPoint { | ||
| 561 | self.tag_type = TagType::MultinaryMid; | ||
| 562 | } else { | ||
| 563 | return Err(CrimError::broken( *end.start(), "Invalid bracket token type.")); | ||
| 564 | } | ||
| 565 | } else { | ||
| 566 | return Err(CrimError::broken( *self.start.start(), "Invalid bracket token type.")); | ||
| 567 | } | ||
| 568 | Ok(()) | ||
| 569 | } | ||
| 570 | |||
| 571 | pub fn start_pos(&self) -> Position { | ||
| 572 | *self.start.start() | ||
| 573 | } | ||
| 574 | |||
| 575 | pub fn is_unary(&self) -> bool { | ||
| 576 | self.tag_type == TagType::Unary | ||
| 577 | } | ||
| 578 | /* | ||
| 579 | pub fn is_multinary_open(&self) -> bool { | ||
| 580 | self.tag_type == TagType::MultinaryOpen | ||
| 581 | } | ||
| 582 | */ | ||
| 583 | pub fn can_have_params(&self) -> bool { | ||
| 584 | match self.name.unwrap().symbol() { | ||
| 585 | SymbolType::View | SymbolType::Output | SymbolType::Loop => true, | ||
| 586 | SymbolType::Show | SymbolType::If | SymbolType::ElIf | | ||
| 587 | SymbolType::Else => false, | ||
| 588 | _ => false, | ||
| 589 | } | ||
| 590 | } | ||
| 591 | |||
| 592 | pub fn can_have_expr(&self) -> bool { | ||
| 593 | match self.name.unwrap().symbol() { | ||
| 594 | SymbolType::View | SymbolType::Output | SymbolType::Loop => false, | ||
| 595 | SymbolType::Show | SymbolType::If | SymbolType::ElIf | | ||
| 596 | SymbolType::Else => true, | ||
| 597 | _ => false, | ||
| 598 | } | ||
| 599 | } | ||
| 600 | |||
| 601 | pub fn can_have_props(&self) -> bool { | ||
| 602 | true | ||
| 603 | } | ||
| 604 | /* | ||
| 605 | pub fn can_have_children(&self) -> bool { | ||
| 606 | if self.tag_type == TagType::MultinaryOpen || | ||
| 607 | self.tag_type == TagType::MultinaryMid { | ||
| 608 | true | ||
| 609 | } else { | ||
| 610 | false | ||
| 611 | } | ||
| 612 | } | ||
| 613 | */ | ||
| 614 | pub fn is_name(&self, name: &SymbolType<'a>) -> bool { | ||
| 615 | if let Some(a) = self.name { | ||
| 616 | a.symbol() == name | ||
| 617 | } else { | ||
| 618 | false | ||
| 619 | } | ||
| 620 | } | ||
| 621 | |||
| 622 | pub fn name(&self) -> Option<Symbol<'a>> { | ||
| 623 | self.name | ||
| 624 | } | ||
| 625 | /* | ||
| 626 | pub fn params(&self) -> &Vec<ParamValue> { | ||
| 627 | &self.params | ||
| 628 | } | ||
| 629 | */ | ||
| 630 | pub fn set_name(&mut self, name: Symbol<'a>) { | ||
| 631 | self.name = Some(name); | ||
| 632 | } | ||
| 633 | |||
| 634 | pub fn add_param(&mut self, param: ParamValue) { | ||
| 635 | self.params.push( param ); | ||
| 636 | } | ||
| 637 | |||
| 638 | pub fn add_prop(&mut self, key: String, value: String) { | ||
| 639 | self.props.insert( key, value ); | ||
| 640 | } | ||
| 641 | |||
| 642 | pub fn add_child(&mut self, tb: Entry<'a>) { | ||
| 643 | self.children.push( tb ); | ||
| 644 | } | ||
| 645 | /* | ||
| 646 | pub fn append_children(&mut self, children: &mut Vec::<Entry<'a>>) { | ||
| 647 | self.children.append( children ); | ||
| 648 | } | ||
| 649 | */ | ||
| 650 | pub fn add_chain(&mut self, tb: Entry<'a>) { | ||
| 651 | self.chain.push( tb ); | ||
| 652 | } | ||
| 653 | /* | ||
| 654 | pub fn set_type(&mut self, tag_type: TagType) { | ||
| 655 | self.tag_type = tag_type; | ||
| 656 | } | ||
| 657 | */ | ||
| 658 | pub fn tag_type(&self) -> TagType { | ||
| 659 | self.tag_type | ||
| 660 | } | ||
| 661 | |||
| 662 | pub fn build(mut self, ctx: &mut Context ) -> CrimResult<BuildResult> { | ||
| 663 | if let Some(sym) = self.name { | ||
| 664 | match sym.symbol() { | ||
| 665 | SymbolType::View => { | ||
| 666 | let name = if let ParamValue::Literal(s) = self.params.swap_remove(0) { | ||
| 667 | s.to_string() | ||
| 668 | } else { | ||
| 669 | return Err(CrimError::parse(Position::none(), "Expected string literal for view name.".to_string())); | ||
| 670 | }; | ||
| 671 | Ok(BuildResult::View(View { | ||
| 672 | name: name, | ||
| 673 | source: ctx.source(), | ||
| 674 | outputs: self.children.to_outputs(ctx)?, | ||
| 675 | //theme: Option<String> | ||
| 676 | layout: self.props.get("layout").cloned(), | ||
| 677 | })) | ||
| 678 | } | ||
| 679 | SymbolType::Output => { | ||
| 680 | let name = if let ParamValue::Literal(s) = self.params.swap_remove(0) { | ||
| 681 | s.to_string() | ||
| 682 | } else { | ||
| 683 | return Err(CrimError::parse(Position::none(), "Expected string literal for output name.".into())); | ||
| 684 | }; | ||
| 685 | Ok(BuildResult::Output(Output { | ||
| 686 | name: name, | ||
| 687 | code: self.children.to_tokens(ctx)?, | ||
| 688 | })) | ||
| 689 | } | ||
| 690 | SymbolType::Loop => { | ||
| 691 | let id = if let ParamValue::Identifier(id) | ||
| 692 | = self.params.swap_remove(0) { | ||
| 693 | id | ||
| 694 | } else { | ||
| 695 | return Err(CrimError::parse( | ||
| 696 | Position::none(), | ||
| 697 | "Identifier for loop variable name.".into()) | ||
| 698 | ); | ||
| 699 | }; | ||
| 700 | Ok(BuildResult::Token( | ||
| 701 | Token::Loop(id, self.children.to_tokens(ctx)?) | ||
| 702 | )) | ||
| 703 | } | ||
| 704 | SymbolType::Show => { | ||
| 705 | let id = if let ParamValue::Identifier(id) | ||
| 706 | = self.params.swap_remove(0) { | ||
| 707 | id | ||
| 708 | } else { | ||
| 709 | return Err(CrimError::parse( | ||
| 710 | Position::none(), | ||
| 711 | "Identifier for show variable name.".into()) | ||
| 712 | ); | ||
| 713 | }; | ||
| 714 | Ok(BuildResult::Token(Token::Show(id, self.props))) | ||
| 715 | } | ||
| 716 | SymbolType::If => { | ||
| 717 | let id = if let ParamValue::Identifier(id) | ||
| 718 | = self.params.swap_remove(0) { | ||
| 719 | id | ||
| 720 | } else { | ||
| 721 | return Err(CrimError::parse( | ||
| 722 | Position::none(), | ||
| 723 | "Identifier for if variable name.".into()) | ||
| 724 | ); | ||
| 725 | }; | ||
| 726 | |||
| 727 | let is_else = if self.chain.len() == 1 && | ||
| 728 | let Entry::TagBuilder(tb) = &self.chain[0] && | ||
| 729 | tb.is_name(&SymbolType::Else) && | ||
| 730 | tb.params.len() == 0 { | ||
| 731 | true | ||
| 732 | } else { | ||
| 733 | false | ||
| 734 | }; | ||
| 735 | |||
| 736 | if is_else && | ||
| 737 | let Entry::TagBuilder(mut tb) = self.chain.swap_remove(0) { | ||
| 738 | self.chain.clear(); | ||
| 739 | self.chain.append(&mut tb.children); | ||
| 740 | } | ||
| 741 | Ok(BuildResult::Token(Token::If(id, self.children.to_tokens(ctx)?, self.chain.to_tokens(ctx)?))) | ||
| 742 | } | ||
| 743 | SymbolType::ElIf => { | ||
| 744 | let id = if let ParamValue::Identifier(id) = self.params.swap_remove(0) { | ||
| 745 | id | ||
| 746 | } else { | ||
| 747 | return Err(CrimError::parse( | ||
| 748 | Position::none(), | ||
| 749 | "Identifier for elif variable name.".into()) | ||
| 750 | ); | ||
| 751 | }; | ||
| 752 | |||
| 753 | // this is copied from if, since they're the same this | ||
| 754 | // should be encapsulated and moved into a function... | ||
| 755 | // ...but I can't do that right now. | ||
| 756 | let is_else = if self.chain.len() == 1 && | ||
| 757 | let Entry::TagBuilder(tb) = &self.chain[0] && | ||
| 758 | tb.is_name(&SymbolType::Else) && | ||
| 759 | tb.params.len() == 0 { | ||
| 760 | true | ||
| 761 | } else { | ||
| 762 | false | ||
| 763 | }; | ||
| 764 | |||
| 765 | if is_else && | ||
| 766 | let Entry::TagBuilder(mut tb) = self.chain.swap_remove(0) { | ||
| 767 | self.chain.clear(); | ||
| 768 | self.chain.append(&mut tb.children); | ||
| 769 | } | ||
| 770 | |||
| 771 | |||
| 772 | Ok(BuildResult::Token(Token::If(id, self.children.to_tokens(ctx)?, self.chain.to_tokens(ctx)?))) | ||
| 773 | } | ||
| 774 | SymbolType::Else => { | ||
| 775 | Ok(BuildResult::Token(Token::If(Identifier::new(), self.children.to_tokens(ctx)?, self.chain.to_tokens(ctx)?))) | ||
| 776 | } | ||
| 777 | _ => { | ||
| 778 | Err(CrimError::parse( self.start_pos(), "Bad tag type".into())) | ||
| 779 | } | ||
| 780 | } | ||
| 781 | } else { | ||
| 782 | Err(CrimError::parse(self.start_pos(), "Bad tag type".into())) | ||
| 783 | } | ||
| 784 | } | ||
| 785 | } | ||
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 @@ | |||
| 1 | use std::fmt; | ||
| 2 | use std::cmp::Ordering; | ||
| 3 | |||
| 4 | #[derive(Copy,Clone)] | ||
| 5 | pub struct Position { | ||
| 6 | pub line: u32, | ||
| 7 | pub column: u32, | ||
| 8 | } | ||
| 9 | |||
| 10 | impl Position { | ||
| 11 | pub fn new( line: u32, column: u32 ) -> Self { | ||
| 12 | Self { | ||
| 13 | line, column, | ||
| 14 | } | ||
| 15 | } | ||
| 16 | |||
| 17 | pub fn none() -> Self { | ||
| 18 | Self { | ||
| 19 | line: 0, | ||
| 20 | column: 0, | ||
| 21 | } | ||
| 22 | } | ||
| 23 | |||
| 24 | pub fn is_none(&self) -> bool { | ||
| 25 | self.line == 0 || self.column == 0 | ||
| 26 | } | ||
| 27 | } | ||
| 28 | |||
| 29 | impl fmt::Debug for Position { | ||
| 30 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| 31 | if self.is_none() { | ||
| 32 | write!(f, "none") | ||
| 33 | } else { | ||
| 34 | write!(f, "{}:{}", self.line, self.column ) | ||
| 35 | } | ||
| 36 | } | ||
| 37 | } | ||
| 38 | |||
| 39 | impl PartialOrd for Position { | ||
| 40 | fn partial_cmp(&self, other: &Self) -> Option<Ordering> { | ||
| 41 | let ord = self.line.partial_cmp( &other.line ); | ||
| 42 | if Some(Ordering::Equal) == ord { | ||
| 43 | self.column.partial_cmp( &other.column ) | ||
| 44 | } else { | ||
| 45 | ord | ||
| 46 | } | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | impl Ord for Position { | ||
| 51 | fn cmp(&self, other: &Self) -> Ordering { | ||
| 52 | let ord = self.line.cmp( &other.line ); | ||
| 53 | if Ordering::Equal == ord { | ||
| 54 | self.column.cmp( &other.column ) | ||
| 55 | } else { | ||
| 56 | ord | ||
| 57 | } | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | impl PartialEq for Position { | ||
| 62 | fn eq(&self, other: &Self) -> bool { | ||
| 63 | self.line == other.line && self.column == other.column | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | impl Eq for Position {} | ||
| 68 | |||
| 69 | #[derive(Copy,Clone)] | ||
| 70 | pub struct Range { | ||
| 71 | start: Position, | ||
| 72 | end: Position, | ||
| 73 | } | ||
| 74 | |||
| 75 | impl Range { | ||
| 76 | pub fn new( start: Position, end: Position ) -> Self { | ||
| 77 | Self { | ||
| 78 | start, end, | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | pub fn include(&mut self, pos: &Position ) { | ||
| 83 | if *pos < self.start { | ||
| 84 | self.start = pos.clone(); | ||
| 85 | } | ||
| 86 | if *pos > self.end { | ||
| 87 | self.end = pos.clone(); | ||
| 88 | } | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 92 | impl fmt::Debug for Range { | ||
| 93 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| 94 | write!(f, "{:?}-{:?}", self.start, self.end ) | ||
| 95 | } | ||
| 96 | } | ||
