<?php
set_error_handler ('php_error_handler');
error_reporting (E_ALL | E_STRICT); ini_set ('display_errors', 1);
date_default_timezone_set ('Europe/Moscow');

ini_set ('memory_limit', '2048M'); gc_enable();
ini_set ('max_execution_time', 0);

/* * */

$check_format_numbers = (argIsPresent ($argv, '--check-format-numbers')) ? 1 : 0;
$check_command_names = (argIsPresent ($argv, '--check-command-names')) ? 1 : 0;
$check_commad_numbers = (argIsPresent ($argv, '--check-commad-numbers')) ? 1 : 0;
$reset_format_files = (argIsPresent ($argv, '--reset-all-format-files')) ? 1 : 0;
$generate_format_files = (argIsPresent ($argv, '--generate-format-files')) ? 1 : 0;

$path = dirname (dirname (dirname (__FILE__)));

if ($check_format_numbers)  { checkFormatNumbers ($path);    yield_time (); }
if ($check_command_names)   { checkCommandNames ($path);     yield_time (); }
if ($check_commad_numbers)  { checkCommandNumbers ($path);   yield_time (); }
if ($check_commad_numbers)  { checkRISCMCDNumbers ($path);   yield_time (); }
if ($check_commad_numbers)  { checkCommandArguments ($path); yield_time (); }
if ($reset_format_files)    { resetAllFormatFiles ($path);   yield_time (); }
if ($generate_format_files) { generateFormatFiles ($path);   yield_time (); }

exit;

/* * */

function checkFormatNumbers ($path) {
    print 'Checking format numbers...'."\n";

    $format_numbers = array ();
    $instruction_numbers = array ();

    $mask = 'Format-*-*.+';
    $files = rglob ($path.'/', $mask); sort ($files);

    $previous_format_length = '';
    $previous_instruction_id = '';

    foreach ($files as $file_name) {
        $lines = file ($file_name);
        $format_number = array ();
        $instruction_number = array ();

        for ($cntr = 0; $cntr < count ($lines); $cntr ++) {
             if (strpos ($lines[$cntr], 'T:') !== false) $format_number[] = trim ($lines[$cntr]);
             if (strpos ($lines[$cntr], 'I:') !== false) $instruction_number[] = trim ($lines[$cntr]);
        }

        $format_number = implode ('', $format_number);
        $instruction_number = implode ('', $instruction_number);

        $format_id = substr ($format_number, strpos ($format_number, '[') + 1);
        $format_id = substr ($format_id, 0, strpos ($format_id, ' '));
        $format_id = strreplace ($format_id, ';', '');

        $format_prefix = substr ($format_id, 0, strpos ($format_id, '!'));

        $format_length = strlen ($format_prefix) - strlen (strreplace ($format_prefix, '0', ''));
        $instruction_id = strlen ($instruction_number) - strlen (strreplace ($instruction_number, '#', ''));

        if (strpos ($file_name, '-RISC-') || strpos ($file_name, '-MCD-') || 
            strpos ($file_name, '-MIPS-') || strpos ($file_name, '-ARM-')) {
            print ' '.str_pad ($format_length, 2, ' ', STR_PAD_LEFT)."\t".'  '.((!strpos ($file_name, '-1.+')) ? substr (strreplace ($file_name, '-sparse', ''), -3, 1) : '')."\n";
        } else {
            print ' '.str_pad ($format_length, 2, ' ', STR_PAD_LEFT)."\t".'  '.(($instruction_id > 1)          ? $instruction_id            : '')."\n";
        }

        $number = $format_length.':'.$instruction_id;

        if ($format_length) {
            if (!isset ($format_numbers[$number])) {
                $format_numbers[$number] = $file_name;
            } else {
                print 'Duplicated format number: '.$number.' -- '.$file_name.' - '.$format_numbers[$number]; exit;
            }

            if ($previous_format_length) {
                if ($format_length < $previous_format_length) {
                    print 'Format is shorter than previous: '.$format_length. ' < '.$previous_format_length.' -- '.$file_name; exit;
                }
            }

            if ($previous_instruction_id) {
                $previous_number = $previous_format_length.':'.$previous_instruction_id;

                if (strlen ($number) < strlen ($previous_number)) {
                    print 'Instruction is shorter than previous: '.$instruction_id. ' < '.$previous_instruction_id.' -- '.$file_name; exit;
                }
            }
        }

        $previous_format_length = $format_length;
        $previous_instruction_id = $instruction_id;
    }
}

function checkCommandNames ($path) {
    print 'Checking command names...'."\n";

    $directories = array ();
    $command_names = array ();

    $cmd_counter    = 0;
    $legacy_counter = 0;

    $mask = '*-Cmd.rc';
    $files = rglob ($path.'/', $mask); sort ($files);

    foreach ($files as $file_name) {
        $lines = file ($file_name);
        $name_and_parameters = explode (' ', trim (strreplace ($lines[0], '[] ', '')));
        $name = trim ($name_and_parameters[0]);

        $directory = dirname (dirname (dirname (dirname ($file_name))));

        rememberNode ($directories, $directory);

        if (!isset ($command_names[$name])) {
            $command_names[$name] = $directory;

            if (!strpos ($file_name, '-RISC.') && !strpos ($file_name, '-MCD.') && 
                !strpos ($file_name, '-MIPS.') && !strpos ($file_name, '-ARM.')) {
                $cmd_counter ++;

                if (strpos ($name, '.(') ||
                    strpos ($name, 'AV/') !== false) $legacy_counter ++;
            } else {
                ;
            }
        } else {
            if ($command_names[$name] != $directory) {
                print 'Duplicated command name: '.$name.' -- '.$file_name.' - '.$command_names[$name]; exit;
            } else {
                ;
            }
        }
    }

    checkParity ($directories, 'checking command names');

    $report = ' '.$cmd_counter.' total,'.
              ' '.($cmd_counter - $legacy_counter).'+'.$legacy_counter."\n";

    print $report; 

    if ($cmd_counter % 2 != 0)    { print 'Parity check failed when checking command names: '.$cmd_counter."\n";    exit; }
    if ($legacy_counter % 2 != 0) { print 'Parity check failed when checking command names: '.$legacy_counter."\n"; exit; }

    writeStatistics ($report, $path.'\0001-Intro\Include\Statistics-1.rc');
}

