Вы находитесь на странице: 1из 18

DEFINE

Short Reference

Syntax

DEFINE macro.
... &1 ... &9 ...
END-OF-DEFINITION.

Effect

The statement DEFINE defines a macro. Naming conventions apply to the


name macro and you cannot use ABAP words. Between the
statements DEFINE and END-OF-DEFINITION, you can use any number of complete
ABAP statements, with the exception of DEFINE, END-OF-DEFINITION, and program-
initiating statements. These statements constitute a section of source code that can
be inserted into a program under the name macro. The definition of a macro is not
restricted to the limits of processing blocks.

The validity of a macro is defined by its position in the source code. It can be
inserted at any position after END-OF-DEFINITION. If another macro is defined with
the same name, it overwrites the previous macro from its new position.

Within a macro, you can use up to nine placeholders &1 ... &9 instead of ABAP
words and operands. These placeholders must be replaced by fixed words when the
macro is inserted.
As well as in the source code of a program, you can also store macros in the database table TRMAC,
where they can be used by any program. The system first searches in the current program for a macro,
and then in the table TRMAC. Do not define your own macros in TRMAC. One example of a macro in
TRMAC is break, which sets a breakpoint in the system field sy-uname, depending on the current user
name.

In macros, you cannot set any breakpoints. In the ABAP Debugger, the
statements of a macro cannot be performed in individual steps.
Macros must not include more than a few lines, and should be used
sparingly, since it is very difficult to analyze macro errors. Instead of
macros, use internal procedures or include programs.

Inserting Macros

macro [p1 p2 ... ].


If a previously defined macro is listed as the first word in an ABAP statement
instead of a valid ABAP keyword, then these statements are inserted in the source
code at this position. Appropriate ABAP words or operands p1 p2 ... must be
specified for all placeholders of the macro. p1 p2 ... replace the placeholders
literally, one after the other.
Note

A macro can insert other macros, but not itself.


Example

In this example, a macro write_frame, which draws a frame around a


placeholder &1 in a list, is defined and then implemented.
DATA: x TYPE i, y TYPE i, l TYPE i.
DEFINE write_frame.
x = sy-colno. y = sy-linno.
WRITE: '|' NO-GAP, &1 NO-GAP, '|' NO-GAP.
l = sy-colno - x.
y = y - 1. SKIP TO LINE y. POSITION x.
ULINE AT x(l).
y = y + 2. SKIP TO LINE y. POSITION x.
ULINE AT x(l).
y = y - 1. x = sy-colno. SKIP TO LINE y. POSITION x.
END-OF-DEFINITION.
SKIP.
write_frame 'In a frame!'.

Use Macros. Use Them Wisely.


Posted by Rdiger Plantiko in ABAP Development on Oct 28, 2012 9:51:07 PM
inShare

A Sample Refactoring Session


Mapping Structures
Declarative programming: A button states example
Debugging
Symbols and Literals
Macros as Building Blocks for Internal DSLs
Structured Constants as Dictionary
Name Composition
Order of the parameters
Naming
Summary
References

Usually, macros are not welcomed by developers, and in many teams they are banned in the programming guidelines.
Obviously, there are arguments that speak against macros. But in recent years, I learned to appreciate their positive features.
I would not recommend a general macro ban in an ABAP development project. In this blog, I will explain why.

No doubt: Macros are no real modularization technique, like includes they are lacking an own execution context. And, yes,
with the exception of extremely simple macros, they are difficult to debug (I will come to that later). They simply are source
code transformations at a pre-compile stage. But they have a number of advantages.

The positive points that I see with macros are:


They help to make code more readable.
They help hide implementation details from the code's main intentions.
They help avoid code repetition.

A Sample Refactoring Session


Consider the following, typical example. It is a piece from our codebase (although 10 years old, if you accept this as
excuse...).

1. FORM alv1_fieldcat_create CHANGING ct_fieldcat TYPE slis_t_fieldcat_alv.


