Code Walkthrough

For testing, you should extend code from this section.

Common Utils

import os
import sys
import re
import sys

from jinja2 import Environment, BaseLoader
Template = Environment(loader=BaseLoader, block_start_string="<?", block_end_string="?>",variable_start_string="<?=", variable_end_string="?>")

def entity_to_struct(blob, archetypes):
    a_types = blob["archetypes"]
    atypes_of_struct = []
    for a in a_types:
        for b in archetypes:
            if b["name"] == a:
                atypes_of_struct.append(b)

    final_c_fields = blob["c_fields"]
    for e in atypes_of_struct:
        final_c_fields += e["c_fields"]

    return "struct {\n" + final_c_fields + "\n} " + blob["name"] + ";\n\n"


def write_file(name, fn, stash):
    _ = stash
    code = Template.from_string(_[name]).render(_=_)
    f = open(fn, "w")
    f.write(code)
    f.close()

def cpp(file):
    filename, file_extension = os.path.splitext(file)
    filename, ignore  = os.path.splitext(filename) # ignore gen
    os.system("clang -E " + file + " > " + (filename +  ".cpp" + file_extension))

def pre_process(file, stash):
    _ = stash
    filename, file_extension = os.path.splitext(file)
    filename, ignore  = os.path.splitext(filename) # ignore cpp

    os.system("python tq.py < " + file + " > "  + filename + ".tq" + file_extension)
    os.system("python ns.py < " + filename + ".tq" + file_extension + " > "  + filename + file_extension)


def compile(program, file):
    os.system("scons -s")

def output_printer(text):
    print(text, end="")

def word_split(word):
    return [char for char in word]

def output_capture():
    final_str = ""

    def getter():
        nonlocal final_str
        return final_str

    def setter(x):
        nonlocal final_str
        final_str += x

    return [getter, setter]

Archetype

For the time being yaml is used intead of full fledged syntax. Archetype is compile time version of js prototypes. Not related to ECS archetype. Come up with a better name ?

archetypes:
    - name: test
      c_fields: |
        int x;

Entity

Todo

Automatically generate ctor / dtor / getter / setter

entity:
    name: test
    archetypes: ["test"]
    c_fields: |
        int y;

Parsers

Ideally each parser can parse the complete C spec + New Syntax. Logic for piping between multiple syntaxes.

  1. Source -> Syntax Parser 1 -> Generated C code 1

  2. Generated C Code 1 -> Syntax Parser 2 -> Generated C Code 2

Todo

Use a proper lexer from pycparser

Namespaces

Very difficult to implement properly as you will have to manage the symbol table and typedefs. Currently “:” is translated to “_”.

import os
import sys
import re
from flexicon import Lexer
from source.utils import *

def parse_ns(text, printer):
    EXPRESSION_LEXER = Lexer().simple(
        (r'([A-Za-z0-9_:]+)\(', lambda x: ('NS',x)),
        (r'(.|\s)', lambda x: ('C', x)),
    )

    tokens = EXPRESSION_LEXER.lex(text)
    count = 1
    line_count = 1

    for e in tokens:
        if e[0] == "NS":
            name = e[1]
            printer(name.replace(":","_") + "(")
        if e[0] == "C":
            printer(e[1])

test = """
foo:baz()
END
"""

if __name__ == "__main__":
    parse_ns(sys.stdin.read(), output_printer)

Heredocs

import os
import sys
import re
from flexicon import Lexer
from source.utils import *

def parse_hd(text, printer):

    EXPRESSION_LEXER = Lexer().simple(
        (r'<<<([A-Z]+)\n', lambda x: ('TQ',x)),
        (r'([^\s]+?)', lambda x: ('C', x)),
        (r'(\s)', lambda x: ('SP', x)),
    )

    tokens = EXPRESSION_LEXER.lex(text)
    count = 1
    line_count = 1

    in_hd  = False
    hd = ""
    end = ""

    for e in tokens:
        if e[0] == "TQ":
            in_hd = True
            end = e[1]
        if e[0] == "C":
            if in_hd:
                hd += e[1]
                if hd.endswith(end):
                    size = len(hd)
                    mod_string = hd[:size - (len(end))]
                    in_hd = False
                    end = ""
                    for l in mod_string.split("\n"):
                        printer("\"" + l + "\\n\"")
                        printer("\n")
                    printer(";")

            else:
                printer(e[1])
        if e[0] == "SP":
            if in_hd:
                hd += e[1]
            else:
                printer(e[1])

test = """
char[] x = <<<END
foo
bar
END
"""

if __name__ == "__main__":
    parse_hd(sys.stdin.read(), output_printer)

Generics

<? macro sort(type) -?>
void swap(<?= type ?>* a, <?= type ?>* b)
{
    <?= type ?> t = *a;
    *a = *b;
    *b = t;
}

int partition (<?= type ?> arr[], int low, int high)
{
    int pivot = arr[high];    // pivot
    int i = (low - 1);  // Index of smaller element

    for (int j = low; j <= high- 1; j++)
    {
        if (arr[j] <= pivot)
        {
            i++;    // increment index of smaller element
            swap(&arr[i], &arr[j]);
        }
    }
    swap(&arr[i + 1], &arr[high]);
    return (i + 1);
}

void quickSort(<?= type ?> arr[], int low, int high)
{
    if (low < high)
    {
        int pi = partition(arr, low, high);

        quickSort(arr, low, pi - 1);
        quickSort(arr, pi + 1, high);
    }
}
<?- endmacro ?>