function checkCommandNumbers ($path) {
    print 'Checking command numbers...'."\n";

    $directories = array ();
    $r_src_directories = array ();
    $m_src_directories = array ();

    $command_numbers = array ();

    $cmd_counter   = 0;
    $short_counter = 0; $middle_counter = 0;
    $long_counter  = 0; $large_counter  = 0;

    $templates_and_numbers = array ();

    $mask = '*.x';
    $files = rglob ($path.'/', $mask); sort ($files);

    foreach ($files as $file_name) {
        $numbers = parseInstructionFormatFile ($file_name);

        $template_name = $numbers['template_name'];
        $command_type = $numbers['command_type'];
        $command_number = $numbers['command_number'];

        if (strpos ($template_name, '*') !== false) {
            $numbers = discoverNumbers ($file_name);

            $template_name = $numbers['template_name'];
            $command_type = $numbers['command_type'];
            $command_number = $numbers['command_number'];
        }

        $id = $command_type.':'.$command_number.' ['.$template_name.']';

        $directory = dirname (dirname (dirname (dirname ($file_name))));

        $is_register_operation = isRegisterOperation ($file_name, $template_name, $command_type);

        rememberNode ($directories, $directory);
        rememberNode ($r_src_directories, $directory, '', 'REMEMBER_PRIMARY', $is_register_operation, 1);
        rememberNode ($m_src_directories, $directory, '', 'REMEMBER_SECONDARY', $is_register_operation, 0);

        if (!isset ($command_numbers[$id])) {
            $command_numbers[$id] = $file_name;

            if (!strpos ($file_name, '-RISC.') && !strpos ($file_name, '-MCD.') && 
                !strpos ($file_name, '-MIPS.') && !strpos ($file_name, '-ARM.')) {
                $cmd_counter ++;

                $short_counter  += (strpos ($template_name, '16-') !== false) ? 1 : 0;
                $middle_counter += (strpos ($template_name, '32-') !== false) ? 1 : 0;
                $long_counter   += (strpos ($template_name, '48-') !== false) ? 1 : 0;
                $large_counter  += (strpos ($template_name, '64-') !== false) ? 1 : 0;
            } else {
                ;
            }
        } else {
            print 'Duplicated command number: '.$id.' -- '.$file_name.' - '.$command_numbers[$id]; exit;
        }

        if ($is_register_operation) {
            if (!isset ($templates_and_numbers[$template_name])) {
                $templates_and_numbers[$template_name] = array ();
            }

            $templates_and_numbers[$template_name][] = bindec (strreplace ($command_type, ';', ''));
        }
    }

    checkParity ($directories,       'checking command numbers');
    checkParity ($r_src_directories, 'checking primary command numbers');
    checkParity ($m_src_directories, 'checking secondary command numbers');

    checkNumbers ($templates_and_numbers);

    $average_length = round (
                              (($short_counter * 16) + ($middle_counter * 32) + ($long_counter * 48) + ($large_counter * 64)) / $cmd_counter, 2
                            );

    $report = ' '.$cmd_counter.' total,'.
              ' '.$short_counter.'+'.$middle_counter.'+'.$long_counter.'+'.$large_counter.' ('.$average_length.')'."\n";

    print $report; 

    writeStatistics ($report, $path.'\0001-Intro\Include\Statistics-2.rc');
}