2. DATA: ls_fieldcat TYPE slis_fieldcat_alv.
3. ls_fieldcat-fieldname = 'CHECKBOX'.
4. ls_fieldcat-tabname = gc_tab_head.
5. ls_fieldcat-col_pos = 0.
6. APPEND ls_fieldcat TO ct_fieldcat.
7. CLEAR ls_fieldcat.
8. ls_fieldcat-fieldname = 'ABELN'.
9. ls_fieldcat-tabname = gc_tab_head.
10. ls_fieldcat-col_pos = 1.
11. ls_fieldcat-hotspot = gc_x.
12. APPEND ls_fieldcat TO ct_fieldcat.
13. CLEAR ls_fieldcat.
14. ls_fieldcat-fieldname = 'BEZCH'.
15. ls_fieldcat-tabname = gc_tab_head.
16. ls_fieldcat-col_pos = 2.
17. ls_fieldcat-hotspot = gc_x.
18. APPEND ls_fieldcat TO ct_fieldcat.
19. CLEAR ls_fieldcat.
20. ls_fieldcat-fieldname = 'STAT_FIELD'.
21. ls_fieldcat-tabname = gc_tab_head.
22. ls_fieldcat-col_pos = 3.
23. APPEND ls_fieldcat TO ct_fieldcat.
24. CLEAR ls_fieldcat.
25. ls_fieldcat-fieldname = 'AUFAR'.
26. ls_fieldcat-tabname = gc_tab_head.
27. ls_fieldcat-col_pos = 4.
28. APPEND ls_fieldcat TO ct_fieldcat.
29. CLEAR ls_fieldcat.
30. ls_fieldcat-fieldname = 'COUNT_POS'.
31. ls_fieldcat-tabname = gc_tab_head.
32. ls_fieldcat-col_pos = 5.
33. APPEND ls_fieldcat TO ct_fieldcat.
34. CLEAR ls_fieldcat.
35. ls_fieldcat-fieldname = 'MESSAGE'.
36. ls_fieldcat-tabname = gc_tab_head.
37. ls_fieldcat-col_pos = 6.
38. APPEND ls_fieldcat TO ct_fieldcat.
39. ENDFORM. " alv1_fieldcat_create

What is the intention of this code? A field catalog has to be filled: For each field, the column name and the position in the
grid (col_pos) are specified. Two of the fields are designed as hotspot fields (hotspot = 'X').

The code repetition is annoying. It distracts the eye from the essential part of the code. Only the parts that are varying from
block to block, are what is really essential in this code. The rest is inessential boilerplate code. It is waste of time to be forced
to study this repetitive code line by line, when this routine has to be inspected in a support or enhancement session.
An obvious improvement would be to parametrize the code and to use a modularization unit - in this case, a subroutine:

1. form add_field using iv_fieldname type c


2. iv_col_pos type i
3. changing ct_fieldcat type slis_t_fieldcat_alv.
4.
5. data: ls_fieldcat type slis_fieldcat_alv.
6. ls_fieldcat-fieldname = iv_fieldname.
7. ls_fieldcat-tabname = gc_tab_head.
8. ls_fieldcat-col_pos = iv_col_pos.
9. append ls_fieldcat to ct_fieldcat.
10.
11. endform. "add_field
12.
13. form set_hotspot using iv_fieldname type c
14. changing ct_fieldcat type slis_t_fieldcat_alv.
15.
16. data: ls_fieldcat type slis_fieldcat_alv.
17. ls_fieldcat-hotspot = 'X'.
18. modify ct_fieldcat from ls_fieldcat
19. transporting hotspot
20. where fieldname = iv_fieldname.
21. assert sy-subrc eq 0.
22.
23. endform. "set_hotspot

Note the assertion in routine set_hotspot which makes the program crash if a non-existing fieldname has been typed by the
developer as actual parameter. This tribute to the "crash early" principle helps avoid typographical errors.

With these routines available, ALV1_FIELDCAT_CREATE could be identically rewritten as follows:

1. form alv2_fieldcat_create using ct_fieldcat type slis_t_fieldcat_alv.


2. if list_var = 3.
3. perform add_field using 'CHECKBOX' 0
4. changing ct_fieldcat.
5. endif.
6. perform add_field using 'ABELN' 1
7. changing ct_fieldcat.
8. perform add_field using 'BEZCH' 2
9. changing ct_fieldcat.
10. perform add_field using 'STAT_FIELD' 3
11. changing ct_fieldcat.
12. perform add_field using 'AUFAR' 4
13. changing ct_fieldcat.
14. perform add_field using 'COUNT_POS' 5
15. changing ct_fieldcat.
16. perform add_field using 'MESSAGE' 6
17. changing ct_fieldcat.
18. perform set_hotspot using 'ABELN'
19. changing ct_fieldcat.
20. perform set_hotspot using 'BEZCH'
21. changing ct_fieldcat.
22. endform. " alv2_fieldcat_create
With this change, we effectively saved 12 lines of code in comparison to the original version. Moreover, 14 lines of the new
code, the two form routines, appear to be reusable in other programs as well. They could be extracted into a general-purpose
subroutine pool for filling ALV field catalogs. In comparison, not a single line of the original code was reusable.

