flat assembler
Message board for the users of flat assembler.
 Home   FAQ   Search   Register 
 Profile   Log in to check your private messages   Log in 
flat assembler > Macroinstructions > Tricky stuff in fasmg, part 4: Generated macros

Author
Thread Post new topic Reply to topic
Tomasz Grysztar
Assembly Artist


Joined: 16 Jun 2003
Posts: 6509
Location: Kraków, Poland
Tricky stuff in fasmg, part 4: Generated macros
This is the fourth part of a series on advanced trickery that can be done with fasmg. The other installments: part 1, part 2, part 3.
_______

As the samples in the previous part demonstrated, it is very easy to make macro generate another macro, and the text of the new one may contain values created by the outer macro or passed from its arguments (and with copied identifier recognition context):

Code:
macro origin o
        macro offset v
                dd v - o
        end macro
end macro

BASE = 100h
origin BASE
BASE = 200h
offset 220h     ; dd 20h

However it is no longer so easy when we need to algorithmically generate a macro that may contain a variable number of lines. Let's say that we need a "generate" macro that could be called like this:

Code:
generate POINTx dd ?,y dd ?,z dd ?

and it would then define the macro looking like:

Code:
struc POINT xValue:?,yValue:?,zValue:?
        .x dd xValue
        .y dd yValue
        .z dd zValue
end struc

We can iterate through the arguments of "generate" and construct the text of the lines we need after deconstructing the arguments with MATCH, however after we start the definition of an inner macro, the iteration is no longer going to work. Any ITERATE or END ITERATE encountered when a macro definition is under way would simply become the part of the macro instead of being immediately processed.

A relatively simple solution that comes to mind, is to generate the text of macro that we need to construct in form of data in a VIRTUAL block, then execute it with EVAL:

Code:
macro generate name,fields&
        virtual at 0
        db 'struc ',`name,' '
        iterate fieldfields
                if % > 1
                        db ','
                end if
                match labeltypedefaultfield
                        db `label,'Value:',`default
                end match
        end iterate
        db 13,10
        iterate fieldfields
                match labeltypedefaultfield
                        db '.',`label,' ',`type,' ',`label,'Value',13,10
                end match
        end iterate
        db 'end struc'
        local text
        load text:$ from 0
        eval text
        end virtual
end macro

Another variant could achieve the same effect without VIRTUAL, by building the text variable directly with a string concatenation macro (currently there is no built-in string concatenation in fasmg, so this has to be done with bit shifts):

