summaryrefslogtreecommitdiff
path: root/crimtag/src
diff options
context:
space:
mode:
Diffstat (limited to 'crimtag/src')
-rw-r--r--crimtag/src/context.rs289
-rw-r--r--crimtag/src/error.rs59
-rw-r--r--crimtag/src/lexer.rs368
-rw-r--r--crimtag/src/lib.rs544
-rw-r--r--crimtag/src/parser.rs785
-rw-r--r--crimtag/src/position.rs96
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 @@
1use std::collections::HashMap;
2use std::fmt;
3
4use crate::error::CrimResult;
5
6use crate::{Identifier,IdentifierValue};
7
8pub type Context = HashMap<String,Value>;
9
10pub trait MappedStructure<'a> {
11 fn get_value(&'a self, id: &str) -> Option<MappedValue<'a>>;
12}
13
14impl<'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
20pub 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
25impl<'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
43impl<'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
61impl<'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
79pub type MappedHash<'a> = HashMap<String, MappedValue<'a>>;
80
81impl<'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)]
88pub 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
107impl<'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
130pub struct StatefulContext<'a> {
131 root: &'a dyn MappedStructure<'a>,
132 local: &'a dyn MappedStructure<'a>,
133}
134
135impl<'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
184impl<'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)]
199pub 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
209impl From<&str> for Value {
210 fn from(val: &str ) -> Value {
211 Value::String(val.into())
212 }
213}
214
215impl From<String> for Value {
216 fn from(val: String) -> Value {
217 Value::String(val)
218 }
219}
220
221impl From<i64> for Value {
222 fn from(val: i64) -> Value {
223 Value::Int(val)
224 }
225}
226
227impl From<f64> for Value {
228 fn from(val: f64) -> Value {
229 Value::Float(val)
230 }
231}
232
233impl From<bool> for Value {
234 fn from(val: bool) -> Value {
235 Value::Bool(val)
236 }
237}
238
239impl From<Vec<Value>> for Value {
240 fn from(val: Vec<Value>) -> Value {
241 Value::List(val)
242 }
243}
244
245impl From<&[Value]> for Value {
246 fn from(val: &[Value]) -> Value {
247 Value::List(Vec::from(val))
248 }
249}
250
251impl From<Context> for Value {
252 fn from(val: Context) -> Value {
253 Value::Dictionary(val)
254 }
255}
256
257impl<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/*
264impl 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
2use core::error::Error;
3use std::fmt;
4
5use crate::position::*;
6
7#[derive(Debug)]
8pub struct CrimError {
9 position: Position,
10 what: String,
11}
12
13impl 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
51impl fmt::Display for CrimError {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 write!(f, "Error parsing input: {}", "yeah")
54 }
55}
56
57impl Error for CrimError { }
58
59pub 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 @@
1use std::iter::Iterator;
2//use core::error::Error;
3use std::str::CharIndices;
4use std::fmt;
5
6use crate::Position;
7
8#[derive(Debug,Copy,Clone,PartialEq,Eq)]
9pub enum ErrorType {
10 UnexpectedChar(char),
11}
12
13#[derive(Debug,Copy,Clone,PartialEq,Eq)]
14pub 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)]
38pub struct Symbol<'a> {
39 symbol: SymbolType<'a>,
40 start: Position,
41 end: Position,
42}
43
44impl<'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
50impl<'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)]
77enum Mode {
78 Text,
79 InTag,
80}
81
82pub 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
91fn 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
108impl<'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
361impl<'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 @@
1use std::path::{PathBuf,Path};
2use std::collections::HashMap;
3use core::error::Error;
4use std::time::SystemTime;
5
6mod lexer;
7mod parser;
8mod position;
9mod error;
10pub mod context;
11
12pub use error::{CrimError,CrimResult};
13pub use position::*;
14pub use context::{Context,Value,MappedStructure,StatefulContext,MappedValue,MappedList,MappedHash};
15
16#[derive(Debug)]
17pub struct Crimtag {
18 views: HashMap<String, View>,
19 sources: Vec<ViewSource>,
20}
21
22#[derive(PartialEq,Eq,Debug,Clone)]
23pub enum ViewSource {
24 File {
25 path: PathBuf,
26 loaded: SystemTime,
27 },
28 Static,
29 External {
30 key: String,
31 },
32}
33
34#[derive(Debug)]
35struct View {
36 name: String,
37 source: usize,
38 outputs: Vec<Output>,
39// theme: Option<String>
40 layout: Option<String>,
41}
42
43impl 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)]
57struct Output {
58 name: String,
59 code: Vec<Token>,
60}
61
62type Properties = HashMap<String, String>;
63
64#[derive(Debug)]
65enum 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)]
73pub enum IdentifierValue {
74 Root,
75 Name(String),
76 Index(usize),
77}
78
79pub type Identifier = Vec<IdentifierValue>;
80
81impl 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
196fn 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)]
300mod 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|]
324That 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
328Now 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 @@
1use crate::lexer::*;
2use crate::*;
3
4struct Context<'a> {
5 cur: [Option<Symbol<'a>>;2],
6 icur: usize,
7 ll: Lexer<'a>,
8 source: usize,
9}
10
11impl<'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
53trait 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
59impl<'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
90pub 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 */
180impl 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)]
432enum TagType {
433 Unknown,
434 Unary,
435 MultinaryOpen,
436 MultinaryMid,
437 MultinaryClose,
438}
439
440#[derive(Debug)]
441enum Entry<'a>{
442 Token(Token),
443 TagBuilder(TagBuilder<'a>),
444}
445
446type EntryList<'a> = Vec<Entry<'a>>;
447
448trait 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
454impl<'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)]
513struct 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)]
524enum ParamValue {
525 Literal(String),
526 Identifier(Identifier),
527}
528
529enum BuildResult {
530 View(View),
531 Output(Output),
532 Token(Token),
533}
534
535impl<'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 @@
1use std::fmt;
2use std::cmp::Ordering;
3
4#[derive(Copy,Clone)]
5pub struct Position {
6 pub line: u32,
7 pub column: u32,
8}
9
10impl 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
29impl 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
39impl 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
50impl 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
61impl PartialEq for Position {
62 fn eq(&self, other: &Self) -> bool {
63 self.line == other.line && self.column == other.column
64 }
65}
66
67impl Eq for Position {}
68
69#[derive(Copy,Clone)]
70pub struct Range {
71 start: Position,
72 end: Position,
73}
74
75impl 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
92impl fmt::Debug for Range {
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 write!(f, "{:?}-{:?}", self.start, self.end )
95 }
96}