function checkRISCMCDNumbers ($path) {
    $files = array_merge (
                           rglob ($path.'/', '*-Format-RISC.x'), rglob ($path.'/', '*-Format-MCD.x'),
                           rglob ($path.'/', '*-Format-MIPS.x'), rglob ($path.'/', '*-Format-ARM.x')
                         ); sort ($files);

    $all_lines = array ();

    foreach ($files as $file_name) {
        $name = strreplace ($file_name, '-[Empty]', '');

        if (!isset ($all_lines[$name])) { $all_lines[$name] = file ($file_name); }
        else { print 'Duplicated RISC format file name: '.$file_name; exit; }
    }

    $risc_ri_counter  = 0;
    $risc_rr_counter  = 0;
    $risc_rri_counter = 0;
    $risc_rii_counter = 0;
    $risc_rrr_counter = 0;

    $mcd_ri_counter   = 0;
    $mcd_rr_counter   = 0;
    $mcd_rx_counter   = 0;
    $mcd_rw_counter   = 0;
    $mcd_16_counter   = 0;
    $mcd_32_counter   = 0;
    $mcd_48_counter   = 0;

    $mips_ri_counter  = 0;
    $mips_rr_counter  = 0;

    $arm_ri_counter   = 0;
    $arm_rr_counter   = 0;

    foreach ($all_lines as $name=>$lines) {
        $format_number = '';

        if (strpos ($name, '-RISC.x') && strpos ($name, '-RI-'))  { $format_number = '-03-1'; $risc_ri_counter ++;  }
        if (strpos ($name, '-RISC.x') && strpos ($name, '-RR-'))  { $format_number = '-03-2'; $risc_rr_counter ++;  }
        if (strpos ($name, '-RISC.x') && strpos ($name, '-RII-')) { $format_number = '-04-1'; $risc_rri_counter ++; }
        if (strpos ($name, '-RISC.x') && strpos ($name, '-RRI-')) { $format_number = '-04-1'; $risc_rii_counter ++; }
        if (strpos ($name, '-RISC.x') && strpos ($name, '-RRR-')) { $format_number = '-04-2'; $risc_rrr_counter ++; }

        if (strpos ($name, '-MCD.x')  && strpos ($name, '-RI-'))  { $format_number = '-01-1'; $mcd_ri_counter ++;   }
        if (strpos ($name, '-MCD.x')  && strpos ($name, '-RR-'))  { $format_number = '-01-2'; $mcd_rr_counter ++;   }
        if (strpos ($name, '-MCD.x')  && strpos ($name, '-RX-'))  { $format_number = '-01-3'; $mcd_rx_counter ++;   }
        if (strpos ($name, '-MCD.x')  && strpos ($name, '-RW-'))  { $format_number = '-01-4'; $mcd_rw_counter ++;   }
        if (strpos ($name, '-MCD.x')  && strpos ($name, '+16-'))  {                           $mcd_16_counter ++;   }
        if (strpos ($name, '-MCD.x')  && strpos ($name, '+32-'))  {                           $mcd_32_counter ++;   }
        if (strpos ($name, '-MCD.x')  && strpos ($name, '+48-'))  {                           $mcd_48_counter ++;   }

        if (strpos ($name, '-MIPS.x') && strpos ($name, '-RRI-')) { $format_number = '-01-1'; $mips_ri_counter ++;  }
        if (strpos ($name, '-MIPS.x') && strpos ($name, '-RRR-')) { $format_number = '-01-2'; $mips_rr_counter ++;  }

        if (strpos ($name, '-ARM.x')  && strpos ($name, '-RI-'))  { $format_number = '-01-1'; $arm_ri_counter ++;   }
        if (strpos ($name, '-ARM.x')  && strpos ($name, '-RR-'))  { $format_number = '-01-2'; $arm_rr_counter ++;   }

        if ($format_number) {
            $line = strreplace (trim ($lines[0]), '-sparse', '');

            if (substr ($line, strlen ($line) - strlen ($format_number), strlen ($format_number)) != $format_number) {
                print 'Incorrect RISC format in: '.$name.' -- '.$line; exit;
            }
        }
    }

    if ($risc_ri_counter  % 2 != 0) { print 'Parity check failed for RISC -RI.';  exit; }
    if ($risc_rr_counter  % 2 != 0) { print 'Parity check failed for RISC -RR.';  exit; }
    if ($risc_rri_counter % 2 != 0) { print 'Parity check failed for RISC -RRI.'; exit; }
    if ($risc_rii_counter % 2 != 0) { print 'Parity check failed for RISC -RII.'; exit; }
    if ($risc_rrr_counter % 2 != 0) { print 'Parity check failed for RISC -RRR.'; exit; }

    if ($mcd_ri_counter   % 2 != 0) { print 'Parity check failed for MCD -RI.';   exit; }
    if ($mcd_rr_counter   % 2 != 0) { print 'Parity check failed for MCD -RR.';   exit; }
    if ($mcd_rx_counter   % 2 != 0) { print 'Parity check failed for MCD -RX.';   exit; }
    if ($mcd_rw_counter   % 2 != 0) { print 'Parity check failed for MCD -RW.';   exit; }
    if ($mcd_16_counter   % 2 != 0) { print 'Parity check failed for MCD +16-.';  exit; }
    if ($mcd_32_counter   % 2 != 0) { print 'Parity check failed for MCD +32-.';  exit; }
    if ($mcd_48_counter   % 2 != 0) { print 'Parity check failed for MCD +48-.';  exit; }

    if ($mips_ri_counter  % 2 != 0) { print 'Parity check failed for MIPS -RI.';  exit; }
    if ($mips_rr_counter  % 2 != 0) { print 'Parity check failed for MIPS -RR.';  exit; }

    if ($arm_ri_counter   % 2 != 0) { print 'Parity check failed for ARM -RI.';   exit; }
    if ($arm_rr_counter   % 2 != 0) { print 'Parity check failed for ARM -RR.';   exit; }

    for ($step = 0; $step < 6; $step ++) {
         foreach ($all_lines as $name=>$lines) {
             $pair = ''; 

             if ($step == 0 && strpos ($name, '-RI-'))  $pair = strreplace ($name, '-RI-',  '-RR-');
             if ($step == 1 && strpos ($name, '-RR-'))  $pair = strreplace ($name, '-RR-',  '-RI-');
             if ($step == 2 && strpos ($name, '-RRI-')) $pair = strreplace ($name, '-RRI-', '-RRR-');
             if ($step == 3 && strpos ($name, '-RRR-')) $pair = strreplace ($name, '-RRR-', '-RRI-');
             if ($step == 4 && strpos ($name, '-RII-')) $pair = strreplace ($name, '-RII-', '-RRR-');
             if ($step == 5 && strpos ($name, '-RRR-')) $pair = strreplace ($name, '-RRR-', '-RII-');

             if ($pair && isset ($all_lines[$pair])) {
                 $pair_lines = $all_lines[$pair];

                 $line = trim ($lines[0]);
                 $pair_line = trim ($pair_lines[0]);

                 if (substr ($line,      0, strlen ($line)      - 1) !=
                     substr ($pair_line, 0, strlen ($pair_line) - 1)) {
                     print 'Incorrect RISC format name: '.$name.' -- '.$line.' - '.$pair_line; exit;
                 }

                 $line = trim ($lines[1]);
                 $pair_line = trim ($pair_lines[1]);

                 if ($line != $pair_line) {
                     print 'Incorrect RISC instruction type: '.$name.' -- '.$line.' - '.$pair_line; exit;
                 }

                 $line = trim ($lines[2]);
                 $pair_line = trim ($pair_lines[2]);

                 if ($line != $pair_line) {
                     print 'Incorrect RISC instruction code: '.$name.' -- '.$line.' - '.$pair_line; exit;
                 }
             }
         }
    }
}

function writeStatistics ($report, $file_name) {
    $text = trim (strreplace ($report, 'total', '¢á¥£®'));

    $existing_text = '';

    if (file_exists ($file_name)) {
        $file = fopen ($file_name, 'r');
        $existing_text = fread ($file, filesize ($file_name));
        fclose ($file); unset ($file);
    }

    if ($text != $existing_text) {
        $file = fopen ($file_name, 'w');
        fwrite ($file, $text);
        fclose ($file); unset ($file);
    }
}

