flat assembler
Message board for the users of flat assembler.

Index > Macroinstructions > Tricky stuff in fasmg, part 3: Identifier context transfer

Author
Thread Post new topic Reply to topic
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8356
Location: Kraków, Poland
Tomasz Grysztar 08 Oct 2016, 18:33
This is the third part of a series on advanced trickery that can be done with fasmg. The other installments: part 1, part 2, part 4.
_______

When designing a macro for a general use, it is important to ensure that it works correctly when called in different contexts. The LOCAL directive allows to create private symbols for every instance of macro without interfering with an unknown environment. But sometimes a variable may be needed to be shared across the multiple macro calls. A globally defined symbol usually works well enough for this purpose, but it is something that may be interfered with. Let's consider the following example:
Code:
GLOBAL_STATE = 0

macro state 
        db GLOBAL_STATE
end macro

macro switch value
        GLOBAL_STATE = GLOBAL_STATE xor (value)
end macro    
These macros have a few issues. First, if someone defines symbol called GLOBAL_STATE for other purposes, it is going to interfere with them. A choice of rare and specific name for a global may help a bit, and if there are multiple global symbols needed, it is good to create a special namespace for them and attach this namespace to a global symbol with such specific name.
The other problem would show up if these macro were used inside the NAMESPACE block, because the the re-definition of GLOBAL_STATE would instead create a symbol in the other namespace. We have already seen the solution to this problem - it is enough to add the dot at the end of the identifier to make sure that the defined symbol is the same whose value is accessed on the right-hand side:
Code:
        GLOBAL_STATE. = GLOBAL_STATE xor (value)    

But there is also another option. There is a mechanism that allows the arguments to a macro to preserve the context for any identifiers inside them. The following example demonstrates this effect:
Code:
macro tester arg
        namespace X
                a = 0
                db a
                db arg
        end namespace
end macro

a = 3
tester a    
The text of both data-generating lines that get assembled is the same, "db a", but the second one interprets the "a" identifier in the context in which the macro was called and generates the 3 instead of 0. This is achieved though a mechanism that could be interpreted as a kind of text "coloring". The value of parameter "arg" has an additional property, like a color of text, that makes first "db a" different from the second "db a". Now, if we define a macro inside another macro, then the text of that inner macro is going to preserve that "coloring". The following sample uses this mechanism to improve the definition of "state"/"switch" macros that were used as an example earlier:
Code:
macro setup variable

        variable = 0

        macro state
                db variable
        end macro

        macro switch value
                variable = variable xor (value)
        end macro

end macro

setup GLOBAL_STATE

namespace Program

        switch 8

        GLOBAL_STATE = 0

        state

end namespace    
When the "setup GLOBAL_STATE" gets assembled, the "switch" macro is defined with a body containing the text:
        GLOBAL_STATE = GLOBAL_STATE xor (value)
The color used above shows the parts of the text that have the global context attached to them. The parts that are not specially colored are going to be interpreted in the context in which the macro gets executed. Now when "switch 8" line is assembled, the line generated by macro is:
        GLOBAL_STATE = GLOBAL_STATE xor (8)
This time there is an additional color which reflects that the contents of "value" parameter also preserves the context - this time the one in which the "switch" macro was called.

Therefore in this version both "switch" and "state" always use the global variable no matter what context they are used in. This bears a similarity to the concept of closure used in many programming languages.

The macros in the above sample correctly access their variable even though in the local namespace there is a variable defined with the same name. But, because their variable is global, the other code may still interfere with it. To make the variable completely outside the reach of others, we can use LOCAL directive. The LOCAL declaration creates a special parameter that replaces its name with the same text, but this time "colored" in such a way, that it carries the context unique to the instance of a macro:
Code:
macro setup

        local variable

        variable = 0

        macro state
                db variable
        end macro

        macro switch value
                variable = variable xor (value)
        end macro

end macro

setup    
This simple trick also allows to further improve the encapsulation macros from the previous part:
Code:
macro encapsulate? Namespace

        local postponed,$$%,@%

        macro postponed
        end macro

        virtual at 0 
        $$% = 0 
        @% = 0 
        define $% ($$% + $ - $$) 
        define $%% ($$% + $@ - $$ - 1/($@-$$+1)*@%) 

        macro org? address 
                local addr 
                addr = address 
                $$% = $% 
                @% = $% - $%% 
                end virtual 
                virtual at addr 
        end macro 

        macro section? address 
                local addr 
                addr = address 
                $$% = $%% 
                @% = 0 
                end virtual 
                virtual at addr 
        end macro 

        macro postpone?! 
            esc macro postponed
        end macro 

        macro end?.postpone?! 
                postponed
            esc end macro 
        end macro 

        namespace Namespace

        macro end?.encapsulate? 

                postponed 
                end namespace 

                purge org?,section?,postpone?,end?.postpone? 
                restore $%,$%% 

                repeat 1, Length:($$% + $ - $$) 
                        display `Namespace,': ',`Length,' bytes.',13,10 
                end repeat 

                end virtual 
        end macro 
