From de39a41da62c4b3820b4805ddb7c4970c36bc257 Mon Sep 17 00:00:00 2001
From: "Rafael G. Martins" <rafael@rafaelmartins.eng.br>
Date: Sat, 18 Apr 2015 17:17:37 -0300
Subject: added loader, error handling and cli. tests needed

---
 src/error.c           | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++
 src/error.h           |  34 +++++++++++++++
 src/loader.c          | 104 ++++++++++++++++++++++++++++++++++++++++++++
 src/loader.h          |  22 ++++++++++
 src/main.c            |  98 ++++++++++++++++++++++++++++++++++++++++--
 src/output.c          |  39 -----------------
 src/output.h          |  17 --------
 src/source-parser.c   |  37 ++++++++++------
 src/source-parser.h   |   6 ++-
 src/template-parser.c |  87 +++++++++++++++++++++++++++++--------
 src/template-parser.h |   4 +-
 11 files changed, 471 insertions(+), 93 deletions(-)
 create mode 100644 src/error.c
 create mode 100644 src/error.h
 create mode 100644 src/loader.c
 create mode 100644 src/loader.h
 delete mode 100644 src/output.c
 delete mode 100644 src/output.h

(limited to 'src')

diff --git a/src/error.c b/src/error.c
new file mode 100644
index 0000000..8475d66
--- /dev/null
+++ b/src/error.c
@@ -0,0 +1,116 @@
+/*
+ * blogc: A blog compiler.
+ * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file COPYING.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include "utils/utils.h"
+#include "error.h"
+
+
+blogc_error_t*
+blogc_error_new(blogc_error_type_t type, const char *msg)
+{
+    blogc_error_t *err = malloc(sizeof(blogc_error_t));
+    err->type = type;
+    err->msg = b_strdup(msg);
+    return err;
+}
+
+
+blogc_error_t*
+blogc_error_new_printf(blogc_error_type_t type, const char *format, ...)
+{
+    va_list ap;
+    va_start(ap, format);
+    char *tmp = b_strdup_vprintf(format, ap);
+    va_end(ap);
+    blogc_error_t *rv = blogc_error_new(type, tmp);
+    free(tmp);
+    return rv;
+}
+
+
+blogc_error_t*
+blogc_error_parser(blogc_error_type_t type, const char *src, size_t src_len,
+    size_t current, const char *format, ...)
+{
+    va_list ap;
+    va_start(ap, format);
+    char *msg = b_strdup_vprintf(format, ap);
+    va_end(ap);
+
+    b_string_t *str = b_string_new();
+    while (current < src_len) {
+        char c = src[current];
+
+        if (c == '\r' || c == '\n')
+            break;
+
+        b_string_append_c(str, c);
+
+        current++;
+    }
+    char *line = b_string_free(str, false);
+
+    blogc_error_t *rv = NULL;
+
+    if (strlen(line) == 0)  // "near to" message isn't useful if line is empty
+        rv = blogc_error_new(type, msg);
+    else
+        rv = blogc_error_new_printf(type,
+            "%s\nError occurred near to \"%s\".", msg, line);
+
+    free(msg);
+    free(line);
+
+    return rv;
+}
+
+
+void
+blogc_error_print(blogc_error_t *err)
+{
+    if (err == NULL)
+        return;
+
+    char *tmp = NULL;
+
+    switch(err->type) {
+        case BLOGC_ERROR_SOURCE_PARSER:
+            tmp = b_strdup("Source parser error");
+            break;
+        case BLOGC_ERROR_TEMPLATE_PARSER:
+            tmp = b_strdup("Template parser error");
+            break;
+        case BLOGC_ERROR_LOADER:
+            tmp = b_strdup("Loader error");
+            break;
+        default:
+            tmp = b_strdup("Unknown error");
+    }
+
+    fprintf(stderr, "%s: %s\n", tmp, err->msg);
+
+    free(tmp);
+}
+
+
+void
+blogc_error_free(blogc_error_t *err)
+{
+    if (err == NULL)
+        return;
+    free(err->msg);
+    free(err);
+}
diff --git a/src/error.h b/src/error.h
new file mode 100644
index 0000000..98aeeb9
--- /dev/null
+++ b/src/error.h
@@ -0,0 +1,34 @@
+/*
+ * blogc: A blog compiler.
+ * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file COPYING.
+ */
+
+#ifndef _ERROR_H
+#define _ERROR_H
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include "utils/utils.h"
+
+typedef enum {
+    BLOGC_ERROR_SOURCE_PARSER = 1,
+    BLOGC_ERROR_TEMPLATE_PARSER,
+    BLOGC_ERROR_LOADER,
+} blogc_error_type_t;
+
+typedef struct {
+    char *msg;
+    blogc_error_type_t type;
+} blogc_error_t;
+
+blogc_error_t* blogc_error_new(blogc_error_type_t type, const char *msg);
+blogc_error_t* blogc_error_new_printf(blogc_error_type_t type, const char *format, ...);
+blogc_error_t* blogc_error_parser(blogc_error_type_t type, const char *src,
+    size_t src_len, size_t current, const char *format, ...);
+void blogc_error_print(blogc_error_t *err);
+void blogc_error_free(blogc_error_t *err);
+
+#endif /* _ERROR_H */
diff --git a/src/loader.c b/src/loader.c
new file mode 100644
index 0000000..35ecdcf
--- /dev/null
+++ b/src/loader.c
@@ -0,0 +1,104 @@
+/*
+ * blogc: A blog compiler.
+ * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file COPYING.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include "utils/utils.h"
+#include "source-parser.h"
+#include "template-parser.h"
+#include "loader.h"
+#include "error.h"
+
+
+char*
+blogc_file_get_contents(const char *path, size_t *len, blogc_error_t **err)
+{
+    if (err == NULL || *err != NULL)
+        return NULL;
+
+    *len = 0;
+    FILE *fp = fopen(path, "r");
+
+    if (fp == NULL) {
+        int tmp_errno = errno;
+        *err = blogc_error_new_printf(BLOGC_ERROR_LOADER,
+            "Failed to open file (%s): %s", path, strerror(tmp_errno));
+        return NULL;
+    }
+
+    b_string_t *str = b_string_new();
+    char buffer[BLOGC_FILE_CHUNK_SIZE];
+
+    while (!feof(fp)) {
+        size_t read_len = fread(buffer, sizeof(char), BLOGC_FILE_CHUNK_SIZE, fp);
+        *len += read_len;
+        b_string_append_len(str, buffer, read_len);
+    }
+    fclose(fp);
+    return b_string_free(str, false);
+}
+
+
+b_slist_t*
+blogc_template_parse_from_file(const char *f, blogc_error_t **err)
+{
+    if (err == NULL || *err != NULL)
+        return NULL;
+    size_t len;
+    char *s = blogc_file_get_contents(f, &len, err);
+    if (s == NULL)
+        return NULL;
+    b_slist_t *rv = blogc_template_parse(s, len, err);
+    free(s);
+    return rv;
+}
+
+
+blogc_source_t*
+blogc_source_parse_from_file(const char *f, blogc_error_t **err)
+{
+    if (err == NULL || *err != NULL)
+        return NULL;
+    size_t len;
+    char *s = blogc_file_get_contents(f, &len, err);
+    if (s == NULL)
+        return NULL;
+    blogc_source_t *rv = blogc_source_parse(s, len, err);
+    free(s);
+    return rv;
+}
+
+
+b_slist_t*
+blogc_source_parse_from_files(b_slist_t *l, blogc_error_t **err)
+{
+    blogc_error_t *tmp_err = NULL;
+    b_slist_t *rv = NULL;
+
+    for (b_slist_t *tmp = l; tmp != NULL; tmp = tmp->next) {
+        char *f = tmp->data;
+        blogc_source_t *s = blogc_source_parse_from_file(f, &tmp_err);
+        if (s == NULL) {
+            *err = blogc_error_new_printf(BLOGC_ERROR_LOADER,
+                "An error occurred while parsing source file: %s\n\n%s",
+                f, tmp_err->msg);
+            blogc_error_free(tmp_err);
+            tmp_err = NULL;
+            b_slist_free_full(rv, blogc_source_free);
+            rv = NULL;
+            break;
+        }
+        rv = b_slist_append(rv, s);
+    }
+    return rv;
+}
diff --git a/src/loader.h b/src/loader.h
new file mode 100644
index 0000000..42a0abf
--- /dev/null
+++ b/src/loader.h
@@ -0,0 +1,22 @@
+/*
+ * blogc: A blog compiler.
+ * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br>
+ *
+ * This program can be distributed under the terms of the BSD License.
+ * See the file COPYING.
+ */
+
+#ifndef _LOADER_H
+#define _LOADER_H
+
+#include "utils/utils.h"
+#include "error.h"
+
+#define BLOGC_FILE_CHUNK_SIZE 1024
+
+char* blogc_file_get_contents(const char *path, size_t *len, blogc_error_t **err);
+b_slist_t* blogc_template_parse_from_file(const char *f, blogc_error_t **err);
+blogc_source_t* blogc_source_parse_from_file(const char *f, blogc_error_t **err);
+b_slist_t* blogc_source_parse_from_files(b_slist_t *l, blogc_error_t **err);
+
+#endif /* _LOADER_H */
diff --git a/src/main.c b/src/main.c
index 4e72b6e..90e374c 100644
--- a/src/main.c
+++ b/src/main.c
@@ -10,16 +10,108 @@
 #include <config.h>
 #endif /* HAVE_CONFIG_H */
 
+#include <stdbool.h>
 #include <stdio.h>
+#include <string.h>
 
+#include "utils/utils.h"
 #include "source-parser.h"
 #include "template-parser.h"
-#include <string.h>
+#include "loader.h"
+#include "error.h"
+
+
+static void
+blogc_print_help(void)
+{
+    printf(
+        "usage:\n"
+        "    blogc [-h] -t TEMPLATE [-o OUTPUT] SOURCE [SOURCE ...] - A blog compiler.\n"
+        "\n"
+        "positional arguments:\n"
+        "    SOURCE       source file(s)\n"
+        "\n"
+        "optional arguments:\n"
+        "    -h, --help   show this help message and exit\n"
+        "    -t TEMPLATE  template file\n"
+        "    -o OUTPUT    output file\n");
+}
+
+
+static void
+blogc_print_usage(void)
+{
+    printf("usage: blogc [-h] -t TEMPLATE [-o OUTPUT] SOURCE [SOURCE ...]\n");
+}
 
 
 int
 main(int argc, char **argv)
 {
-    printf("Hello, World!\n");
-    return 0;
+    int rv = 0;
+
+    char *template = NULL;
+    char *output = NULL;
+    b_slist_t *sources = NULL;
+
+    for (int i = 1; i < argc; i++) {
+        if (argv[i][0] == '-') {
+            switch (argv[i][1]) {
+                case 'h':
+                    blogc_print_help();
+                    goto cleanup;
+                case 't':
+                    if (i + 1 < argc)
+                        template = b_strdup(argv[++i]);
+                    break;
+                case 'o':
+                    if (i + 1 < argc)
+                        output = b_strdup(argv[++i]);
+                    break;
+            }
+        }
+        else
+            sources = b_slist_append(sources, b_strdup(argv[i]));
+    }
+
+    if (template == NULL) {
+        blogc_print_usage();
+        fprintf(stderr, "blogc: error: argument -t is required\n");
+        rv = 2;
+        goto cleanup;
+    }
+
+    if (b_slist_length(sources) == 0) {
+        blogc_print_usage();
+        fprintf(stderr, "blogc: error: at least one source file is required\n");
+        rv = 2;
+        goto cleanup;
+    }
+
+    blogc_error_t *err = NULL;
+
+    b_slist_t* l = blogc_template_parse_from_file(template, &err);
+    if (err != NULL) {
+        blogc_error_print(err);
+        goto cleanup2;
+    }
+
+    b_slist_t *s = blogc_source_parse_from_files(sources, &err);
+    if (err != NULL) {
+        blogc_error_print(err);
+        goto cleanup3;
+    }
+
+    printf("%d\n", s == NULL);
+
+cleanup3:
+    b_slist_free_full(s, blogc_source_free);
+cleanup2:
+    blogc_template_free_stmts(l);
+    blogc_error_free(err);
+cleanup:
+    free(template);
+    free(output);
+    b_slist_free_full(sources, free);
+    return rv;
 }