function checkCommandArguments ($path) {
    $instruction_sets = array ('-RISC', '-MCD', '-MIPS', '-ARM', '*');

    foreach ($instruction_sets as $iset) {
        $files = rglob ($path.'/', '*-Cmd'.$iset.'.rc'); sort ($files);

        $empty_commands = 0;
        $flags_commands = 0;
        $risc_commands  = 0;
        $mcd_commands   = 0;

        $mips_commands  = 0;
        $arm_commands   = 0;

        $commands_with_RF     = 0; $commands_with_RD     = 0; $commands_with_R1     = 0; $commands_with_R2     = 0;
        $commands_with_R3     = 0; $commands_with_R4     = 0; $commands_with_RP     = 0; $commands_with_RS     = 0;     
        $commands_with_RA     = 0; $commands_with_RR     = 0; $commands_with_RT     = 0; $commands_with_RX     = 0;     
        $commands_with_RH     = 0; $commands_with_RB     = 0; $commands_with_R0     = 0; $commands_with_flags  = 0; 
        $commands_with_c_sr   = 0; $commands_with_b_slot = 0; $commands_with_imply  = 0; $commands_with_dt     = 0; 
        $commands_with_size   = 0; $commands_with_sign   = 0; $commands_with_const  = 0; $commands_with_offset = 0; 
        $commands_with_length = 0; $commands_with_shift  = 0; $commands_with_FPC2   = 0; $commands_with_AV     = 0; 
        $commands_with_scale  = 0; $commands_with_jump_b = 0; $commands_with_CMD    = 0; $commands_with_format = 0; 
        $commands_with_CF     = 0; $commands_with_p      = 0; $commands_with_vx     = 0; $commands_with_FP     = 0; 
        $commands_with_slash  = 0; $commands_with_plus   = 0; $commands_with_minus  = 0; $commands_with_star   = 0; 
        $commands_with_delay  = 0; $commands_with_WT     = 0; $commands_with_0      = 0; $commands_with_1      = 0; 
        $commands_with_iset   = 0; $commands_with_jback  = 0; $commands_with_x      = 0; $commands_with_period = 0;

        foreach ($files as $file_name) {
            if (strpos ($file_name, '[Empty]')   !== false) $empty_commands ++;
            if (strpos ($file_name, 'Flags')     !== false) $flags_commands ++;
            if (strpos ($file_name, '-RISC.')    !== false) $risc_commands ++;
            if (strpos ($file_name, '-MCD.')     !== false) $mcd_commands ++;

            if (strpos ($file_name, '-MIPS.')    !== false) $mips_commands ++;
            if (strpos ($file_name, '-ARM.')     !== false) $arm_commands ++;

            $file = fopen ($file_name, 'r');
            $command = fread ($file, filesize ($file_name));
            fclose ($file); unset ($file);

            if (strpos ($command, ' RF')         !== false) $commands_with_RF ++;
            if (strpos ($command, ' RD')         !== false) $commands_with_RD ++;
            if (strpos ($command, ' R1')         !== false) $commands_with_R1 ++;
            if (strpos ($command, ' R2')         !== false) $commands_with_R2 ++;
            if (strpos ($command, ' R3')         !== false) $commands_with_R3 ++;
            if (strpos ($command, ' R4')         !== false) $commands_with_R4 ++;
            if (strpos ($command, ' RP')         !== false) $commands_with_RP ++;
            if (strpos ($command, ' RS')         !== false) $commands_with_RS ++;
            if (strpos ($command, ' RA')         !== false) $commands_with_RA ++;
            if (strpos ($command, ' RR')         !== false) $commands_with_RR ++;
            if (strpos ($command, ' RT')         !== false) $commands_with_RT ++;
            if (strpos ($command, ' RX')         !== false) $commands_with_RX ++;
            if (strpos ($command, ' RH')         !== false) $commands_with_RH ++;
            if (strpos ($command, ' RB')         !== false) $commands_with_RB ++;
            if (strpos ($command, '/R0')         !== false) $commands_with_R0 ++;
            if (strpos ($command, 'F/')          !== false) $commands_with_flags ++;
            if (strpos ($command, ' c_sr-1')     !== false) $commands_with_c_sr ++;
            if (strpos ($command, ' b_slot-1')   !== false) $commands_with_b_slot ++;
            if (strpos ($command, ' <')          !== false) $commands_with_imply ++;
            if (strpos ($command, ' dt_flags')   !== false) $commands_with_dt ++;
            if (strpos ($command, ' d_size')     !== false) $commands_with_size ++;
            if (strpos ($command, ' d_sign')     !== false) $commands_with_sign ++;
            if (strpos ($command, ' immediate')  !== false) $commands_with_const ++;
            if (strpos ($command, 'offset')      !== false) $commands_with_offset ++;
            if (strpos ($command, 'length')      !== false) $commands_with_length ++;
            if (strpos ($command, 'shift_')      !== false) $commands_with_shift ++;
            if (strpos ($command, 'FPC2/')       !== false) $commands_with_FPC2 ++;
            if (strpos ($command, 'AV/')         !== false) $commands_with_AV ++;
            if (strpos ($command, 'scale')       !== false) $commands_with_scale ++;
            if (strpos ($command, 'direction')   !== false) $commands_with_jump_b ++;
            if (strpos ($command, ' cmd')        !== false) $commands_with_CMD ++;
            if (strpos ($command, ' format')     !== false) $commands_with_format ++;
            if (strpos ($command, ' c_flags')    !== false) $commands_with_CF ++;
            if (strpos ($command, ' p_')         !== false) $commands_with_p ++;
            if (strpos ($command, '/V ')         !== false) $commands_with_vx ++;
            if (strpos ($command, 'FP')          !== false) $commands_with_FP ++;
            if (strpos ($command, '/')           !== false) $commands_with_slash ++;
            if (strpos ($command, '+')           !== false) $commands_with_plus ++;
            if (strpos ($command, '-')           !== false) $commands_with_minus ++;
            if (strpos ($command, '*')           !== false) $commands_with_star ++;
            if (strpos ($command, '+D ')         !== false) $commands_with_delay ++;
            if (strpos ($command, '+WT ')        !== false) $commands_with_WT ++;
            if (strpos ($command, '/0 ')         !== false) $commands_with_0 ++;
            if (strpos ($command, '/1 ')         !== false) $commands_with_1 ++;
            if (strpos ($command, '.(')          !== false) $commands_with_iset += ($iset == '*') ? 1 : 0;
            if (strpos ($command, ' j_size')     !== false) $commands_with_jback += ($iset == '*') ? 1 : 0;
            if (strpos ($command, '.(')          !== false) $commands_with_x += ($iset == '*') ? 1 : 0;
            if (strpos ($command, '.')           !== false) $commands_with_period += ($iset == '*') ? 1 : 0;
        }

        if ($empty_commands          % 2 != 0) { print 'Parity check failed for [Empty] commands';          print ' ('.$iset.').'; exit; }
        if ($flags_commands          % 2 != 0) { print 'Parity check failed for !Flags commands';           print ' ('.$iset.').'; exit; }
        if ($risc_commands           % 2 != 0) { print 'Parity check failed for -RISC commands';            print ' ('.$iset.').'; exit; }
        if ($mcd_commands            % 2 != 0) { print 'Parity check failed for -MCD commands';             print ' ('.$iset.').'; exit; }

        if ($mips_commands           % 2 != 0) { print 'Parity check failed for -MIPS commands';            print ' ('.$iset.').'; exit; }
        if ($arm_commands            % 2 != 0) { print 'Parity check failed for -ARM commands';             print ' ('.$iset.').'; exit; }

        if ($commands_with_RF        % 2 != 0) { print 'Parity check failed for commands with !RF';         print ' ('.$iset.').'; exit; }
        if ($commands_with_RD        % 2 != 0) { print 'Parity check failed for commands with !RD';         print ' ('.$iset.').'; exit; }
        if ($commands_with_R1        % 2 != 0) { print 'Parity check failed for commands with !R1';         print ' ('.$iset.').'; exit; }
        if ($commands_with_R2        % 2 != 0) { print 'Parity check failed for commands with !R2';         print ' ('.$iset.').'; exit; }
        if ($commands_with_R3        % 2 != 0) { print 'Parity check failed for commands with !R3';         print ' ('.$iset.').'; exit; }
        if ($commands_with_R4        % 2 != 0) { print 'Parity check failed for commands with !R4';         print ' ('.$iset.').'; exit; }
        if ($commands_with_RP        % 2 != 0) { print 'Parity check failed for commands with !RP';         print ' ('.$iset.').'; exit; }
        if ($commands_with_RS        % 2 != 0) { print 'Parity check failed for commands with !RS';         print ' ('.$iset.').'; exit; }
        if ($commands_with_RA        % 2 != 0) { print 'Parity check failed for commands with !RA';         print ' ('.$iset.').'; exit; }
        if ($commands_with_RR        % 2 != 0) { print 'Parity check failed for commands with !RR';         print ' ('.$iset.').'; exit; }
        if ($commands_with_RT        % 2 != 0) { print 'Parity check failed for commands with !RT';         print ' ('.$iset.').'; exit; }
        if ($commands_with_RX        % 2 != 0) { print 'Parity check failed for commands with !RX';         print ' ('.$iset.').'; exit; }
        if ($commands_with_RH        % 2 != 0) { print 'Parity check failed for commands with !RH';         print ' ('.$iset.').'; exit; }
        if ($commands_with_RB        % 2 != 0) { print 'Parity check failed for commands with !RB';         print ' ('.$iset.').'; exit; }
        if ($commands_with_R0        % 2 != 0) { print 'Parity check failed for commands with /R0';         print ' ('.$iset.').'; exit; }
        if ($commands_with_flags     % 2 != 0) { print 'Parity check failed for commands with F/...';       print ' ('.$iset.').'; exit; }
        if ($commands_with_c_sr      % 2 != 0) { print 'Parity check failed for commands with C_SR';        print ' ('.$iset.').'; exit; }
        if ($commands_with_b_slot    % 2 != 0) { print 'Parity check failed for commands with B_SLOT';      print ' ('.$iset.').'; exit; }
        if ($commands_with_imply     % 2 != 0) { print 'Parity check failed for commands with <IMPLY>';     print ' ('.$iset.').'; exit; }
        if ($commands_with_dt        % 2 != 0) { print 'Parity check failed for commands with DT';          print ' ('.$iset.').'; exit; }
        if ($commands_with_size      % 2 != 0) { print 'Parity check failed for commands with D_SIZE';      print ' ('.$iset.').'; exit; }
        if ($commands_with_sign      % 2 != 0) { print 'Parity check failed for commands with D_SIGN';      print ' ('.$iset.').'; exit; }
        if ($commands_with_const     % 2 != 0) { print 'Parity check failed for commands with [CONST]';     print ' ('.$iset.').'; exit; }
        if ($commands_with_offset    % 2 != 0) { print 'Parity check failed for commands with [OFFSET]';    print ' ('.$iset.').'; exit; }
        if ($commands_with_length    % 2 != 0) { print 'Parity check failed for commands with [LENGTH]';    print ' ('.$iset.').'; exit; }
        if ($commands_with_shift     % 2 != 0) { print 'Parity check failed for commands with [SHIFT]';     print ' ('.$iset.').'; exit; }
        if ($commands_with_FPC2      % 2 != 0) { print 'Parity check failed for commands with FPC2/';       print ' ('.$iset.').'; exit; }
        if ($commands_with_AV        % 2 != 0) { print 'Parity check failed for commands with AV/...';      print ' ('.$iset.').'; exit; }
        if ($commands_with_scale     % 2 != 0) { print 'Parity check failed for commands with [SCALE]';     print ' ('.$iset.').'; exit; }
        if ($commands_with_jump_b    % 2 != 0) { print 'Parity check failed for commands with [DIRECTION]'; print ' ('.$iset.').'; exit; }
        if ($commands_with_CMD       % 2 != 0) { print 'Parity check failed for commands with [CMD]';       print ' ('.$iset.').'; exit; }
        if ($commands_with_format    % 2 != 0) { print 'Parity check failed for commands with [FORMAT]';    print ' ('.$iset.').'; exit; }
        if ($commands_with_CF        % 2 != 0) { print 'Parity check failed for commands with [C_FLAGS]';   print ' ('.$iset.').'; exit; }
        if ($commands_with_p         % 2 != 0) { print 'Parity check failed for commands with [P_OFFSET]';  print ' ('.$iset.').'; exit; }
        if ($commands_with_vx        % 2 != 0) { print 'Parity check failed for commands with V/';          print ' ('.$iset.').'; exit; }
        if ($commands_with_FP        % 2 != 0) { print 'Parity check failed for commands with FP...';       print ' ('.$iset.').'; exit; }
        if ($commands_with_slash     % 2 != 0) { print 'Parity check failed for commands with /';           print ' ('.$iset.').'; exit; }
        if ($commands_with_plus      % 2 != 0) { print 'Parity check failed for commands with +';           print ' ('.$iset.').'; exit; }
        if ($commands_with_minus     % 2 != 0) { print 'Parity check failed for commands with -';           print ' ('.$iset.').'; exit; }
        if ($commands_with_star      % 2 != 0) { print 'Parity check failed for commands with *';           print ' ('.$iset.').'; exit; }
        if (($commands_with_delay +
            $commands_with_WT)       % 2 != 0) { print 'Parity check failed for commands with +D and +WT';  print ' ('.$iset.').'; exit; }
        if (($commands_with_0 +
            $commands_with_1)        % 2 != 0) { print 'Parity check failed for commands with [0] and [1]'; print ' ('.$iset.').'; exit; }
        if ($commands_with_iset      % 2 != 0) { print 'Parity check failed for commands with .()';         print ' ('.$iset.').'; exit; }
        if ($commands_with_jback     % 2 != 0) { print 'Parity check failed for commands with [J-BACK]';    print ' ('.$iset.').'; exit; }
        if ($commands_with_x         % 2 != 0) { print 'Parity check failed for commands with "x"';         print ' ('.$iset.').'; exit; }
        if ($commands_with_period    % 2 != 0) { print 'Parity check failed for commands with "."';         print ' ('.$iset.').'; exit; }
    }
}