But the real win is that the routine alv2_fieldcat_create is now much better readable. Also, opening an extra stack level for
each add_field call is no performance problem, since we are calling it only for a couple of fields (and not thousands of times
- and even then: in the majority of the cases, not the call of a routine is a performance issue, but the code contained within
that routine!).

Moreover, we do not need to explicitly initialize the workarea ls_fieldcat any more, since we have a little box with fresh,
initialized data for each call. The need to expliticitly initialize something is bad, since it could be forgotten. In fact, I
consider it as a code smell when I encounter a "CLEAR something" statement several times in the middle of a code block:
The variable "something" obviously is reused for several purposes in the same code block. It is better to extract the code
parts beginning with that "CLEAR something" statement into an own module (form routine, method, function module).

But I am still not happy with ALV2_FIELDCAT_CREATE. There is too much ado, too much "code noise" for such a simple
task as adding some rows with field names to an internal table. The many "perform add_field" are still disturbing. I would
like to employ the really useful "Colon/Comma" notation in order to write "perform add_field" only once.

Applying the "Colon/Comma" notation, gives the following:

1. form alv3_fieldcat_create using ct_fieldcat type slis_t_fieldcat_alv.


2. if list_var = 3.
3. perform add_field using 'CHECKBOX' 0
4. changing ct_fieldcat.
5. endif.
6. perform add_field using :
7. 'ABELN' 1
8. changing ct_fieldcat,
9. 'BEZCH' 2
10. changing ct_fieldcat,
11. 'STAT_FIELD' 3
12. changing ct_fieldcat,
13. 'AUFAR' 4
14. changing ct_fieldcat,
15. 'COUNT_POS' 5
16. changing ct_fieldcat,
17. 'MESSAGE' 6
18. changing ct_fieldcat.
19. perform set_hotspot using :
20. 'ABELN'
21. changing ct_fieldcat,
22. 'BEZCH'
23. changing ct_fieldcat.
24. endform. " alv3_fieldcat_create

This is slightly better, but still contains unwanted repetition. It's clear that we are working with ct_fieldcat. We don't want
our attention led every second line to this fact. On the other hand, by the syntax rules, a CHANGING parameter has to come
at the end of a PERFORM call and therefore always is part of the statement's "tail" which can't beabbreviated away with the
Colon/Comma syntax.

This is where macros come into play. We could use these two very short macros for encapsulating the perform calls:

1. define _add_field.
2. perform add_field using &1 &2 changing ct_fieldcat.
3. end-of-definition.
4. define _set_hotspot.
5. perform set_hotspot using &1 changing ct_fieldcat.
6. end-of-definition.

Note that these macros require the existence of certain named variables in the context where they are called: In this case,
there needs to exist a variable with the name ct_fieldcat. This implies that these macros are not universally usable, but only
for a specific context.

With these macros, the form routine now shrinks to its minimum size and almost maximum expressiveness:

1. form alv4_fieldcat_create using ct_fieldcat type slis_t_fieldcat_alv.


2. if list_var = 3.
3. _add_field 'CHECKBOX' 0.
4. endif.
5. _add_field :
6. 'ABELN' 1,
7. 'BEZCH' 2,
8. 'STAT_FIELD' 3,
9. 'AUFAR' 4,
10. 'COUNT_POS' 5,
11. 'MESSAGE' 6.
12. _set_hotspot :
13. 'ABELN',
14. 'BEZCH'.
15. endform. " alv4_fieldcat_create

If we add the code lines for the macros, the savings in lines of code are not gigantic. But that is not the important part. What
really matters is that the routine for filling the field catalog is now reduced to its intended parts: What you see written in
ALV4_FIELDCAT_CREATE is precisely what the developer intended. Not more. The rest is code noise, banned behind the
scenes.

If the assignment of the col_pos to the field is not used elsewhere (and why should it?), I could even think of a version which
auto-generates the col_pos. Since the code itself has a sequence, it is not necessary to specify this sequence a second time
with the col_pos parameter.

The routine add_field could look like this:

1. form add_field using iv_fieldname type c


