Scenergy #01
31 мая 1999 |
|
Coding - On the use of macros or TASM 4.12 - RULEZ!
On the use of macros or TASM v4.12 - rulez! 0. Entry A little strange title for an article, not find? :) But the content is corresponds to the name. Write this article prompted me conversations FIDO'shnoy echo ZX.SPECTRUM, from I've learned one in particular amazing thing for me - it turns out most people simply do not know what such macros and conditional compilation and What do they eat it all. Another driving power to writing this article was burning desire to once again say: "What happiness that the world has such a wonderful assembly as TASM v4.12 by RST7/CBS! ". Well, he said, is now available sleep peacefully. :) In other words part This article aims to show what Still TASM 4 highlights from the rest assemblers that it is such a thing No, unfortunately, nowhere else, and because Why I chose it after tried about a dozen assemblers. But it is distinguished by its two above-mentioned things - the presence of macros and conditional compilation. More precisely, there is another thing - keyboard macros, but the theme of this Article does not apply, although too is nemeryannom rulez'om:) I will immediately say: this article - no agitation All encoders "go to the TASM!" (Well can only to some extent), and rather a call to the authors assemblers - why such wonderful things so far not all assemblers? But back to the main topic. First, for those who do not familiar with notions of "macro" and "conditional compilation, "I will try to outline explain what it is. 1. What are macros? Thus, the macro is called design following form: DEFMAC <Macro name> <The body of a macro> ENDMAC At the same time <Macro name> - any label, and macro body - any piece of code in standard syntax of the assembler. Example macro: DEFMAC CLRSCR LD HL, # 4000 LD DE, # 4001 LD BC, # 17FF LD (HL), L LDIR ENDMAC Using macros in the code carried out by specifying in the body program a macro name: HALT XOR A OUT (# FE), A CLRSCR; Use a Macro When you compile will be generated the following code: HALT XOR A OUT (# FE), A LD HL, # 4000 This piece of LD DE, # 4001, package insert LD BC, # 17FF; instead of macro name. LD (HL), L; LDIR; Macros can have parameters. In Syntax TASM'a parameters are referred to as \ 0 \ 1 \ 2, ... \ 9. Ie each macro You can submit up to 10 parameters. We'll see how it is used as an example the same treatment. But now it will be is not only clean the screen, and any chunk: DEFMAC CLEAR LD HL, \ 0 LD DE, \ 1 LD BC, \ 2 LD (HL), 0 LDIR ENDMAC Accordingly, a call to the macro: HALT XOR A OUT (# FE), A CLEAR # 4000, # 4001, # 17FF The generated code is naturally identical. But this does not look very nice. Therefore it is better to alter this slightly Macro: DEFMAC CLEAR LD HL, \ 0 LD DE, 0 + ((\ 0) +1) LD BC, 0 + ((\ 1) -1) LD (HL), 0 LDIR ENDMAC Accordingly, change and challenge: CLEAR # 4000, # 1800 I hope you understand? The only question may be on terms for DE and BC. Let me explain: the parameters passed to the macro, in brackets in order to be was passed as a parameter is not only constants, but also an expression. A "0 +" in beginning is necessary in order to show assembler that this is not a team LD DE, (nn), and LD DE, nn. Naturally described by macro primitive but it is clear and gives an idea of that such macros. 2. What is conditional compilation? Now the conditional compilation. In Syntax TASM'a conditional compilation as follows: . IF <expression> <1 st piece of code> . ELSE <2 second piece of code> . ENDIF <expression> - Any expression understood assembler. So, if the value of <expression> on compilation stage will be equal to 0, will compiled "1 st piece of code> if the same No, it will be compiled <2-d piece code>. Why do it? This is most often used to have ability to generate different code from the same source code without changes. A typical example - you have a project that you develop. While the project is in development and debugging, there are usually pieces debugging code (different trap plugs, press SPACE to check output, some counters and interrupts etc.). Of course, that this whole debug code will have to clean up the stage assembling the finished project. Typically, this results in a crawl on the source from muttering to himself, "Well, because somewhere here was this test ... Where she had gone ... ?:) And then, when suddenly again be required to return the debug code in place (which also happens often, because finished project respectable size rarely going from the first time), all of this procedure is repeated again, and then another and again ... One word - sad sight ... Now consider the same situation but with using conditional compilation: 1) At the beginning of the source code is written the line: DEBUG EQU 0 0 - condition 2) The whole debug code written as: . IF DEBUG. ENDIF Now, in order to compile debug version of the program we need set DEBUG = 0, and to compile version without debugging code - set DEBUG = 1. That's it! I think you will agree with my opinion, that work so much easier. 3. Examples of use Consider some other possibilities use of macros and conditional compilation in your programs. Because teacher out of me worthless, and examples on the go, I come up with I can not - I will take only the real examples of the use of my programs. 3.1 Memory Allocation Perhaps it is the use of macros found most widespread in my programs. Simply put, all my programs written in the last year to work with in this way memory allocation. And I find it very convenient. Here I will briefly describe the methodology and give Examples of macros, and the sources with this module can be found in the annex. The main idea is to facilitate the programmer task allocation dynamic memory and automate control over its overflow. I think any programmer faced with such situations, when his buggy program due to the fact that he was singled out by table or generated by the procedure for 1 bytes less memory than necessary. Also probably all experienced the need to manually recalculate the addresses of location in the memory of a dozen plates, If the first of them suddenly changed size. So, the use of macros allows completely relieve the programmer from all these problems! First you need to reach agreement on the organization of memory. I will describe how it's done for me, but actually this method You can fit under any scheme memory allocation. Thus, memory conventionally divided into following areas: # 6000 - # 7FFF - Slow memory. Used to store tables. # 8000 - # xxxx - A program code. # Xxxx-# BDFF - Fast memory. Used to store the generated procedures, as well as those tables, which are not penetrated into the slow memory. # BE00-# BFFF - Table of interruptions and all that associated with a handler interrupts. # C000-# FFFF - Upper memory. Used for the storage of large tables and / or procedures. Using a memory allocation, we additionally provide greater probability of normal operation of programs machines with slower memory. Well, okay, is irrelevant. As a rule, you want to type 2 released Memory - with an arbitrary address and address aligned to the boundary of 256 bytes (Ie the address mask # XX00). So way to solve the problem of allocating memory, we need six macros (3 types Memory * type 2 selection): To allocate memory with arbitrary Address: GET_SLOW_MEMORY - in slow memory GET_FAST_MEMORY - in fast memory GET_UPPER_MEMORY - in upper memory To isolate the memory address, aligned along the boundary of 256 bytes: GET_SLOW_MEM_XX00 - in slow memory GET_FAST_MEM_XX00 - in fast memory GET_UPPER_MEM_XX00 - in upper memory The general format of the call: GET_nnn_MEMORY Variable, Mem_Size GET_nnn_MEM_XX00 Variable, Mem_Size Example: GET_SLOW_MEMORY PROC_1, $ 120 GET_SLOW_MEMORY PROC_2, $ A0 GET_SLOW_MEM_XX00 TABLE_1, $ 200 GET_SLOW_MEM_XX00 TABLE_2, $ 100 As a result, when we compile: PROC_1 = # 6000 PROC_2 = # 6120 TABLE_1 = # 6200; And no # 61C0! TABLE_2 = # 6400 Accordingly, if the size of any allocated memory area will change - all addresses will be converted at compile automatically, while preserving all necessary alignment of boundaries 256 B! Well, not a fairy tale right? :) We'll see how it is implemented: ; SLOW_MEMORY - a variable that ; Stores a pointer to the first free ; Bytes. ; Originally SLOW_MEMORY = # 6000. ; Macro Settings: ; \ 0 - The name of the variable created , \ 1 - Size of memory allocation DEFMAC GET_SLOW_MEMORY ; Create a variable \ 0 EQU 0 ; Assign it to the current value of the index ; Free memory \ 0 = SLOW_MEMORY ; We correct pointer value in , According to the size of allocated block ; Memory. SLOW_MEMORY = SLOW_MEMORY + (\ 1) ENDMAC DEFMAC GET_SLOW_MEM_XX00 ; Create a variable \ 0 EQU 0 ; If the low byte of current address ; Pointer is not zero ... . IF SLOW_MEMORY '# FF-0 . ELSE ; ... That reposition the pointer to the next ; Memory address aligned to the boundary And 256 bytes. SLOW_MEMORY = ((SLOW_MEMORY / # 100) +1) * # 100 . ENDIF ; Assign a variable current value ; Index of free memory. \ 0 = SLOW_MEMORY , We correct value of the pointer in , According to the size of allocated block ; Memory. SLOW_MEMORY = SLOW_MEMORY + (\ 1) ENDMAC Macros for fast and upper memory work similarly. In addition, the file MEMORY.A, located in the application, you can see the macro DISPLAY_INFO, which helps track overflow the allocated memory. Because of its Large I not give it here I will. 3.2 Calculations Macros are great easy task computation of an expression in your program. It is often necessary to ask any address in the form of tags in order to continue to use it. If like to address 1 - it can be defined manually. But if, for example, ten, moreover they have from time to time change - unwillingly think. Especially often happens with screen addresses. For This, too, can write enough simple macros, which, nevertheless, life much easier: Calculation of the display address. ; Macro Options: ; \ 0 - The name of the variable created , \ 1 - X coordinate (in the familiarity) , \ 2 - Y coordinate (in pixels) DEFMAC GET_SCRADR \ 0 EQU # 4000 \ 0 = \ 0 + ((\ 2) '# C0 * 32) + ((/ 2) '7 * # 100) + ((\ 2)' # 38 * 4) + ((\ 1) '# 1F ) ENDMAC Calculation of addresses in the attributes. ; Macro Options: ; \ 0 - The name of the variable created , \ 1 - X coordinate (in the familiarity) , \ 2 - Y coordinate (in the familiarity) DEFMAC GET_ATTRADR \ 0 EQU # 5800 \ 0 = \ 0 + ((\ 2) * 32) + ((\ 1) '# 1F) ENDMAC In addition, if you want the macros elementary altered the fact that immediately write to the address of the memory - it very convenient for the preparation of various Tables of addresses. For example: DEFMAC DEFW_ATTRADR DEFW # 5800 + ((\ 2) * 32) + ((\ 1) '# 1F) ENDMAC ADR_TABLE DEFW_ATTRADR 10,5 DEFW_ATTRADR 12,6 DEFW_ATTRADR 14,7 DEFW_ATTRADR 16,8 DEFW_ATTRADR 18,9 This, of course, just examples. Real to macros can be implemented much more complicated things. For example, in Scenergy main menu on my macros written calculation table of pointers to sprites, components of the animation menu items. When This set only the number of sprites and their size, and macros recalculate addresses, and even with the transition to next page (as a sprite assigned to 2 pages). But here I am this Macro will not lead because of its size (2 screen TASM'e:)). 3.3 Data Structures With the help of macros can be easily realized work with data structures. Surely each of you for writing program had to deal with the fact that data on some objects have several fields, these fields of different type. For example, data on the menu: [Byte] - X coordinate [Byte] - Y coordinate [Word] - Pointer to next paragraph [Word] - Pointer to the previous item [Byte] - Length [Byte] - Hotkey [Byte] - Type menu item [Byte] - Additional flags [Word] - Pointer to a procedure handler [Word] - Pointer to the parameter [Word] - Pointer to the commentary [Data] - The name of the menu item Incidentally, this data structure is actually describes a menu item in Scenergy Setup'e. Each element of the data structure characterized by 2 things: - Its type - Offset of the structure As a rule, these programs shift are given constants. Accordingly, If, God forbid, some sort of box should be remove or change its type (a hence the size) - all the bias and change! And therefore, you will be at best, re-counted all bias and stuff them into the appropriate EQUS. Or, if you write all ugorazdilo these shifts within the program as a numbers - You will spend a long time searching for all these shifts, but More then check out - and whether he had forgotten something? In one word - fun on the verge masochism:) What is here can help macros? But nothing. Write a little macro: ___ORG EQU 0 DEFMAC _SZ \ 0 EQU ___ORG ___ORG = ___ORG + (\ 1) ENDMAC And ask all the bias in the following form: ___ORG = 0 _SZ _MI.X, A _SZ _MI.Y, A _SZ _MI.NEXT, 2 _SZ _MI.PREV, 2 _SZ _MI.LEN, A _SZ _MI.HOTKEY, A _SZ _MI.TYPE, A _SZ _MI.FLAGS, A _SZ _MI.HANDLER, 2 _SZ _MI.PARAM, 2 _SZ _MI.COMMENT, 2 _SZ _MI.TITLE, 0 What we do for you? 1) There is no need to manually assume that all the bias, there is no the probability of making a mistake in this case. 2) It turns out more clearly. 3) Any change in all recounts displacements will be made automatically! And another thing. Namely, how to set these data structures. Usually it looks something like this (for the structure of the menu item): MI_1; Previous menu MI_2 DEFB 3,5 DEFW MI_3, MI_1 DEFB 10 . ENDIF . IF USE_SMULT8 . ENDIF . IF USE_MULT16 . ENDIF . IF USE_DIV8 . ENDIF . IF USE_DIV16 . ENDIF All this is recorded as a separate module, for example MATH.A. Now if DEFB "I", 2,5 DEFW HANDLER, PARAM, 0 DEFB "MENU ITEM", 0 MI_3; next menu item Using the same macro it can as follows: MI.X = 0 MI.Y = 1 MI.LEN = 12 MI.TYPE = MIT_NORMAL MI.FLAGS = MIF_ACTIVE + MIF_SELECTED MI.HANDLER = CONTROLS MI.PARAM = 0 MI.COMMENT = C011 MENUITEMDATA "~ CONTROLS ..." This is a real piece of source code from Scenergy Setup'a. In addition, the use of macros allowed in this case get rid of the tedious operations combining menu items in a doubly-linked list. But here I am, these macros also not cite due to their bulkiness. I personally this way of defining data like more, though, as it turned out, my view is shared by no means all. For example, universally respected Oldman criticized this way to the nines:) Of course, this case taste, but people have a right to know about alternatives to the standard work on structures. 3.4 Enhanced Syntax In principle, work with data structures implemented on a macro is also extension of the syntax, but I made it to the separate part. Here we talk about extending the syntax of the assembler. Of course, best of all it can tell author TASM'a - RST7. One can think of to his library 6502.A, which is not simply extends the syntax of the language, and, in essentially turns the TASM to compiler an entirely different processor! But to such heights, we do not rise shall we start with something more simple. Incidentally, I deliberately did not touch such a possibility as macros in TASM'e parse the string parameters as this thing is not so trivial to explain whatever it is. Those to whom it will be be - yourself and read the description TASM'a understand. So, what about the syntax. To start see how we can implement STROM'ovskie macros, such as LD HL, DE: DEFMAC LD_HL_DE LD H, D LD L, E ENDMAC Work with multiple parameters in LD can also be done with macros, but this is not a trivial task. Who is will have to - do it yourself. So with the help of macros can be done more readable some areas Your code. I have already cited the example of cleaning, these things can come up with set. In addition, using conditional compilation You can make back the beginnings of optimization. For example: DEFMAC LD_A . IF \ 0 XOR A . ELSE LD A, 0 + (\ 0) . ENDIF ENDMAC I hope you understand? If the option is a macro zero - instead of LD A, n is XOR A. This is necessary if A is not assigned constant, and the expression, or mark and advance know what will be its value. This only a simple example that you have caught the basic idea. 4. Increase the flexibility of code using conditional compilation. What I mean by the words, in the headline? A very simple thing - the minimization of patches ready sources to give them a kind of adapted for use in other programs. Consider an example. Let's say You have a piece of code that is almost unchanged passes from one of your program to another. This may be different things. For example, I have this group of modules providing a variety of low-level function to create effects in chunk'ah. What usually makes a programmer, if he want to use a piece of his source code in another of his program? Usually first he knows his previous source code, trying to understand what other procedures and tables are needed piece of code that he wants to move, What is the subtlety in its use. We must also control - not I copied something extra? Using conditional compilation avoids all these problems and make your source code portable between all of your programs. Consider a typical example - the procedure ralizuyuschie math. As Typically, each encoder has a set of similar functions for all occasions - multiplication, division, etc. And often These functions are not coder wrote himself, and takes ready. Ie to repeat them "out of my head," he unlikely to be able to. So, for example, he needed a procedure for multiplication. That is he doing? Usually begins remember: as to what program I used this procedure last? Then start searching for the source of this program. Then - in search of this procedure source code. And so every time. You can certainly go the other way - keep all such procedures as separate source code and take pieces of code from him. Can be stored on a separate disk many small ishodnichkov. And you can go in another way: All these procedures are combined into one module. But not just so, and with additional rail: - First is the definition of a set conditions: USE_MULT8 EQU 1; Multiplying D * E = HL, unsigned USE_SMULT8 EQU 1; Multiplying D * E = HL, signed USE_MULT16 EQU 1; Multiplication HL * DE = HLDE, unsigned USE_DIV8 EQU 1; division of H / L = HL, unsigned USE_DIV16 EQU 1; division of HL / DE = HL.DE, unsigned In the future, using them, we can We provide the necessary procedures. - Then comes the description of the procedures themselves: . IF USE_MULT8 any program you need any procedure of multiplication / division of one of those that is in a module, you will need to only do 2 things: - Add to the source line: USE_
Other articles:
Similar articles:
В этот день... 21 November