Code:
macro append variable,texts&
        iterate texttexts
                variable = string (variable) + (textshl (lengthof (variableshl 3)
        end iterate
end macro

macro generate name,fields&
        local text
        text = 'struc '
        append text,`name,' '
        iterate fieldfields
                if % > 1
                        append text,','
                end if
                match labeltypedefaultfield
                        append text,`label,'Value:',`default
                end match
        end iterate
        append text,13,10
        iterate fieldfields
                match labeltypedefaultfield
                        append text,'.',`label,' ',`type,' ',`label,'Value',13,10
                end match
        end iterate
        append text,'end struc'
        eval text
end macro

(temporarily replacing EVAL with DB allows to ensure that the generated text is correct).

But these solutions have some disadvantages. Not only may they be considered wasteful, since they generate a raw text than then needs to be parsed back by the assembler - but the raw text generated in such way can no longer preserve any context for identifiers - this can sometimes be a serious flaw.

The trick that we can use to construct a macro from a variable number of lines that we have prepared as a preprocessed text (which may include some context information) lies in the nested macro calls. Let's look at the following sample:

Code:
macro builder
        esc macro test
end macro

macro builder
        builder
        db 1
end macro

macro builder
        builder
        db 2
end macro

macro builder
        builder
        esc end macro
end macro

builder

The call to the final "builder" macro initiates the cascade of nested macro calls until it reaches the deepest one which begins the definition of new macro "test". The one after one the nested macros get closed, each one adding an additional line to the macro, until the topmost one is reached which closes the definition with "end macro". The result is definition of this plain and simple macro:

Code:
macro test
        db 1
        db 2
end macro

Before we apply this trick to our problem, we need to realize that we still need EVAL to generate the name of macro parameter, because any parameter is recognized by the preprocessor only when it is a single token. But this EVAL is going to be much shorter and simpler. We are also going to use IRPV to quickly retrieve previously prepared symbolic values into a parameter to generate the line of newly created macro:

Code:
macro append variable,texts&
        iterate texttexts
                variable = string (variable) + (textshl (lengthof (variableshl 3)
        end iterate
end macro

macro generate name,fields&
        local declarations,argument,line,rmatchN,builder
        iterate fieldfields
                match labeltypedefaultfield
                        rmatchN = 'rmatch N,'
                        append rmatchN,`label
                        append rmatchN,'Value'
                        eval rmatchN
                                define argument N:default
                                define line label type N
                        end rmatch
                        if % = 1
                                declarations equ argument
                        else
                                declarations equ declarations,argument
                        end if
                end match
        end iterate
        match argumentsdeclarations
                macro builder
                        struc name arguments
                end macro
        end match
        irpv definitionline
                macro builder
                        builder
                        .#definition
                end macro
        end irpv
        builder
        end struc
end macro

The initial ".#" is attached to the line while defining a "builder" macro, to avoid associating any foreign context with the initial dot, because this dot needs to be interpreted in the context of builded "struc" macro. On the other hand, any contexts for the identifiers in the definitions of the default values are preserved correctly this time.

If we define the building macro as "macro builder!", it is possible to use it to generate only a portion of a macro, because the unconditional macro gets expanded even when it is encountered while definition of a new macro is under way. This allows to re-structure the above sample:

Code:
macro append variable,texts&
        iterate texttexts
                variable = string (variable) + (textshl (lengthof (variableshl 3)
        end iterate
end macro

macro generate name,fields&
        local declarations,argument,rmatchN,builder
        macro builder!
        end macro
        iterate fieldfields
                match labeltypedefaultfield
                        rmatchN = 'rmatch N,'
                        append rmatchN,`label
                        append rmatchN,'Value'
                        eval rmatchN
                                define argument N:default
                                macro builder!
                                        esc builder
                                        .#label type N
                                end macro
                        end rmatch
                        if % = 1
                                declarations equ argument
                        else
                                declarations equ declarations,argument
                        end if
                end match
        end iterate
        match argumentsdeclarations
                struc name arguments
                        builder
                end struc
        end match
end macro

In this variant ESC is used to avoid calling the previous unconditional macro when defining the next one - to create the cascade like in the previous example. But it would still work without ESC and in such case each consecutive macro would contain the entire sequence of prepared lines.
Post 10 Oct 2016, 20:57
View user's profile Send private message Visit poster's website Reply with quote
Tomasz Grysztar
Assembly Artist


Joined: 16 Jun 2003
Posts: 6509
Location: Kraków, Poland
In the Mach-O macros I created to let fasmg assemble itself as MacOS executable I used this simple set of macros based on the above ideas. They allow to build new macro line-by-line with an easy to use syntax:

Code:
define macroBuilder?

macro macroBuilder? declaration&
        macro macroBuilder?.definition
                esc macro declaration
        end macro
end macro

macro macroBuilder?.line? content&
        macro macroBuilder?.definition
                macroBuilder?.definition
                content
        end macro
end macro

macro macroBuilder?.end?
        macroBuilder?.definition
        esc end macro
end macro

For example this:

Code:
macroBuilder test arg

        iterate symbolA,B,C,D
                if % = 1
                        macroBuilder.line match =symbol?arg
                else
                        macroBuilder.line else match =symbol?arg
                end if
                macroBuilder.line display 'Detected symbol ',`symbol,13,10
        end iterate

        macroBuilder.line end match

macroBuilder.end

constructs the following macro:

Code:
macro test arg
        match =A?arg
                display 'Detected symbol ','A',13,10
        else match =B?arg
                display 'Detected symbol ','B',13,10
        else match =C?arg
                display 'Detected symbol ','C',13,10
        else match =D?arg
                display 'Detected symbol ','D',13,10
        end match
end macro

Post 31 Aug 2017, 18:27
View user's profile Send private message Visit poster's website Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  


< Last Thread | Next Thread >

Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Powered by phpBB © 2001-2005 phpBB Group.

Main index   Download   Documentation   Examples   Message board
Copyright © 2004-2016, Tomasz Grysztar.