#!/usr/bin/env perl
#
# $FML: gen_rules.pl,v 1.8 2006/03/08 13:35:27 fukachan Exp $
#

use strict;
use Carp;

my $debug      = $ENV{'debug'} || 0;
my $var_rules  = {};
my $var_count  = 0;
my $recursive  = 0;
my $if_state   = 0;
my $if_stack   = 0;
my (@if_stack) = ();
my $ignore_regexp = 'unavailable|not_yet_configurable';

# 1. read RULES.txt
_parse_rule_file(@ARGV);

# 2. show translation rules (perl script).
_print_preamble();
for my $var_name (sort {$a <=> $b} keys %$var_rules) {
    _print_translated_rules($var_name, $var_rules->{ $var_name });
}
_print_trailor();

exit 0;


######################################################################
#
# INITIALIZATION
#
######################################################################


# Descriptions: parse rules in specified file.
#    Arguments: STR($rule_file)
# Side Effects: update $var_rules HASH_REF.
# Return Value: none
sub _parse_rule_file
{
    my ($rule_file) = @_;

    use FileHandle;
    my $rh = new FileHandle $rule_file;
    if (defined $rh) {
	my $var_name = '';
	my $buf;

      LINE:
	while ($buf = <$rh>) {
	    next LINE if $buf =~ /^\s*$/o;
	    next LINE if $buf =~ /^\#/o;

	    # o.k.
	    next LINE if $buf =~ /^\.ok/o;

	    if ($buf =~ /^\.if\s+(\S+)/o) {
		$var_name = sprintf("%s_%s", $var_count++, $1);
		$var_rules->{ $var_name } .= $buf;
		next LINE;
	    }

	    if ($var_name) {
		if ($buf =~ /^\s*\.($ignore_regexp)/o) {
		    if ($debug) {
			print STDERR "var_name = $var_name\n";
			print STDERR "\tignored ($1)\n";
		    }
		    # $var_rules->{ $var_name } = '';
		}
		elsif ($buf =~ /\S+/o) {
		    if ($debug) {
			print STDERR "var_name = $var_name\n";
			print STDERR "\t", $buf, "\n";
		    }
		    $var_rules->{ $var_name } .= $buf;
		}
		else {
		    if ($debug) {
			print STDERR "var_name = $var_name\n";
			print STDERR "\t", $buf, "\n";
		    }
		    $var_rules->{ $var_name } .= $buf;
		}
	    }

	}
	$rh->close();
    }
    else {
	croak("cannot open $rule_file\n");
    }
}


######################################################################
#
# MODULE TEMPLATE
#
######################################################################