Headers

Currently headers are causing errors in pycparser as non standard C extensions are used in them. So including them separately for now.

#include <libdill.h>
#include <stdio.h>
#include <gc.h>
#define malloc(x) GC_malloc(x)
#define calloc(n,x) GC_malloc((n)*(x))
#define realloc(p,x) GC_realloc((p),(x))
#define free(x) (x) = NULL
#include <fio.h>
#include <fio_cli.h>
#include <http.h>


Scons

env = Environment(platform='posix')
Program('example.c',  LIBS=[
    'libdill',
    'libgc',
    'libfacil.io'
    ], CPPPATH = [
    'vendor/libdill-master',
    'vendor/bdwgc-master',
    'vendor/appname/libdump/all'
    ], LIBPATH=[
        'vendor/libdill-master',
        'vendor/bdwgc-master',
        'vendor/appname'
    ])

Main

<?= _["Headers"]  ?>
<?= sort("int")  ?>


<?= _["Entity"] ?>

void print_array(int arr[], int size) {
    int i;
    for (i=0; i < size; i++)
        printf("%d ", arr[i]);
    printf("\n");
}

coroutine void worker(int count, const char *text) {
    int i;
    for(i = 0; i != count; ++i) {
        printf("%s\n", text);
        msleep(now() + 10);
    }
}

void initialize_cli(int argc, char const *argv[]);

static void on_http_request(http_s *h) {
  http_send_body(h, "Hello World!", 12);
}


int main(int argc, char **argv) {
  GC_set_all_interior_pointers(0);
  GC_INIT();

    char greeting[] = <<<END
Hello World
END
    printf("Hello World");

    int arr[] = {10, 7, 8, 9, 1, 5};
    int n = sizeof(arr) / sizeof(arr[0]);
    quickSort(arr, 0, n-1);
    printf("Sorted array: \n");
    print:array(arr, n);
    go(worker(4, "a"));
    msleep(now() + 100);

  for (int i = 0; i < 20000; ++i) {
        malloc(4096);
  }
  printf("Final heap size is %lu\n", (unsigned long)GC_get_heap_size());
  initialize_cli(argc, argv);
  /* listen for inncoming connections */
  if (http_listen(fio_cli_get("-p"), fio_cli_get("-b"),
                  .on_request = on_http_request,
                  .max_body_size = fio_cli_get_i("-maxbd"),
                  .public_folder = fio_cli_get("-public"),
                  .log = fio_cli_get_bool("-log"),
                  .timeout = fio_cli_get_i("-keep-alive")) == -1) {
    /* listen failed ?*/
    perror(
        "ERROR: facil.io couldn't initialize HTTP service (already running?)");
    exit(1);
  }
  fio_start(.threads = fio_cli_get_i("-t"), .workers = fio_cli_get_i("-w"));
  return 0;
}

void initialize_cli(int argc, char const *argv[]) {
  fio_cli_start(
      argc, argv, 0, 0, NULL,
      // Address Binding arguments
      FIO_CLI_PRINT_HEADER("Address Binding:"),
      FIO_CLI_INT("-port -p port number to listen to. defaults port 3000"),
      "-bind -b address to listen to. defaults any available.",
      // Concurrency arguments
      FIO_CLI_PRINT_HEADER("Concurrency:"),
      FIO_CLI_INT("-workers -w number of processes to use."),
      FIO_CLI_INT("-threads -t number of threads per process."),
      // HTTP Settings arguments
      FIO_CLI_PRINT_HEADER("HTTP Settings:"),
      "-public -www public folder, for static file service.",
      FIO_CLI_INT(
          "-keep-alive -k HTTP keep-alive timeout (0..255). default: 10s"),
      FIO_CLI_INT(
          "-max-body -maxbd HTTP upload limit in Mega Bytes. default: 50Mb"),
      FIO_CLI_BOOL("-log -v request verbosity (logging)."),
      // WebSocket Settings arguments
      FIO_CLI_PRINT_HEADER("WebSocket Settings:"),
      FIO_CLI_INT("-ping websocket ping interval (0..255). default: 40s"),
      FIO_CLI_INT("-max-msg -maxms incoming websocket message "
                  "size limit in Kb. default: 250Kb"),
      "-redis -r an optional Redis URL server address.",
      FIO_CLI_PRINT("\t\ta valid Redis URL would follow the pattern:"),
      FIO_CLI_PRINT("\t\t\tredis://user:password@localhost:6379/"));

  /* Test and set any default options */
  if (!fio_cli_get("-p")) {
    /* Test environment as well */
    char *tmp = getenv("PORT");
    if (!tmp)
      tmp = "3000";
    /* Set default (unlike cmd line arguments, aliases are manually set) */
    fio_cli_set("-p", tmp);
    fio_cli_set("-port", tmp);
  }
  if (!fio_cli_get("-b")) {
    char *tmp = getenv("ADDRESS");
    if (tmp) {
      fio_cli_set("-b", tmp);
      fio_cli_set("-bind", tmp);
    }
  }
  if (!fio_cli_get("-public")) {
    char *tmp = getenv("HTTP_PUBLIC_FOLDER");
    if (tmp) {
      fio_cli_set("-public", tmp);
      fio_cli_set("-www", tmp);
    }
  }

  if (!fio_cli_get("-public")) {
    char *tmp = getenv("REDIS_URL");
    if (tmp) {
      fio_cli_set("-redis-url", tmp);
      fio_cli_set("-ru", tmp);
    }
  }
}