added selector expressions
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Tue, 1 Nov 2022 03:13:24 +0000 (22:13 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Tue, 1 Nov 2022 03:13:24 +0000 (22:13 -0500)
src/app.onyx
src/html-templates/src/otmp.onyx
src/html-templates/src/parser.onyx
src/html-templates/src/render.onyx
src/html-templates/src/types.onyx
www/static/css/style.css
www/templates/base.html
www/templates/index.html

index 4c52d9799ee661b26f5c185297f5ef45b4cd99bb..d923656299f9ff1efd237e4ad80056963ed2d7ef 100644 (file)
@@ -15,6 +15,14 @@ reg: otmp.TemplateRegistry;
         y = "123123",
         numbers = .[1, 2, 3, 4],
         names   = .["joe", "jim", "john"],
+
+        matrix = .[
+            .[ 1, 2, 3, 4 ],
+            .[ 5, 6, 7, 8 ],
+            .[ 9, 10, 11, 12 ]
+        ],
+
+        test = ^reg,
     });
     res->status(200);
 }
index e40d4f3d9f1415118e984f1fb6a68feff0466f07..9b7f7579cc104adb249484cb0d296f6cc4f0ba83 100644 (file)
@@ -62,7 +62,9 @@ delete :: (t: ^TemplateRegistry) {
         temp.filepath  = filename |> string.alloc_copy(as_allocator(^self.arena));
         temp.name      = permanent_name;
 
-        parse_template(temp, ^contents);
+        if err := parse_template(temp, ^contents); err != .None {
+            return .Template_Parse_Error;
+        }
 
         self.templates[permanent_name] = temp;
         return .None;
index 7ed451e1362844ddcf92cb79ff3d9c118fd18372..c48935fce3664b5963940c8a6e6cd2de57965174 100644 (file)
@@ -51,6 +51,8 @@ TemplateToken :: struct {
         String_Literal;
 
         Variable;
+        Symbol;
+        Dot;
     }
 
     type: Type;
@@ -78,7 +80,36 @@ TemplateToken :: struct {
             return tkn;
         }
 
-        return self->eat_next_token();
+        tkn := self->eat_next_token();
+        return tkn;
+    }
+
+    eat_characters :: (self: ^TemplateLexer, chars := 1) -> str {
+        for chars {
+            if self.s.data[it] == #char "\n" {
+                self.line += 1;
+                self.col   = 0;
+            }
+
+            self.col += 1;
+        }
+
+        defer string.advance(self.s, chars);
+        return self.s.data[0 .. chars];
+    }
+
+    eat_whitespace :: (self: ^TemplateLexer) {
+        while !string.empty(*self.s) {
+            switch self.s.data[0] {
+                case #char "\n", #char "\t", #char "\r", #char " " {
+                    self->eat_characters(1);
+                }
+
+                case #default {
+                    break break;
+                }
+            }
+        }
     }
 
     eat_next_token :: (self: ^TemplateLexer) -> TemplateToken {
@@ -86,7 +117,7 @@ TemplateToken :: struct {
         tkn.line = self.line;
         tkn.col  = self.col;
         
-        string.strip_leading_whitespace(self.s);
+        self->eat_whitespace();
 
         if string.empty(*self.s) {
             self.hit_eof = true;
@@ -130,7 +161,7 @@ TemplateToken :: struct {
         }
 
         if self.inside_command || self.inside_expression {
-            string.strip_leading_whitespace(self.s);
+            self->eat_whitespace();
 
             token_consume("block",      .Keyword_Block);            
             token_consume("endblock",   .Keyword_EndBlock);            
@@ -138,32 +169,51 @@ TemplateToken :: struct {
             token_consume("endforeach", .Keyword_EndForeach);            
             token_consume("in",         .Keyword_In);
             token_consume("extends",    .Keyword_Extends);
+            token_consume(".",          .Dot);
 
             if self.s.data[0] == #char "\"" {
                 // :TODO add escaped strings
-                string.advance(self.s, 1);
+                self->eat_characters(1);
 
-                tkn.text, *self.s = string.bisect(*self.s, #char "\"");
+                index := string.index_of(*self.s, #char "\"");
+                tkn.text = self->eat_characters(index);
+                self->eat_characters(1);
                 
                 yield_token(.String_Literal);
             }
 
             if self.s.data[0] == #char "$" {
-                string.advance(self.s, 1);
+                self->eat_characters(1);
+
+                chars := 0;
+                while chars < self.s.length && self.s.data[chars]->is_alphanum() {
+                     chars += 1;
+                }
 
-                tkn.text = string.read_alphanum(self.s);
+                tkn.text = self->eat_characters(chars);
 
                 yield_token(.Variable);
             }
 
+            if self.s.data[0]->is_alphanum() {
+                chars := 0;
+                while chars < self.s.length &&
+                    (self.s.data[chars]->is_alphanum() || self.s.data[chars] == #char "_") {
+                     chars += 1;
+                }
+
+                tkn.text = self->eat_characters(chars);
+
+                yield_token(.Symbol);
+            }
+
         } else {
             length := 1;
             while self.s.data[length] != #char "{" && length < self.s.length {
                 length += 1;
             }
 
-            tkn.text = self.s.data[0 .. length];
-            string.advance(self.s, length);
+            tkn.text = self->eat_characters(length);
             yield_token(.Text);
         }
 
@@ -174,7 +224,7 @@ TemplateToken :: struct {
 
         token_match :: macro (t: str, body: Code) {
             if string.starts_with(*self.s, t) {
-                string.advance(self.s, t.length);
+                tkn.text = self->eat_characters(t.length);
 
                 #unquote body;
             }
@@ -182,8 +232,7 @@ TemplateToken :: struct {
         
         token_consume :: macro (t: str, kind: TemplateToken.Type) {
             if string.starts_with(*self.s, t) {
-                tkn.text = self.s.data[0 .. t.length];
-                string.advance(self.s, t.length);
+                tkn.text = self->eat_characters(t.length);
                 yield_token(kind);
             }
         }
@@ -332,6 +381,9 @@ parse_statement :: (use p: ^TemplateParser) -> ParseError {
 
 #local
 parse_expression :: (use p: ^TemplateParser) -> (^TExpr, ParseError) {
+    retval: ^TExpr = null;
+    err := ParseError.None;
+
     switch tkn := p.l->consume(); tkn.type {
         case .Keyword_Block {
             name, err := parse_string(p);
@@ -340,7 +392,7 @@ parse_expression :: (use p: ^TemplateParser) -> (^TExpr, ParseError) {
             block_expr := make_expr(t, TExprBlock);
             block_expr.block_name = name;
 
-            return block_expr, .None;
+            retval = block_expr;
         }
 
         case .Variable {
@@ -349,11 +401,40 @@ parse_expression :: (use p: ^TemplateParser) -> (^TExpr, ParseError) {
             var_expr := make_expr(t, TExprVar);
             var_expr.var_name = name;
 
-            return var_expr, .None;
+            retval = var_expr;
+        }
+    }
+
+    if retval == null {
+        err = .Unexpected_Token;
+    }
+
+    while true do switch tkn := p.l->peek(); tkn.type {
+        case .Dot {
+            p.l->consume();
+
+            sym_tkn: TemplateToken;
+
+            // this is gross...
+            if err := do {
+                expect_token(p, .Symbol, #(sym_tkn));
+                return .None;
+            }; err != .None {
+                err = .Unexpected_Token;
+                break break;
+            }
+
+            select_expr := make_expr(t, TExprSelector);
+            select_expr.var = retval;
+            select_expr.field = sym_tkn.text |> string.alloc_copy(as_allocator(^t.node_storage));
+
+            retval = select_expr;
         }
+
+        case #default do break break;
     }
 
-    return null, .Unexpected_Token;
+    return retval, err;
 }
 
 #local
@@ -374,7 +455,7 @@ expect_token :: #match #local {}
 #overload
 expect_token :: macro (p: ^TemplateParser, type: TemplateToken.Type, out: Code) {
     if (p.l->peek()).type != type {
-        return .Expected_Token;
+        return ParseError.Expected_Token;
     }
 
     (#unquote out) = p.l->consume();
@@ -383,7 +464,7 @@ expect_token :: macro (p: ^TemplateParser, type: TemplateToken.Type, out: Code)
 #overload
 expect_token :: macro (p: ^TemplateParser, type: TemplateToken.Type) {
     if (p.l->peek()).type != type {
-        return .Expected_Token;
+        return ParseError.Expected_Token;
     }
 
     p.l->consume();
index cc3b985dfb4fb875d2066eaaece7448b6039bcef..a24a0952c295a5167e05c49c2c64122b49ffff4c 100644 (file)
@@ -1,6 +1,7 @@
 package otmp
 
 use core {io, tprintf}
+use core.misc {any_iter, any_dereference, any_selector}
 
 #package
 TemplateRenderer :: struct {
@@ -54,6 +55,22 @@ render_instructions :: (use r: ^TemplateRenderer, instrs: [..] ^TNode) -> Error
             }
 
             case TNodeForeach {
+                // :Temporary :TemplateVariables
+                for_node := cast(^TNodeForeach) it;
+                var := cast(^TExprVar) for_node.list;
+                if !(scope->has(var.var_name)) {
+                    continue;
+                }
+
+                for any_iter(scope->get(var.var_name)) {
+                    scope->put(for_node.var_name, it);
+
+                    if err := render_instructions(r, for_node.body); err != .None {
+                        return err;
+                    }
+                }
+
+                scope->delete(for_node.var_name);
             }
 
 
@@ -69,14 +86,11 @@ render_instructions :: (use r: ^TemplateRenderer, instrs: [..] ^TNode) -> Error
                 }
             }
 
-            case TExprVar {
-                // :Temporary :TemplateVariables
-                var := cast(^TExprVar) it;
-                if !(scope->has(var.var_name)) {
-                    continue;
-                }
+            case TExprVar, TExprSelector {
+                var := resolve_expr_to_any(r, cast(^TExpr) it);
+                if !var.data do continue;
 
-                io.write_format_va(w, "{}", .[scope->get(var.var_name)]);
+                io.write_format_va(w, "{}", .[var]);
             }
 
             case #default {
@@ -88,3 +102,24 @@ render_instructions :: (use r: ^TemplateRenderer, instrs: [..] ^TNode) -> Error
 
     return .None;
 }
+
+#local
+resolve_expr_to_any :: (use r: ^TemplateRenderer, expr: ^TExpr) -> any {
+    switch expr.type {
+        case TExprVar {
+            var := cast(^TExprVar) expr;
+            // :ErrorHandling if variable does not exist
+            return scope->get(var.var_name);
+        }
+
+        case TExprSelector {
+            selector := cast(^TExprSelector) expr;
+
+            sub_any := resolve_expr_to_any(r, selector.var);
+            sub_any  = any_dereference(sub_any);
+            return any_selector(sub_any, selector.field);
+        }
+    }
+
+    return .{null, void};
+}
index 8e1388fd8aacef9b8567c6eef15dfab30ff1b886..90286bb6ea2e2c8dec3a896fe9924635a77bff23 100644 (file)
@@ -79,6 +79,8 @@ Error :: enum {
     Template_Not_Found;
     Duplicate_Template;
 
+    Template_Parse_Error;
+
     Render_Error;
 }
 
index 8000e7224a56aa035189b32b873781f08ce16e7f..1cefa94e9f309f3e9c99fce1e064d94f572619df 100644 (file)
@@ -2,6 +2,9 @@
     padding: 0;
     margin:  0;
     box-sizing: border-box;
+
+    background-color: #111;
+    color: #fff;
 }
 
 
index a1ae78180b9e2e8bde38471dcd074a3adce0fdcb..41f8552a08aaa6ad1262899d11aa77261cc82a89 100644 (file)
@@ -11,4 +11,4 @@
     <body>
         {% block "content" %}
     </body>
-</html>
\ No newline at end of file
+</html>
index 1ef7886599833a13fe46ac6343d8669c9b453cf9..484b9c545b59d5222ccf2fe92c670beee1d8bd01 100644 (file)
         <li>{% $name %}</li>
     {{endforeach}}
     </ul>
+
+    <ol>
+    {{foreach $num in $numbers}}
+        <li>{% $num %}</li>
+    {{endforeach}}
+    </ol>
+
+    <table>
+        <tbody>
+            {{foreach $row in $matrix}}
+            <tr>
+                {{foreach $col in $row}}
+                <td>{% $col %}</td>
+                {{endforeach}}
+            </tr>
+            {{endforeach}}
+        </tbody>
+    </table>
+
+    <p>Name: {% $test.templates %} </p>
+    <p>Age:  {% $test.arena %} </p>
+
 {{endblock}}
 
 {{extends "base"}}