2. changing ct_fieldcat type slis_t_fieldcat_alv.
3.
4. data: ls_fieldcat type slis_fieldcat_alv.
5. ls_fieldcat-fieldname = iv_fieldname.
6. ls_fieldcat-tabname = gc_tab_head.
7. ls_fieldcat-col_pos = lines( ct_fieldcat ).
8. append ls_fieldcat to ct_fieldcat.
9.
10. endform. "add_field

Correspondingly, the macro passes only one parameter:

1. define _add_field.
2. perform add_field using &1 changing ct_fieldcat.
3. end-of-definition.

And finally the explicit col_pos can be thrown out of the code:

1. form alv5_fieldcat_create using ct_fieldcat type slis_t_fieldcat_alv.


2. if list_var = 3.
3. _add_field
4. 'CHECKBOX'.
5. endif.
6. _add_field :
7. 'ABELN',
8. 'BEZCH',
9. 'STAT_FIELD',
10. 'AUFAR',
11. 'COUNT_POS',
12. 'MESSAGE'.
13. _set_hotspot :
14. 'ABELN',
15. 'BEZCH'.
16. endform. " alv5_fieldcat_create

Summing up, we have refactored by using a combination of reusable subroutines and macros, taking advantage of both of
these techniques:
Subroutines (or methods or function modules) contribute by providing an own execution context, with auto-initialized local
variables and clear parameters for import and export.
Macros contribute by simplifying the notation of the call, improving readability of the client code ("client" in the sense of:
being a caller of the subroutines).

All the three advantages I mentioned above are achieved:


Nobody would deny that the final version alv5_fieldcat_create is better readable than the original.
The implementation details (here the "boring" population of an internal table) has been extracted from the code.
Instead of several almost-identical code segments, we are now reusing code, and only the variable parts are visible in the
top-level code block.

Mapping Structures
Another examples for illustration: move-corresponding is a great statement for mapping between different structures, if you
control the DDIC definition of both source and target structure. At the edge between two different code parts, however, this
"move-by-name" rule will not suffice, and an explicit mapping of structure components is required. This produces noisy
code (again, a real-life example with very old, but still productive code):

1. gs_kundendaten-customer = gs_kundenauftragsdaten-partn_numb.
2. gs_kundendaten-title_key = gs_kundenauftragsdaten-title_key.
3. gs_kundendaten-title_p = gs_kundenauftragsdaten-title.
4. gs_kundendaten-firstname = gs_kundenauftragsdaten-name_2.
5. gs_kundendaten-lastname = gs_kundenauftragsdaten-name.
6. * gs_kundendaten-secondname
7. gs_kundendaten-street = gs_kundenauftragsdaten-street.
8. * gs_kundendaten-house_no
9. gs_kundendaten-postl_cod1 = gs_kundenauftragsdaten-postl_code.
10. gs_kundendaten-city = gs_kundenauftragsdaten-city.
11. gs_kundendaten-tel1_numbr = gs_kundenauftragsdaten-telephone.
12. gs_kundendaten-tel2_numbr = gs_kundenauftragsdaten-telephone2.
13. * gs_kundendaten-tel3_numbr
14. gs_kundendaten-fax_number = gs_kundenauftragsdaten-fax_number.
15. * gs_kundendaten-e_mail
16. gs_kundendaten-langu_p = gs_kundenauftragsdaten-langu.
17. * gs_kundendaten-langu_descr
18. gs_kundendaten-country = gs_kundenauftragsdaten-country.

When I had to change another piece of the method which contained that code, I placed this little macro in the classes macro
section:

1. define _move_from_to.
2. &2-&4 = &1-&3.
3. end-of-definition.

With this one-line macro, that code part could be rewritten in a more readable way [sorry for the screenshot, but I see no way
to customize "jive" code sections with a monospaced font, as would be natural]:

As was the case in the original code, mistyped column names will be detected at compile-time - which is good. Macro-based
code only looks like free text, but is equally strict concerning the syntax as any other ABAP code.

Declarative programming: A button states example


Suppose, there is a number of buttons requested for a new web application. The state of these buttons - whether they are
invisible, inactive (i.e. visible but not clickable), or active, has been specified depending on the current transactional mode
(create mode, change mode, display mode).

The change request might contain the following little table:


With macros, it is possible to take this table over into the coding as it is written in the spec!

For this, let us create a little dictionary: We need a data type for the fcode (the user command = the button he pressed), for
the transactional mode (create, change or display), and for the button state (active, inactive, invisible).

