#!/usr/bin/perl
#
# Preprocess the instruction pattern file.
#
# Generate some common repeated patterns here.
#
# Tag instructions which set the flags, and tag instructions
# which already do upper-zeroing.
#
# This can also be used to create other implied flags.
#

use integer;
use strict;

require 'x86/insns-iflags.ph';

our %macros;
our($macro, $outfile, $infile, $line);	# Public for error messages

# Common pattern for the basic 7 arithmetric functions
$macros{'arith'} = {
    'def' => *def_eightfold,
    'txt' => <<'EOL'
$$bwdq $op	rm#,reg#			[mr:	$hle o# $00# /r				]	8086,FL,SM,$lock
$$bwdq $op	reg#,rm#			[rm:	o# $02# /r				]	8086,FL,SM
$op		reg_al,imm8			[-i:	o8 $04 ib				]	8086,FL,SM
$op		rm8,imm8			[mi:	$hle 80 /$n ib				]	8086,FL,SM,$lock
$op		rm16,sbyteword16		[mi:	$hle o16 83 /$n ib,s			]	8086,FL,SM,$lock
$op		reg_ax,imm16			[-i:	o16 $05 iw				]	8086,FL,SM
$op		rm16,imm16			[mi:	$hle o16 81 /$n iw			]	8086,FL,SM,$lock
$op		rm32,sbytedword32		[mi:	$hle o32 83 /$n ib,s			]	386,FL,SM,$lock,$zu
$op		reg_eax,imm32			[-i:	o32 $05 id				]	386,FL,SM,$zu
$op		rm32,imm32			[mi:	$hle o32 81 /$n id			]	386,FL,SM,$lock,$zu
$op		rm64,sbytedword64		[mi:	$hle o64 83 /$n ib,s			]	X86_64,FL,LONG,SM,$lock,$zu
$op		reg_rax,sdword64		[-i:	o64 $05 id,s				]	X86_64,FL,LONG,SM,$zu
$op		rm64,sdword64			[mi:	$hle o64 81 /$n id,s			]	X86_64,FL,LONG,SM,$lock,$zu
$$bwdq $op	reg#?,reg#,rm#			[vrm:	evex.ndx.$nf.l0.m4.o#     $02# /r	]	$evex,FL,APX,SM
$$bwdq $op	reg#?,rm#,reg#			[vmr:	evex.ndx.$nf.l0.m4.o#     $00# /r	]	$evex,FL,APX,SM
$$wdq  $op	reg#?,rm#,sbyte#		[vmi:	evex.ndx.$nf.l0.m4.o#     83 /$n ib,s	]	$evex,FL,APX,SM
$$bwdq $op	reg#?,rm#,imm#			[vmi:	evex.ndx.$nf.l0.m4.o#     80# /$n i#	]	$evex,FL,APX,SM
EOL
};

# Common pattern for the basic shift and rotate instructions
# Separate legacy and EVEX versions because additional patterns are
# needed to handle the -X VEX versions
$macros{'shift'} = {
    'def' => *def_eightfold,
	'txt' => <<'EOL'
$$bwdq $op	rm#,unity			[m-:	o# d0# /$n]				]	8086,FL
$$bwdq $op	rm#,reg_cl			[m-:	o# d2# /$n]				]	8086,FL
$$bwdq $op	rm#,reg_cx			[m-:	o# d2# /$n]				]	8086,FL,ND
$$bwdq $op	rm#,reg_ecx			[m-:	o# d2# /$n]				]	8086,FL,ND
$$bwdq $op	rm#,reg_rcx			[m-:	o# d2# /$n]				]	8086,FL,ND
$$bwdq $op	rm#,imm8			[mi:	o# c0# /$n ib,u]			]	186,FL
EOL
};

