<?php

function tokenize ($str, &$out) {
    $out = array();

    while (strlen($str) > 0) {
        if (preg_match('/^\\/\\/.*/', $str, $matches)) {
            $str = substr($str, strlen($matches[0]));
        }
        else if (preg_match('/^\\s+/', $str, $matches)) {
            $str = substr($str, strlen($matches[0]));
        }
        else if (preg_match('/^include/', $str, $matches)) {
            $out[] = array('include', null);
            $str = substr($str, strlen($matches[0]));
        }
        else if (preg_match('/^structure/', $str, $matches)) {
            $out[] = array('structure', null);
            $str = substr($str, strlen($matches[0]));
        }
        else if (preg_match('/^size/', $str, $matches)) {
            $out[] = array('size', null);
            $str = substr($str, strlen($matches[0]));
        }
        else if (preg_match('/^align/', $str, $matches)) {
            $out[] = array('align', null);
            $str = substr($str, strlen($matches[0]));
        }
        else if (preg_match('/^{/', $str, $matches)) {
            $out[] = array('spar', null);
            $str = substr($str, strlen($matches[0]));
        }
        else if (preg_match('/^}/', $str, $matches)) {
            $out[] = array('epar', null);
            $str = substr($str, strlen($matches[0]));
        }
        else if (preg_match('/^\(/', $str, $matches)) {
            $out[] = array('srpar', null);
            $str = substr($str, strlen($matches[0]));
        }
        else if (preg_match('/^\)/', $str, $matches)) {
            $out[] = array('erpar', null);
            $str = substr($str, strlen($matches[0]));
        }
        else if (preg_match('/^\[/', $str, $matches)) {
            $out[] = array('sbracket', null);
            $str = substr($str, strlen($matches[0]));
        }
        else if (preg_match('/^\]/', $str, $matches)) {
            $out[] = array('ebracket', null);
            $str = substr($str, strlen($matches[0]));
        }
        else if (preg_match('/^;/', $str, $matches)) {
            $out[] = array('semicolon', null);
            $str = substr($str, strlen($matches[0]));
        }
        else if (preg_match('/^,/', $str, $matches)) {
            $out[] = array('comma', null);
            $str = substr($str, strlen($matches[0]));
        }
        else if (preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*/', $str, $matches)) {
            $out[] = array('name', $matches[0]);
            $str = substr($str, strlen($matches[0]));
        }
        else if (preg_match('/^"([^"]*)"/', $str, $matches)) {
            $out[] = array('string', $matches[1]);
            $str = substr($str, strlen($matches[0]));
        }
        else {
            return FALSE;
        }
    }

    return TRUE;
}

function fatal_error ($message)
{
    fwrite(STDERR, "Fatal error: $message\n");

    ob_get_clean();
    exit(1);
}

function make_size ($entry)
{
    switch ($entry["type"]["type"]) {
        case "sizealign":
            return "({$entry["type"]["size"]})";
        case "structure":
            return "o->{$entry["name"]}_params.len";
        default:
            assert(0);
    }
}

function make_align ($entry)
{
    switch ($entry["type"]["type"]) {
        case "sizealign":
            return "({$entry["type"]["align"]})";
        case "structure":
            return "o->{$entry["name"]}_params.align";
        default:
            assert(0);
    }
}

function generate_header ($name, $directives, $structures)
{
    ob_start();

    echo <<<EOD
/*
    DO NOT EDIT THIS FILE!
    This file was automatically generated by the bstruct generator.
*/

#include <stdint.h>

#include <misc/balign.h>


EOD;

    foreach ($directives as $directive) {
        if ($directive["type"] == "include") {
            echo <<<EOD
#include "{$directive["file"]}"

EOD;
        }
    }

    echo <<<EOD


EOD;

    foreach ($structures as $struct) {
        if ($struct["parameters"] == "") {
            $add_parameters = "";
        } else {
            $add_parameters = ", {$struct["parameters"]}";
        }

        echo <<<EOD
typedef struct {$struct["name"]}_struct {$struct["name"]};

typedef struct {

EOD;

        foreach ($struct["entries"] as $entry) {
            if ($entry["type"]["type"] == "structure") {
                echo <<<EOD
    {$entry["type"]["name"]}Params {$entry["name"]}_params;

EOD;
            }

            echo <<<EOD
    int {$entry["name"]}_off;
    int {$entry["name"]}_size;
    #ifndef NDEBUG
    int {$entry["name"]}_count;
    #endif


EOD;
        }

        echo <<<EOD
    int len;
    int align;
} {$struct["name"]}Params;

static void {$struct["name"]}Params_Init ({$struct["name"]}Params *o{$add_parameters})
{
    int cur_size;
    int cur_align;
    int cur_count;

    o->len = 0;
    o->align = 1;


EOD;

        // Calculate the alignment of the structure as the maximum of alignments of its entries.
        // This assumes the alignments are powers of two; in general we would need the least
        // common multiple of the alignments.

        $prev = NULL;
        foreach ($struct["entries"] as $entry) {
            if ($entry["type"]["type"] == "structure") {
                if ($entry["type"]["parameters"] == "") {
                    $init_add_parameters = "";
                } else {
                    $init_add_parameters = ", {$entry["type"]["parameters"]}";
                }
                echo <<<EOD
    {$entry["type"]["name"]}Params_Init(&o->{$entry["name"]}_params{$init_add_parameters});

EOD;
            }

            $size = make_size($entry);
            $align = make_align($entry);



            echo <<<EOD
    cur_size = {$size};
    cur_align = {$align};
    cur_count = ({$entry["num"]});

EOD;

            $off = "BALIGN_UP_N(o->len, cur_align)";
            $len = "(cur_count * cur_size)";

            echo <<<EOD
    o->{$entry["name"]}_off = {$off};
    o->{$entry["name"]}_size = cur_size;
    #ifndef NDEBUG
    o->{$entry["name"]}_count = cur_count;
    #endif
    o->len = o->{$entry["name"]}_off + {$len};
    o->align = (cur_align > o->align ? cur_align : o->align);


EOD;
        }

        echo <<<EOD
}


EOD;

        $prev = NULL;
        foreach ($struct["entries"] as $entry) {
            echo <<<EOD
static {$entry["type"]["ctype"]} * {$struct["name"]}_{$entry["name"]} ({$struct["name"]}Params *o, {$struct["name"]} *s)
{
    return ({$entry["type"]["ctype"]} *)((uint8_t *)s + o->{$entry["name"]}_off);
}

static {$entry["type"]["ctype"]} * {$struct["name"]}_{$entry["name"]}_at ({$struct["name"]}Params *o, {$struct["name"]} *s, int i)
{
    ASSERT(i >= 0)
    ASSERT(i < o->{$entry["name"]}_count)

    return ({$entry["type"]["ctype"]} *)((uint8_t *)s + o->{$entry["name"]}_off + i * o->{$entry["name"]}_size);
}


EOD;
        }

        echo <<<EOD

EOD;

    }

    return ob_get_clean();
}