end macro    

The context that may be carried by the text of argument includes the namespace that applied to it and also the symbol that at the time was the "parent" for the identifiers starting with dot.

There is also one other place where this kind of context transfer occurs: the values of symbolic variables. When a symbol is defined with EQU or DEFINE, its entire value becomes equipped with the context that was present at the time of definition. When the text of such value is assigned to a parameter with MATCH or IRPV, the text's "colors" are preserved:
Code:
First:
        .x = 1

define LIST .x

Second:
        .x = 2

LIST equ LIST, .x

match values, LIST
        display `values
        db values
end match    
The DISPLAY shows that the text extracted from LIST variable with MATCH is ".x, .x". But the texts of these two identifiers have different contexts attached to them, so the list really contains two different values.

If a MATCH cuts the text of an identifier into many parts, all parts preserve the "color" of their text. And if an identifier becomes patched up from the parts of text with many different "colors", only the context associated with the initial part has any effect on the recognition of this symbol.

In case it was needed that some text is returned back to the "neutral coloring" and interpreted in the current context, the RMATCH directive does this to the text of parameters it defines. The similar effect could also be achieved by converting the value of parameter into a string with the ` operator and then interpreting this pure text with EVAL.

But there is also a neat little trick based on the fact that only the context associated with the initial part of identifier has any effect. It is enough to prepend to parameter with "#" character to form an identifier interpreted in current context:
Code:
macro tester name
        namespace my
                name db ?       ; symbol defined in its original namespace
                #name db ?      ; symbol defined in "my" namespace
        end namespace
end macro    


Last edited by Tomasz Grysztar on 11 May 2017, 13:28; edited 4 times in total
Post 08 Oct 2016, 18:33
View user's profile Send private message Visit poster's website Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8356
Location: Kraków, Poland
Tomasz Grysztar 08 Oct 2016, 18:46
On a side note: the namespace that contains symbols declared with LOCAL is disconnected from the main tree of namespaces - in other words, the outer namespaces are not accessible from there. If such symbol is used as an argument to the NAMESPACE directive, suddenly all the built-in instructions of the assembler are going to become inaccessible, even the END NAMESPACE is not going to work:
Code:
macro test
        local space
        namespace space
        end namespace   ; <- error, even END is undefined in this context
end macro

test    
But, through the trickery of the recognition context transfer it is possible to "implant" some working instructions into this blank namespace:
Code:
macro test
        local space,link

        define link end

        match _end, link
                macro space.end? name   ; implant END directive into a barren namespace
                        _end name
                end macro
        end match

        namespace space
        end namespace
end macro

test    
This could be an interesting playground, since in such namespace all the symbols are available for the taking and it would be possible to provide a directive set that would be completely distinct from the default ones.
Post 08 Oct 2016, 18:46
View user's profile Send private message Visit poster's website Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8356
Location: Kraków, Poland
Tomasz Grysztar 09 Oct 2016, 18:14
Another small improvement to the encapsulation macros could be to include these definitions in the macro:
Code:
        define Namespace.$ $
        define Namespace.$$ $$
        define Namespace.$@ $@    
These ensure that encapsulated source cannot modify the global $, $$ and $@ symbols with definitions like "$. = 0", they could only modify the local symbol. Therefore even in such cases the expressions defining the $% and $%% would still work correctly, because they access global symbols (thanks to the context preserved by DEFINE).
Post 09 Oct 2016, 18:14
View user's profile Send private message Visit poster's website Reply with quote
jacobly



Joined: 04 Feb 2016
Posts: 44
jacobly 13 Jun 2017, 10:19
I noticed a more concise way to implant preexisting symbols into the local namespace:
Code:
macro test display, hello, end
        local space
        namespace space
                display hello, 'local namespaces', 10
        end namespace
end macro

hello = 'Hello '
test display, hello, end    

This is indeed an interesting feature that I will definitely find uses for.

Edit: I just realized that this only implants them inside the body of the macro, which is also useful, and even makes namespace implanting more concise:

Code:
macro test hello, display, end
        local space
        macro space.display? args&
            display args
        end macro
        namespace space
            hello 'local namespace'
        end namespace
end macro

macro hello name
    display 'Hello ', name, 10 ; display needs to be implanted in the namespace because
end macro                      ; we don't have access to the other macro's arguments
test hello, display, end    


Edit2: Default arguments let you only list the imports once and you can use the imports in the local namespace until you call a macro or include a file.
Code:
macro test macro:macro, hello:hello, display:display, end:end
        local space
        namespace space
                macro #display? args& ; # prevents the name from looking like the import, but
                        display args  ; you could also use a different case or rename the import
                end macro
                hello 'local namespace'
        end namespace
end macro

macro hello name
        display 'Hello ', name, 10
end macro
test
test    
Post 13 Jun 2017, 10:19
View user's profile Send private message 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-2024, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.