# set to 1 to trace object creation/destruction
CREATE $(TRACE) = 0

GOTO main


FUNCTION return_ifnot conds
    IF $(conds) ~ &
        P if (!($(conds:# && =))) return $(INIT|0);
    ENDIF
RETURN

FUNCTION index_or_return index,return
    IF $(index) !~ #
        P int index = aisc_talking_get_index(0, $(index));
        SET $(return) return $(return);
        P if (aisc_server_error) $(return: ;=;);
    ENDIF
RETURN

FUNCTION define_callback a,type,func
    CONST $(a) # releative key
    CONST $(type) # function type (get, set etc.)
    CONST $(func) # function name

    CREATE $(num) = $(+ $(a)+$(KEYC))
    CREATE $(cb) $(type)_list[$(num)]
    # $(cb) contains name of callback variable

    IF $($(cb))
        IF $($(cb)) ~ 0
        ELSE
            ERROR Number $(KEYC) for '$(func)' already in use by '$($(cb))'
        ENDIF
    ELSE
        ERROR Invalid number $(KEYC) for '$(func)'. Increase MAX_KEY to $(num) in $(argv[2])
    ENDIF
    SET $($(cb)) = $(func)
RETURN

FUNCTION accessors struct,a,c
    # struct = struct/member name
    # a = relative key (int)
    # c = conditions

    CONST $(struct)
    CONST $(a)
    CONST $(c)

    CREATE $(su) $(setup)
    CREATE $(ac) $(access)
    CREATE $(in) $(index)

    CONST $(su)
    CONST $(ac)
    CONST $(in)

    CREATE $(type)
    CREATE $(member)
    CREATE $(conds)
    CREATE $(fun)

    FOR $({/KEY)
        SET $(member) $(struct)$(KEY)
        SET $(conds) $(c)
        SET $(setup) $(su)

        IF $(TYPE) = dllh
            SET $(fun) get_$(member)PRED
            CALL define_callback $(a),get,$(fun)
            IF $(extern_$(fun))
            ELSE
                P static $(maintype) *$(fun)(const $(maintype) *THIS) {
                INDENT +1
                    CALL return_ifnot $(conds)
                    #P return ($(maintype) *)$(ac)previous;
                    P return $(ac)previous;
                INDENT -1
                P }
            ENDIF

            SET $(fun) get_$(member)NEXT
            CALL define_callback $(+ $(a)+1),get,$(fun)
            IF $(extern_$(fun))
            ELSE
                P static $(maintype) *$(fun)(const $(maintype) *THIS) {
                INDENT +1
                    CALL return_ifnot $(conds)
                    #P return ($(maintype) *)$(ac)next;
                    P return $(ac)next;
                INDENT -1
                P }
            ENDIF

            PUSH
            CREATE $(subadd) $(+ 2+$(+ $(a)+$(KEYC)))
            SET $(access) $(ac)$(IDENT).
            MOVETO $(/AISC/DATA/STRUCT.dll_header)
            GOSUB accessors $(member),$(subadd),$(conds)
            POP
            CONTINUE
        ENDIF
 
        SET $(index) $(in)
        SET $(access) $(ac)$(IDENT)

        IF $(REF) ~ *
            SET $(conds) $(conds) && $(access)
            SET $(index) $(SIZE:THIS->=$(ac))
            IF $(REF) ~ o,d,l,r
                SET $(type) $(TYPE) *
            ELSE
                SET $(type) $(TYPE)
            ENDIF
            SET $(setup) $(setup)if (!$(access)) $(access) = ($(type)*)calloc(sizeof($(type)),$(index));$n
            SET $(access) $(access)[index]
        ELSEIF $(REF) ~ v
            SET $(index) $(SIZE:THIS->=$(ac))
            SET $(access) $(access)[index]
        ENDIF

        IF $(REF) ~ s
            IF $(REF) ~ o,l,r
                SET $(conds) $(conds) && $(access)
                SET $(setup) $(setup)if (!$(access)) $(access) = create_$(TYPE)();$n
                SET $(access) $(access)->
            ELSEIF $(REF) ~ i
                SET $(access) $(access).
            ENDIF

            PUSH
            CREATE $(subadd) = $(+ $(a)+$(KEYC))
            MOVETO $(/AISC/DATA/STRUCT.$(TYPE))
            CALL accessors $(member),$(subadd),$(conds)
            POP
            CONTINUE
        ENDIF

        IF $(REF) ~ o,d
            IF $(ACC) !~ c
            ELSE
                # CREATE FUNCTIONS
                SET $(fun) create_$(member)
                CALL define_callback $(a),create,$(fun)
                IF $(extern_$(fun))
                ELSE
                    P static $(TYPE) *$(fun)($(maintype) *THIS) {
                    INDENT +1
                        CALL return_ifnot $(conds)
                        CALL index_or_return $(index),0
                        P $(TYPE) *obj = create_$(TYPE)();
                        P if (obj) { 
                        INDENT +1
                            P aisc_make_sets((long *)obj);
                            P if (aisc_server_error) {
                            INDENT +1
                                P destroy_$(TYPE)(obj);
                                P obj = NULL;
                            INDENT -1
                            P }
                            P else {
                            INDENT +1
                                IF $(REF) ~ d
                                    P aisc_server_error = aisc_link((dllpublic_ext*)&($(ac)p$(IDENT)), (dllheader_ext*)obj);
                                ELSE
                                    P if ($(access)) destroy_$(TYPE)($(access));
                                    P $(access) = obj;
                                ENDIF
                            INDENT -1
                            P }
                        INDENT -1
                        P }
                        P return obj;
                    INDENT -1
                    P }
                ENDIF

                # COPY FUNCTIONS
                SET $(fun) copy_$(member)
                CALL define_callback $(a),copy,$(fun)
                IF $(extern_$(fun))
                ELSE
                    P static $(TYPE) *$(fun)($(maintype) *THIS) {
                    INDENT +1
                        CALL return_ifnot $(conds)
                        CALL index_or_return $(index),0
                        P trf_begin();
                        P $(TYPE) *obj = copy_$(TYPE)($(access));
                        P trf_commit(0);
                        P if (obj) { 
                        INDENT +1
                            P aisc_make_sets((long *)obj);
                            P if (aisc_server_error) {
                            INDENT +1
                                P destroy_$(TYPE)(obj);
                                P obj = NULL;
                            INDENT -1
                            P }
                            P else {
                            INDENT +1
                                IF $(REF) ~ d
                                    P aisc_server_error = aisc_link((dllpublic_ext*)&($(ac)p$(IDENT)), (dllheader_ext*)obj);
                                ELSE
                                    P if ($(access)) destroy_$(TYPE)($(access));
                                    P $(access) = obj;
                                ENDIF
                            INDENT -1
                            P }
                        INDENT -1
                        P }
                        P return obj;
                    INDENT -1
                    P }
                ENDIF
            ENDIF
            # FIND FUNCTIONS
            IF $(ACC) !~ f
            ELSEIF $(REF) ~ d
                SET $(fun) find_$(member)
                CALL define_callback $(a),find,$(fun)
                IF $(extern_$(fun))
                ELSE
                    P static $(TYPE) *$(fun)(const $(maintype) *THIS, const char *key) {
                    INDENT +1
                        CALL return_ifnot $(conds)
                        P $(TYPE) *obj = ($(TYPE)*)aisc_read_hash((aisc_hash_node**)$(ac)p$(IDENT).hash, key);
                        P return obj;
                    INDENT -1
                    P }
                ENDIF
            ENDIF
            # CNT FUNCTION
            IF $(REF) ~ d
                SET $(fun) get_$(member)_CNT
                CALL define_callback $(+ $(a)+1),get,$(fun)
                IF $(extern_$(fun))
                ELSE
                    P static int $(fun)(const $(maintype) *THIS) {
                    INDENT +1
                        CALL return_ifnot $(conds)
                        P return $(ac)p$(IDENT).cnt;
                    INDENT -1
                    P }
                ENDIF
            ENDIF
        ELSE
            # not $(REF) ~ o,d

            # SET FUNCTIONS
            IF $(ACC) !~ w
            ELSEIF $(REF) ~ $(v)
                CALL define_callback $(a),set,$(IDENT)
                IF $(extern_$(IDENT))
                ELSE
                    OUT import # Generate Prototypes for import
                    P extern int $1$(IDENT)($(maintype)*, $(TYPE:bytestring=bytestring *));
                    OUT save
                ENDIF
            ELSE
                SET $(fun) set_$(member)
                CALL define_callback $(a),set,$(fun)
                IF $(extern_$(fun))
                ELSE
                    IF $(REF) ~ o,l,r
                        P static void $(fun)($(maintype) *THIS, const $(TYPE) *x) {
                    ELSEIF $(REF) ~ t,e
                        P static void $(fun)($(maintype) *THIS, $(TYPE:bytestring=const bytestring *) x) {
                    ELSE
                        ERROR UNKNOWN REF '$(REF|)' in $(STRUCT).$(TYPE)
                    ENDIF
                    INDENT +1
                        CALL index_or_return $(index),
                        P $(setup)$\
                        IF $(TYPE) = aisc_string
                            P freeset($(access), x);
                        ELSEIF $(TYPE) = bytestring
                            P if (x) {
                                P $1freeset($(access).data, x->data);
                                P $1$(access).size = x->size;
                            P }
                            P else {
                                P $1freenull($(access).data);
                                P $1$(access).size = 0;
                            P }
                        ELSE
                            IF $(REF) ~ o
                                P if ($(access)) destroy_$(TYPE)($(access));
                            ENDIF
                            P $(access) = x;
                        ENDIF
                    INDENT -1
                    P }
                ENDIF
            ENDIF
        ENDIF

        # GET FUNCTIONS
        IF $(ACC) !~ r
        ELSEIF $(REF) ~ $(v)
            IF $(extern_$(IDENT))
                CALL define_callback $(a),get,$(IDENT)
            ELSE
                IF $(IDENT) = aisc_get_keystring
                    # special cast for aisc_get_keystring
                    CALL define_callback $(a),get,(const char*(*)(int*))$(IDENT)
                ELSE
                    # explicit cast to overloaded function type before casting to void
                    CALL define_callback $(a),get,($(TYPE:bytestring=bytestring *)(*)(const $(maintype)*))$(IDENT)
                ENDIF
                OUT import      # Generate Prototypes for import
                P extern $(TYPE:bytestring=bytestring *) $(IDENT)(const $(maintype)*);
                OUT save
            ENDIF
        ELSE
            CALL define_callback $(a),get,get_$(member)
            IF $(extern_get_$(member))
            ELSE
                P static $\
                IF $(REF) ~ o,l,r,d
                    P const $(TYPE) *get_$(member)$\
                ELSEIF $(REF) ~ e,t
                    P $(TYPE:bytestring=const bytestring *:aisc_string=aisc_cstring) get_$(member)$\
                ELSE
                    ERROR UNKNOWN REF '$(REF|)' in $(STRUCT).$(TYPE)
                ENDIF
                P (const $(maintype) *THIS) {
                INDENT +1
                    CALL return_ifnot $(conds)
                    CALL index_or_return $(index),0
                    IF $(TYPE) = bytestring
                        P return &($(access));
                    ELSE
                        P return $(access);
                    ENDIF
                INDENT -1
                P }
            ENDIF
        ENDIF
    ENDFOR
RETURN

FUNCTION accessor_table fun
    P void *aisc_talking_functions_$(fun)_$(STRUCT)[] = {
    INDENT +1
        CREATE $(i)
        CREATE $(cb)
        CREATE $(zeros)
        FOR $(i) = 0 TO $(MAX_KEY)
            SET $(cb) = $($(fun)_list[$(i)])
            IF $(cb) = 0
                # collect multiple '0' to print them in one line
                SET $(zeros) = $(zeros) 0,
            ELSE
                IF $(zeros) !=
                    P $(zeros)
                    SET $(zeros)
                ENDIF

                IF $(cb) ~ /*
                    SET $(cb) = $(cb:/*=,/*: ,/*=,/*:,/*=, /*)
                ELSEIF $(cb) ~ //
                    SET $(cb) = $(cb://=,//: ,//=,//:,//=, //)
                ELSE
                    SET $(cb) = $(cb),
                ENDIF

                P (void *)$(cb)
            ENDIF
        ENDFOR
        IF $(zeros) !=
            P $(zeros) 0
        ELSE
            P 0
        ENDIF
    INDENT -1
    P };
RETURN

FUNCTION accessor_tables
    CALL accessor_table get
    CALL accessor_table set
    CALL accessor_table create
    CALL accessor_table find
    CALL accessor_table copy
    --
RETURN

FUNCTION build_accessors
    # init callback tables
    CREATE $(i) = 0
    FOR $(i) = 0 TO $(MAX_KEY)
        CREATE $(set_list[$(i)]) 0
        CREATE $(get_list[$(i)]) 0
        CREATE $(create_list[$(i)]) 0
        CREATE $(find_list[$(i)]) 0
        CREATE $(copy_list[$(i)]) 0
    ENDFOR

    #CREATE $(conds)
    CREATE $(access)
    CREATE $(maintype)
    CREATE $(setup)
    CREATE $(index) #

    CREATE $(joined)

    FOR $(STRUCT)
        IF $(SKEY)
            P // $(STRUCT) accessors
            --

            IF $(JOINED)
            ELSE
                FOR $(i) = 0 TO $(MAX_KEY)
                    SET $(set_list[$(i)]) 0
                    SET $(get_list[$(i)]) 0
                    SET $(create_list[$(i)]) 0
                    SET $(find_list[$(i)]) 0
                    SET $(copy_list[$(i)]) 0
                ENDFOR
            ENDIF
            #SET $(conds) = #
            SET $(access) THIS->
            SET $(maintype) $(STRUCT)
            # 'setup' can not be transformed to parameter, cause it is
            # assigned sth containing ,; etc. so we need to use a global here :-(
            SET $(setup)
            SET $(index) #
            GOSUB accessors $(SKEY)_,0,#
            --

            IF $(JOIN_NEXT)
                SET $(joined) = $(joined) $(STRUCT)
            ELSE
                P // $(joined) $(STRUCT) callback tables
                SET $(joined) =
                GOSUB accessor_tables
            ENDIF
        ENDIF
    ENDFOR
RETURN

# --------------------------------------------------------------------------------

FUNCTION init_structure,self,deref
    CREATE $(member)
    CREATE $(isloop)

    FOR $({/TYPE)
        SET $(member) $(self)$(deref)$(IDENT)
        SET $(isloop) 0
        IF $(REF) ~ $(v)
        ELSE
            IF $(REF) ~ v
                SET $(isloop) 1
                PMSTART
                PM for (int index = 0; index < $(SIZE:THIS->=$(self)$(deref)); index++) {
                INDENT +1
                SET $(member) $(member)[index]
            ENDIF
            IF $(TYPE) = dllh
                P $(member).key $|= KEY_$(OBJECT_KEY)_$(SKEY|COMMON);
                PUSH
                MOVETO $(/AISC/DATA/STRUCT.dll_header)
                GOSUB init_structure,$(member),.
                POP
            ELSEIF $(REF) ~ o
                # just a NULL pointer - no init necessary
            ELSEIF $(REF) ~ d
                PUSH
                    SET $(member) = $(self)$(deref)p$(IDENT)
                    MOVETO $(/AISC/DATA/STRUCT.$(TYPE))
                    P $(member).parent $|= (dllheader_ext *)$(self);
                    P $(member).key $|= KEY_$(OBJECT_KEY)_$(SKEY|SKEY_missing);
                POP
            ELSEIF $(REF) ~ s
                IF $(REF) ~ i
                    PUSH
                    MOVETO $(/AISC/DATA/STRUCT.$(TYPE))
                    GOSUB init_structure,$(member),.
                    POP
                ENDIF
            ELSEIF $(REF) ~ t,e,l
                IF $(IDENT) = key
                    IF $(SKEY) !~ COMMON
                        P $(member) $|= KEY_$(OBJECT_KEY)_$(SKEY|COMMON);
                    ENDIF
                ELSEIF $(INIT)
                    IF $(TYPE) = aisc_string
                        P $(member) $|= strdup($(INIT:THIS=$(self)));
                    ELSE
                        P $(member) $|= $(INIT:THIS=$(self));
                    ENDIF
                ENDIF
            ELSE
                ERROR UNKNOWN REF '$(REF|)' in $(STRUCT).$(TYPE)
            ENDIF
            IF $(isloop) = 1
                INDENT -1
                PM }
                PMEND
            ENDIF
        ENDIF
    ENDFOR
RETURN

FUNCTION build_init
    P // ------------
    P // constructors
    --
    P inline void alloc_err(const char *objectname) {
    P $1aisc_server_errorf("Could not allocate '%s'", objectname);
    P }
    --
    
    FOR $(STRUCT)
        IF $(extern_create_$(STRUCT))
        ELSE
            P $(STRUCT) *create_$(STRUCT)()$\
            P  {
            INDENT +1
                P $(STRUCT) *o = ($(STRUCT)*)calloc(sizeof(*o), 1);
                P if (!o) alloc_err("$(STRUCT)");
                PMSTART
                PM else {
                INDENT +1
                    GOSUB init_structure o,->
                INDENT -1
                PM }
                PMEND
                GOSUB trace "created $(STRUCT)"
                P return o;
            INDENT -1
            P }
        ENDIF
        --
    ENDFOR
RETURN

FUNCTION delete_structure,self,deref
    CREATE $(member)
    CREATE $(isloop)
    CREATE $(isinif)
    CREATE $(start)

    FOR $({/TYPE)
        SET $(member) $(self)$(deref)$(IDENT)
        SET $(isloop) 0
        SET $(isinif) 0

        IF $(DESTROY)
            P $(DESTROY:THIS=obj);
        ELSEIF $(REF) ~ $(v)
            # virtual function
        ELSE
            IF $(REF) ~ *,v
                PMSTART
                IF $(REF) ~ *
                    SET $(isinif) 1 
                    PM if ($(member)) {
                    INDENT +1
                ENDIF
                SET $(isloop) 1
                PM for (int index = 0; index < $(SIZE:THIS->=$(self)$(deref)); index++) {
                INDENT +1
                SET $(member) $(member)[index]
            ENDIF
            IF $(TYPE) = dllh
                PUSH
                MOVETO $(/AISC/DATA/STRUCT.dll_header)
                GOSUB delete_structure,$(member),.
                POP
            ELSEIF $(REF) ~ s
                IF $(REF) ~ o,d,i
                    PUSH
                    IF $(REF) ~ i
                        CREATE $(subderef) .
                    ELSE
                        P if ($(member)) {
                        INDENT +1
                        CREATE $(subderef) ->
                    ENDIF
                    MOVETO $(/AISC/DATA/STRUCT.$(TYPE))
                    GOSUB delete_structure,$(member),$(subderef)
                    POP
                    IF $(REF) !~ i
                        P free($(member));
                        INDENT -1
                        P }
                    ENDIF
                ENDIF
            ELSEIF $(REF) ~ d,o
                IF $(REF) ~ d
                    P while $\
                ELSE
                    P if $\
                ENDIF
                P ($(member)) {
                INDENT +1
                    P destroy_$(TYPE)($(member));
                    P if (aisc_server_error) return;
                INDENT -1
                P }
            ELSEIF $(REF) ~ t,e
                IF $(TYPE) = aisc_string
                    P free($(member));
                ELSEIF $(TYPE) = bytestring
                    P free($(member).data);
                ENDIF
            ELSEIF $(REF) ~ l
            ELSE
                ERROR UNKNOWN REF '$(REF|)' in $(STRUCT).$(TYPE)
            ENDIF

            IF $(isloop) = 1
                INDENT -1
                PM }
                IF $(isinif) = 1
                    INDENT -1
                    PM }
                ENDIF
                PMEND
            ENDIF
        ENDIF
    ENDFOR
RETURN

FUNCTION build_delete
    P // -----------
    P // destructors
    --
    
    FOR $(STRUCT)
        IF $(extern_destroy_$(STRUCT))
        ELSE
            PUSH
            CREATE $(islinked) = 0
            CREATE $(ident)
    
            PUSH
            MOVETO $({/.)
            IF $(TYPE) = dllh
                SET $(islinked) = 1
                SET $(ident) = $(IDENT)
            ENDIF
            POP
    
            # pass pointers to not-linked objects by reference and set them to NULL below
            # linked object-pointers are set to NULL by aisc_unlink  
            P void destroy_$(STRUCT)($(STRUCT)$\
            IF $(islinked) = 1
                P  *obj$\
            ELSE
                P *& obj$\
            ENDIF
            P ) {
            INDENT +1
                IF $(islinked) = 1
                    P if (obj->$(ident).parent) {
                    INDENT +1
                        P aisc_unlink((dllheader_ext*)obj);
                        P if (aisc_server_error) return;
                    INDENT -1
                    P }
                ENDIF
                GOSUB delete_structure,obj,->
                IF $(islinked) = 1
                    P free(obj);
                ELSE
                    P freenull(obj);
                ENDIF
                GOSUB trace "destroyed $(STRUCT)"
            INDENT -1
            P }
            --
            POP
        ENDIF
    ENDFOR
RETURN

FUNCTION move_structure
    CREATE $(isloop)
    CREATE $(my_key)
    CREATE $(index)
    CREATE $(access)

    FOR $({/TYPE)
        SET $(index)
        SET $(access) $(IDENT)
        SET $(isloop) 0
        IF $(REF) ~ $(v)
        ELSEIF $(IDENT) = key
        ELSE
            IF $(REF) ~ *,v
                IF $(SIZE)
                    SET $(index) $(SIZE:THIS->=sobj->)
                ELSE
                    ERROR Missing SIZE in STRUCT $(STRUCT) ident $(IDENT)
                ENDIF
                IF $(REF) ~ *
                    P if (dobj->$(access))$\
                ENDIF

                SET $(isloop) 1
                PMSTART
                PM for (int index = 0; index < $(index); index++) {
                INDENT +1
                SET $(access) $(access)[index]
            ENDIF
            IF $(TYPE) = dllh
                P move_dll_header(&(sobj->$(access)), &(dobj->$(access)));
            ELSEIF $(REF) ~ l,r
                P dobj->$(access) $|= sobj->$(access); $\
                P $|trf_link((long)sobj->$(access), (long*)&dobj->$(access));
            ELSEIF $(REF) ~ o
                P dobj->$(access) $|= copy_$(TYPE)(sobj->$(access));
            ELSEIF $(REF) ~ d
                P for ($(TYPE) *cobj = sobj->$(access); cobj; cobj = cobj->next) {
                INDENT +1
                    P $(TYPE) *d2obj = copy_$(TYPE)(cobj);
                    P if (!d2obj) return 1;
                    P aisc_server_error = aisc_link((dllpublic_ext*)&(dobj->p$(access)), (dllheader_ext*)d2obj);
                    P if (aisc_server_error) return 1;
                INDENT -1
                P }
            ELSEIF $(REF) ~ s
                IF $(REF) ~ i
                    P if (move_$(TYPE)(&(sobj->$(access)), &(dobj->$(access)))) return 1;
                ENDIF
            ELSEIF $(REF) ~ t,e
                IF $(TYPE) = aisc_string
                    P dobj->$(access) $|= nulldup(sobj->$(access));
                ELSEIF $(TYPE) = bytestring
                    P if (sobj->$(access).data) {
                    INDENT +1
                        P dobj->$(access).data = (char *)malloc(sobj->$(access).size);
                        P memcpy(dobj->$(access).data, sobj->$(access).data, sobj->$(access).size);
                        P dobj->$(access).size = sobj->$(access).size;
                    INDENT -1
                    P }
                ELSE
                    P dobj->$(access) $|= sobj->$(access);
                ENDIF
            ELSE
                PP UNKNOWN REF '$(REF|)' in $(STRUCT).$(TYPE)
            ENDIF
            IF $(isloop) = 1
                INDENT -1
                PM }
                PMEND
            ENDIF
        ENDIF
    ENDFOR
RETURN

FUNCTION build_move
    P // ------------
    P // move objects
    --
    FOR $(STRUCT)
        IF $(extern_move_$(STRUCT))
        ELSE
            P static int move_$(STRUCT)(const $(STRUCT) *sobj, $(STRUCT) *dobj) {
            INDENT +1
                GOSUB move_structure
                P return 0;
            INDENT -1
            P }
            --
        ENDIF
    ENDFOR
RETURN

FUNCTION build_copy
    P // ------------
    P // copy objects
    --
    FOR $(STRUCT)
        IF $(extern_copy_$(STRUCT))
        ELSE
            P $(STRUCT) *copy_$(STRUCT)(const $(STRUCT) *sobj) {
            INDENT +1
                P $(STRUCT) *dobj = NULL;
                P if (sobj) {
                INDENT +1
                    P dobj = create_$(STRUCT)();
                    P if (!dobj) alloc_err("$(STRUCT)");
                    P else {
                    INDENT +1
                        P trf_create((long)sobj, (long)dobj);
                        P if (move_$(STRUCT)(sobj, dobj)) {
                        INDENT +1
                            P destroy_$(STRUCT)(dobj);
                            P dobj = NULL;
                        INDENT -1
                        P }
                    INDENT -1
                    P }
                INDENT -1
                P }
                GOSUB trace "copied $(STRUCT)"
                P return dobj;
            INDENT -1
            P }
            --
        ENDIF
    ENDFOR
RETURN

FUNCTION save_structure self,deref,prekey,fp
    CREATE $(member)
    CREATE $(isloop)
    CREATE $(isinif)
    CREATE $(saved_elems) = 0

    FOR $({/TYPE)
        SET $(member) $(self)$(deref)$(IDENT)
        SET $(isloop) 0
        SET $(isinif) 0

        IF $(REF) ~ $(v)
            # virtual function
        ELSEIF $(SAVE)
            SET $(saved_elems) = $(+ $(saved_elems)+1)
            IF $(REF) ~ *,v
                PMSTART
                IF $(REF) ~ *
                    SET $(isinif) 1 
                    PM if ($(access)) {
                    INDENT +1
                ENDIF
                SET $(isloop) 1
                PM for (int index = 0; index < $(SIZE:THIS->=$(self)$(deref); index++) {
                INDENT +1
                SET $(member) $(member)[index]
            ENDIF
            IF $(TYPE) = dllh
                PUSH
                CREATE $(pk) $(prekey)$(KEY)
                MOVETO $(/AISC/DATA/STRUCT.dll_header)
                GOSUB save_structure,$(member),.,$(pk),$(fp)
                POP
            ELSEIF $(REF) ~ s
                IF $(REF) ~ o,d,i
                    ERROR previously unused - check generated code!
                    PUSH
                    IF $(REF) ~ i
                        CREATE $(subderef) .
                    ELSE
                        PMSTART
                        PM if ($(member)) {
                        INDENT +1
                        CREATE $(subderef) ->
                    ENDIF
                    CREATE $(pk) $(prekey)$(KEY|NOKEYDEFINED)
                    MOVETO $(/AISC/DATA/STRUCT.$(TYPE))
                    GOSUB save_structure,$(member),$(subderef),$(pk),$(fp)
                    POP
                    IF $(REF) !~ i
                        INDENT -1
                        PM }
                        PMEND
                    ENDIF
                ENDIF
            ELSEIF $(REF) ~ d
                IF $(saveable_$(TYPE)) = 0
                    ERROR Can't save $(TYPE) (has no saveable members) 
                ENDIF
                P for ($(TYPE) *s = $(member); s; s = s->next) {
                INDENT +1
                    P fputs("$(prekey)$(KEY|NOKEYDEFINED){ ", $(fp));
                    P save_$(TYPE)(s, $(fp));
                    P fputs("}\n", $(fp));
                INDENT -1
                P }
            ELSEIF $(REF) ~ o
                ERROR previously unused - check generated code!
                IF $(saveable_$(TYPE)) = 0
                    ERROR Can't save $(TYPE) (has no saveable members) 
                ENDIF
                P fputs("$(prekey)$(KEY) {\n", $(fp));
                P save_$TYPE($(member), $(fp));
                P fputs("}\n", $(fp));
            ELSEIF $(REF) ~ t,e
                IF $(TYPE) = aisc_string
                    P if ($(member)) $|save_string("$(prekey)$(KEY|NOKEYDEFINED)", $(member), $(fp));
                ELSEIF $(TYPE) = int,long,char
                    P fprintf($(fp), "$(prekey)$(KEY|NOKEYDEFINED) %i\n", $(member));
                ELSEIF $(TYPE) = float,double
                    P fprintf($(fp), "$(prekey)$(KEY|NOKEYDEFINED) %f\n", $(member));
                ELSE
                    ERROR CANNOT SAVE TYPE '$(TYPE)'
                ENDIF
            ELSEIF $(REF) ~ l
                P fprintf($(fp), "$(prekey)$(KEY|NOKEYDEFINED) %i", $(member));
            ELSE
                ERROR UNKNOWN REF '$(REF|)' in $(STRUCT).$(TYPE)
            ENDIF
            IF $(isloop) = 1
                INDENT -1
                PM }
                IF $(isinif) = 1
                    INDENT -1
                    PM }
                ENDIF
                PMEND
            ENDIF
        ENDIF
    ENDFOR
    IF $(saved_elems) = 0
        ERROR It makes no sense to save $(STRUCT), since it has no saveable data elements
    ENDIF
RETURN

FUNCTION load_structure self,deref,prekey,fp
    CREATE $(member)
    CREATE $(isloop)
    CREATE $(isinif)

    FOR $({/TYPE)
        SET $(member) $(self)$(deref)$(IDENT)
        SET $(isloop) 0
        SET $(isinif) 0

        IF $(REF) ~ $(v)
            # virtual function
        ELSEIF $(SAVE)
            IF $(REF) ~ *,v
                PMSTART
                IF $(REF) ~ *
                    SET $(isinif) 1 
                    PM if ($(member)) {
                    INDENT +1
                ENDIF
                SET $(isloop) 1
                PM for (int index = 0; index < $(SIZE:THIS->=$(self)$(deref); index++) {
                INDENT +1
                SET $(member) $(member)[index]
            ENDIF
            IF $(TYPE) = dllh
                PUSH
                CREATE $(pk) $(prekey)$(KEY)
                MOVETO $(/AISC/DATA/STRUCT.dll_header)
                GOSUB load_structure,$(member),.,$(pk),$(fp)
                POP
            ELSEIF $(REF) ~ s
                IF $(REF) ~ o,d,i
                    ERROR previously unused - check generated code!
                    PUSH
                    IF $(REF) ~ i
                        CREATE $(subderef) .
                    ELSE
                        PMSTART
                        PM if ($(member)) {
                        INDENT +1
                        CREATE $(subderef) ->
                    ENDIF
                    CREATE $(pk) $(prekey)$(KEY|NOKEYDEFINED)
                    MOVETO $(/AISC/DATA/STRUCT.$(TYPE))
                    GOSUB load_structure,$(member),$(subderef),$(pk),$(fp)
                    POP
                    IF $(REF) !~ i
                        INDENT -1
                        PM }
                        PMEND
                    ENDIF
                ENDIF
            ELSEIF $(REF) ~ d
                P if (strcmp("$(prekey)$(KEY|NOKEYDEFINED){", token)==0) {
                INDENT +1
                    P $(TYPE) *cobj = create_$(TYPE)();
                    P if (cobj) {
                    INDENT +1
                        P if (load_$(TYPE)(cobj, $(fp))==0) {  
                        INDENT +1
                            P aisc_link((dllpublic_ext*)&($(self)$(deref)p$(IDENT)), (dllheader_ext*)cobj);
                            P continue;
                        INDENT -1
                        P } 
                        P destroy_$(TYPE)(cobj);
                    INDENT -1
                    P }
                    P return 1;
                INDENT -1
                P }
            ELSEIF $(REF) ~ o
                ERROR code generated here is broken (does save instead of load)
                P fprintf($(fp),"$(prekey)$(KEY) {\n");
                P { int error = save_$TYPE($(member),$(fp));
                P if (error) return error; }
                P fprintf($(fp),"}\n");
            ELSEIF $(REF) ~ t,e
                P if (strcmp("$(prekey)$(KEY|NOKEYDEFINED)", token)==0) {
                INDENT +1
                    P if (aisc_server_load_token($(fp), token, $(TOKENSIZE))) return 1;
                    IF $(TYPE) = aisc_string
                        P $(member) = strdup(token);
                    ELSEIF $(TYPE) = int,long,char
                        P $(member) = atoi(token);
                    ELSEIF $(TYPE) = float,double
                        P $(member) = atof(token);
                    ELSE
                        ERROR Loading type '$(TYPE)' not implemented (yet)
                    ENDIF
                    P continue;
                INDENT -1
                P }
            ELSEIF $(REF) ~ l
                ERROR loading links not implemented (yet)
            ELSE
                ERROR UNKNOWN REF '$(REF|)' in $(STRUCT).$(TYPE)
            ENDIF
            IF $(isloop) = 1
                INDENT -1
                PM }
                IF $(isinif) = 1
                    INDENT -1
                    PM }
                ENDIF
                PMEND
            ENDIF
        ENDIF
    ENDFOR
RETURN

FUNCTION build_saveload
    CREATE $(TOKENSIZE) 1024

    P // -------------
    P // save and load
    --
    P inline void warn_unknown_token(const char *token, const char *objectname) {
    P $1fprintf(stderr, "Unknown token '%s' while loading '%s' (ignored)\n", token, objectname);
    P }
    --
    P inline void save_string(const char *key, const char *value, FILE *out) {
    INDENT +1
        P fputs(key, out);
        P fputc(' ', out);
        P aisc_server_save_token(out, value, $(TOKENSIZE));
    INDENT -1
    P }
    --

    FOR $(STRUCT)
        CREATE $(saveable_$(STRUCT)) = 0
        FOR $({/TYPE)
            IF $(SAVE)
                SET $(saveable_$(STRUCT)) = 1
            ENDIF
        ENDFOR
        #WARNING saveable_$(STRUCT) = $(saveable_$(STRUCT))
    ENDFOR

    FOR $(STRUCT)
        IF $(saveable_$(STRUCT)) = 1
            IF $(extern_save_$(STRUCT))
            ELSE
                P void save_$(STRUCT)(const $(STRUCT) *o, FILE *out) {
                INDENT +1
                    GOSUB save_structure,o,->,,out
                INDENT -1
                P }
                --
            ENDIF
        ENDIF
    ENDFOR
    FOR $(STRUCT)
        IF $(saveable_$(STRUCT)) = 1
            IF $(extern_load_$(STRUCT))
            ELSE
                P int load_$(STRUCT)($(STRUCT) *o, FILE *in) {
                INDENT +1
                    P char token[$(TOKENSIZE)+1];
                    P while (aisc_server_load_token(in, token, $(TOKENSIZE))==0) {
                    INDENT +1
                        P if (*token == '}') break;
                        GOSUB load_structure,o,->,,in
                        P warn_unknown_token(token, "$(STRUCT)");
                    INDENT -1
                    P }
                    P return 0;
                INDENT -1
                P }
                --
            ENDIF
        ENDIF
    ENDFOR
RETURN

FUNCTION trace msg
    IF $(TRACE) = 1
        P fputs("AISC_SERVER_TRACE " $(msg) "\n", stderr); fflush(stderr);
    ENDIF
RETURN

FUNCTION file_header
    P // -----------------------------------------------------------------
    P // Created automagically from ../$(argv[2]) 
    P //                        and ../$(argv[4])
    P // using aisc-script '../AISC/aisc_server.pa'
    P // DO NOT EDIT THIS FILE!!!
    P // -----------------------------------------------------------------
    --
RETURN

# --------------------------------------------------------------------------------

LABEL main
IF $(argc) < 7
    ERROR Wrong number of parameters[$(argc)]. Usage: aisc aisc_server.pa xxx.aisc outfile externfile.aisc import_proto globals.aisc
ENDIF

DATA AISC { $(#FILE $(argv[2])) }, EXTERN { $(#FILE $(argv[4])) }, GLOBALS { $(#FILE $(argv[6])) };
DUMPDATA DUMP/aisc_server.pa__$(argv[2]).dump

OPEN save $(argv[3])
OPEN import $(argv[5])

# set extern functions
MOVETO $(EXTERN/.)
FOR $(FUNCTION)
    CREATE $(extern_$(FUNCTION))
NEXT
    ERROR no extern Functions found
ENDFOR

# set globals passed from Makefile
MOVETO $(/GLOBALS/.)
CREATE $(create_save_code) = $(AISC_SAVE)

OUT import
CALL file_header

OUT save
CALL file_header
P $(#FILE C/aisc_server.h)

MOVETO $(/AISC/DATA/.)

CREATE $(v) = %

CALL build_init
CALL build_delete
CALL build_move
CALL build_copy
CALL build_accessors

IF $(create_save_code) = YES
    CALL build_saveload
ENDIF


CLOSE save
CLOSE import
EXIT