Have in mind that such a dictionary will be used throughout the whole application, it is not defined only for this particular
purpose. It is good style to define data types and structured constants for all the discrete variables appearing in the data
model of the application.

When using code instead of DDIC for illustration, the types could look like this

1. types:
2. ty_fcode type c length 4, " User command
3. ty_state type x length 1, " Button state (active, inactive, invisible)
4. ty_mode type n length 1. " Mode (create, change, display)

To define the different values these types may have, we use structured constants. In real life, we would place these constants
in a type pool or in a class section. Anyway, the declaration of the possible values of all these data will look like this:

1. constants:
2. begin of fcode,
3. actualize type ty_fcode value 'ACTU',
4. save type ty_fcode value 'SAVE',
5. undo type ty_fcode value 'UNDO',
6. reject type ty_fcode value 'CANC',
7. print type ty_fcode value 'PRNT',
8. end of fcode,
9. begin of mode,
10. create type ty_mode value '1',
11. change type ty_mode value '2',
12. display type ty_mode value '3',
13. end of mode,
14. begin of state,
15. active type ty_state value 0,
16. inactive type ty_state value 1,
17. invisible type ty_state value 2,
18. end of state.

Let's say, we define a method which retrieves the button state, depending on transactional mode and button code:

1. methods get_state importing iv_fcode type ty_fcode


2. iv_mode type ty_mode
3. returning value(ev_state) type ty_state.

Then we can introduce the following macro:

1. define _set_button_state_per_mode.
2. if iv_fcode eq fcode-&1.
3. case iv_mode.
4. when mode-create.
5. ev_state = state-&2.
6. when mode-change.
7. ev_state = state-&3.
8. when mode-display.
9. ev_state = state-&4.
10. endcase.
11. endif.
12. end-of-definition.

This finally allows us to implement the method simply by copy-pasting the change request!

How is the change behaviour of this code when requirements will change?

There will hardly ever be a new transactional mode. Therefore, the macro definition will almost never be changed. But there
may be new buttons in the future, and the given state definitions may be changed in forthcoming versions of the software.
Therefore, what is most likely to be changed, is the implementation of the method get_state. In this form, with the ABAP
code actually hidden behind the macro, the part that is most likely to be changed, can be changed with maximum ease,
readability - and safety. There are no direct values like strings (where a typo would appear at runtime, not at compile-time):
the symbols in use - like active or reject - need to be defined in the appropriate structured constant - otherwise the method
will not compile.

Debugging
Since macros do not have an own execution context, they cannot be displayed properly in the debugger. All the statements
listed in the macro will be shown and executed as only one statement in the debugger. It is not possible to single-step
through the instructions of a macro.

This clearly is a problem - but only for highly complex macros which of course should be avoided. A rule of thumb: If you
find out that your macro spans more than three statements, you might consider to extract these lines in a form routine or a
private method, leaving only the call of this method in the macro.

Symbols and Literals


Since macros operate on a pre-compile level, they don't distinguish literals and symbols. The actual parameters can be used
for building symbols (like method names or variable names) as well as for character literals - or both at the same time.

Just for illustration, think of the following macro:

1. define _set_field.
2. ls_fieldcat-&2 = &3.
3. modify ct_fieldcat from ls_fieldcat transporting &2 where fieldname = '&1'.
4. assert sy-subrc eq 0.
5. end-of-definition.

It could be applied like this:

1. _set_field ABELN :
2. hotspot 'X',
3. scrext_m text-001.

Here, &2 and &3 are replaced by a part of a symbol name and a literal, respectively. When using a subroutine or method
instead, the component &2 of a structure would be accessible only by dynamic programming - using a statement like
ASSIGN COMPONENT ... OF STRUCTURE ... TO ... - which is more complicated. Also, a typographical error in the
macro would be detected immediately by the compiler (if there is no component of the given structure).

The first macro parameter is special. It is passed without quotation marks, but is used inside the macro to build a string (...
where name = '&1' ). This works, but is not safe. Somebody could convert your code to lower case. Then the modify
statement will fail. To avoid this, one can normalize the case of the passed parameter first:

1. define _set_field.
2. ls_fieldcat-fieldname = &1.
3. translate ls_fieldcat-fieldname to upper case.
4. ls_fieldcat-&2 = &3.
5. modify ct_fieldcat from ls_fieldcat transporting &2 where fieldname = ls_fieldcat-name.
6. assert sy-subrc eq 0.
7. end-of-definition.