# APX EVEX versions
$macros{'eshift'} = {
    'def' => *def_eightfold,
	'txt' => <<'EOL'
$$bwdq $op	reg#?,rm#,unity			[vm-:	evex.ndx.nf.l0.m4.o#  d0# /$n		]	$apx,FL,SM0-1
$$bwdq $op	reg#?,rm#,reg_cl		[vm-:	evex.ndx.nf.l0.m4.o#  d2# /$n		]	$apx,FL,SM0-1
$$bwdq $op	reg#?,rm#,reg_cx		[vm-:	evex.ndx.nf.l0.m4.o#  d2# /$n		]	$apx,FL,SM0-1,ND
$$bwdq $op	reg#?,rm#,reg_ecx		[vm-:	evex.ndx.nf.l0.m4.o#  d2# /$n		]	$apx,FL,SM0-1,ND
$$bwdq $op	reg#?,rm#,reg_rcx		[vm-:	evex.ndx.nf.l0.m4.o#  d2# /$n		]	$apx,FL,SM0-1,ND
$$bwdq $op	reg#?,rm#,imm8			[vmi:	evex.ndx.nf.l0.m4.o#  c0# /$n ib,u	]	$apx,FL,SM0-1
EOL
};

# -X shifts
$macros{'xshift'} = {
    'func' => sub {
	my($mac, $args, $rawargs) = @_;
	my @ol;
	my $vex = 'vex';
	my $vfl = '';
	if (grep { /^evex=1$/ } @$rawargs) {
	    $vex = 'evex';
	    $vfl = 'APX';
	}
	foreach my $xf (['X',"$vfl"], ['', "$vfl,ND,NF!,OPT"]) {
	    my($x,$fl) = @$xf;
	    foreach my $os (32, 64) {
		my $w = ($os eq 32) ? 'w0' : 'w1';
		my $ixor = sprintf('%02x', $os-1);
		push(@ol, "ROR$x reg$os,rm$os,imm8       [rmi: $vex.lz.f2.0f3a.$w f0 /r ib] BMI2,SM0-1,!FL,$fl");
		push(@ol, "ROL$x reg$os,rm$os,imm_known8 [rmi: $vex.lz.f2.0f3a.$w f0 /r ib^$ixor] BMI2,SM0-1,!FL,$fl");
		foreach my $ss (8, 16, 32, 64) {
		    foreach my $opp (['SHL','66'], ['SAL','66'], ['SAR','f3'], ['SHR','f2']) {
			my($op,$pp) = @$opp;
			my $ndss = ',ND' unless ($ss == $os && $op ne 'SAL');
			push(@ol, "$op$x reg$os,rm${os}*,reg$ss [rmv: $vex.lz.$pp.0f38.$w f7 /r] BMI2,SM0-1,!FL,$fl,$ndss");
		    }
		}
	    }
	}
	return @ol;
    }
};

#
# Common pattern for multiple 32/64, 16/32/64, or 8/16/32/64 instructions.
# 'z' is used for a null-prefixed default-sized instruction (osm/osd)
#
my @sizename = ('z', 'b', 'w', 'd', 'q');

for (my $i = 1; $i <= 31; $i++) {
    my $n;
    for (my $j = 0; $j < scalar @sizename; $j++) {
	$n .= $sizename[$j] if ($i & (1 << $j));
    }
    $macros{$n} = { 'func' => *func_multisize, 'mask' => $i };
}

