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