1# $Id$ 2 3package CPU::Z80::Assembler::Macro; 4 5#------------------------------------------------------------------------------ 6 7=head1 NAME 8 9CPU::Z80::Assembler::Macro - Macro pre-processor for the Z80 assembler 10 11=cut 12 13#------------------------------------------------------------------------------ 14 15use strict; 16use warnings; 17 18use CPU::Z80::Assembler::Parser; 19use Iterator::Simple::Lookahead; 20use Asm::Preproc::Token; 21 22our $VERSION = '2.18'; 23 24#------------------------------------------------------------------------------ 25# Class::Struct cannot be used with Exporter 26#use Class::Struct ( 27# name => '$', # macro name 28# params => '@', # list of macro parameter names 29# locals => '%', # list of macro local labels 30# tokens => '@', # list of macro tokens 31#); 32sub new { my($class, %args) = @_; 33 return bless [ 34 $args{name}, 35 $args{params} || [], 36 $args{locals} || {}, 37 $args{tokens} || [] 38 ], $class; 39} 40sub name { defined($_[1]) ? $_[0][0] = $_[1] : $_[0][0] } 41sub params { defined($_[1]) ? $_[0][1] = $_[1] : $_[0][1] } 42sub locals { defined($_[1]) ? $_[0][2] = $_[1] : $_[0][2] } 43sub tokens { defined($_[1]) ? $_[0][3] = $_[1] : $_[0][3] } 44 45#------------------------------------------------------------------------------ 46 47=head1 SYNOPSIS 48 49 use CPU::Z80::Assembler::Macro; 50 51 my $macro = CPU::Z80::Assembler::Macro->new( 52 name => $name, 53 params => \@params_names, 54 locals => \%local_labels, 55 tokens => \@token_list); 56 $macro->parse_body($input); 57 $macro->expand_macro($input); 58 59=head1 DESCRIPTION 60 61This module provides a macro pre-processor to parse macro definition statements, 62and expand macro calls in the token stream. Both the input and output streams 63are L<Iterator::Simple::Lookahead|Iterator::Simple::Lookahead> objects returning sequences 64of tokens. 65 66The object created by new() describes one macro. It is used during the parse phase 67to define the macro object while reading the input token stream. 68 69=head1 EXPORTS 70 71None. 72 73=head1 FUNCTIONS 74 75=head2 new 76 77Creates a new macro definition object, see L<Class::Struct|Class::Struct>. 78 79=head2 name 80 81Get/set the macro name. 82 83=head2 params 84 85Get/set the formal parameter names list. 86 87=head2 locals 88 89Get/set the list of local macro labels, stored as a hash. 90 91=head2 tokens 92 93Get/set the list of tokens in the macro definition. 94 95=cut 96 97#------------------------------------------------------------------------------ 98 99=head2 parse_body 100 101This method is called with the token input stream pointing at the first token 102after the macro parameter list, i.e. the '{' or ':' or "\n" character. 103 104It parses the macro body, leaving the input stream after the last token of the 105macro definition ('endm' or closing '}'), with all the "\n" characters of the 106macro defintion pre-pended, and filling in locals() and tokens(). 107 108=cut 109 110#------------------------------------------------------------------------------ 111 112sub parse_body { 113 my($self, $input) = @_; 114 my $token; 115 116 # skip { 117 my $opened_brace; 118 defined($token = $input->peek) 119 or Asm::Preproc::Token->error_at($token, "macro body not found"); 120 if ($token->type eq '{') { 121 $input->next; 122 $opened_brace++; 123 } 124 elsif ($token->type =~ /^[:\n]$/) { 125 # OK, macro body follows on next line 126 } 127 else { 128 $token->error("unexpected '". $token->type ."'"); 129 } 130 131 # retrieve tokens 132 my @macro_tokens; 133 my @line_tokens; 134 my %locals; 135 136 # need to note all the labels in the macro, 137 # i.e. NAME after statement end 138 my $last_stmt_end = 1; 139 140 my $parens = 0; 141 while (defined($token = $input->peek)) { 142 my $type = $token->type; 143 if ($type eq "{") { 144 $parens++; 145 push @macro_tokens, $token; 146 $input->next; 147 } 148 elsif ($type eq "endm") { 149 $opened_brace 150 and $token->error("expected \"}\""); 151 $input->next; # skip delimiter 152 last; 153 } 154 elsif ($type eq "}") { 155 if ($parens > 0) { 156 $parens--; 157 push @macro_tokens, $token; 158 $input->next; 159 } 160 else { 161 $input->next if $opened_brace; # skip delimiter 162 last; 163 } 164 } 165 elsif ($type eq "NAME" && $last_stmt_end) { # local label 166 $locals{$token->value}++; 167 push @macro_tokens, $token; 168 $input->next; 169 } 170 else { 171 push @macro_tokens, $token; 172 push @line_tokens, $token if $type eq "\n"; 173 # save new-lines for listing 174 $input->next; 175 } 176 $last_stmt_end = ($type =~ /^[:\n]$/); 177 } 178 defined($token) 179 or Asm::Preproc::Token->error_at($token, "macro body not finished"); 180 ($parens == 0) 181 or $token->error("Unmatched braces"); 182 183 # prepend all seen LINE tokens in input 184 $input->unget(@line_tokens); 185 186 $self->tokens(\@macro_tokens); 187 $self->locals(\%locals); 188} 189 190#------------------------------------------------------------------------------ 191 192=head2 expand_macro 193 194This method is called with the input stream pointing at the first token 195after the macro name in a macro call. It parses the macro arguments, if any 196and expands the macro call, inserting the expanded tokens in the input stream. 197 198=cut 199 200#------------------------------------------------------------------------------ 201 202sub expand_macro { 203 my($self, $input) = @_; 204 our $instance++; # unique ID for local labels 205 206 my $start_token = $input->peek; # for error messages 207 defined($start_token) or die; # must have at least a "\n" 208 209 my $args = $self->parse_macro_arguments($input); 210 211 # compute token expansion 212 my $macro_stream = Iterator::Simple::Lookahead->new(@{$self->tokens}); 213 my $expand_stream = Iterator::Simple::Lookahead->new( 214 sub { 215 for(;;) { 216 my $token = $macro_stream->next; 217 defined($token) or return undef; # end of expansion 218 219 $token = $token->clone; # make a copy 220 $token->line($start_token->line); # set the line of invocation 221 222 if ($token->type eq 'NAME') { 223 my $name = $token->value; 224 if (exists $args->{$name}) { 225 my @tokens = @{$args->{$name}}; # expansion of the name 226 return sub {shift @tokens}; # insert a new iterator to return 227 # these - $macro_stream->unget(); 228 # would allow recursive expansion 229 # of arg names - not intended 230 } 231 elsif (exists $self->locals->{$name}) { 232 $token->value("_macro_".$instance."_".$name); 233 return $token; 234 } 235 else { 236 return $token; 237 } 238 } 239 else { 240 return $token; 241 } 242 } 243 }); 244 245 # prepend the expanded stream in the input 246 $input->unget($expand_stream); 247} 248 249#------------------------------------------------------------------------------ 250 251=head2 parse_macro_arguments 252 253This method is called with the input stream pointing at the first token 254after the macro name in a macro call. It parses the macro arguments, leaves 255the input stream after the macro call, and returns an hash reference mapping 256formal argument names to list of tokens in the actual parameters. 257 258The arguments are list of tokens separated by ','. An argument can be enclosed 259in braces '{' '}' to allow ',' to be passed - the braces are not part of the argument 260value. 261 262=cut 263 264#------------------------------------------------------------------------------ 265 266sub parse_macro_arguments { 267 my($self, $input) = @_; 268 my %args; 269 my $token; 270 271 my @params = @{$self->params}; # formal parameters 272 for (my $i = 0; $i < @params; $i++) { 273 my $param = $params[$i]; 274 $token = $input->peek; 275 defined($token) && $token->type !~ /^[:\n,]$/ 276 or Asm::Preproc::Token->error_at($token, 277 "expected value for macro parameter $param"); 278 my @arg = $self->_parse_argument($input); 279 $args{$param} = \@arg; 280 281 if ($i != $#params) { # expect a comma 282 $token = $input->peek; 283 defined($token) && $token->type eq ',' 284 or Asm::Preproc::Token->error_at($token, 285 "expected \",\" after macro parameter $param"); 286 $input->next; 287 } 288 } 289 290 # expect end of statement, keep input at end of statement marker 291 $token = $input->peek; 292 (!defined($token) || $token->type =~ /^[:\n]$/) 293 or Asm::Preproc::Token->error_at($token, "too many macro arguments"); 294 295 return \%args; 296} 297 298#------------------------------------------------------------------------------ 299# @tokens = _parse_argument($input) 300# Extract the sequence of input tokens from $input into @tokens up to and 301# not including the delimiter token 302sub _parse_argument { 303 my($class, $input) = @_; 304 my $token; 305 306 # retrieve tokens 307 my @tokens; 308 my $parens = 0; 309 my $opened_brace; 310 while (defined($token = $input->peek)) { 311 my $type = $token->type; 312 if ($type =~ /^[:\n,]$/ && $parens == 0) { 313 last; 314 } 315 elsif ($type eq '{') { 316 $parens++; 317 push(@tokens, $token) if $opened_brace++; 318 $input->next; 319 } 320 elsif ($type eq '}') { 321 if ($parens > 0) { 322 $parens--; 323 push(@tokens, $token) if --$opened_brace; 324 $input->next; 325 } 326 else { 327 $input->next if $opened_brace; # skip delimiter 328 last; 329 } 330 } 331 else { 332 push(@tokens, $token); 333 $input->next; 334 } 335 } 336 Asm::Preproc::Token->error_at($token, "unmatched braces") 337 if $parens != 0; 338 339 return @tokens; 340} 341 342#------------------------------------------------------------------------------ 343 344=head1 SYNTAX 345 346=head2 Macros 347 348Macros are created thus. This example creates an "instruction" called MAGIC 349that takes two parameters: 350 351 MACRO MAGIC param1, param2 { 352 LD param1, 0 353 BIT param2, L 354 label = 0x1234 355 ... more real instructions go here. 356 } 357 358Within the macro, param1, param2 etc will be replaced with whatever 359parameters you pass to the macro. So, for example, this: 360 361 MAGIC HL, 2 362 363Is the same as: 364 365 LD HL, 0 366 BIT 2, L 367 ... 368 369Any labels that you define inside a macro are local to that macro. Actually 370they're not but they get renamed to _macro_NN_... so that they 371effectively *are* local. 372 373There is an alternative syntax, for compatibility with other assemblers, with exactly the 374same effect. 375 376 MACRO MAGIC param1, param2 377 LD param1, 0 378 BIT param2, L 379 label = 0x1234 380 ... more real instructions go here. 381 ENDM 382 383A ',' can be passed as part of a macro argument, by enclosing the arguments between {braces}. 384 385 MACRO PAIR x { 386 LD x 387 } 388 PAIR {A,B} 389 390expands to: 391 392 LD A,B 393 394=head1 BUGS and FEEDBACK 395 396See L<CPU::Z80::Assembler|CPU::Z80::Assembler>. 397 398=head1 SEE ALSO 399 400L<CPU::Z80::Assembler|CPU::Z80::Assembler> 401L<Iterator::Simple::Lookahead|Iterator::Simple::Lookahead> 402 403=head1 AUTHORS, COPYRIGHT and LICENCE 404 405See L<CPU::Z80::Assembler|CPU::Z80::Assembler>. 406 407=cut 408 409#------------------------------------------------------------------------------ 410 4111; 412