diff --git a/src/output.c b/src/output.c
deleted file mode 100644
index 1664afe..0000000
--- a/src/output.c
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * blogc: A blog compiler.
- * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br>
- *
- * This program can be distributed under the terms of the BSD License.
- * See the file COPYING.
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif /* HAVE_CONFIG_H */
-
-#include <stdio.h>
-#include "utils/utils.h"
-#include "output.h"
-
-
-void
-blogc_parser_syntax_error(const char *name, const char *src, size_t src_len,
-    size_t current)
-{
-    b_string_t *msg = b_string_new();
-
-    while (current < src_len) {
-        char c = src[current];
-
-        if (c == '\r' || c == '\n')
-            break;
-
-        b_string_append_c(msg, c);
-
-        current++;
-    }
-
-    fprintf(stderr, "%s parser error: failed to parse input near \"%s\".\n", name,
-        msg->str);
-
-    b_string_free(msg, true);
-}
diff --git a/src/output.h b/src/output.h
deleted file mode 100644
index 6995324..0000000
--- a/src/output.h
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * blogc: A blog compiler.
- * Copyright (C) 2015 Rafael G. Martins <rafael@rafaelmartins.eng.br>
- *
- * This program can be distributed under the terms of the BSD License.
- * See the file COPYING.
- */
-
-#ifndef _OUTPUT_H
-#define _OUTPUT_H
-
-#include <stdlib.h>
-
-void blogc_parser_syntax_error(const char *name, const char *src,
-    size_t src_len, size_t current);
-
-#endif /* _OUTPUT_H */
diff --git a/src/source-parser.c b/src/source-parser.c
index ad7e77d..7cace0c 100644
--- a/src/source-parser.c
+++ b/src/source-parser.c
@@ -14,7 +14,7 @@
 
 #include "utils/utils.h"
 #include "source-parser.h"