function resetAllFormatFiles ($path) {
    print 'Deleting format files...'."\n";

    $mask = '*-Format-*.rc';
    $files = rglob ($path.'/', $mask); sort ($files);

    foreach ($files as $file_name) {
        if (strpos ($file_name, '-Format-') &&
            strpos ($file_name, '.rc')) unlink ($file_name);
    }
}

function generateFormatFiles ($path) {
    print 'Generating format files...'."\n";

    $mask = 'Format-*-*.+';
    $files = rglob ($path.'/', $mask); sort ($files);

    $templates = array ();
    $new_data_is_present = 0;

    foreach ($files as $file_name) {
        $lines = file ($file_name); if (!count ($lines)) throw new Exception ('/FF/');

        $name = basename ($file_name);
        $template_name = strreplace (strreplace ($name, 'Format-', ''), '.+', '');

        $template = array ();

        foreach ($lines as $line) {
            if ($line && $line[1] == ':') {
                if ($line[0] == 'H') $line = strreplace ($line, '%%', $template_name);
            }

            $template[] = rtrim ($line)."\r\n";
        }

        $text = trim (implode ('', $template)); if (!$text) throw new Exception ('/FF/');

        $templates[$template_name] = $text;

        /* * */

        $text = makeText ($template); 
        $text = strreplace ($text, '!', '1');

        $rc_file_name = strreplace (dirname (dirname ($file_name)).'/'.$name, '.+', '.rc');

        $existing_text = '';

        if (file_exists ($rc_file_name)) {
            $file = fopen ($rc_file_name, 'r');
            $existing_text = fread ($file, filesize ($rc_file_name));
            fclose ($file); unset ($file);
        }

        if ($text != $existing_text) {
            $file = fopen ($rc_file_name, 'w');
            fwrite ($file, $text);
            fclose ($file); unset ($file);

            $new_data_is_present = 1;

            print ' >> '.$rc_file_name."\n";
        }
    }

    if ($new_data_is_present) print "\n";

    /* * */

    $template_names = array ();
    $r_src_templates = array ();
    $m_src_templates = array ();

    $mask = '*.x';
    $files = rglob ($path.'/', $mask); sort ($files);

    foreach ($files as $file_name) {
        $numbers = parseInstructionFormatFile ($file_name);

        $template_name = $numbers['template_name'];
        $command_type = $numbers['command_type'];
        $command_number = $numbers['command_number'];

        if (strpos ($template_name, '*') !== false) {
            $numbers = discoverNumbers ($file_name);

            $template_name = $numbers['template_name'];
            $command_type = $numbers['command_type'];
            $command_number = $numbers['command_number'];
        }

        if (!isset ($templates[$template_name])) { print "\n".'Incorrect template name in '.$file_name.' -- '.$template_name; exit; }

        rememberNode ($template_names, $template_name);
        rememberNode ($r_src_templates, $template_name, '-R');
        rememberNode ($m_src_templates, $template_name, '-M');

        $text = $templates[$template_name];
        $digits = (strpos ($text, 'W;')) ? '0;'.$command_type.':'.$command_number : $command_type.':'.$command_number;

        $characters = array ('W;', 'M;', 'I;', 'S;', 'N;', '#');
        $c_reserved = array ('W;',                   'N;'     );
        $addr_modes = array (      'M;', 'I;', 'S;'           );

        for ($cntr = 0; $cntr < strlen ($digits); $cntr ++) {
             if ($digits[$cntr] == ';' || $digits[$cntr] == ':') {
                 ;
             } else {
                 if ($digits[$cntr] == '0' || $digits[$cntr] == '1') {
                     $c_number = 0;
                     $position = 0;

                     for ($i = 0; $i < count ($characters); $i ++) {
                          $c_number = $i;
                          $position = strpos ($text, $characters[$i]);

                          if ($position) break;
                     }

                     if ($position) { 
                         for ($i = 0; $i < count ($c_reserved); $i ++) {
                              if ($characters[$c_number][0] == $c_reserved[$i][0] && $digits[$cntr] != '0') {
                                  print "\n".$text;
                                  print "\n".'Incorrect command number in '.$file_name; exit; 
                              }
                         }

                         /*

                         for ($i = 0; $i < count ($addr_modes); $i ++) {
                              ...
                         }

                         */
                     }

                     if ($position) { 
                         $text[$position] = $digits[$cntr];
                     } else {
                         print "\n".$text;
                         print "\n".'Incorrect format or command number in '.$file_name; exit; 
                     }
                 } else {
                     print "\n".$text;
                     print "\n".'Incorrect command number in '.$file_name; exit; 
                 }
             }
        }

        for ($cntr = 0; $cntr < count ($characters); $cntr ++) {
             if (strpos ($text, $characters[$cntr])) { 
                 print "\n".$text;
                 print "\n".'Incorrect format or command number in '.$file_name; exit; 
             }
        }

        /* * */

        $text = makeText (array (
                                  'Prover id: '.$template_name,
                                  'Entry code: '.$command_type,
                                  'Cell number: '.$command_number
                                ));

        $rc_file_name = strreplace (dirname (dirname ($file_name)).'/'.basename ($file_name), '.x', '.rc');

        $existing_text = '';

        if (file_exists ($rc_file_name)) {
            $file = fopen ($rc_file_name, 'r');
            $existing_text = fread ($file, filesize ($rc_file_name));
            fclose ($file); unset ($file);
        }

        if ($text != $existing_text) {
            $file = fopen ($rc_file_name, 'w');
            fwrite ($file, $text);
            fclose ($file); unset ($file);

            print ' >> '.$rc_file_name."\n";
        }
    }

    checkParity ($template_names,  'checking format files');
    checkParity ($r_src_templates, 'checking *-R* format files');
    checkParity ($m_src_templates, 'checking *-M* format files');
}