# Descriptions: print out preamble of output of perl script.
#    Arguments: none
# Side Effects: none
# Return Value: none
sub _print_preamble
{
    use File::Basename;
    my $prog = basename($0);

    print qq{#
# -*- perl -*-
#     *** CAUTION ***
#     DO NOT EDIT THIS FILE BY HAND!.
#     THIS FILE IS AUTOMATICALLY GENERATED BY $prog.
#
#  Copyright (C) 2005,2006 Ken'ichi Fukamachi
#   All rights reserved. This program is free software; you can
#   redistribute it and/or modify it under the same terms as Perl itself.
#
# \$FML\$
#
};

print q!
package FML::Merge::FML4::Rules;


# Descriptions: translate fml4 rule to the corresponding fml8 one.
#    Arguments: OBJ($self)
#               HASH_REF($dispatch) HASH_REF($config) HASH_REF($diff)
#               STR($key) STR($value)
# Side Effects: none
# Return Value: STR
sub translate
{
    my ($self, $dispatch, $config, $diff, $key, $value) = @_;
    my $fp_rule_convert             = $dispatch->{ rule_convert };
    my $fp_rule_prefer_fml4_value   = $dispatch->{ rule_prefer_fml4_value };
    my $fp_rule_prefer_fml8_value   = $dispatch->{ rule_prefer_fml8_value };
    my $fp_rule_ignore              = $dispatch->{ rule_ignore };
    my $fp_rule_not_yet_implemented = $dispatch->{ rule_not_yet_implemented };
    my $s = undef;

!;
}


# Descriptions: print out the trailor part of output of perl script.
#    Arguments: none
# Side Effects: none
# Return Value: none
sub _print_trailor
{
    print "\n   return '';\n";
    print "} # sub translate\n";
    print "\n1;\n";
}


######################################################################
#
# TRANSLATION OF RULES
#
######################################################################


# Descriptions: print out translated rules.
#    Arguments: STR($var_name) STR($var_rules)
# Side Effects: none
# Return Value: none
sub _print_translated_rules
{
    my ($var_name, $var_rules) = @_;
    my $found    = 0;
    my $i        = 0;

    print STDERR "ALLOC $var_name => $var_rules\n" if $debug;
    return unless $var_name;
    return unless $var_rules;

  RULE:
    for my $rule (split(/\n/, $var_rules)) {
	$i++;
	print STDERR "$var_name [$i] $rule\n" if $debug;

	# 1st level (/^.if .../ statement)
	if ($rule =~ /^\.if/o) {
	    _parse_if($rule, $recursive);
	    $if_state = 1;
	}
	elsif ($rule =~ /^\S+/) {
	    croak("UNKNOWN RULE: <$rule>\n");
	}
	# 2nd level (/^\s+\S+/ statement)
	else {
	    $rule =~ s/^\s*//;
	    $rule =~ s/\s*$//;

	    if ($rule =~ /^\.if/o) {
		_parse_if($rule, $recursive);
		$if_state = 1;
		next RULE;
	    }

	    # .if statement(s) stacked.
	    if ($if_state) {
		_print_if();
	    }

	    if ($rule eq '.use_fml4_value') {
		$found = 1;
		_print("\$s .= \&\$fp_rule_prefer_fml4_value(\$self, \$config, \$diff, \$key, \$value);");
	    }
	    elsif ($rule eq '.use_fml8_value') {
		$found = 1;
		_print("\$s .= \&\$fp_rule_prefer_fml8_value(\$self, \$config, \$diff, \$key, \$value);");
	    }
	    elsif ($rule eq '.convert') {
		$found = 1;
		_print("\$s .= \&\$fp_rule_convert(\$self, \$config, \$diff, \$key, \$value);");
	    }
	    elsif ($rule =~ /^\s*\.(ignore|not_support)/o) {
		$found = 1;
		_print("\$s .= \&\$fp_rule_ignore(\$self, \$config, \$diff, \$key, \$value);");
	    }
	    elsif ($rule =~ /^\s*\.(not_yet_implemented)/o) {
		$found = 1;
		_print("\$s .= \&\$fp_rule_not_yet_implemented(\$self, \$config, \$diff, \$key, \$value);");
	    }
	    elsif ($rule =~ /^\s*\.($ignore_regexp)/o) {
		$found = 1;
		_print("\$s .= \"\# $rule\";");
	    }
	    else {
		$found = 1;
		_print("\$s .= sprintf(\"$rule\", \$value);");
	    }

	    while ($if_stack > 0) {
		$if_stack--;
		_print("}");
	    }

	} # if
    } # for my $rule (...)

    if ($found) {
	_print("return \$s if defined \$s;");
	_print("\n");
    }
}


# Descriptions: parse 1st and 2nd level .if statement.
#    Arguments: STR($rule) NUM($recursive)
# Side Effects: none
# Return Value: none
sub _parse_if
{
    my ($rule, $recursive) = @_;

    push(@if_stack, $rule);
}


# Descriptions: print 1st level if statement.
#    Arguments: none
# Side Effects: print if statements.
# Return Value: none
sub _print_if
{
    my $i = 0;

    # 1. check if differences are found.
    _print("\$s = undef;");
    __print("if (");
    $if_stack++;
    for my $rule (@if_stack) {
	if ($i) {
	    print " || ";
	}
	if ($rule =~ /^\.if\s+(\S+)/) {
	    print "(\$key eq '$1' && \$diff->{ $1 })";
	    $i++;
	}
    }
    print ") {\n";

    # 2. translate rules based on config.ph values.
    __print_if(@if_stack);

    # 3. reset
    @if_stack = ();
}


# Descriptions: print 2nd level if statement.
#    Arguments: ARRAY(@rules)
# Side Effects: print if statements.
# Return Value: none
sub __print_if
{
    my (@rules)  = @_;

    for my $rule (@rules) {
	if ($rule =~ /^\.if\s+(\S+)\s+(==|>|>=|<|<=)\s+(\S+)/) {
	    my ($key, $op, $value) = ($1, $2, $3);
	    if ($value =~ /^\d+$/o) {
		_print("if (\$config->{ $key } $op $value) {");
		$if_stack++;
	    }
	    else {
		_print("if (\$config->{ $key } eq '$value') {");
		$if_stack++;
	    }
	}
	elsif ($rule =~ /^\.if\s+(\S+)\s+\!=\s+(\S+)/) {
	    my ($key, $value) = ($1, $2);
	    if ($value =~ /^\d+$/o) {
		_print("if (\$config->{ $key } \!= $value) {");
		$if_stack++;
	    }
	    else {
		_print("if (\$config->{ $key } ne '$value') {");
		$if_stack++;
	    }
	}
	elsif ($rule =~ /^\.if\s+(\S+)\s*$/) {
	    my ($key) = $1;
	    _print("if (\$config->{ $key }) {");
	    $if_stack++;
	}
    }
}


######################################################################
#
# UTILITIES
#
######################################################################


# Descriptions: print wrapper to prepend spaces in a line.
#    Arguments: STR($s)
# Side Effects: none
# Return Value: none
sub _print
{
    my ($s) = @_;
    my $rule_out = "    " x ($if_stack + 1);
    printf("%s%s\n", $rule_out, $s);
}


# Descriptions: print wrapper to prepend spaces in a line without "\n".
#    Arguments: STR($s)
# Side Effects: none
# Return Value: none
sub __print
{
    my ($s) = @_;
    my $rule_out = "    " x ($if_stack + 1);
    printf("%s%s", $rule_out, $s);
}