This macro would be case-safe - although it now exceeds three lines of code. Clearly, in this case it is better to pass &1 as a
literal:

1. define _set_field.
2. ls_fieldcat-&2 = &3.
3. modify ct_fieldcat from ls_fieldcat transporting &2 where fieldname = &1.
4. assert sy-subrc eq 0.
5. end-of-definition.

Macros as Building Blocks for Internal DSLs


Recently, my interest for macros was renewed, when I was reading Martin Fowler's book on Domain Specific Languages
(DSL).

What is a DSL? Here is a definition by Martin Fowler:


Domain-specific language (noun): a computer programming language of limited expressiveness focused on a particular
domain.
Important points:
He intentionally writes "computer language", not "programming language". Of course, there is logic and processing
behind any DSL. But often, a DSL feels more declarative than imperative. The text processing system TeX and CSS are
examples of DSLs with a declarative taste, while SQL, regular expressions or the language of makefiles are DSLs that feel
more like a programming language. May this be as it is - at least, all DSLs have language features: They have a grammar, a
dictionary and a set of allowed expressions.
Important is the limited expressiveness of the language. You can't use a DSL to solve any software problem. This is an
important difference to programming languages like ABAP, Java and so on. With Turing complete languages like ABAP,
you can solve any problem, if it is algorithmically solvable at all. But you can't use a DSL to solve any problem. A DSL is
designed for one particular problem, or a particular class of problems.
Being restricted to a particular domain, a good DSL requires a syntax which makes it as readable as possible for the expert
of that particular domain. Keywords and statement structure should be chosen in such a way that the code displays
the intention with maximum readability, minimum code noise. In the examples above (CSS, makefiles and regular
expressions), developers are the users. In a software for business applications, the code should be business-readable.

Martin Fowler describes two categories of domain-specific languages:

The first category, the external DSLs, are defined and parsed outside of the host language, thus requiring an own parser.
On the other hand, the internal DSLs are in fact fragments of code written in the host language (under certain rules). They
are parsed, compiled and executed like any other part of the host language. There is no separate parser necessary.

Here is one of Fowler's examples for an internal DSL. Using the "." member access operator, which is a standard feature of
many languages like Java, JavaScript, or C/C++, he can write down a code fragment which configures a PC, following the
so-called method chaining pattern:

1. computer()
2. .processor()
3. .cores(2)
4. .i386()
5. .disk()
6. .size(150)
7. .disk()
8. .size(75)
9. .speed(7200)
10. .sata()
11. .end();

Due to the language restrictions, there still is some syntactic noise in this code: The brackets, the dots, and the computer()
and end() statement are not part of the code intention. But everybody understands directly that this text is about specifying
the parts of a computer.

How would one design that part in ABAP? Using macros, we could write:

1. processor :
2. cores 2,
3. type i386.
4. disk 1 :
5. size 150.
6. disk 2 :
7. size 75,
8. speed 7200,
9. type sata.

We will see in a minute how to implement the underlying macros.

For the moment, I just want to say that I find this version more expressive, more to-the-point than the Java example with
method chaining.

Only the '1' and '2', the numbering of the disks, is a small technical debt (why couldn't the system increment such a counter
behind the scenes?). This is necessary because otherwise we couldn't detect when the configuration of the first is finished,
and the second disk begins. But we could sell this as a feature: Instead of '1' and '2', we could e.g. use this parameter to
assign names to the different disks...

Everything in this code is under syntax control: If you typed 'xata' instead of 'sata', and xata is not a supported disk type, the
compiler will send a syntax error.
Structured Constants as Dictionary
The first technique for implementing such a DSL is using structured constants. Suppose we want to specify a disk type from
a given list of disk types, using a macro "disk_type". So in the DSL we just want to type

1. disk_type sas.

Then an appropriate field of the semantic model, let's say a component of a structure ls_disk, should be filled with the disk
type, here 'SAS'. Also, it should be impossible to type others than the disk types known to the system.

In order to specify the known disk types, we use a structured constant:

1. begin of gc_disk_type,
2. sata type string value 'SATA',
3. sas type string value 'SAS',
4. scsi type string value 'SCSI',
5. end of gc_disk_type.

Now, we can define the macro (a one-liner, as usual):

1. define disk_type.
2. ls_disk->type = gc_disk_type-&1.
3. end-of-definition.