function makeText ($src) {
    $max_length = 0;

    foreach ($src as $line) { 
        $length = strlen (trim ($line)); if ($length > $max_length) $max_length = $length; 
    }

    $lines = array ();
    foreach ($src as $line) { $lines[] = '  '.rtrim ($line)."\r\n"; }

    $text = 'ÚÄ'.str_pad ('', $max_length).'Ä¿'."\r\n".
            rtrim (implode ('', $lines))       ."\r\n".
            'ÀÄ'.str_pad ('', $max_length).'ÄÙ';

    return $text;
}

/* * */

function isRegisterOperation ($file_name, $template_name, $command_type) {
    $is_register_operation = 1;

    if (substr ($template_name, 0, strlen ('RISC-')) == 'RISC-' || substr ($template_name, 0, strlen ('MCD-')) == 'MCD-' ||
        substr ($template_name, 0, strlen ('MIPS-')) == 'MIPS-' || substr ($template_name, 0, strlen ('ARM-')) == 'ARM-') {
        ;    
    } else {
        if (substr ($template_name, 0, strlen ('16-')) != '16-') {
            if (substr ($template_name, 0, strlen ('32-04-')) == '32-04-' || substr ($template_name, 0, strlen ('32-05-')) == '32-05-') {
                if (substr ($command_type, 0, strlen ('0;')) != '0;') $is_register_operation = 0;
            } else {
                if (substr ($command_type, 0, strlen ('0;0;0;')) != '0;0;0;') $is_register_operation = 0;

                if (substr ($template_name, 0, strlen ('64-')) == '64-' && $command_type[strlen ('0;0;0;0;1') - 1] == '1') {
                    $is_register_operation = 0;
                }

                if (strpos ($file_name, '-RRRI-')) $is_register_operation = 0;
            }
        }
    }

    return $is_register_operation;
}

