Amps is my new toy project. I’ve been writing it after reading the Crafting Interpreters, an excellent book by the way. Amps is a simple text template engine (under development, of course) developed in C++17.

As a side note, I’m impressed with modern C++ expressiveness. Despite of critics that I’ve read recently, it’s becoming a lot easier than old C++. Features like optional and variant give us the power to compose different types in order to create type safe complex structures, almost like dynamically type languages do.

For instance:

    amps::user_map ht {
      {"name", "My name"},
      {"cities", vector{
             "Sao Paulo",
             "Paris",
             "NYC",
             "London",
             "Lisbon"}},
      {"songs", unordered_map{
            {"guns and roses", "patience"},
            {"aerosmith", "crazy"},
            {"led zeppelin", "immigrant song"},
            {"pink floyd", "high hopes"}}},
    };

Isn’t it somewhat similar to Python?

    ht = {
        "name": "My name",
        "cities": ["Sao Paulo", "Paris"],
        "songs": {"aerosmith": "crazy"}}

To make Amps more useful I’ve integrated it to a module for Apache HTTP server. It basically offers dynamic web pages without a real programming language (like PHP, Ruby, Python) behind it. This could be good for small projects, however I’ve not measure the performance yet.

In order to build Apache’s HTTPd I referred to my old post. Then, I wrote a wrapper to connect my C++17 project with an plain C Apache module and nothing else.

    $ cat amps_wrapper.cpp
 1    #ifdef __cplusplus
 2    
 3    #include "engine.h"
 4    
 5    #include "httpd.h"
 6    
 7    #include <unordered_map>
 8    #include <vector>
 9    #include <string>
10    #include <cstring>
11    
12    using std::vector;
13    using std::string;
14    using std::unordered_map;
15    
16    unordered_map<string, string> query_to_map(const char *query)
17    {
18        unordered_map<string, string> result;
19        if (query == nullptr) {
20            return result;
21        }
22    
23        char *tmp = strdup(query);
24        for (char *tok = strtok(tmp, "&"); tok != NULL; tok = strtok(NULL, "&")) {
25            char *value = strchr(tok, '=');
26            if (value == nullptr) {
27                continue;
28            }
29    
30            result[string(tok, value - tok)] = string(&value[1]);
31        }
32    
33        free(tmp);
34        return result;
35    }
36    
37    static void get_custom_template(request_rec *r, char **result)
38    {
39        if (r->args == 0) {
40            return;
41        }
42    
43        amps::error err;
44        amps::engine engine(err);
45        engine.set_template_directory("/tmp");
46    
47        amps::user_map ht {{"user_data", query_to_map(r->args)}};
48    
49        // html template is the default, xml returned when content=xml
50        auto content = user.find("content");
51        if (content == user.end() || content->second == "html") {
52            engine.prepare_template("template.tpl");
53            r->content_type = "text/html";
54        }
55        else {
56            engine.prepare_template("template_xml.tpl");
57            r->content_type = "text/xml";
58        }
59        string rendered = engine.render(ht);
60    
61        *result = (char*)malloc(sizeof(char) * rendered.size() + 1);
62        strcpy(*result, rendered.c_str());
63        (*result)[rendered.size()] = '\0';
64    }
65    #endif
66    
67    extern "C" {
68        #include "amps_wrapper.h"
69    
70        void get_template(request_rec *r, char **result)
71        {
72            get_custom_template(r, result);
73        }
74    }

The HTTPd module simply calls the get_template function.

 1    static int get_handler(request_rec *r)
 2    {
 3        char *result = NULL;
 4    
 5        if (r->header_only || r->args == 0) {
 6            return OK;
 7        }
 8    
 9        get_template(r, &result);
10    
11        /* something bad happened */
12        if (result == NULL) {
13            return DECLINED;
14        }
15    
16        ap_rputs(result, r);
17    
18        free(result);
19    
20        return OK;
21    }

I compile it all with:

    $ g++ -std=c++17 -I/home/ziviani/amps/include \
     -I/home/ziviani/httpd/www/include \
     -fPIC amps_wrapper.cpp -o wrapper.o -c -g3
    $ ../bin/apxs  -I/home/ziviani/amps/include -c -i mod_cool_framework.c wrapper.o libamps-static.a

And I finally get this (html template):

 1    <html>
 2        <head>
 3            <meta charset="utf-8">
 4        <head>
 5    
 6        <body>
 7            <h2>Welcome {= user_data["name"] =}<h2>
 8            <ul>
 9                <li>ALL DATA:<ul>
10                {% for key, value in user_data %}
11                    {% if value eq "<null>" %}
12                        <li>ops, something wrong here<li>
13                    {% else %}
14                        <li>{= value =}<li>
15                    {% endif %}
16                {% endfor %}
17            <ul>
18        <body>
19    <html>

amps html content

and this (xml template):

1    <xml>
2        <user_name>{= user_data["name"] =}</user_name>
3    </xml>

amps xml content

Amps is at its initial stage but it’s been very fun to develop. I intend to continue writing about it in the near future.

Thank you for reading it.