flat assembler
Message board for the users of flat assembler.

Index > Macroinstructions > Tricky stuff in fasmg, part 4: Generated macros

Author
Thread Post new topic Reply to topic
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8358
Location: Kraków, Poland
Tomasz Grysztar 10 Oct 2016, 20:57
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 POINT, x 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 field, fields
                if % > 1
                        db ','
                end if
                match label= type= default, field
                        db `label,'Value:',`default
                end match
        end iterate
        db 13,10
        iterate field, fields
                match label= type= default, field
                        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 text, texts
                variable = string (variable) + (text) shl (lengthof (variable) shl 3)
        end iterate
end macro

macro generate name,fields&
        local text
        text = 'struc '
        append text,`name,' '
        iterate field, fields
                if % > 1
                        append text,','
                end if
                match label= type= default, field
                        append text,`label,'Value:',`default
                end match
        end iterate
        append text,13,10
        iterate field, fields
                match label= type= default, field
                        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 text, texts
                variable = string (variable) + (text) shl (lengthof (variable) shl 3)
        end iterate
end macro

macro generate name,fields&
        local declarations,argument,line,rmatchN,builder
        iterate field, fields
                match label= type= default, field
                        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 arguments, declarations
                macro builder
                        struc name arguments
                end macro
        end match
        irpv definition, line
                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 text, texts
                variable = string (variable) + (text) shl (lengthof (variable) shl 3)
        end iterate
end macro

macro generate name,fields&
        local declarations,argument,rmatchN,builder
        macro builder!
        end macro
        iterate field, fields
                match label= type= default, field
                        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 arguments, declarations
                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



Joined: 16 Jun 2003
Posts: 8358
Location: Kraków, Poland
Tomasz Grysztar 31 Aug 2017, 18:27
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 symbol, A,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
bitRAKE



Joined: 21 Jul 2003
Posts: 4071
Location: vpcmpistri
bitRAKE 10 Dec 2018, 02:16
Maybe a not so obvious, but natural extension:
Code:
macro builderBuilder name*
        define name?

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

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

        macro name?.block?
                macro ?! line&
                        match =name?.=block?,line
                                purge ?
                        else
                                macro name?.definition
                                        name?.definition
                                        line
                                end macro
                        end match
                end macro
        end macro

        macro name?.end?
                name?.definition
                esc end macro
        end macro
end macro    
The dark magic of multiple linear streams with context.

_________________
¯\(°_o)/¯ “languages are not safe - uses can be” Bjarne Stroustrup
Post 10 Dec 2018, 02:16
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


Copyright © 1999-2025, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.