We proceed similarly with a macro processor_type for the different processor types (i386, i686, PPC,...).

The advantage of the structured constant is that it can be used elsewhere in the code, too, as a replacement of a string literal
by a symbolic name (which is good practice anyway).

Name Composition
There is another useful technique in this context. I would like to call it name composition. The idea is to use one of the
macro arguments to build a known symbolic name - e.g. of another macro, of a method, subroutine, function module.

In the example above, the instructions for the processor

1. processor :
2. type i386,
3. cores 2.

are using the colon / comma notation, which fits nicely - the colon has an explanatory meaning, in the sense: "The following
parts specify in detail what is preceded by me." That is exactly what is intended here.

Technically, however, this expands into two calls of a macro named 'processor':

1. processor type i386.


2. processor cores 2.

We will now use the second macro argument for building the name of a specific macro that has to be employed instead. This
is name composition:

1. define processor.
2. processor_&1 &2.
3. end-of-definition.

Now, we can add macros for all the things we want to specify for the processor (which is designed as a component of the
object eo_processor here):

1. define processor_type.
2. eo_computer->gs_processor-type = gc_processor_type-&1.
3. end-of-definition.
4.
5. define processor_cores.
6. eo_computer->gs_processor-cores = &1.
7. end-of-definition.

This works similarly for the disk specification. The only point here is that there are several disks. Therefore, we need the
name argument (the second argument) of the macro, to get the right table line, before the data can be called.

If we have a local reference variable ls_disk, pointing to a disk structure, the macro "disk" is written as follows:

1. define disk.
2. if ls_disk is not bound or ls_disk->name ne &1.
3. ls_disk = eo_computer->get_disk( &1 ).
4. endif.
5. disk_&2 &3.
6. end-of-definition.

Here, a method is called first to position or generate the correct entry in the table of disks:

1. method get_disk.
2. data: ls_disk type ty_disk.
3. read table gt_disks reference into es_disk
4. with table key name = iv_name.
5. if sy-subrc ne 0.
6. ls_disk = get_default_disk( ).
7. ls_disk-name = iv_name.
8. insert ls_disk
9. into table gt_disks
10. reference into es_disk.
11. endif.
12. endmethod. "get_disk
The following form shows how the internal DSL is embedded into the ABAP code: It returns an instance eo_computer
which carries the semantic model. Such a routine (or method, or function) can be called by other programs in order to get
the data of the configured PC.

The DSL section is marked with special comments: The part enclosed by these comments could be edited (and changed)
separate from the rest of the program, if an appropriate editor tool is used. In a forthcoming blog, I plan to explain such an
internal DSL editor tool in more detail.

1. form get_computer changing eo_computer type ref to lcl_computer.


2. data: ls_disk type ref to lcl_computer=>ty_disk.
3. create object eo_computer.
4. * <<< BEGIN DSL
5. processor :
6. type i386,
7. cores 2.
8. disk 1 :
9. size 2000,
10. speed 750,
11. type sata.
12. disk 2 :
13. type sas.
14. * <<< END DSL
15. endform.

The full source code of this internal DSL example can be inspected on my pastebin.

Order of the parameters


If a macro is called several times, there may be parameters whose values vary frequently, and others varying only rarely. The
macro should be designed with the frequently-changing parameters at the end of its parameter list. This, again, is for being
able to exploit the colon/comma mechanism.

Observe my choice of the parameter order in the _move_from_to example above. Only in the order chosen above '&2-&4 =
&1-&3', I can arrange the macro's actual parameter in such a ways that the move process is nicely visible. This shows that
the order of the macro arguments is important. Sometimes, a macro with the wrong parameter order makes the code worse
than it was before.

Naming
For macro names, the same considerations are valid as for any other symbolic name in programming: They should be chosen
in such a way that the code which uses them becomes as readable as possible. This is particularly important in the scope of a
DSL.

Prefixes: In my opinion, it is useful to mark a non-DSL macro (which is only used for simplifying, saving and clarifying
ABAP code) with a special prefix. I use an underscore. The purpose of such a prefix is to distinguish the macro on the first
view from an ABAP language keyword.

For macros used in DSLs, however, a prefix would be inappropriate, as the DSL idea is to separate away the "technical
level".

Summary
Macros are a code reuse technique which, like all the other reuse techniques, have the three advantages of improving code
readability, simplifying code, and removing code duplications.

Macros come out to be particularly useful as building blocks of internal DSLs.