-#include "output.h"
+#include "error.h"
 
 
 typedef enum {
@@ -29,12 +29,14 @@ typedef enum {
 
 
 blogc_source_t*
-blogc_source_parse(const char *src, size_t src_len)
+blogc_source_parse(const char *src, size_t src_len, blogc_error_t **err)
 {
+    if (err == NULL || *err != NULL)
+        return NULL;
+
     size_t current = 0;
     size_t start = 0;
 
-    bool error = false;
     char *key = NULL;
     char *tmp = NULL;
     b_trie_t *config = b_trie_new(free);
@@ -59,7 +61,9 @@ blogc_source_parse(const char *src, size_t src_len)
                     state = SOURCE_SEPARATOR;
                     break;
                 }
-                error = true;
+                *err = blogc_error_parser(BLOGC_ERROR_SOURCE_PARSER, src, src_len,
+                    current,
+                    "Can't find a configuration key or the content separator.");
                 break;
 
             case SOURCE_CONFIG_KEY:
@@ -70,7 +74,8 @@ blogc_source_parse(const char *src, size_t src_len)
                     state = SOURCE_CONFIG_VALUE_START;
                     break;
                 }
-                error = true;
+                *err = blogc_error_parser(BLOGC_ERROR_SOURCE_PARSER, src, src_len,
+                    current, "Invalid configuration key.");
                 break;
 
             case SOURCE_CONFIG_VALUE_START:
@@ -79,7 +84,9 @@ blogc_source_parse(const char *src, size_t src_len)
                     start = current;
                     break;
                 }
-                error = true;
+                *err = blogc_error_parser(BLOGC_ERROR_SOURCE_PARSER, src, src_len,
+                    current, "Configuration value not provided for '%s'.",
+                    key);
                 break;
 
             case SOURCE_CONFIG_VALUE:
@@ -100,7 +107,9 @@ blogc_source_parse(const char *src, size_t src_len)
                     state = SOURCE_CONTENT_START;
                     break;
                 }
-                error = true;
+                *err = blogc_error_parser(BLOGC_ERROR_SOURCE_PARSER, src, src_len,
+                    current,
+                    "Invalid content separator. Must be one or more '-' characters.");
                 break;
 
             case SOURCE_CONTENT_START:
@@ -114,17 +123,16 @@ blogc_source_parse(const char *src, size_t src_len)
                 break;
         }
 
-        if (error)
+        if (*err != NULL)
             break;
 
         current++;
     }
 