sub func_multisize($$$) {
    my($mac, $args, $rawargs) = @_;
    my @sbyte = ('imm8', 'imm8', 'sbyteword16', 'sbytedword32', 'sbytedword64');
    my $long = 0;		# 1 for LONG, 2 for NOLONG, 3 for invalid
    my $wwflag = 0;

    my @ol;
    my $mask = $mac->{'mask'};

    for (my $i = 0; $i < scalar(@sizename); $i++) {
	next unless ($mask & (1 << $i));
	my $s = ($i > 0) ? 4 << $i : '';
	my $sn = $sizename[$i];
	my $sz = $s || 'sm';
	my $o;
	my $ins = join(' ', @$rawargs);
	my $nd = 0;

	# Conditional pattern inclusions
	# Syntax: (which1:text1/which2:text2/text3)
	# ... where "which" is a combination of sizename letters.
	# No colon means unconditional ("else" clause)
	while ($ins =~ /^(.*?)\(([^\)]+)\)(.*)$/) {
	    $o .= $1;
	    my @cpp = split(/\//, $2);
	    $ins = $3;

	    my $found;
	    foreach my $cp (@cpp) {
		if ($cp =~ /^([a-z]+)\:(.*)$/) {
		    $cp = $2;
		    $found = $1 =~ /$sn/;
		} else {
		    $found = 1;
		}
		if ($found) {
		    $o .= $cp;
		    last;
		}
	    }
	}

	$ins = $o.$ins;
	$o = '';

	while ($ins =~ /^(.*?)((?:\b[0-9a-f]{2}(?:\+r)?|\bsbyte|\bimm|\bsel|\bopt\w?|\b[ioa]d?|\b(?:reg_)?[abcd]x|\bk?reg|\bk?rm|\bw|\bS\b)?\#{1,2}|\b(?:reg|rm)64\b|\b(?:o64)?nw\b|\b(?:NO)?LONG\w+\b|\%{1,2}|[ABCD]X\#)(.*)$/) {
	    $o .= $1;
	    my $mw = $2;
	    $ins = $3;
	    if ($mw eq '%') {
		$o .= uc($sn) if ($i);
	    } elsif ($mw eq '%%') {
		if ($i < 2) {
		    die "$0:$infile:$line: $mw cannot be used with z|b\n";
		}
		$o .= uc($sizename[$i-1]) . uc($sn);
	    } elsif ($mw =~ /^([0-9a-f]{2})(\+r)?(\#)?\#$/) {
		my $n;
		$n = ($3 ne '') ? $s > 16 : $s > 8;
		$n <<= 3 if ($2 ne '');
		$o .= sprintf('%02x%s', hex($1) | $n, $2);
	    } elsif ($mw eq 'sbyte#') {
		$o .= $sbyte[$i];
	    } elsif ($mw =~ /^imm\#(\#?)$/) {
		$o .= (($1 eq '' && $s >= 64) ? 'sdword' : 'imm').$s;
	    } elsif ($mw =~ '^([ao])(d?)\#$') {
		$o .= $1.("$2$sz" eq 'dsm' ? 'df' : $sz);
	    } elsif ($mw eq 'i#') {
		$o .= !$i ? 'iwd' : ($s >= 64) ? 'id,s' : "i$sn";
	    } elsif ($mw eq 'i##') {
		$o .= !$i ? 'iwdq' : "i$sn";
	    } elsif ($mw =~ /^(?:reg_)?([abcd])x\#$/i) {
		my $rl = $1;
		my $upr = ($rl =~ /^[A-Z]/);
		if ($i == 1) {
		    $o .= $upr ? "${rl}L" : "reg_${rl}l";
		} elsif ($i == 2) {
		    $o .= $upr ? "${rl}X" : "reg_${rl}x";
		} elsif ($i == 3) {
		    $o .= $upr ? "E${rl}X" : "reg_e${rl}x";
		} elsif ($i == 4) {
		    $o .= $upr ? "R${rl}X" : "reg_r${rl}x";
		    $long |= 1;
		} else {
		    die "$0:$infile:$line: register cannot be used with z\n";
		}
	    } elsif ($mw eq 'sel#') {
		# z is (ab)used to mean the memory form of a segment selector
		if ($i == 0) {
		    $o .= 'mem16';
		} elsif ($i == 1) {
		    die "$0:$infile:$line: $mw cannot be used with b\n";
		} else {
		    $o .= "reg$s";
		}
	    } elsif ($mw =~ /^opt(\w?)\#$/) {
		if ($i >= 2) {
		    my $opt .= $1 eq '' ? "opt$sn" : "opt$1";
		    if (!($opt eq 'optd' && $s == 16) && $opt ne 'optq') {
			$o .= $opt.' ';
		    }
		}
		$o .= "o$s" if ($s > 0);
	    } elsif ($mw =~ /^(o64)?nw$/) {
		$long |= 2 if ($i == 32); # nw = 32 bits not encodable in long mode
		$o .= $mw;
	    } elsif ($mw =~ /^((k?)(?:reg|rm))(\#{1,2}|[0-9]+)$/) {
		# (Possible) GPR reference
		$o     .= $1;
		my $isk = $2;
		my $n   = $3;
		if ($n eq '#') {
		    $n = $s;
		} elsif ($n eq '##') {
		    $n = $s >> 1;
		}
		$o .= $n;
		# 64-bit K registers OK in 32-bit mode
		$long |= 1 if ($n >= 64 && !$isk);
	    } elsif ($mw =~ /^(NO)?LONG(\w+)$/) {
		my $longflag = $1 ? 2 : 1;
		$long |= $longflag if ($2 =~ /$sn/i);
		# Drop
	    } elsif ($mw eq 'w#') {
		if (!$i) {
			$o .= 'ww';
			$wwflag = 1;
		} else {
			$o .= ($s >= 64) ? 'w1' : 'w0';
		}
	    } elsif ($mw eq 'w##') {
		$o .= 'w'.(($i-1) & 1);
	    } elsif ($mw eq 'S#') {
		$o .= 'S'
	    } elsif ($mw eq '#') {
		$o .= $s;
	    } else {
		die "$0:$infile:$line: unknown or invalid sequence \"$mw\"\n";
	    }
	}
	$o .= $ins;

	$o .= ',WW'     if ($wwflag);
	$o .= ',LONG'   if ($long & 1);
	$o .= ',NOLONG' if ($long & 2);
	$o .= ',ND'     if ($nd);

	if ($s >= 32 && $o !~ /\B\!386\b/ && $o =~ /\b(8086|[12]86)\b/) {
	    $o .= ',386';
	}

	push(@ol, $o);
    }

    return @ol;
}

# Near branch operand size patterns
# This allows the "normal" size patterns to be used for
# address size features, as used by JCXZ and LOOP.
# This also allows the syntax "jmp dword foo" in 64-bit
# mode, even though it is really bogus.
$macros{'br'} = {
    'func' =>
	sub {
	    my($mac, $args, $rawargs) = @_;
	    my @ol;
	    my $ins = join(' ', @$rawargs);

	    foreach my $wx ([16,16], [32,32], [64,64], [64,32]) {
		my($w,$iw,$sz) = @$wx;
		my $i = $ins;
		my $argn;
		if ($i =~ /^(.*)\b(near|short)\b/) {
		    my $what = $2;
		    next if ($what eq 'short' && $iw != $w);
		    (my $argn = $1) =~ s/[^,:]+//g;
		    $argn = 'AR'.length($argn);
		}
		$i =~ s/\b(near|short)\b/imm$iw|$1/;
		$i =~ s/\bos\b/nw o$w/;
		$i .= ",$argn";
		$i .= ($iw != $w) ? ',SX,ND' : ',OSIZE';
		$i .= ($w == 64) ? ',LONG' : ',NOLONG';
		push(@ol, $i);
	    }
	    return(@ol);
    }
};

# Common pattern for K-register instructions
$macros{'k'} = {
    'func' =>
	sub {
	    my($mac, $args, $rawargs) = @_;
	    my @ol;
	    my $ins = join(' ', @$rawargs);
	    my $xins;
	    my $n;

	    $ins .= ',ZU';
	    ($xins = $ins) =~ s/\bSM[0-9-]*,?//;
	    push(@ol, $xins);
	    ($xins = $ins) =~ s/\%//;
	    push(@ol, $xins);
	    if ($xins =~ s/\%//) {
		push(@ol, $xins);
	    }

	    # Allow instruction without K, as long as they aren't "TEST"
	    if ($ins !~ /^\bK\w*TEST/) {
		my @on;
		foreach my $oi (@ol) {
		    # Remove first capital K
		    ($xins = $oi) =~ s/\bK//;
		    push(@on, $xins);
		}
		push(@ol, @on);
	    }

	    # Allow SHIFT -> SH
	    if ($ins =~ /SHIFT/) {
		my @on;
		foreach my $oi (@ol) {
		    # Remove first capital K
		    ($xins = $oi) =~ s/SHIFT/SH/;
		    push(@on, $xins);
		}
	    push(@ol, @on);
	    }

	    # All instruction patterns except the first are ND
	    for (my $i = 1; $i < scalar(@ol); $i++) {
		$ol[$i] .= ',ND';
	    }
	    return(@ol);
    }
};

# Common pattern for the HINT_NOPx pseudo-instructions
$macros{'hint_nops'} = {
    'func' =>
	sub {
	    my($mac, $args, $rawargs) = @_;
	    my @ol;

	    for (my $i = 0; $i < 64; $i++) {
		push(@ol,
		     sprintf("\$wdq HINT_NOP%d\trm#\t[m: o# 0f %02x /%d]\tP6,UNDOC,ND",
			     $i, 0x18+($i >> 3), $i & 7));
	    }
	    return @ol;
    }
};

#
# Macro helper functions for common constructs
#

# Parse arguments handling variable setting
sub parse_args($@) {
    my $uvars = shift(@_);
    my %initvars = defined($uvars) ? %$uvars : ();
    my @oa;
    my $n = 0;			# Argument counter

    foreach my $ops (@_) {
	my %vars = %initvars;
	$vars{'n'}  = $n;
	$vars{'nd'} = 0;
	my @oaa;
	my $is_op;
	foreach my $op ($ops =~ /(?:[^\,\[\]\"]+|\[.*?\]|\".*?\")+/g) {
	    $op =~ s/\"//g;

	    $vars{'nd'} = 'nd' if ($op =~ s/^\@//);
	    if ($op =~ /^(\w+)\=(.*)$/) {
		$vars{$1} = $2;
		next;
	    } elsif ($op =~ /^([\!\+\-])(\w+)$/) {
		# The commas around KILL guarantees that it is a separate token
		$vars{$2} =
		    ($1 eq '+') ? $2 :
		    ($1 eq '!') ? ',KILL,' :
		    '';
		next;
	    } elsif ($op =~ /^\-?$/) {
		# Null (placeholder) operand
		$is_op = 1;
		next;
	    }

	    $vars{'op'} = $op;
	    push(@oaa, {%vars});
	    $vars{'nd'} = 'nd';
	    $is_op = 1;
	}
	if ($is_op) {
	    push(@oa, [@oaa]);
	    $n++;
	} else {
	    # Global variable setting
	    %initvars = %vars;
	}
    }

    return @oa;
}

# "8-fold" or similar sequential instruction patterns
sub def_eightfold($$$) {
    my($var, $arg, $mac) = @_;

    my $shift = $arg->{'shift'};
    $shift = 3 unless (defined($shift));

    if ($var =~ /^[0-9a-f]{1,2}$/) {
	return sprintf('%02x', hex($var) + ($arg->{'n'} << $shift));
    } else {
	return $var;
    }
}

#
# Substitute variables in a pattern
#
sub substitute($$;$) {
    my($pat, $vars, $defs) = @_;
    my $o = '';
    my $def;
    my @defargs;

    if (defined($defs)) {
	@defargs = @$defs;
	$def = shift(@defargs);
    }

    while ($pat =~ /^(.*?)\$(?:(\w+\b|\$+)|\{(\w+|\$+)\})(.*)$/s) {
	$o .= $1;
	$pat = $4;
	my $vn = $2.$3;
	my $vv;
	if ($vn =~ /^\$/) {
	    $vv = $vn;		# Reduce by one $
	} else {
	    $vv = $vars->{$vn};
	    if (!defined($vv)) {
		if (defined($def)) {
		    $vv = $def->($vn, @defargs);
		}
		$vv = $vn unless(defined($vv));
	    }
	}
	$vv =~ s/\s+$// if ($pat =~ /^\s/);
	$o .= $vv;
    }
    $o .= $pat;

    return $o;
}

#
# Build output by substituting the variables for each argument,
#
sub subst_list($$;$$) {
    my($pat, $args, $def, $mac) = @_;
    my @o = ();

    foreach my $a0 (@$args) {
	foreach my $arg (@$a0) {
	    push(@o, substitute($pat, $arg, [$def, $arg, $mac]));
	}
    }

    return @o;
}

#
# Actually invoke a macro
#
sub process_macro(@) {
    $macro = shift(@_);
    my $mac = $macros{$macro};

    if (!defined($mac)) {
	die "$0:$infile:$line: no macro named \$$macro\n";
    }

    my @args = parse_args($mac->{'vars'}, @_);
    my $func = $mac->{'func'};
    my @o;
    if (defined($func)) {
	@o = $func->($mac, \@args, \@_);
    } else {
	@o = subst_list($mac->{'txt'}, \@args, $mac->{'def'}, $mac);
    }
    return map { split(/\n/, $_) } @o;
}

#
# Main program
#
($infile, $outfile) = @ARGV;
$line = 0;

## XXX: fix special case: XCHG
## XXX: check: CMPSS, CMPSD
## XXX: check VEX encoded instructions that do not write

# Instructions which (possibly) change the flags without annotations
# The FL or !FL flags will override this
my $flaggy = '^(aa[adms]|ad[dc]|ad[co]x|aes\w*kl|and|andn|arpl|bextr|bl[sc]ic?|bl[sc]msk|bl[sc]r|\
bs[rf]|bt|bt[crs]|bzhi|clac|clc|cld|cli|clrssbsy|cmc|cmp|cmpxchg.*|da[as]|dec|div|\
encodekey.*|enqcmd.*|fu?comip?|idiv|imul|inc|iret.*|kortest.*|ktest.*|lar|loadiwkey|\
lsl|[lt]zcnt|mul|neg|or|pconfig|popcnt|popf.*|r[co][lr]|rdrand|rdseed|sahf|s[ah][lr]|\
sbb|scas.*|sh[lr]d|stac|stc|std|sti|sub|test|testui|tpause|v?u?comis[sdh]|uiret|\
umwait|ver[rw]|vtestp[ps]|xadd|xor|xtest|getsec|rsm|sbb|cmps[bwdq]|hint_.*)$';

# Instructions which don't write their leftmost operand are inherently not {zu}
my $nozero = '^(jmp|call|bt|test|cmp|ud[012].*|ptwrite|tpause|u?monitor.*|u?mwait.*|incssp.*|\
enqcmds?|senduipi|hint_.*|jmpe|nop|inv.*|push2?p?|vmwrite|clzero|clflush|clwb|lkgs)$';

sub add_flag($@) {
    my $flags = shift(@_);

    foreach my $fl (@_) {
	$flags->{$fl}++ unless ($fl =~ /^\s*$/);
    }
}

sub has_flag($@) {
    my $flags = shift(@_);
    foreach my $fl (@_) {
	return $flags->{$fl} if ($flags->{$fl});
    }
    return undef;
}

sub adjust_fl_zu(@) {
    my($opcode, $operands, $encoding, $flags) = @_;

    # Flag-changing instructions
    if ($encoding =~ /\bnf\b/) {
	add_flag($flags, 'NF');
    }

    if (!has_flag($flags, '!FL', 'NF', 'NF!')) {
	add_flag($flags, 'FL') if ($opcode =~ /$flaggy/io);
    }

    ## XXX: fix special case: XCHG
    ## XXX: check: CMPSS, CMPSD
    ## XXX: check VEX encoded instructions that do not write

    # Zero-upper. This can also be used to select the AVX forms
    # to clear the upper part of a vector register.
    if (!$flags->{'!ZU'} &&
	(($encoding =~ /\be?vex\b/ && $operands =~ /^(xyz)mm(reg|rm)/) ||
	 $operands =~ /^(reg_[re]([abcd]x|[sb]p|[sd]i))\b/) &&
	$opcode !~ /$nozero/io) {
	add_flag($flags, 'ZU');
    }

    return $flags;
}

sub adjust_instruction_flags(@) {
    my @i = @_;

    $i[3] = adjust_fl_zu(@i);

    return undef unless (defined($i[3]));

    if ($i[0] =~ /\bKILL\b/ || $i[1] =~ /\bKILL\b/ ||
	$i[2] =~ /\bKILL\b/ || has_flag($i[3], 'KILL')) {
	return undef;
    }

    if ($i[2] =~ /\b(a16|rex\.l)\b/) {
	add_flag($i[3], 'NOLONG');
    } elsif ($i[2] =~ /\b(o64(nw)?\b|rex2?|a64\b)/) {
	add_flag($i[3], 'LONG');
    }
    if (has_flag($i[3], 'NOLONG') && has_flag($i[3], 'LONG')) {
	# This is obviously not very useful...
	return undef;
    }

    if (has_flag($i[3], 'LONG')) {
	add_flag($i[3], 'X86_64');
    }

    return $i[3];
}

sub process_insn($$) {
    my($out, $l) = @_;

    if ($l !~ /^\s*([^\s\;]+)\s+([^\s\;]+)\s+([^\s\;]+|\[[^\;]*?\])\s+([^\s\;]+)\s*(\;.*?)?\s*$/) {
	print $out $l, "\n";
	return;
    }

    print $out $5, "\n" unless ($5 eq ''); # Comment

    my $opcode   = $1;
    my $operands = $2;
    my $encoding = $3;
    my $flagstr  = $4;

    my $nopr = ($operands =~ /^(void|ignore)$/) ? 0 : scalar(split(/[\,\:]/, $operands));

    # Modify the instruction flags
    my $flags = {split_flags($flagstr)};
    set_implied_flags($flags, $nopr);

    $flags = adjust_instruction_flags($opcode, $operands, $encoding, $flags);
    return unless (defined($flags));
    $flagstr = merge_flags($flags, 1);

    # Tidy up the encoding for readability
    $encoding =~ s/\s+/ /g;
    if ($encoding =~ /^\s*\[\s*(.*?\:)?\s*([^\:]*?)\s*\]\s*$/) {
	$encoding = sprintf('[%-10s%-34s]', $1, $2);
    }

    print $out sprintf("%-23s %-39s %-47s %s\n", $opcode, $operands, $encoding, $flagstr);
}

open(my $in, '<', $infile) or die "$0:$infile: $!\n";
open(my $out, '>', $outfile) or die "$0:$outfile: $!\n";


while (defined(my $l = <$in>)) {
    $line++;
    chomp $l;
    my @insi = ($l);

    while (defined(my $li = shift(@insi))) {
	if ($li =~ /^\s*\$(\w+[^\;]*?)\s*(\;.*)?$/) {
	    push(@insi, "$2\n") unless ($2 eq ''); # Retain comment
	    my @args = ($1 =~ /(?:\[[^\]]*\]|\"[^\"]*\"|[^\[\]\"\s])+/g);
	    unshift(@insi, process_macro(@args));
	} else {
	    process_insn($out, $li);
	}
    }
}

close($in);
close($out);