Of course, there always is the possibility of abuse, in particular when the macro contains too many lines. But this is no
reason to forbid them in the programming guidelines. As already the Romans said: Abusus non tollit usum.

Y9030013: A macro (DEFINE ) Definition and processing int.


table
A small macro should show that these interesting control statements can be inserted in quite
different ways. In this example only scratches the surface of the macro possibilities.

DEFINE VARTAB
&1 = The table name of the internal table is determined.
&2 = A field name with the length 30 of the type C.
&3 = The table structure is specified by the 3rd parameter.
E.g. of a macro call: VARTAB IT100 FIELDNAME T100.
DEFINE FILLTAB1
MOVE &1 TO &2+30. = The content of source field &1 moved to the target field (&2+30)
MOVE &3 TO &2-FIELDNAME. = The &2 parameter will be connected with FIELDNAME.
APPEND &2. = The internal table &2 will be filled. E.g. of a macro call: FILLTAB1 T100 IT100
T100 .

The parameters of the call of FILLTAB1 must refer to the parameters used in VARTAB call.
Consequently the ABAP interpreter must already know the real field names of the internal table
when calling the macro FILLTAB. The internal table is only effective on internal level. The same
memory area is allocated for both tables.

ABAP-Source-Code
You can cut and paste the source code directly into the ABAP-Workbench.
REPORT Y9030013 LINE-SIZE 130. "Release 3.1G, 4.5A
1
************************************************************************
2 * Copyright (c) 1999 by CT-Team, 33415 Verl, http://www.ct-software.com
3 *
4 * You can use or modify this report for your own work as long
5 * as you don't try to sell or republish it.
* In no event will the author be liable for indirect, special,
6 * Incidental, or consequental damages (if any) arising out of
7 * the use of this report.
8 * only use with the CT-DEBUG_Simulator
9 ************************************************************************
10 *BREAK-POINT.
*//////////////////////////////////////////////////////////////////////*
11 MOVE: 'Read tables and use a VARTAB (DEFINE-Macro)'
12 TO SY-TITLE.
13 *//////////////////////////////////////////////////////////////////////*
14 *************** External tables **********************
TABLES: YT100.
15 *
16 *************** Macro definitions **********************
17 DEFINE VARTAB.
18 DATA: BEGIN OF &1 OCCURS 0.
DATA: &2(30) TYPE C.
19 INCLUDE STRUCTURE &3.
20 DATA: END OF &1.
21 END-OF-DEFINITION.
22 *
DEFINE FILLTAB1.
23 MOVE &1 TO &2+30.
24 MOVE &3 TO &2-FIELDNAME.
25 APPEND &2.
26 END-OF-DEFINITION.
27 *
*//////////////////////////////////////////////////////////////////////*
28 ************* - Main Section *******************
29 *//////////////////////////////////////////////////////////////////////*
30 *
31 PERFORM READ-DISPLAY-YT100.
*
32 *//////////////////////////////////////////////////////////////////////*
33 ************* Subroutines *******************
34 *//////////////////////////////////////////////////////////////////////*
35 ************************************************************************
36 * Read and display a few rows of T100
************************************************************************
37 FORM READ-DISPLAY-YT100.
38 *
39 ULINE. SKIP 2.
40 WRITE: /5 'Step 1: Read and display IT100 ' COLOR 5.
41 *.......................................................................
*
42 VARTAB IT100 FIELDNAME YT100. "macro
43 *.......................................................................
44 SELECT * FROM YT100
45 UP TO 50 ROWS
WHERE ZSPRSL EQ 'E'
46 AND ZARBGB GT 'Z10'.
47 *
48 FILLTAB1 YT100 IT100 'YT100 '. "macro
49 *
50 ENDSELECT.
*#######################################################################
51 WRITE: /1 'ZSPRSL',
52 8 'ZARBGB',
53 29 'ZMSGNR',
54 36 'Messagetext1',
77 'Messagetext2'.
55 ULINE. SKIP.
56 *
57 LOOP AT IT100.
58 WRITE: /1 IT100-ZSPRSL,
59 8 IT100-ZARBGB,
29 IT100-ZMSGNR,
60 36(40) IT100-ZTEXT1,
61 77(40) IT100-ZTEXT2.
62 ENDLOOP.
63 *
ENDFORM.
64
************************************************************************
65 ************************************************************************
66 ******************* END OF PROGRAM *************************************
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81