function discoverNumbers ($secondary_file_name) {
    $file_name = $secondary_file_name;

    if (strpos ($file_name, '-S3R-')) {
        $file_name = strreplace ($file_name, '-S3R-', '-S3I-');
    }

    $primary_names = primaryOperationFileNames ();
    $secondary_names = secondaryOperationFileNames ();

    foreach ($primary_names as $primary_name) {
        foreach ($secondary_names as $secondary_name) {
            if ($primary_name != $secondary_name) {
                $file_name_step_2 = strreplace ($file_name, $secondary_name, $primary_name);

                if (file_exists ($file_name_step_2)) {
                    $file = fopen ($file_name_step_2, 'r');
                    $existing_text = fread ($file, filesize ($file_name_step_2));
                    fclose ($file); unset ($file);

                    if (!strpos ($existing_text, '*')) {
                        return applyAddressingMode (parseInstructionFormatFile ($file_name_step_2), $secondary_file_name);
                    }
                }
            }
        }
    }

    print 'Unable to fill secondary file: '.$secondary_file_name; exit;
}

function primaryOperationFileNames () {
    return array (
                   'RRR-', 'RR-',
                   'RRI-', 'RI-', 'RII-'
                 );
}

function secondaryOperationFileNames () {
    return array (
                   'RI-',  'RM-',
                   'RII-', 'RMI-', 'RRI-',

                   'RRI-',  'RRM-',  'RMI-',  'RII-',
                   'RRII-', 'RRMI-', 'RMII-', 'RIII-', 'RRRI-',

                   'MI-', 'MII-', 'MIII-',
                   'MR-', 'MRI-', 'MRII-',

                   '-M-', '-RMM-',

                   '-S3R-'
                 );
}

function parseInstructionFormatFile ($file_name) {
    $lines = file ($file_name);
    $template_name = trim (strreplace ($lines[0], 'Format:', ''));
    $command_type = trim (strreplace ($lines[1], 'Instruction type:', ''));
    $command_number = trim (strreplace ($lines[2], 'Instruction code:', ''));

    return array (
                   'template_name' => $template_name,
                   'command_type' => $command_type,
                   'command_number' => $command_number
                 );
}

