tagged release 0.6.4
[parrot.git] / languages / m4 / src / macro.pir
bloba8b2d5e26b5b634bb188dc646876ec89f0d275fa
1 # $Id$
3 =head1 NAME
5 src/macro.pir - does macro substitution
7 =head1 DESCRIPTION
9 Copyright:  2004-2005 Bernhard Schmalhofer.  All Rights Reserved.
10 SVN Info:   $Id$
11 History:    Ported from GNU m4 1.4
12 References: http://www.gnu.org/software/m4/m4.html
14 =head1 SUBROUTINES
16 =head2 void expand_input( Hash state )
18 Loop through some input files.
19 TODO: read files in next_token()
21 =cut
23 .sub expand_input
24   .param pmc  state
26   .local string token_data, token_type  # current token data and type
28   # go through the input, token for token
29 NEXT_TOKEN:
30   ( token_type, token_data ) = next_token( state )
31   expand_token( state, token_type, token_data )
32   if token_type != 'TOKEN_EOF' goto NEXT_TOKEN
34 FINISH_EXPAND_INPUT:
35 .end
38 =head2 void expand_token( Hash state, string token_type, string token_data )
40 Expand one token, according to its type.  Potential macro names
41 (TOKEN_WORD) are looked up in the symbol table, to see if they have a
42 macro definition.  If they have, they are expanded as macros, otherwise
43 the text are just copied to the output.
45 =cut
47 .sub expand_token
48   .param pmc       state
49   .param string    token_type
50   .param string    token_data
52   .local pmc symtab
53   symtab = state['symtab']
55   # TOKEN_EOF and TOKEN_MACDEF are not expanded
56   if token_type == 'TOKEN_EOF'    goto FINISH_EXPAND_TOKEN
57   if token_type == 'TOKEN_MACDEF' goto FINISH_EXPAND_TOKEN
59   # 'TOKEN_STRING' does the same as 'TOKEN_SIMPLE',
60   if token_type == 'TOKEN_STRING' goto SHIPOUT_TEXT
61   if token_type == 'TOKEN_SIMPLE' goto SHIPOUT_TEXT
63   if token_type != 'TOKEN_WORD' goto NO_TOKEN_WORD
64     .local int symbol_exists
65     symbol_exists = exists symtab[token_data]
66     unless symbol_exists goto SHIPOUT_TEXT
67       .local pmc symbol
68       symbol = symtab[token_data]
69       .local string symbol_type
70       symbol_type = symbol['type']
71       if symbol_type != 'TOKEN_FUNC' goto EXPAND_MACRO
72         # there are two types of macro invocations:
73         # macro
74         # macro( arg1, ... )
75         # Most macros needs a parameter list, but e.g. __file__ has not args
76         .local int blind_no_args
77         blind_no_args = symbol['blind_no_args']
78         unless blind_no_args goto EXPAND_MACRO
79           .local string input_string
80           input_string = state['stack';'input';0;'string']
81           .local int first_char, open_parenthesis
82           first_char = ord input_string
83           open_parenthesis = ord '('
84           if first_char != open_parenthesis goto SHIPOUT_TEXT
85           goto EXPAND_MACRO
87 EXPAND_MACRO:
88   expand_macro( state, symbol )
89   goto FINISH_EXPAND_TOKEN
91 NO_TOKEN_WORD:
92   printerr "unknown token type: "
93   printerr token_type
94   end
96 SHIPOUT_TEXT:
97   shipout_text( state, token_data )
98   goto FINISH_EXPAND_TOKEN
100 FINISH_EXPAND_TOKEN:
101 .end
104 =head2 string processed_token expand_macro( Hash state, Hash symbol )
106 The macro expansion is handled by expand_macro().
107 It parses the arguments in collect_arguments() and
108 builds a ResizablePMCArray containing the arguments.
109 The arguments themselves are stored on a local obstack.
110 expand_macro() uses call_macro() to do the call of the macro.
112 expand_macro() is potentially recursive, since it calls expand_argument(),
113 which might call expand_token (), which might call expand_macro().
115 =cut
117 .sub expand_macro
118   .param pmc  state
119   .param pmc  symbol
121   .local int expansion_level
122   expansion_level = state['expansion_level']
123   .local int nesting_limit
124   nesting_limit = state['nesting_limit']
125   inc expansion_level
126   if expansion_level <= nesting_limit goto NESTING_LIMIT_NOT_REACHED_YET
127     printerr "ERROR: Recursion limit of "
128     printerr nesting_limit
129     printerr "exceeded, use -L<N> to change it"
130     end
131 NESTING_LIMIT_NOT_REACHED_YET:
132   state['expansion_level'] = expansion_level
134   .local pmc arguments
135   arguments = new .ResizablePMCArray
136   collect_arguments( state, arguments )
138   .local string text
139   ( text ) = call_macro( state, symbol, arguments )
140   .local string input_string
141   input_string = state['stack';'input';0;'string']
142   input_string = text . input_string
143   state['stack';'input';0;'string'] = input_string
145   expansion_level = state['expansion_level']
146   dec expansion_level
147   state['expansion_level'] = expansion_level
149 .end
152 =head2 string processed_token call_macro( ResizablePMCArray macro, string token )
154 Apply macro to a token.
156 TODO: distinguish between TOKEN_FUNC and TOKEN_WORD
158 =cut
160 .sub call_macro
161   .param pmc       state
162   .param pmc       symbol
163   .param pmc       arguments
165   .local string symbol_type, symbol_name
166   symbol_type = symbol['type']
167   symbol_name = symbol['name']
169   .local string    text
170   if symbol_type != 'TOKEN_TEXT' goto NO_TOKEN_TEXT
171     text = symbol['text']
172     goto FINISH_CALL_MACRO
173 NO_TOKEN_TEXT:
175   if symbol_type == 'TOKEN_FUNC' goto TOKEN_FUNC
176     printerr "INTERNAL ERROR: Bad symbol type in call_macro"
177     end
178 TOKEN_FUNC:
179   .local pmc func
180   func = symbol['func']
181   # indirect call of subs, seems to need elaborate PIR syntax
182   .begin_call
183     .arg state
184     .arg arguments
185   .call func
186     ret_func_1:
187     .result text
188   .end_call
190 FINISH_CALL_MACRO:
191   .return ( text )
192 .end
195 =head2 void collect_arguments
197 Collect all the arguments to a call of a macro.
199 =cut
201 .sub collect_arguments
202   .param pmc state
203   .param pmc arguments
205   # The macro name has already been read in, thus we need to match
206   # something like "('furcht', 'Hallo Welt')"
207   # and capture the name 'furcht' and the substitution 'Hallo Welt'.
208   # Thus we need to remenber the start and the length of these two captures
209   .local int cnt_stack
210   .local string input_string
211   input_string = state['stack';'input';0;'string']
213   # We need a '(' at beginning of string
214   .local int index_opening
215   index_opening = index input_string, '('
216   if index_opening != 0 goto NOT_A_ARGUMENT_LIST
218   # expand strings
219   .local int start_index
220   start_index = 0
221   .local int num_args
222   num_args = 0
223   .local int index_comma, index_closing, index_start_string, index_end_string, len_string
224   .local string arg
225 EXPAND_ARG:
226   # find a string before ')'
227   index_closing = index input_string, ')', start_index
229   if index_closing == -1 goto NO_MORE_ARGS
230   if num_args == 0 goto SKIP_SKIP_COMMA
231   index_comma = index input_string, ',', start_index
232   if index_comma == -1 goto NO_MORE_ARGS
233   if index_closing < index_comma goto NO_MORE_ARGS
234   start_index = index_comma
235 SKIP_SKIP_COMMA:
236   index_start_string = index input_string, '`', start_index
237   if index_start_string == -1 goto NO_MORE_ARGS
238   inc index_start_string
239   if index_closing < index_start_string goto NO_MORE_ARGS
240   index_end_string = index input_string, "'", index_start_string
241   if index_end_string == -1 goto NO_MORE_ARGS
242   len_string = index_end_string - index_start_string
243   substr arg, input_string, index_start_string, len_string
244   start_index = index_end_string
245   push arguments, arg
246   num_args = arguments
247   goto EXPAND_ARG
249 NO_MORE_ARGS:
250   inc index_closing
251   substr input_string, 0, index_closing, ''
253 NOT_A_ARGUMENT_LIST:
254   state['stack';'input';0;'string'] = input_string
255 .end
257 # Local Variables:
258 #   mode: pir
259 #   fill-column: 100
260 # End:
261 # vim: expandtab shiftwidth=4 ft=pir: