flat assembler
Message board for the users of flat assembler.

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

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


Joined: 16 Jun 2003
Posts: 6864
Location: Kraków, Poland
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
Assembly Artist


Joined: 16 Jun 2003
Posts: 6864
Location: Kraków, Poland
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
Assembly Artist


Joined: 16 Jun 2003
Posts: 6864
Location: Kraków, Poland
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: 26
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 © 2004-2018, Tomasz Grysztar.

Powered by rwasa.