function applyAddressingMode ($numbers, $secondary_file_name) {
    $result = array (
                      'template_name' => $numbers['template_name'],
                      'command_type' => $numbers['command_type'],
                      'command_number' => $numbers['command_number']
                    );

    for ($step = 0; $step < 2; $step ++) {
         if ($step == 0) {
             $from = ''; $to = '';

             if (strpos ($secondary_file_name, '-32.x') || strpos ($secondary_file_name, '-48.x')) {
                 if (!$from && !$to && strpos ($secondary_file_name, '-RR-'))   { $from = '0;0;0;';   $to = '0;0;0;';   }
                 if (!$from && !$to && strpos ($secondary_file_name, '-RRI-'))  { $from = '0;0;0;';   $to = '0;0;1;';   }
                 if (!$from && !$to && strpos ($secondary_file_name, '-RI-'))   { $from = '0;0;0;';   $to = '0;1;0;';   }
                 if (!$from && !$to && strpos ($secondary_file_name, '-RII-'))  { $from = '0;0;0;';   $to = '0;1;1;';   }

                 if (!$from && !$to && strpos ($secondary_file_name, '-RM-'))   { $from = '0;0;0;';   $to = '1;0;0;';   }
                 if (!$from && !$to && strpos ($secondary_file_name, '-RMI-'))  { $from = '0;0;0;';   $to = '1;0;1;';   }
                 if (!$from && !$to && strpos ($secondary_file_name, '-MI-'))   { $from = '0;0;0;';   $to = '1;1;0;';   }
                 if (!$from && !$to && strpos ($secondary_file_name, '-MII-'))  { $from = '0;0;0;';   $to = '1;1;1;';   }
             }

             if (strpos ($secondary_file_name, '-64.x')) {
                 if (!$from && !$to && strpos ($secondary_file_name, '-RRR-'))  { $from = '0;0;0;0;'; $to = '0;0;0;0;'; }
                 if (!$from && !$to && strpos ($secondary_file_name, '-RRRI-')) { $from = '0;0;0;0;'; $to = '0;0;0;1;'; }
                 if (!$from && !$to && strpos ($secondary_file_name, '-RRI-'))  { $from = '0;0;0;0;'; $to = '0;0;1;0;'; }
                 if (!$from && !$to && strpos ($secondary_file_name, '-RRII-')) { $from = '0;0;0;0;'; $to = '0;0;1;1;'; }
                 if (!$from && !$to && strpos ($secondary_file_name, '-RII-'))  { $from = '0;0;0;0;'; $to = '0;1;0;0;'; }
                 if (!$from && !$to && strpos ($secondary_file_name, '-RIII-')) { $from = '0;0;0;0;'; $to = '0;1;0;1;'; }

                 if (!$from && !$to && strpos ($secondary_file_name, '-RRM-'))  { $from = '0;0;0;0;'; $to = '1;0;0;0;'; }
                 if (!$from && !$to && strpos ($secondary_file_name, '-RRMI-')) { $from = '0;0;0;0;'; $to = '1;0;0;1;'; }
                 if (!$from && !$to && strpos ($secondary_file_name, '-RMI-'))  { $from = '0;0;0;0;'; $to = '1;0;1;0;'; }
                 if (!$from && !$to && strpos ($secondary_file_name, '-RMII-')) { $from = '0;0;0;0;'; $to = '1;0;1;1;'; }
                 if (!$from && !$to && strpos ($secondary_file_name, '-MII-'))  { $from = '0;0;0;0;'; $to = '1;1;0;0;'; }
                 if (!$from && !$to && strpos ($secondary_file_name, '-MIII-')) { $from = '0;0;0;0;'; $to = '1;1;0;1;'; }

                 if (!$from && !$to && strpos ($secondary_file_name, '-RR-'))   { $from = '0;0;0;0;'; $to = '0;0;0;0;'; }
                 if (!$from && !$to && strpos ($secondary_file_name, '-RI-'))   { $from = '0;0;0;0;'; $to = '0;0;1;0;'; }

                 if (!$from && !$to && strpos ($secondary_file_name, '-RM-'))   { $from = '0;0;0;0;'; $to = '1;0;0;0;'; }
                 if (!$from && !$to && strpos ($secondary_file_name, '-MI-'))   { $from = '0;0;0;0;'; $to = '1;1;0;0;'; }
             }

             if ($from && $to) {
                 $result['command_type'] = $to.
                                           substr ($result['command_type'], strlen ($from));
             } else {
                 print 'Unable to fill secondary format: '.$secondary_file_name; exit;
             }
         }

         if ($step == 1) {
             if (strpos ($secondary_file_name, '-S3I-')) {
                 $result['command_type'][strlen ('0;0;0;0;0') - 1] = '0';
             }

             if (strpos ($secondary_file_name, '-S3R-')) {
                 $result['command_type'][strlen ('0;0;0;0;1') - 1] = '1';
             }
         }
    }

    return $result;
}

/* * */

function rememberNode (&$nodes, $node_name, $mask = '', $check_flag = '', $flag = 0, $value = 0) {
    if (!$mask) {
        if (!$check_flag) {
            _doRememberNode ($nodes, $node_name);
        } else {
            if ($flag == $value) {
                _doRememberNode ($nodes, $node_name);
            } else {
                ;
            }
        }
    } else {
        if (!$check_flag) {
            if (strpos ($node_name, $mask)) {
                _doRememberNode ($nodes, $node_name);
            } else {
                ;
            }
        } else {
            if ($flag == $value) {
                if (strpos ($node_name, $mask)) {
                    _doRememberNode ($nodes, $node_name);
                } else {
                    ;
                }
            } else {
                ;
            }
        }
    }
}

function _doRememberNode (&$nodes, $node_name) {
    if (!isset ($nodes[$node_name])) {
        $nodes[$node_name] = 1;
    } else {
        $nodes[$node_name] ++;
    }
}

function checkParity ($nodes, $iteration_name) {
    foreach ($nodes as $node=>$counter) {
        if ($counter % 2 != 0) {
            print 'Parity check failed when '.$iteration_name.': '.$counter.' -- '.$node."\n"; exit;
        }
    }

    if (count ($nodes) % 2 != 0) {
        print 'Parity check failed when '.$iteration_name.': '.count ($nodes)."\n"; exit;
    }
}

function checkNumbers ($templates_and_numbers) {
    $min_max = array ();
    $numbers = array ();

    foreach ($templates_and_numbers as $template=>$related_numbers) {
        foreach ($related_numbers as $number) {
            if (!isset ($min_max[$template])) {
                $min_max[$template] = array (
                                              'min' => $number,
                                              'max' => $number
                                            );
            } else {
                if ($number < $min_max[$template]['min']) $min_max[$template]['min'] = $number;
                if ($number > $min_max[$template]['max']) $min_max[$template]['max'] = $number;
            }

            if (!isset ($numbers[$template])) $numbers[$template] = array ();
            if (!isset ($numbers[$template][$number])) $numbers[$template][$number] = '*';
        }
    }

    foreach ($min_max as $template=>$levels) {
        if (strpos ($template, '-sparse')) continue;

        if ($levels['min'] != 0) {
            print 'Empty space at '.$template.' -- '.$levels['min']."\n"; exit; 
        }

        for ($number = $levels['min']; $number <= $levels['max']; $number ++) {
             if (!isset ($numbers[$template][$number])) {
                 print 'Empty space in '.$template.' -- '.decbin ($number)."\n"; exit; 
             }
        }
    }
}

/* * */

function rglob ($path_w_slash, $pattern, $flags = 0) {
    if (substr ($path_w_slash, -1) != '/') $path_w_slash .= '/';

    $paths = glob ($path_w_slash.'*', GLOB_ONLYDIR);
    $files = glob ($path_w_slash.$pattern, $flags);

    foreach ($paths as $next_step_path) {
        $next_step_files = rglob ($next_step_path.'/', $pattern, $flags); 
        foreach ($next_step_files as $next_step_file) { $files[] = $next_step_file; }
    }

    return $files;
}

function argIsPresent ($argv, $name) {
    foreach ($argv as $arg) {
        if ($arg == $name) return 1;
    }

    return 0;
}

/* * */

function strreplace ($haystack, $needle, $str) { return str_replace ($needle, $str, $haystack); }
function yield_time () { if (function_exists ('proc_nice')) { proc_nice (abs (31)); } else { usleep (25); } }

function php_error_handler ($errno, $errstr, $errfile, $errline, $vars = null) { 
    die ("\n\n".'['.$errno.'] '.$errstr.' ('.strtr ($errfile, '\\', '/').', '.$errline.')'."\n\n");
}
