/****************************************************************************
**
**  This file is part of GAP, a system for computational discrete algebra.
**
**  Copyright of GAP belongs to its developers, whose names are too numerous
**  to list here. Please refer to the COPYRIGHT file for details.
**
**  SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "syntaxtree.h"

#include "bool.h"
#include "calls.h"
#include "code.h"
#include "common.h"
#include "error.h"
#include "exprs.h"
#include "gvars.h"
#include "integer.h"
#include "lists.h"
#include "modules.h"
#include "opers.h"
#include "plist.h"
#include "precord.h"
#include "profile.h"
#include "records.h"
#include "stats.h"
#include "stringobj.h"
#include "vars.h"

#include <ctype.h>
#include <stdarg.h>

enum { MAX_ARITY = 4 };

typedef Obj (*CompileFuncT)(Obj node, Expr expr);
typedef Obj (*CompileArgT)(Expr expr);
typedef Expr (*CodeFuncT)(CodeState * cs, Obj node);
typedef Expr (*CodeArgT)(CodeState * cs, Obj node);


typedef struct {
    const Char * argname;
    CompileArgT  argcomp;
    CodeArgT     argcode;
    Int          isStat;
} ArgT;

typedef struct {
    UInt1        tnum;
    CompileFuncT compile;
    CodeFuncT    code;
    const Char * name;
    UInt         arity;
    ArgT         args[MAX_ARITY];
} CompilerT;

// We put compilers for statements and expressions into the same static array,
// assuming that the set of their respective ids are disjoint.
static const CompilerT Compilers[];
#define COMPILER_ARITY(...) (sizeof((ArgT[]){ __VA_ARGS__ }) / sizeof(ArgT))
#define COMPILER_GENERIC(tnum, compiler, coder, ...)                         \
    [tnum] = {                                                               \
        tnum,           compiler, coder, #tnum, COMPILER_ARITY(__VA_ARGS__), \
        { __VA_ARGS__ }                                                      \
    }

#define COMPILER(tnum, compiler, coder)                                      \
    COMPILER_GENERIC(tnum, compiler, coder)

#define COMPILER_(tnum, ...)                                                 \
    COMPILER_GENERIC(tnum, SyntaxTreeDefaultCompiler,                        \
                     SyntaxTreeDefaultCoder, __VA_ARGS__)

#define ARG(name, func, coder, isStat)                                       \
    {                                                                        \
        name, func, coder, isStat                                            \
    }

// ARG_EXPR indicates an "expression argument", i.e., that the corresponding
// slot of the statement or expression type it belongs to is meant to
// reference an expression. This information is used for validation when
// coding an AST back into a T_BODY object. The expression is stored in resp.
// read from the record entry <name>.
#define ARG_EXPR(name, func, coder) ARG(name, func, coder, 0)

// ARG_STAT is like ARG_EXPR, except that it indicates that the corresponding
// slot should contain a statement instead of an expression. There is no
// ARG_STAT, because it is never used.


// ARG_EXPR_ can be used instead of ARG_EXPR if the default compiler
// and coder should be used to compile the corresponding expression or code
// the corresponding subnode.
#define ARG_EXPR_(name)                                                      \
    ARG(name, SyntaxTreeCompiler, SyntaxTreeDefaultExprCoder, 0)
#define ARG_STAT_(name)                                                      \
    ARG(name, SyntaxTreeCompiler, SyntaxTreeDefaultStatCoder, 1)

// ARGS_EXPR can be used instead of ARG_EXPR as the last argument to the
// COMPILER_ macro, and indicates that a variable number of arguments follow;
// all are supposed to be expressions; and all of them are to be stored into a
// plist which is stored in resp. read from the record entry <name>.
#define ARGS_EXPR(name) ARG(name, 0, 0, 0)
#define ARGS_STAT(name) ARG(name, 0, 0, 1)

// plain list mapping statement tnums to strings
// like "EXPR_INT"
static Obj typeStrings;

// plain record mapping strings like "EXPR_INT" to the
// corresponding statement tnum
static Obj typeRec;


static Obj ElmRecST(UInt1 tnum, Obj node, const char * name)
{
    RequirePlainRec("ElmRecST", node);
    UInt rnam = RNamName(name);
    if (!IsbPRec(node, rnam)) {
        const char * typename = Compilers[tnum].name;
        ErrorQuit("while coding %s: node field <%s> must be present",
                  (Int) typename, (Int)name);
    }
    return ElmPRec(node, rnam);
}

static Obj SyntaxTreeFunc(Obj result, Obj func);

static UInt1 FIND_TNUM_FROM_STRING(Obj typestr)
{
    UInt rnam = RNamObj(typestr);
    if (IsbPRec(typeRec, rnam)) {
        return (UInt1)UInt_ObjInt(ElmPRec(typeRec, rnam));
    }
    ErrorQuit("Unrecognized syntax tree node type %g", (Int)typestr, 0);
}

static UInt1 GetTypeTNum(Obj node)
{
    RequirePlainRec("GetTypeTNum", node);
    UInt type = RNamName("type");
    if (!IsbPRec(node, type)) {
        ErrorQuit("while coding: <type> field is not present", 0, 0);
    }
    return FIND_TNUM_FROM_STRING(ElmPRec(node, type));
}

static inline Obj NewSyntaxTreeNode(UInt1 tnum)
{
    Obj result;
    Obj typestr;

    typestr = ELM_LIST(typeStrings, tnum + 1);
    result = NEW_PREC(2);
    AssPRec(result, RNamName("type"), typestr);

    return result;
}

static Obj SyntaxTreeCompiler(Expr expr)
{
    Obj       result;
    UInt1     tnum;
    CompilerT comp;

    // TODO: GAP_ASSERT
    tnum = TNUM_EXPR(expr);
    comp = Compilers[tnum];

    result = NewSyntaxTreeNode(comp.tnum);

    return comp.compile(result, expr);
}

static Obj SyntaxTreeRNam(Expr expr)
{
    return NAME_RNAM(expr);
}

static Expr SyntaxTreeCodeRNamObj(CodeState * cs, Obj node)
{
    return RNamObj(node);
}


static Obj SyntaxTreeDefaultCompiler(Obj result, Expr expr)
{
    int       i;
    UInt1     tnum;
    CompilerT comp;
    BOOL      didvariadic = 0;
    Int       offset = 0;

    // TODO: GAP_ASSERT tnum range
    tnum = TNUM_EXPR(expr);
    comp = Compilers[tnum];

    for (i = 0; i < comp.arity; i++) {
        UInt rnam = RNamName(comp.args[i].argname);
        Obj  compiled;

        if (comp.args[i].argcomp) {
            Expr subexpr = READ_EXPR(expr, i + offset);
            compiled = comp.args[i].argcomp(subexpr);
        }
        else {
            // special case: one argument may have zero as decompiler,
            // which expresses that this slot is "variadic", and
            // is decompiled into a single list
            GAP_ASSERT(!didvariadic);
            didvariadic = 1;
            // Avoid warning about used variable when not in debug mode
            (void)didvariadic;

            // compile the variadic part into a single list
            const UInt nr = SIZE_EXPR(expr) / sizeof(expr);
            const UInt varlength = nr - (comp.arity - 1);
            compiled = NEW_PLIST(T_PLIST, nr - (comp.arity - 1));
            for (; offset < varlength; offset++) {
                Expr subexpr = READ_EXPR(expr, i + offset);
                // handle 0 to properly deal with EXPR_LIST
                Obj obj = subexpr ? SyntaxTreeCompiler(subexpr) : 0;
                PushPlist(compiled, obj);
            }
            // offset gets set one bigger final time around loop
            offset--;
        }

        AssPRec(result, rnam, compiled);
    }

    GAP_ASSERT(i + offset == SIZE_EXPR(expr) / sizeof(expr));
    return result;
}

static Stat SyntaxTreeDefaultStatCoder(CodeState *, Obj);
static Stat SyntaxTreeDefaultExprCoder(CodeState *, Obj);

static Expr SyntaxTreeDefaultCoder(CodeState * cs, Obj node)
{

    RequirePlainRec("SyntaxTreeDefaultCoder", node);

    UInt1 tnum = GetTypeTNum(node);

    CompilerT comp = Compilers[tnum];

    if (comp.code != SyntaxTreeDefaultCoder) {
        return comp.code(cs, node);
    }

    // compute how big the statement/expression we are generating needs to
    // be, i.e., how many Obj-sized "slots" we need; this is usually
    // comp.arity, but we need to take into account variadic argument lists
    UInt slots = comp.arity;
    UInt arity = comp.arity;

    Int varargloc = -1;
    for (UInt i = 0; i < comp.arity; ++i) {
        if (comp.args[i].argcomp == 0) {
            GAP_ASSERT(varargloc == -1);
            varargloc = i;
        }
    }

    Obj vararglist = 0;

    if (varargloc != -1) {
        vararglist = ElmRecST(tnum, node, comp.args[varargloc].argname);
        slots = arity - 1 + LEN_LIST(vararglist);
    }

    // reserve space for the statement or expressions
    Expr expr = NewStatOrExpr(cs, tnum, slots * sizeof(Expr), 0);

    Int  i;
    UInt offset = 0;

    for (i = 0; i < arity; i++) {
        if (i == varargloc) {
            for (; offset < LEN_LIST(vararglist); offset++) {
                Obj elem = ELM0_LIST(vararglist, offset + 1);
                // Deal with empty entries in list expressions
                if (elem == 0) {
                    WRITE_EXPR(cs, expr, i + offset, 0);
                }
                else if (comp.args[i].isStat) {
                    WRITE_EXPR(cs, expr, i + offset,
                               SyntaxTreeDefaultStatCoder(cs, elem));
                }
                else {
                    WRITE_EXPR(cs, expr, i + offset,
                               SyntaxTreeDefaultExprCoder(cs, elem));
                }
            }
            // offset gets set one bigger final time around loop
            offset--;
        }
        else {
            Obj subast = ElmRecST(tnum, node, comp.args[i].argname);
            WRITE_EXPR(cs, expr, i + offset,
                       comp.args[i].argcode(cs, subast));
        }
    }

    return expr;
}


// Checks if node codes a statement
static Stat SyntaxTreeDefaultStatCoder(CodeState * cs, Obj node)
{
    RequirePlainRec("SyntaxTreeDefaultStatCoder", node);
    Int tnum = GetTypeTNum(node);
    if (!(FIRST_STAT_TNUM <= tnum && tnum <= LAST_STAT_TNUM))
        ErrorQuit("tried to code expression %s as statement",
                  (Int)Compilers[tnum].name, 0);
    return (Stat)SyntaxTreeDefaultCoder(cs, node);
}

// Checks if node codes an expression
static Expr SyntaxTreeDefaultExprCoder(CodeState * cs, Obj node)
{
    RequirePlainRec("SyntaxTreeDefaultExprCoder", node);
    UInt1 tnum = GetTypeTNum(node);
    if (!(FIRST_EXPR_TNUM <= tnum && tnum <= LAST_EXPR_TNUM))
        ErrorQuit("tried to code statement %s as expression",
                  (Int)Compilers[tnum].name, 0);
    return (Expr)SyntaxTreeDefaultCoder(cs, node);
}

static Expr SyntaxTreeCodeGVar(CodeState * cs, Obj name)
{
    RequireStringRep("SyntaxTreeCodeGVar", name);
    return GVarName(CONST_CSTR_STRING(name));
}

static Obj SyntaxTreeRefLVar(Obj result, Expr expr)
{
    AssPRec(result, RNamName("lvar"), INTOBJ_INT(LVAR_REF_LVAR(expr)));
    return result;
}

static Expr SyntaxTreeCodeRefLVar(CodeState * cs, Obj node)
{
    RequirePlainRec("SyntaxTreeCodeRefLVar", node);
    Obj lvar = ElmRecST(EXPR_REF_LVAR, node, "lvar");
    RequireSmallInt("SyntaxTreeCodeRefLVar", lvar);
    return REF_LVAR_LVAR(INT_INTOBJ(lvar));
}

static Expr SyntaxTreeCodeObjInt(CodeState * cs, Obj node)
{
    return UInt_ObjInt(node);
}

static Obj SyntaxTreeEvalCompiler(Obj result, Expr expr)
{
    pauseProfiling();
    Obj o = EVAL_EXPR(expr);
    unpauseProfiling();
    AssPRec(result, RNamName("value"), o);
    return result;
}

static Obj SyntaxTreeFuncExpr(Obj result, Expr expr)
{
    Obj fexp = GET_VALUE_FROM_CURRENT_BODY(READ_EXPR(expr, 0));

    SyntaxTreeFunc(result, fexp);

    return result;
}

static Obj SyntaxTreeRangeExpr(Obj result, Expr expr)
{
    Obj first;
    Obj second;
    Obj last;

    if (SIZE_EXPR(expr) == 2 * sizeof(Expr)) {
        first = SyntaxTreeCompiler(READ_EXPR(expr, 0));
        last = SyntaxTreeCompiler(READ_EXPR(expr, 1));

        AssPRec(result, RNamName("first"), first);
        AssPRec(result, RNamName("last"), last);
    }
    else {
        first = SyntaxTreeCompiler(READ_EXPR(expr, 0));
        second = SyntaxTreeCompiler(READ_EXPR(expr, 1));
        last = SyntaxTreeCompiler(READ_EXPR(expr, 2));

        AssPRec(result, RNamName("first"), first);
        AssPRec(result, RNamName("second"), second);
        AssPRec(result, RNamName("last"), last);
    }

    return result;
}

static Expr SyntaxTreeCodeRangeExpr(CodeState * cs, Obj node)
{
    RequirePlainRec("SyntaxTreeCodeRangeExpr", node);
    UInt hassecond = ISB_REC(node, RNamName("second"));
    UInt size = hassecond ? 3 : 2;
    Expr result = NewStatOrExpr(cs, EXPR_RANGE, size * sizeof(Expr), 0);
    WRITE_EXPR(
        cs, result, 0,
        SyntaxTreeDefaultExprCoder(cs, ElmRecST(EXPR_RANGE, node, "first")));
    WRITE_EXPR(
        cs, result, size - 1,
        SyntaxTreeDefaultExprCoder(cs, ElmRecST(EXPR_RANGE, node, "last")));
    if (hassecond) {
        WRITE_EXPR(cs, result, 1,
                   SyntaxTreeDefaultExprCoder(
                       cs, ElmRecST(EXPR_RANGE, node, "second")));
    }
    return result;
}

static Obj SyntaxTreeRecExpr(Obj result, Expr expr)
{
    Obj  key;
    Obj  val;
    Obj  list;
    Obj  subrec;
    Expr tmp;
    Int  i, len;

    len = SIZE_EXPR(expr) / (2 * sizeof(Expr));
    list = NEW_PLIST(T_PLIST, len);

    for (i = 1; i <= len; i++) {
        tmp = READ_EXPR(expr, 2 * i - 2);
        GAP_ASSERT(tmp != 0);

        subrec = NEW_PREC(2);
        PushPlist(list, subrec);

        if (IS_INTEXPR(tmp)) {
            key = NAME_RNAM((UInt)INT_INTEXPR(tmp));
        }
        else {
            key = SyntaxTreeCompiler(tmp);
        }
        AssPRec(subrec, RNamName("key"), key);

        tmp = READ_EXPR(expr, 2 * i - 1);
        val = SyntaxTreeCompiler(tmp);
        AssPRec(subrec, RNamName("value"), val);
    }
    AssPRec(result, RNamName("keyvalue"), list);

    return result;
}

static Expr SyntaxTreeCodeRecExpr(CodeState * cs, Obj node)
{
    RequirePlainRec("SyntaxTreeCodeRecExpr", node);
    UInt1 tnum = GetTypeTNum(node);
    Obj   keyvalue = ElmRecST(tnum, node, "keyvalue");
    UInt  len = LEN_LIST(keyvalue);
    Expr  record = NewStatOrExpr(cs, tnum, 2 * len * sizeof(Expr), 0);
    for (int i = 0; i < len; i++) {
        Obj  keyvaluepair = ELM_LIST(keyvalue, i + 1);
        Obj  keynode = ElmRecST(tnum, keyvaluepair, "key");
        Obj  valuenode = ElmRecST(tnum, keyvaluepair, "value");
        Expr key;
        if (IS_STRING(keynode)) {
            key = INTEXPR_INT(RNamObj(keynode));
        }
        else {
            key = SyntaxTreeDefaultExprCoder(cs, keynode);
        }
        Expr value = SyntaxTreeDefaultExprCoder(cs, valuenode);
        WRITE_EXPR(cs, record, 2 * i, key);
        WRITE_EXPR(cs, record, 2 * i + 1, value);
    }
    return record;
}

static Obj SyntaxTreeFloatLazy(Obj result, Expr expr)
{
    Obj string = GET_VALUE_FROM_CURRENT_BODY(READ_EXPR(expr, 1));
    AssPRec(result, RNamName("value"), string);
    return result;
}

static Obj SyntaxTreeFloatEager(Obj result, Expr expr)
{
    Obj   value = GET_VALUE_FROM_CURRENT_BODY(READ_EXPR(expr, 0));
    Obj   string = GET_VALUE_FROM_CURRENT_BODY(READ_EXPR(expr, 1));
    UChar chr = (UChar)READ_EXPR(expr, 2);
    AssPRec(result, RNamName("value"), value);
    AssPRec(result, RNamName("string"), string);
    AssPRec(result, RNamName("mark"), ObjsChar[chr]);
    return result;
}

static Expr SyntaxTreeCodeFloatLazy(CodeState * cs, Obj node)
{
    RequirePlainRec("SyntaxTreeCodeFloatLazy", node);
    Obj value = ElmRecST(EXPR_FLOAT_LAZY, node, "value");
    return CodeLazyFloatExpr(cs, value, 0);
}

static Expr SyntaxTreeCodeFloatEager(CodeState * cs, Obj node)
{
    RequirePlainRec("SyntaxTreeCodeFloatEager", node);
    Obj  value = ElmRecST(EXPR_FLOAT_EAGER, node, "value");
    Obj  string = ElmRecST(EXPR_FLOAT_EAGER, node, "string");
    Obj  mark = ElmRecST(EXPR_FLOAT_EAGER, node, "mark");
    Expr fl = NewStatOrExpr(cs, EXPR_FLOAT_EAGER, 3 * sizeof(UInt), 0);
    WRITE_EXPR(cs, fl, 0, AddValueToBody(cs, value));
    WRITE_EXPR(cs, fl, 1, AddValueToBody(cs, string));
    WRITE_EXPR(cs, fl, 2, (UInt)CHAR_VALUE(mark));
    return fl;
}

static Obj SyntaxTreeIf(Obj result, Stat stat)
{
    Obj cond;
    Obj then;
    Obj pair;
    Obj branches;

    Int i, nr;

    nr = SIZE_STAT(stat) / (2 * sizeof(Stat));
    branches = NEW_PLIST(T_PLIST, nr);

    AssPRec(result, RNamName("branches"), branches);

    for (i = 0; i < nr; i++) {
        cond = SyntaxTreeCompiler(READ_STAT(stat, 2 * i));
        then = SyntaxTreeCompiler(READ_STAT(stat, 2 * i + 1));

        pair = NEW_PREC(2);
        AssPRec(pair, RNamName("condition"), cond);
        AssPRec(pair, RNamName("body"), then);

        PushPlist(branches, pair);
    }
    return result;
}

static Obj SyntaxTreeCompilePragma(Obj result, Stat stat)
{
    Obj message = GET_VALUE_FROM_CURRENT_BODY(READ_EXPR(stat, 0));
    AssPRec(result, RNamName("value"), message);
    return result;
}

static Expr SyntaxTreeCodeIf(CodeState * cs, Obj node)
{
    RequirePlainRec("SyntaxTreeCodeIf", node);
    UInt1 tnum = GetTypeTNum(node);
    Obj   branches = ElmRecST(tnum, node, "branches");
    UInt  len = LEN_LIST(branches);
    Expr  ifexpr = NewStatOrExpr(cs, tnum, 2 * len * sizeof(Expr), 0);
    for (int i = 0; i < len; i++) {
        Obj  condbodypair = ELM_LIST(branches, i + 1);
        Obj  conditionnode = ElmRecST(tnum, condbodypair, "condition");
        Obj  bodynode = ElmRecST(tnum, condbodypair, "body");
        Expr condition = SyntaxTreeDefaultExprCoder(cs, conditionnode);
        Stat body = SyntaxTreeDefaultStatCoder(cs, bodynode);
        WRITE_EXPR(cs, ifexpr, 2 * i, condition);
        WRITE_EXPR(cs, ifexpr, 2 * i + 1, body);
    }
    return ifexpr;
}

static Expr SyntaxTreeCodeImmediateInteger(CodeState * cs, Obj node)
{
    RequirePlainRec("SyntaxTreeCodeImmediateInteger", node);
    Obj value = ElmRecST(EXPR_INT, node, "value");
    RequireSmallIntEx("SyntaxTreeCodeImmediateInteger", value,
                      "<node>.value");
    return INTEXPR_INT(INT_INTOBJ(value));
}

static Expr SyntaxTreeCodeValue(CodeState * cs, Obj node)
{
    RequirePlainRec("SyntaxTreeCodeValue", node);
    UInt1 tnum = GetTypeTNum(node);
    Obj   value = ElmRecST(tnum, node, "value");
    Expr  expr = NewStatOrExpr(cs, tnum, sizeof(UInt), 0);
    Int   ix = AddValueToBody(cs, value);
    WRITE_EXPR(cs, expr, 0, ix);
    return expr;
}

static Expr SyntaxTreeCodeChar(CodeState * cs, Obj node)
{
    RequirePlainRec("SyntaxTreeCodeChar", node);
    Obj  chr = ElmRecST(EXPR_CHAR, node, "value");
    Char currchar = CHAR_VALUE(chr);
    Expr lit = NewStatOrExpr(cs, EXPR_CHAR, sizeof(UInt), 0);
    WRITE_EXPR(cs, lit, 0, currchar);
    return lit;
}

static Obj SyntaxTreeFunc(Obj result, Obj func)
{
    Obj stats;

    Bag oldFrame;
    Int narg;
    Int nloc;

    if (NAME_FUNC(func)) {
        AssPRec(result, RNamName("name"), NAME_FUNC(func));
    }

    narg = NARG_FUNC(func);
    if (narg < 0) {
        AssPRec(result, RNamName("variadic"), True);
        narg = -narg;
    }
    else {
        AssPRec(result, RNamName("variadic"), False);
    }
    AssPRec(result, RNamName("narg"), INTOBJ_INT(narg));
    nloc = NLOC_FUNC(func);
    AssPRec(result, RNamName("nloc"), INTOBJ_INT(nloc));

    // names of arguments and locals
    AssPRec(result, RNamName("nams"), NAMS_FUNC(func));

    // switch to this function (so that 'READ_STAT' and 'READ_EXPR' work)
    oldFrame = SWITCH_TO_NEW_LVARS(func, narg, nloc);
    stats = SyntaxTreeCompiler(OFFSET_FIRST_STAT);
    SWITCH_TO_OLD_LVARS(oldFrame);

    AssPRec(result, RNamName("stats"), stats);

    return result;
}

static Expr SyntaxTreeCodeFunc(CodeState * cs, Obj node)
{
    RequirePlainRec("SyntaxTreeCodeFunc", node);
    Int narg = INT_INTOBJ(ElmRecST(EXPR_FUNC, node, "narg"));
    Int nloc = INT_INTOBJ(ElmRecST(EXPR_FUNC, node, "nloc"));
    Obj nams = ElmRecST(EXPR_FUNC, node, "nams");
    Obj variadic = ElmRecST(EXPR_FUNC, node, "variadic");
    if (variadic == True) {
        narg = -narg;
    }
    CodeFuncExprBegin(cs, narg, nloc, nams, 0, 0);
    Obj  stat_rec = ElmRecST(EXPR_FUNC, node, "stats");
    Obj  body_stats = ElmRecST(EXPR_FUNC, stat_rec, "statements");
    UInt nr_stats = LEN_LIST(body_stats);
    for (int i = 1; i <= nr_stats; i++) {
        Expr current =
            SyntaxTreeDefaultStatCoder(cs, ELM_LIST(body_stats, i));
        PushStat(current);
    }
    return CodeFuncExprEnd(cs, nr_stats, FALSE, 0);
}

static Obj FuncSYNTAX_TREE_CODE(Obj self, Obj tree)
{
    RequirePlainRec(SELF_NAME, tree);
    CodeState cs;
    CodeBegin(&cs);
    SyntaxTreeCodeFunc(&cs, tree);
    Obj func = CodeEnd(&cs, 0);
    if (IsbPRec(tree, RNamName("name"))) {
        Obj name = ELM_REC(tree, RNamName("name"));
        SET_NAME_FUNC(func, name);
    }
    return func;
}

static const CompilerT Compilers[] = {
    COMPILER_(STAT_PROCCALL_0ARGS, ARG_EXPR_("funcref"), ARGS_EXPR("args")),
    COMPILER_(STAT_PROCCALL_1ARGS, ARG_EXPR_("funcref"), ARGS_EXPR("args")),
    COMPILER_(STAT_PROCCALL_2ARGS, ARG_EXPR_("funcref"), ARGS_EXPR("args")),
    COMPILER_(STAT_PROCCALL_3ARGS, ARG_EXPR_("funcref"), ARGS_EXPR("args")),
    COMPILER_(STAT_PROCCALL_4ARGS, ARG_EXPR_("funcref"), ARGS_EXPR("args")),
    COMPILER_(STAT_PROCCALL_5ARGS, ARG_EXPR_("funcref"), ARGS_EXPR("args")),
    COMPILER_(STAT_PROCCALL_6ARGS, ARG_EXPR_("funcref"), ARGS_EXPR("args")),
    COMPILER_(STAT_PROCCALL_XARGS, ARG_EXPR_("funcref"), ARGS_EXPR("args")),

    COMPILER_(STAT_PROCCALL_OPTS, ARG_EXPR_("opts"), ARG_STAT_("call")),

    COMPILER_(STAT_EMPTY),

    COMPILER_(STAT_SEQ_STAT, ARGS_STAT("statements")),
    COMPILER_(STAT_SEQ_STAT2, ARGS_STAT("statements")),
    COMPILER_(STAT_SEQ_STAT3, ARGS_STAT("statements")),
    COMPILER_(STAT_SEQ_STAT4, ARGS_STAT("statements")),
    COMPILER_(STAT_SEQ_STAT5, ARGS_STAT("statements")),
    COMPILER_(STAT_SEQ_STAT6, ARGS_STAT("statements")),
    COMPILER_(STAT_SEQ_STAT7, ARGS_STAT("statements")),

    COMPILER(STAT_IF, SyntaxTreeIf, SyntaxTreeCodeIf),
    COMPILER(STAT_IF_ELSE, SyntaxTreeIf, SyntaxTreeCodeIf),
    COMPILER(STAT_IF_ELIF, SyntaxTreeIf, SyntaxTreeCodeIf),
    COMPILER(STAT_IF_ELIF_ELSE, SyntaxTreeIf, SyntaxTreeCodeIf),

    COMPILER_(STAT_FOR,
              ARG_EXPR_("variable"),
              ARG_EXPR_("collection"),
              ARGS_STAT("body")),
    COMPILER_(STAT_FOR2,
              ARG_EXPR_("variable"),
              ARG_EXPR_("collection"),
              ARGS_STAT("body")),
    COMPILER_(STAT_FOR3,
              ARG_EXPR_("variable"),
              ARG_EXPR_("collection"),
              ARGS_STAT("body")),

    COMPILER_(STAT_FOR_RANGE,
              ARG_EXPR_("variable"),
              ARG_EXPR_("collection"),
              ARGS_STAT("body")),
    COMPILER_(STAT_FOR_RANGE2,
              ARG_EXPR_("variable"),
              ARG_EXPR_("collection"),
              ARGS_STAT("body")),
    COMPILER_(STAT_FOR_RANGE3,
              ARG_EXPR_("variable"),
              ARG_EXPR_("collection"),
              ARGS_STAT("body")),

    COMPILER_(STAT_WHILE, ARG_EXPR_("condition"), ARGS_STAT("body")),
    COMPILER_(STAT_WHILE2, ARG_EXPR_("condition"), ARGS_STAT("body")),
    COMPILER_(STAT_WHILE3, ARG_EXPR_("condition"), ARGS_STAT("body")),

    COMPILER_(STAT_REPEAT, ARG_EXPR_("condition"), ARGS_STAT("body")),
    COMPILER_(STAT_REPEAT2, ARG_EXPR_("condition"), ARGS_STAT("body")),
    COMPILER_(STAT_REPEAT3, ARG_EXPR_("condition"), ARGS_STAT("body")),

#ifdef HPCGAP
    COMPILER_(STAT_ATOMIC,
              ARG_STAT_("body"),
              ARGS_EXPR("locks")),    // TODO: better output
#endif

    COMPILER_(STAT_BREAK),
    COMPILER_(STAT_CONTINUE),
    COMPILER_(STAT_RETURN_OBJ, ARG_EXPR_("obj")),
    COMPILER_(STAT_RETURN_VOID),

    COMPILER_(STAT_ASS_LVAR,
              ARG_EXPR("lvar", ObjInt_UInt, SyntaxTreeCodeObjInt),
              ARG_EXPR_("rhs")),
    COMPILER_(STAT_UNB_LVAR,
              ARG_EXPR("lvar", ObjInt_UInt, SyntaxTreeCodeObjInt)),

    COMPILER_(STAT_ASS_HVAR,
              ARG_EXPR("hvar", ObjInt_UInt, SyntaxTreeCodeObjInt),
              ARG_EXPR_("rhs")),
    COMPILER_(STAT_UNB_HVAR,
              ARG_EXPR("hvar", ObjInt_UInt, SyntaxTreeCodeObjInt)),

    COMPILER_(STAT_ASS_GVAR,
              ARG_EXPR("gvar", NameGVar, SyntaxTreeCodeGVar),
              ARG_EXPR_("rhs")),
    COMPILER_(STAT_UNB_GVAR, ARG_EXPR("gvar", NameGVar, SyntaxTreeCodeGVar)),

    COMPILER_(
        STAT_ASS_LIST, ARG_EXPR_("list"), ARG_EXPR_("pos"), ARG_EXPR_("rhs")),
    COMPILER_(STAT_ASS_MAT,
              ARG_EXPR_("list"),
              ARG_EXPR_("row"),
              ARG_EXPR_("col"),
              ARG_EXPR_("rhs")),
    COMPILER_(STAT_ASSS_LIST,
              ARG_EXPR_("list"),
              ARG_EXPR_("poss"),
              ARG_EXPR_("rhss")),
    COMPILER_(STAT_ASS_LIST_LEV,
              ARG_EXPR_("lists"),
              ARG_EXPR_("pos"),
              ARG_EXPR_("rhss"),
              ARG_EXPR("level", ObjInt_UInt, SyntaxTreeCodeObjInt)),
    COMPILER_(STAT_ASSS_LIST_LEV,
              ARG_EXPR_("lists"),
              ARG_EXPR_("poss"),
              ARG_EXPR_("rhss"),
              ARG_EXPR("level", ObjInt_UInt, SyntaxTreeCodeObjInt)),
    COMPILER_(STAT_UNB_LIST, ARG_EXPR_("list"), ARG_EXPR_("pos")),

    COMPILER_(STAT_ASS_REC_NAME,
              ARG_EXPR_("record"),
              ARG_EXPR("rnam", SyntaxTreeRNam, SyntaxTreeCodeRNamObj),
              ARG_EXPR_("rhs")),
    COMPILER_(STAT_ASS_REC_EXPR,
              ARG_EXPR_("record"),
              ARG_EXPR_("expression"),
              ARG_EXPR_("rhs")),
    COMPILER_(STAT_UNB_REC_NAME,
              ARG_EXPR_("record"),
              ARG_EXPR("rnam", SyntaxTreeRNam, SyntaxTreeCodeRNamObj)),
    COMPILER_(
        STAT_UNB_REC_EXPR, ARG_EXPR_("record"), ARG_EXPR_("expression")),

    COMPILER_(STAT_ASS_POSOBJ,
              ARG_EXPR_("posobj"),
              ARG_EXPR_("pos"),
              ARG_EXPR_("rhs")),
    COMPILER_(STAT_UNB_POSOBJ, ARG_EXPR_("posobj"), ARG_EXPR_("pos")),

    COMPILER_(STAT_ASS_COMOBJ_NAME,
              ARG_EXPR_("comobj"),
              ARG_EXPR("rnam", SyntaxTreeRNam, SyntaxTreeCodeRNamObj),
              ARG_EXPR_("rhs")),
    COMPILER_(STAT_ASS_COMOBJ_EXPR,
              ARG_EXPR_("comobj"),
              ARG_EXPR_("expression"),
              ARG_EXPR_("rhs")),
    COMPILER_(STAT_UNB_COMOBJ_NAME,
              ARG_EXPR_("comobj"),
              ARG_EXPR("rnam", SyntaxTreeRNam, SyntaxTreeCodeRNamObj)),
    COMPILER_(
        STAT_UNB_COMOBJ_EXPR, ARG_EXPR_("comobj"), ARG_EXPR_("expression")),

    COMPILER_(
        STAT_INFO, ARG_EXPR_("sel"), ARG_EXPR_("lev"), ARGS_EXPR("args")),
    COMPILER_(STAT_ASSERT_2ARGS, ARG_EXPR_("level"), ARG_EXPR_("condition")),
    COMPILER_(STAT_ASSERT_3ARGS,
              ARG_EXPR_("level"),
              ARG_EXPR_("condition"),
              ARG_EXPR_("message")),
    COMPILER(STAT_PRAGMA, SyntaxTreeCompilePragma, SyntaxTreeCodeValue),


    // Statements
    COMPILER_(EXPR_FUNCCALL_0ARGS, ARG_EXPR_("funcref"), ARGS_EXPR("args")),
    COMPILER_(EXPR_FUNCCALL_1ARGS, ARG_EXPR_("funcref"), ARGS_EXPR("args")),
    COMPILER_(EXPR_FUNCCALL_2ARGS, ARG_EXPR_("funcref"), ARGS_EXPR("args")),
    COMPILER_(EXPR_FUNCCALL_3ARGS, ARG_EXPR_("funcref"), ARGS_EXPR("args")),
    COMPILER_(EXPR_FUNCCALL_4ARGS, ARG_EXPR_("funcref"), ARGS_EXPR("args")),
    COMPILER_(EXPR_FUNCCALL_5ARGS, ARG_EXPR_("funcref"), ARGS_EXPR("args")),
    COMPILER_(EXPR_FUNCCALL_6ARGS, ARG_EXPR_("funcref"), ARGS_EXPR("args")),
    COMPILER_(EXPR_FUNCCALL_XARGS, ARG_EXPR_("funcref"), ARGS_EXPR("args")),

    COMPILER(EXPR_FUNC, SyntaxTreeFuncExpr, SyntaxTreeCodeFunc),

    COMPILER_(EXPR_FUNCCALL_OPTS, ARG_EXPR_("opts"), ARG_EXPR_("call")),


    COMPILER_(EXPR_OR, ARG_EXPR_("left"), ARG_EXPR_("right")),
    COMPILER_(EXPR_AND, ARG_EXPR_("left"), ARG_EXPR_("right")),
    COMPILER_(EXPR_NOT, ARG_EXPR_("op")),
    COMPILER_(EXPR_EQ, ARG_EXPR_("left"), ARG_EXPR_("right")),
    COMPILER_(EXPR_NE, ARG_EXPR_("left"), ARG_EXPR_("right")),
    COMPILER_(EXPR_LT, ARG_EXPR_("left"), ARG_EXPR_("right")),
    COMPILER_(EXPR_GE, ARG_EXPR_("left"), ARG_EXPR_("right")),
    COMPILER_(EXPR_GT, ARG_EXPR_("left"), ARG_EXPR_("right")),
    COMPILER_(EXPR_LE, ARG_EXPR_("left"), ARG_EXPR_("right")),
    COMPILER_(EXPR_IN, ARG_EXPR_("left"), ARG_EXPR_("right")),
    COMPILER_(EXPR_SUM, ARG_EXPR_("left"), ARG_EXPR_("right")),
    COMPILER_(EXPR_AINV, ARG_EXPR_("op")),
    COMPILER_(EXPR_DIFF, ARG_EXPR_("left"), ARG_EXPR_("right")),
    COMPILER_(EXPR_PROD, ARG_EXPR_("left"), ARG_EXPR_("right")),
    COMPILER_(EXPR_QUO, ARG_EXPR_("left"), ARG_EXPR_("right")),
    COMPILER_(EXPR_MOD, ARG_EXPR_("left"), ARG_EXPR_("right")),
    COMPILER_(EXPR_POW, ARG_EXPR_("left"), ARG_EXPR_("right")),

    COMPILER(
        EXPR_INT, SyntaxTreeEvalCompiler, SyntaxTreeCodeImmediateInteger),
    COMPILER(EXPR_INTPOS, SyntaxTreeEvalCompiler, SyntaxTreeCodeValue),
    COMPILER_(EXPR_TRUE),
    COMPILER_(EXPR_FALSE),
    COMPILER_(EXPR_TILDE),
    COMPILER(EXPR_CHAR, SyntaxTreeEvalCompiler, SyntaxTreeCodeChar),
    COMPILER_(EXPR_PERM, ARGS_EXPR("cycles")),
    COMPILER_(EXPR_PERM_CYCLE, ARGS_EXPR("points")),
    COMPILER_(EXPR_LIST, ARGS_EXPR("list")),
    COMPILER_(EXPR_LIST_TILDE, ARGS_EXPR("list")),
    COMPILER(EXPR_RANGE, SyntaxTreeRangeExpr, SyntaxTreeCodeRangeExpr),
    COMPILER(EXPR_STRING, SyntaxTreeEvalCompiler, SyntaxTreeCodeValue),
    COMPILER(EXPR_REC, SyntaxTreeRecExpr, SyntaxTreeCodeRecExpr),
    COMPILER(EXPR_REC_TILDE, SyntaxTreeRecExpr, SyntaxTreeCodeRecExpr),

    COMPILER(
        EXPR_FLOAT_EAGER, SyntaxTreeFloatEager, SyntaxTreeCodeFloatEager),
    COMPILER(EXPR_FLOAT_LAZY, SyntaxTreeFloatLazy, SyntaxTreeCodeFloatLazy),

    // EXPR_REF_LVAR is encoded differently from all other
    //           references to LVARs, so we have to treat
    //           them specially here
    COMPILER(EXPR_REF_LVAR, SyntaxTreeRefLVar, SyntaxTreeCodeRefLVar),
    COMPILER_(EXPR_ISB_LVAR,
              ARG_EXPR("lvar", ObjInt_UInt, SyntaxTreeCodeObjInt)),

    COMPILER_(EXPR_REF_HVAR,
              ARG_EXPR("hvar", ObjInt_UInt, SyntaxTreeCodeObjInt)),
    COMPILER_(EXPR_ISB_HVAR,
              ARG_EXPR("hvar", ObjInt_UInt, SyntaxTreeCodeObjInt)),

    COMPILER_(EXPR_REF_GVAR, ARG_EXPR("gvar", NameGVar, SyntaxTreeCodeGVar)),
    COMPILER_(EXPR_ISB_GVAR, ARG_EXPR("gvar", NameGVar, SyntaxTreeCodeGVar)),

    // TODO: can this be unified?
    COMPILER_(EXPR_ELM_LIST, ARG_EXPR_("list"), ARG_EXPR_("pos")),
    COMPILER_(
        EXPR_ELM_MAT, ARG_EXPR_("list"), ARG_EXPR_("row"), ARG_EXPR_("col")),
    COMPILER_(EXPR_ELMS_LIST, ARG_EXPR_("list"), ARG_EXPR_("poss")),
    COMPILER_(EXPR_ELM_LIST_LEV,
              ARG_EXPR_("lists"),
              ARGS_EXPR("pos"),
              ARG_EXPR("level", ObjInt_UInt, SyntaxTreeCodeObjInt)),
    COMPILER_(EXPR_ELMS_LIST_LEV,
              ARG_EXPR_("lists"),
              ARG_EXPR_("poss"),
              ARG_EXPR("level", ObjInt_UInt, SyntaxTreeCodeObjInt)),
    COMPILER_(EXPR_ISB_LIST, ARG_EXPR_("list"), ARGS_EXPR("pos")),
    COMPILER_(EXPR_ELM_REC_NAME,
              ARG_EXPR_("record"),
              ARG_EXPR("name", SyntaxTreeRNam, SyntaxTreeCodeRNamObj)),
    COMPILER_(
        EXPR_ELM_REC_EXPR, ARG_EXPR_("record"), ARG_EXPR_("expression")),
    COMPILER_(EXPR_ISB_REC_NAME,
              ARG_EXPR_("record"),
              ARG_EXPR("name", SyntaxTreeRNam, SyntaxTreeCodeRNamObj)),
    COMPILER_(
        EXPR_ISB_REC_EXPR, ARG_EXPR_("record"), ARG_EXPR_("expression")),
    COMPILER_(EXPR_ELM_POSOBJ, ARG_EXPR_("posobj"), ARG_EXPR_("pos")),
    COMPILER_(EXPR_ISB_POSOBJ, ARG_EXPR_("posobj"), ARG_EXPR_("pos")),
    COMPILER_(EXPR_ELM_COMOBJ_NAME,
              ARG_EXPR_("comobj"),
              ARG_EXPR("name", SyntaxTreeRNam, SyntaxTreeCodeRNamObj)),
    COMPILER_(
        EXPR_ELM_COMOBJ_EXPR, ARG_EXPR_("comobj"), ARG_EXPR_("expression")),
    COMPILER_(EXPR_ISB_COMOBJ_NAME,
              ARG_EXPR_("comobj"),
              ARG_EXPR("name", SyntaxTreeRNam, SyntaxTreeCodeRNamObj)),
    COMPILER_(
        EXPR_ISB_COMOBJ_EXPR, ARG_EXPR_("comobj"), ARG_EXPR_("expression")),
};

static Obj FuncSYNTAX_TREE(Obj self, Obj func)
{
    Obj result;

    if (!IS_FUNC(func) || IsKernelFunction(func) || IS_OPERATION(func)) {
        RequireArgument(SELF_NAME, func, "must be a plain GAP function");
    }

    result = NewSyntaxTreeNode(EXPR_FUNC);
    return SyntaxTreeFunc(result, func);
}

static StructGVarFunc GVarFuncs[] = { GVAR_FUNC_1ARGS(SYNTAX_TREE, func),
                                      GVAR_FUNC_1ARGS(SYNTAX_TREE_CODE, tree),
                                      { 0, 0, 0, 0, 0 } };

static Int InitKernel(StructInitInfo * module)
{
    // init filters and functions
    InitHdlrFuncsFromTable(GVarFuncs);

    InitGlobalBag(&typeStrings, "typeStrings");
    InitGlobalBag(&typeRec, "typeRec");

    return 0;
}

static Int InitLibrary(StructInitInfo * module)
{
    // init filters and functions
    InitGVarFuncsFromTable(GVarFuncs);

    typeStrings = NEW_PLIST(T_PLIST, ARRAY_SIZE(Compilers));
    typeRec = NEW_PREC(0);
    for (UInt tnum = 0; tnum < ARRAY_SIZE(Compilers); tnum++) {
        const char * str = Compilers[tnum].name;
        if (str) {
            AssPRec(typeRec, RNamName(str), ObjInt_UInt(tnum));
            ASS_LIST(typeStrings, tnum + 1, MakeImmString(str));
        }
    }


    return 0;
}

static StructInitInfo module = {
    // init struct using C99 designated initializers; for a full list of
    // fields, please refer to the definition of StructInitInfo
    .type = MODULE_BUILTIN,
    .name = "syntaxtree",
    .initKernel = InitKernel,
    .initLibrary = InitLibrary,
};

StructInitInfo * InitInfoSyntaxTree(void)
{
    return &module;
}