-    if (error) {
+    if (*err != NULL) {
         free(key);
         free(content);
         b_trie_free(config);
-        blogc_parser_syntax_error("source", src, src_len, current);
         return NULL;
     }
 
@@ -137,11 +145,12 @@ blogc_source_parse(const char *src, size_t src_len)
 
 
 void
-blogc_source_free(blogc_source_t *source)
+blogc_source_free(void *source)
 {
     if (source == NULL)
         return;
-    free(source->content);
-    b_trie_free(source->config);
-    free(source);
+    blogc_source_t *s = source;
+    free(s->content);
+    b_trie_free(s->config);
+    free(s);
 }
diff --git a/src/source-parser.h b/src/source-parser.h
index e33a7d4..c816e5b 100644
--- a/src/source-parser.h
+++ b/src/source-parser.h
@@ -11,13 +11,15 @@
 
 #include <stdlib.h>
 #include "utils/utils.h"
+#include "error.h"
 
 typedef struct {
     b_trie_t *config;
     char *content;
 } blogc_source_t;
 
-blogc_source_t* blogc_source_parse(const char *src, size_t src_len);
-void blogc_source_free(blogc_source_t *source);
+blogc_source_t* blogc_source_parse(const char *src, size_t src_len,
+    blogc_error_t **err);
+void blogc_source_free(void *source);
 
 #endif /* _SOURCE_PARSER_H */
diff --git a/src/template-parser.c b/src/template-parser.c
index 54dc642..1038190 100644
--- a/src/template-parser.c
+++ b/src/template-parser.c
@@ -15,7 +15,7 @@
 
 #include "utils/utils.h"
 #include "template-parser.h"
-#include "output.h"
+#include "error.h"
 
 
 typedef enum {
@@ -44,13 +44,15 @@ typedef enum {
 
 
 b_slist_t*
-blogc_template_parse(const char *src, size_t src_len)
+blogc_template_parse(const char *src, size_t src_len, blogc_error_t **err)
 {
+    if (err == NULL || *err != NULL)
+        return NULL;
+
     size_t current = 0;
     size_t start = 0;
     size_t end = 0;
 
-    bool error = false;
     char *tmp = NULL;
 
     unsigned int if_count = 0;
@@ -95,7 +97,11 @@ blogc_template_parse(const char *src, size_t src_len)
                         stmts = b_slist_append(stmts, stmt);
                         stmt = NULL;
                     }
+                    break;
                 }
+                *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src,
+                    src_len, current,
+                    "Invalid statement syntax. Must be '%%' or '{'.");
                 break;
 
             case TEMPLATE_BLOCK_START:
@@ -106,7 +112,9 @@ blogc_template_parse(const char *src, size_t src_len)
                     start = current;
                     break;
                 }
-                error = true;
+                *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src,
+                    src_len, current,
+                    "Invalid statement syntax. Must begin lowercase letter.");
                 break;
 
             case TEMPLATE_BLOCK_TYPE:
@@ -120,6 +128,9 @@ blogc_template_parse(const char *src, size_t src_len)
                             start = current;
                             break;
                         }
+                        *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER,
+                            src, src_len, current, "Blocks can't be nested.");
+                        break;
                     }
                     else if (0 == strncmp("endblock", src + start, current - start)) {
                         if (block_state != BLOCK_CLOSED) {
@@ -128,6 +139,10 @@ blogc_template_parse(const char *src, size_t src_len)
                             block_state = BLOCK_CLOSED;
                             break;
                         }
+                        *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER,
+                            src, src_len, current,
+                            "'endblock' statement without an open 'block' statement.");
+                        break;
                     }
                     else if (0 == strncmp("if", src + start, current - start)) {
                         if (block_state == BLOCK_SINGLE_SOURCE || block_state == BLOCK_MULTIPLE_SOURCES) {
@@ -137,6 +152,11 @@ blogc_template_parse(const char *src, size_t src_len)
                             if_count++;
                             break;
                         }
+                        *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER,
+                            src, src_len, current,
+                            "'if' statements only allowed inside 'single_source' "
+                            "and 'multiple_sources' blocks.");
+                        break;
                     }
                     else if (0 == strncmp("endif", src + start, current - start)) {
                         if (block_state == BLOCK_SINGLE_SOURCE || block_state == BLOCK_MULTIPLE_SOURCES) {
@@ -146,10 +166,21 @@ blogc_template_parse(const char *src, size_t src_len)
                                 if_count--;
                                 break;
                             }
+                            *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER,
+                                src, src_len, current,
+                                "'endif' statement without an open 'if' statement.");
+                            break;
                         }
+                        *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER,
+                            src, src_len, current,
+                            "'endif' statements only allowed inside 'single_source' "
+                            "and 'multiple_sources' blocks.");
+                        break;
                     }
                 }
-                error = true;
+                *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src,
+                    src_len, current,
+                    "Invalid statement type: Allowed types are: block, endblock, if, endif.");
                 break;
 
             case TEMPLATE_BLOCK_BLOCK_TYPE_START:
@@ -160,7 +191,9 @@ blogc_template_parse(const char *src, size_t src_len)
                     start = current;
                     break;
                 }
-                error = true;
+                *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src,
+                    src_len, current,
+                    "Invalid block syntax. Must begin with lowercase letter.");
                 break;
 
             case TEMPLATE_BLOCK_BLOCK_TYPE:
@@ -186,7 +219,9 @@ blogc_template_parse(const char *src, size_t src_len)
                         break;
                     }
                 }
-                error = true;
+                *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src,
+                    src_len, current,
+                    "Invalid block type. Allowed types are: single_source, multiple_sources, multiple_sources_once.");
                 break;
 
             case TEMPLATE_BLOCK_IF_VARIABLE_START:
@@ -197,7 +232,9 @@ blogc_template_parse(const char *src, size_t src_len)
                     start = current;
                     break;
                 }
-                error = true;
+                *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src,
+                    src_len, current,
+                    "Invalid variable name. Must begin with uppercase letter.");
                 break;
 
             case TEMPLATE_BLOCK_IF_VARIABLE:
@@ -208,7 +245,9 @@ blogc_template_parse(const char *src, size_t src_len)
                     state = TEMPLATE_BLOCK_END;
                     break;
                 }
-                error = true;
+                *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src,
+                    src_len, current,
+                    "Invalid variable name. Must be uppercase letter, number or '_'.");
                 break;
 
             case TEMPLATE_BLOCK_END:
@@ -218,7 +257,9 @@ blogc_template_parse(const char *src, size_t src_len)
                     state = TEMPLATE_CLOSE_BRACKET;
                     break;
                 }
-                error = true;
+                *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src,
+                    src_len, current,
+                    "Invalid statement syntax. Must end with '%}'.");
                 break;
 
             case TEMPLATE_VARIABLE_START:
@@ -231,8 +272,15 @@ blogc_template_parse(const char *src, size_t src_len)
                         start = current;
                         break;
                     }
+                    *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER,
+                        src, src_len, current,
+                        "variable statements only allowed inside 'single_source' "
+                        "and 'multiple_sources' blocks.");
+                    break;
                 }
-                error = true;
+                *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src,
+                    src_len, current,
+                    "Invalid variable name. Must begin with uppercase letter.");
                 break;
 
             case TEMPLATE_VARIABLE:
@@ -248,7 +296,9 @@ blogc_template_parse(const char *src, size_t src_len)
                     state = TEMPLATE_CLOSE_BRACKET;
                     break;
                 }
-                error = true;
+                *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src,
+                    src_len, current,
+                    "Invalid variable name. Must be uppercase letter, number or '_'.");
                 break;
 
             case TEMPLATE_VARIABLE_END:
@@ -258,7 +308,9 @@ blogc_template_parse(const char *src, size_t src_len)
                     state = TEMPLATE_CLOSE_BRACKET;
                     break;
                 }
-                error = true;
+                *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src,
+                    src_len, current,
+                    "Invalid statement syntax. Must end with '}}'.");
                 break;
 
             case TEMPLATE_CLOSE_BRACKET:
@@ -275,24 +327,25 @@ blogc_template_parse(const char *src, size_t src_len)
                     start = current + 1;
                     break;
                 }
-                error = true;
+                *err = blogc_error_parser(BLOGC_ERROR_TEMPLATE_PARSER, src,
+                    src_len, current,
+                    "Invalid statement syntax. Must end with '}'.");
                 break;
 
         }
 
-        if (error)
+        if (*err != NULL)
             break;
 
         current++;
     }
 
-    if (error) {
+    if (*err != NULL) {
         if (stmt != NULL) {
             free(stmt->value);
             free(stmt);
         }
         blogc_template_free_stmts(stmts);
-        blogc_parser_syntax_error("template", src, src_len, current);
         return NULL;
     }
 
diff --git a/src/template-parser.h b/src/template-parser.h
index 1f43fe8..d95c87a 100644
--- a/src/template-parser.h
+++ b/src/template-parser.h
@@ -10,6 +10,7 @@
 #define _TEMPLATE_PARSER_H
 
 #include "utils/utils.h"
+#include "error.h"
 
 typedef enum {
     BLOGC_TEMPLATE_IF_STMT = 1,
@@ -25,7 +26,8 @@ typedef struct {
     char *value;
 } blogc_template_stmt_t;
 
-b_slist_t* blogc_template_parse(const char *src, size_t src_len);
+b_slist_t* blogc_template_parse(const char *src, size_t src_len,
+    blogc_error_t **err);
 void blogc_template_free_stmts(b_slist_t *stmts);
 
 #endif /* _TEMPLATE_GRAMMAR_H */
-- 
cgit v1.2.3-18-g5258