#!/usr/bin/perl

use re 'eval';


#
# "sub" populates the following global variables:
#
# $end[rule-number] = 0 / 1
# $match[rule-number]{field}[0] = original-pattern
# $match[rule-number]{field}[1] = RE1 
# $match[rule-number]{field}[2] = RE2
# $action[rule-number]{field} = value
#
# $match_stack[depth]{field}[0] = original-pattern
# $match_stack[depth]{field}[1] = RE1
# $match_stack[depth]{field}[2] = RE2
# $action_stack[depth]{field} = value
# $may_cont = 0 / 1
# $last
# $last_action
#

#
# $cvn_from{internal-handle} = index
# $cvn_to{internal-handle} = index
# $cvn_unit{internal-handle} = unit-name
# $cvn_num = internal-handle
# $found{field-or-subfield} = string


#
# We convert each input pattern into two regular expressions: the first matches
# units in the nXn notation, e.g., 4u7 or 100R. The second matches them in SI
# notation (sans space).
#
# When matching (sub_match), we first apply the first expression. Each time we
# encounter a unit ($R, $F, etc.), __cvn is called. __cvn stores the index of
# the unit in %cvn_from and %cvn_to.
#
# We then pick these substrings from the input string and convert the units to
# SI notation. At the same time, we normalize the mantissa. Once done, we run
# the second expression. This one always matches (hopefully :-)
#
# All (...) ranges in the original pattern have been replaced with named
# capture buffers in the second expression, so all these subfields are now
# gathered in the $+ array. (The same also happened in the first pass, but we
# ignore it.)
#
# Finally, when expanding a value (sub_expand), we look for $field and
# $field:index, and expand accordingly.
#


sub __cvn
{
    local ($num) = @_;

    $cvn_from{$num} = $-[$#-];
    $cvn_to{$num} = $+[$#+];
}


sub sub_match
{
    local ($s, $field, $m1, $m2) = @_;

    #
    # Perform the first match and record where we saw $<unit> patterns.
    #
    undef %cvn_from;
    undef %cvn_to;
    return undef unless $s =~ $m1;

    #
    # Convert the unit patterns to almost-SI notation. (We don't put a space
    # after the number, but the rest is SI-compliant.)
    #
    my $off = 0;
    for (keys %cvn_from) {
	my $unit = $cvn_unit{$_};
	my $from = $cvn_from{$_}+$off;
	my $len = $cvn_to{$_}-$cvn_from{$_};
	die unless substr($s, $from, $len) =~
	    /(\d+)$unit(\d*)|(\d+)([GMkmunpf])(\d*)/;

	#
	# Normalize to \d+.\d*
	#
	my $v = "$1$3.$2$5";
	my $exp = $4 eq "" ? " " : $4;

	#
	# Remove leading zeroes.
	#
	$v =~ s/^0*(\d+)/\1/;

	#
	# Mantissa must be < 1000.
	# Do the math as string operation to avoid rounding errors.
	#
	while ($v =~ /(\d+)(\d{3})\./) {
	    $v = "$1.$2$'";
	    $exp =~ tr/GMk munpf/TGMk munp/;
	}

	#
	# Mantissa must be >= 1.
	#
	while ($v =~ /\b0\.(\d+)/) {
	    if (length $1 < 3) {
		$v = $1.("0" x (3-length $1)).".";
	    } else {
		$v = substr($1, 0, 3).".".substr($1, 3);
	    }
	    $exp =~ tr/GMk munpf/Mk munpa/;
	}

	#
	# Remove trailing zeroes
	#
	$v =~ s/(\.[1-9]*)0*/\1/;

	$exp =~ s/ //;
	$v =~ s/\.$//;
	$v = $v.$exp.$unit;
	$off += length($v)-$len;
	substr($s, $from, $len, $v);
    }

    #
    # Run the second match on the string to process any (...) patterns
    #
    $found{$field} = $s;
    die $m2 unless $s =~ $m2;
    for (keys %+) {
	$found{$_} = $+{$_};
    }
    return $s;
}


sub sub_expand
{
    local ($s) = @_;

    while ($s =~ /^([^\$]*)\$([A-Za-z_]\w*)(:(\d+))?|^([^\$]*)\${([A-Za-z_]\w*)(:(\d+))?}/) {
	my $name = "$2$6";
	$name .= "__$4$8" if defined($4) || defined($8);
	if (!defined $found{$name}) {
		die "don't know \"$name\"".
		  (defined $__match_error ?
		  " (processing \"$__match_error\")" : "");
	}
	$s = $1.$5.$found{$name}.$';
    }
    return $s;
}


#
# return 0 if all rules have been exhausted, 1 if there was an explicit halt.
#

sub apply_rules
{
    RULE: for (my $i = 0; $i <= $#match; $i++) {
	print STDERR "RULE #$i\n" if $debug;
	%found = %field;
	FIELD: for my $f (keys %{ $match[$i] }) {
	    my @f = $f ne "FN" ? ($f) :
	      ("F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9");
	    for (@f) {
		print STDERR "  MATCH $_=$match[$i]{$f}[0] " if $debug;
		if (!defined $found{$_}) {
		    print STDERR "NO FIELD\n" if $debug;
		    next;
		}
		print STDERR "FIELD $found{$_} " if $debug;
		if (!defined &sub_match($found{$_}, $f,
		  $match[$i]{$f}[1], $match[$i]{$f}[2])) {
		    print STDERR "MISS\n" if $debug;
		    next;
		}
		print STDERR "MATCH\n" if $debug;
		next FIELD;
	    }
	    next RULE;
	}
	for (keys %{ $action[$i] }) {
	    my $s = &sub_expand($action[$i]{$_});
	    print STDERR "  SET $_=$action[$i]{$_} => $s\n" if $debug;
	    $field{$_} = $s;
	}
	if ($end[$i]) {
	    print STDERR "  END\n" if $debug;
	    return 1;
	}
    }
    return 0;
}


sub match_set_error
{
    $__match_error = $_[0];
}


return 1;