<?php

/**
 *
 * brainph*ck - a PHP class for handling the brainf*ck programming language
 *
 * Copyright (C) 2005 Vincent Negrier aka. sIX <six@aegis-corp.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA *
 *
 */

/*

What is Brainf*ck
-----------------

BF is a turing-complete programming language in its simplest and purest form.

BF has only eight instruction, none take any parameters :

>    Increment the pointer.
<    Decrement the pointer.
+    Increment the byte at the pointer.
-    Decrement the byte at the pointer.
.    Output the byte at the pointer.
,    Input a byte and store it in the byte at the pointer.
[    Jump forward past the matching ] if the byte at the pointer is zero.
]    Jump backward to the matching [ unless the byte at the pointer is zero.

Additionally, BrainPhuck supports the following extensions to cannonical BF :

!    Stop the program
#    Print debugging informations

Other characters are treated as comments and are ignored.

Here is a sample "Hello world" program in BF :

>+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]>++++++++[<++++>-]
<.>+++++++++++[<++++++++>-]<-.--------.+++.------.--------.[-]>++++++++[<++++>-
]<+.[-]++++++++++.


How to use it
-------------

require "brainphuck/brainphuck.php";
$bf = new BrainPhuck();
$bf->Load_File("beer.bf");
echo $bf->Execute();

you can also use $bf = new BrainPhuck("> my bf program ++++++++.,.,.");

Load_File() and Load_String() methods load and convert BF code to the
intermediate BPH code.

Execute() has two ways of executing your BF program :

- the internal BPH executor, which is quite fast and nice for fine debugging
- the PHP converter, which converts BPH to PHP code and can be a LOT faster

the default executor is the internal executor, you have to call Execute(true)
in order to use the PHP converter.

you can also compile the loaded BPH code to DOS/Windows binary .COM file or
Linux a.out with the Compile() method just like this :

file_put_contents("mybf.com", $bf->Compile(BrainPhuck::COMPILE_COM));
file_put_contents("mybf", $bf->Compile(BrainPhuck::COMPILE_AOUT));

the resulting file will execute way faster than any of the two executors.

the Dump_Debug_Data() method can be used to dump the BPH code along with
some converter / executor numbers

use the Set_Input() method to set the BF input to some string

*/

class BrainPhuck {

    const 
VERSION "1.1.0-dev";

    const 
OP_INCV 1;
    const 
OP_INCP 2;
    const 
OP_DECV 3;
    const 
OP_DECP 4;
    const 
OP_ADDV 5;
    const 
OP_ADDP 6;
    const 
OP_SUBV 7;
    const 
OP_SUBP 8;
    const 
OP_ZERO 9;
    const 
OP_SETV 10;
    const 
OP_JNZ 11;
    const 
OP_JZ 12;
    const 
OP_IN 13;
    const 
OP_OUT 14;
    const 
OP_RET 15;
    const 
OP_DBG 16;
    const 
OP_RINC 17;
    const 
OP_RDEC 18;
    const 
OP_RADD 19;
    const 
OP_RSUB 20;
    const 
OP_RZ 21;
    const 
OP_RSET 22;
    const 
OP_NOP 23;

    const 
COMPILE_COM 1;
    const 
COMPILE_AOUT 2;

    const 
PARSE_STRICT 1;
    const 
PARSE_EXT 2;

    const 
OPTIMIZE_NONE 0x00;
    const 
OPTIMIZE_ZERO 0x01;
    const 
OPTIMIZE_SETV 0x02;
    const 
OPTIMIZE_ROPS 0x04;
    const 
OPTIMIZE_LOOPS 0x08;
    const 
OPTIMIZE_NOPS 0x80;
    const 
OPTIMIZE_ALL 0xFF;
    const 
OPTIMIZE_DEFAULT 0x7F;

    private 
$op_array = array();
    private 
$op_pointer 0;

    private 
$op_bf_count 0;
    private 
$op_exec_count 0;

    private 
$done false;

    private 
$idcp 0;
    private 
$idcv 0;

    private 
$dataset = array();
    private 
$dataptr 0;

    private 
$input "";
    private 
$output "";

    private 
$start_time;

    private 
$od_cache;

    public function 
__construct($s=false$mode=self::PARSE_EXT) {

        if (
$s !== false$this->Load_String($s$mode);

    }

    public function 
Load_File($f$mode=self::PARSE_EXT) {

        if (!
is_readable($f)) {

            throw new 
Exception("can not read file '{$f}'");

            return 
false;

        }

        
$this->Load_String(file_get_contents($f), $mode);

    }

    public function 
Load_String($s$mode=self::PARSE_EXT) {

        
$rjmp = array();

        
$l strlen($s);

        for (
$a 0$a $l$a++) {

            
$ops = array();

            ++
$this->op_bf_count;

            switch (
$s{$a}) {

                case 
"-":
                
$this->Add_Pending_IDCP();
                --
$this->idcv;
                break;

                case 
"+":
                
$this->Add_Pending_IDCP();
                ++
$this->idcv;
                break;

                case 
"<":
                
$this->Add_Pending_IDCV();
                --
$this->idcp;
                break;

                case 
">":
                
$this->Add_Pending_IDCV();
                ++
$this->idcp;
                break;

                case 
"[":
                
$rjmp[$this->Find_Matching_Bracket($s$a$l)] = $this->Op_Add(self::OP_JZ);
                break;

                case 
"]":
                
$this->op_array[$rjmp[$a] - 1][1] = $this->Op_Add(self::OP_JNZ$rjmp[$a]);
                break;

                case 
",":
                
$this->Op_Add(self::OP_IN);
                break;

                case 
".":
                
$this->Op_Add(self::OP_OUT);
                break;

                case 
"!":

                if (
$mode == self::PARSE_STRICT) {

                    --
$this->op_bf_count;

                } else {

                    
$this->Op_Add(self::OP_RET);

                }

                break;

                case 
"#":

                if (
$mode == self::PARSE_STRICT) {

                    --
$this->op_bf_count;

                } else {

                    
$this->Op_Add(self::OP_DBG);

                }

                break;

                default:
                --
$this->op_bf_count;
                break;

            }

        }

        
$this->Op_Add(self::OP_RET);

    }

    public function 
Initialize() {

        
$this->op_exec_count 0;

        
$this->dataset = array();
        
$this->dataptr 0;

        
$this->input_pointer 0;
        
$this->op_pointer 0;

        
$this->output "";

        
$this->done false;

    }

    public function 
Execute($as_php=false) {

        
$this->Initialize();

        
$this->start_time microtime(true);

        if (
$as_php) {

            
$code $this->Dump_PHP_Code();
            eval(
$code);

        } else {

            while (!
$this->done$this->Execute_Op();

        }

        return 
$this->output;

    }

    public function 
Set_Input($s) {

        
$this->input $s;
        
$this->input_pointer 0;

    }

    private function 
BF_Input() {

        return 
$this->input{$this->input_pointer++};

    }

    public function 
Get_Output() {

        return 
$this->output;

    }

    public function 
Execute_Op() {

        list(
$op$arg$arg2) = $this->op_array[$this->op_pointer++];

        switch (
$op) {

            case 
self::OP_INCV:        ++$this->dataset[$this->dataptr];                break;
            case 
self::OP_INCP:        ++$this->dataptr;                                break;
            case 
self::OP_DECV:        --$this->dataset[$this->dataptr];                break;
            case 
self::OP_DECP:        --$this->dataptr;                                break;
            case 
self::OP_ADDV:        $this->dataset[$this->dataptr] += $arg;            break;
            case 
self::OP_ADDP:        $this->dataptr += $arg;                            break;
            case 
self::OP_SUBV:        $this->dataset[$this->dataptr] -= $arg;            break;
            case 
self::OP_SUBP:        $this->dataptr -= $arg;                            break;
            case 
self::OP_ZERO:        $this->dataset[$this->dataptr] = 0;                break;
            case 
self::OP_SETV:        $this->dataset[$this->dataptr] = $arg;            break;

            case 
self::OP_RINC:        ++$this->dataset[$this->dataptr $arg2];        break;
            case 
self::OP_RDEC:        --$this->dataset[$this->dataptr $arg2];        break;
            case 
self::OP_RADD:        $this->dataset[$this->dataptr $arg2] += $arg;    break;
            case 
self::OP_RSUB:        $this->dataset[$this->dataptr $arg2] -= $arg;    break;
            case 
self::OP_RZ:        $this->dataset[$this->dataptr $arg2] = 0;        break;
            case 
self::OP_RSET:        $this->dataset[$this->dataptr $arg2] = $arg;    break;

            case 
self::OP_NOP:        continue;

            case 
self::OP_JZ:        if (!$this->dataset[$this->dataptr]) $this->op_pointer $arg;        break;
            case 
self::OP_JNZ:        if ($this->dataset[$this->dataptr]) $this->op_pointer $arg;        break;

            case 
self::OP_IN:        $this->dataset[$this->dataptr] = ord($this->BF_Input());    break;
            case 
self::OP_OUT:        $this->BF_Output(chr($this->dataset[$this->dataptr]));        break;

            case 
self::OP_RET:        $this->done true;                                break;

            case 
self::OP_DBG:        $this->Dump_Debug_Data();                        break;

        }

        ++
$this->op_exec_count;

    }

    private function 
Add_Pending_IDCV() {

        if (
$this->idcv == -1) {

            
$this->Op_Add(self::OP_DECV);

        } else if (
$this->idcv == 1) {

            
$this->Op_Add(self::OP_INCV);

        } else if (
$this->idcv 0) {

            
$this->Op_Add(self::OP_SUBVabs($this->idcv));

        } else if (
$this->idcv 0) {

            
$this->Op_Add(self::OP_ADDV$this->idcv);

        }

        
$this->idcv 0;

    }

    private function 
Add_Pending_IDCP() {

        if (
$this->idcp == -1) {

            
$this->Op_Add(self::OP_DECP);

        } else if (
$this->idcp == 1) {

            
$this->Op_Add(self::OP_INCP);

        } else if (
$this->idcp 0) {

            
$this->Op_Add(self::OP_SUBPabs($this->idcp));

        } else if (
$this->idcp 0) {

            
$this->Op_Add(self::OP_ADDP$this->idcp);

        }

        
$this->idcp 0;

    }

    private function 
Add_Pending_IDC() {

        
$this->Add_Pending_IDCV();
        
$this->Add_Pending_IDCP();

    }

    private function 
Op_Add($op$arg=NULL) {

        if ((
$op == self::OP_JNZ) || ($op == self::OP_JZ) || ($op == self::OP_IN) || ($op == self::OP_OUT) || ($op == self::OP_DBG)) {

            
$this->Add_Pending_IDC();

        }

        
$this->op_array[$this->op_pointer++] = array($op$arg);

        return 
$this->op_pointer;

    }

    private function 
Find_Matching_Bracket($s$offset$l) {

        
$blvl 0;

        for (
$a $offset$a $l$a++) {

            if (
$s{$a} == "[") {

                ++
$blvl;

            } else if (
$s{$a} == "]") {

                --
$blvl;

            }

            if (
$blvl == 0) return $a;

        }

        throw new 
Exception("no matching end bracket found for '[' at offset {$offset}");

    }

    protected function 
BF_Output($s) {

        
$this->output .= $s;

    }

    public function 
Dump_Debug_Data() {

        
$elapsed microtime(true) - $this->start_time;

        echo 
"== Code =======================================================================\n";

        
$op_str = array("???""INCV""INCP""DECV""DECP""ADDV""ADDP""SUBV""SUBP""ZERO""SETV""JNZ""JZ""IN""OUT""RET""DBG""RINC""RDEC""RADD""RSUB""RZ""RSET""NOP");

        foreach (
$this->op_array as $k => $oa) {

            echo 
str_pad($k4"0"STR_PAD_LEFT)."  ".str_pad($op_str[$oa[0]], 5)." ".str_pad($oa[1], 5)." ".str_pad($oa[2], 5)."\n";

        }

        echo 
"== Opcode Distribution ========================================================\n";

        
$tc count($this->op_array);

        
$i 0;

        foreach (
$op_str as $op) {

            if (
$op == "???") continue;

            
$nb_ops 0;

            if (isset(
$this->od_cache[$op])) {

                
$nb_ops $this->od_cache[$op];

            } else {

                
$op_v constant("self::OP_" $op);

                foreach (
$this->op_array as $oa) if ($oa[0] == $op_v) ++$nb_ops;

                
$this->od_cache[$op] = $nb_ops;

            }

            echo 
str_pad($op4)." ".str_pad($nb_ops4" "STR_PAD_LEFT)." (".str_pad(number_format($nb_ops 100 $tc1), 4" "STR_PAD_LEFT)."%)".(++$i%"   " "\n");

        }

        if (
$i%4) echo "\n";

        echo 
"== Data =======================================================================\n";

        
$datamin = (($this->dataptr 32) > 0) ? ($this->dataptr 32) : 0;
        
$datamax $datamin 64;

        for (
$a 0$a <= 3$a++) {

            echo 
str_pad($datamin + ($a 16), 4"0"STR_PAD_LEFT)."  ";

            for (
$b 0$b 16$b++) {

                echo 
str_pad(strtoupper(substr(dechex($this->dataset[$datamin + ($a 16) + $b]), -2)), 2"0"STR_PAD_LEFT) . " ";

            }

            echo 
"  ";

            for (
$b 0$b 16$b++) {

                
$c chr($this->dataset[$datamin + ($a 16) + $b]);
                if ((
$c == "\r") || ($c == "\n") || ($c == "\t") || ($c == "\0")) $c " ";

                echo 
$c;

            }

            echo 
"\n";

        }

        echo 
"== Stats ======================================================================\n";
        echo 
"BF ops       : ".(int)$this->op_bf_count."\n";
        echo 
"BPH ops      : ".count($this->op_array)."\n";
        echo 
"Executed ops : ".(int)$this->op_exec_count."\n";
        if (isset(
$this->start_time)) echo "Elapsed time : ".number_format($elapsed3)." sec\n";
        echo 
"Op Pointer   : ".$this->op_pointer."\n";
        echo 
"Data Pointer : ".$this->dataptr."\n";
        echo 
"Input        : ".strlen($this->input)." bytes (".(int)$this->input_pointer." read)\n";
        echo 
"Output       : ".strlen($this->output)." bytes\n";
        echo 
"===============================================================================\n";

    }

    private function 
Ops_Remove($idx$len) {

        
$ops = array();

        foreach (
$this->op_array as $k => $op) {

            if ((
$op[1] >= ($idx $len)) && (($op[0] === self::OP_JZ) || ($op[0] === self::OP_JNZ))) $op[1] = $op[1] - $len;

            if ((
$k $idx) || ($k >= ($idx $len))) $ops[] = $op;

        }

        
$this->op_array $ops;

    }

    private function 
Op_Get_Next($base$idx=1) {

        
$oc count($this->op_array);
        
$i $base;
        
$cidx 0;

        while (
$i $oc) {

            if (
$this->op_array[++$i][0] !== self::OP_NOP) ++$cidx;

            if (
$cidx === $idx) return array($i$this->op_array[$i]);

        }

        return array(
false);

    }

    private function 
Optimize_Zero() {

        
$opt false;
        
$oc count($this->op_array);

        for (
$a 0$a $oc$a++) if ($this->op_array[$a][0] === self::OP_JZ) {

            list(
$p1$o1) = $this->Op_Get_Next($a1);
            list(
$p2$o2) = $this->Op_Get_Next($a2);

            if ((
$o1[0] === self::OP_DECV) && ($o2[0] === self::OP_JNZ)) {

                
$opt true;

                
$this->op_array[$a] = array(self::OP_ZERO);
                
$this->op_array[$p1] = array(self::OP_NOP);
                
$this->op_array[$p2] = array(self::OP_NOP);

                
$a $p2;

            }

        }

        return 
$opt;

    }

    private function 
Optimize_Setv() {

        
$opt false;
        
$oc count($this->op_array);

        for (
$a 0$a $oc$a++) {

            if (
$this->op_array[$a][0] === self::OP_ZERO) {

                list(
$p1$o1) = $this->Op_Get_Next($a1);

                if (
$o1[0] === self::OP_INCV) {

                    
$opt true;

                    
$this->op_array[$a] = array(self::OP_SETV1);
                    
$this->op_array[$p1] = array(self::OP_NOP);

                    
$a $p1;

                } else if (
$o1[0] === self::OP_ADDV) {

                    
$opt true;

                    
$this->op_array[$a] = array(self::OP_SETV$this->op_array[$a 1][1]);
                    
$this->op_array[$p1] = array(self::OP_NOP);

                    
$a $p1;

                }

            }

        }

        return 
$opt;

    }

    private function 
Optimize_Rops() {

    }

    public function 
Optimize($level=self::OPTIMIZE_DEFAULT) {

        if (
$level self::OPTIMIZE_ZERO$this->Optimize_Zero();
        if (
$level self::OPTIMIZE_SETV$this->Optimize_Setv();
        if (
$level self::OPTIMIZE_ROPS$this->Optimize_Rops();

    }

    private function 
Dump_PHP_Code() {

        
$lvl 0;
        
$prf "";

        
$out "";

        foreach (
$this->op_array as $oa) {

            switch (
$oa[0]) {

                case 
self::OP_INCV:
                
$out .= $prf '++$this->dataset[$this->dataptr];' "\n";
                break;

                case 
self::OP_INCP:
                
$out .= $prf '++$this->dataptr;' "\n";
                break;

                case 
self::OP_DECV:
                
$out .= $prf '--$this->dataset[$this->dataptr];' "\n";
                break;

                case 
self::OP_DECP:
                
$out .= $prf '--$this->dataptr;' "\n";
                break;

                case 
self::OP_ADDV:
                
$out .= $prf '$this->dataset[$this->dataptr] += ' $oa[1] . ";\n";
                break;

                case 
self::OP_ADDP:
                
$out .= $prf '$this->dataptr += ' $oa[1] . ";\n";
                break;

                case 
self::OP_SUBV:
                
$out .= $prf '$this->dataset[$this->dataptr] -= ' $oa[1] . ";\n";
                break;

                case 
self::OP_SUBP:
                
$out .= $prf '$this->dataptr -= ' $oa[1] . ";\n";
                break;

                case 
self::OP_ZERO:
                
$out .= $prf '$this->dataset[$this->dataptr] = 0;' "\n";
                break;

                case 
self::OP_SETV:
                
$out .= $prf '$this->dataset[$this->dataptr] = ' $oa[1] . ";\n";
                break;
/*
                case self::OP_RINCV:
                $out .= $prf . '++$this->dataset[$this->dataptr + ' . $oa[2] . "];\n";
                break;

                case self::OP_RDECV:
                $out .= $prf . '--$this->dataset[$this->dataptr + ' . $oa[2] . "];\n";
                break;

                case self::OP_RADDV:
                $out .= $prf . '$this->dataset[$this->dataptr + ' . $oa[2] . '] += ' . $oa[1] . ";\n";
                break;

                case self::OP_RSUBV:
                $out .= $prf . '$this->dataset[$this->dataptr + ' . $oa[2] . '] -= ' . $oa[1] . ";\n";
                break;

                case self::OP_RZERO:
                $out .= $prf . '$this->dataset[$this->dataptr + ' . $oa[2] . "] = 0;\n";
                break;

                case self::OP_RSETV:
                $out .= $prf . '$this->dataset[$this->dataptr + ' . $oa[2] . '] = ' . $oa[1] . ";\n";
                break;
*/
                
case self::OP_JZ:
                
$out .= "\n" $prf 'while ($this->dataset[$this->dataptr]) {' "\n\n";
                
$prf str_pad("", ++$lvl 4);
                break;

                case 
self::OP_JNZ:
                
$prf str_pad("", --$lvl 4);
                
$out .= "\n" $prf '}' "\n\n";
                break;

                case 
self::OP_IN:
                
$out .= $prf '$this->dataset[$this->dataptr] = ord($this->BF_Input());' "\n\n";
                break;

                case 
self::OP_OUT:
                
$out .= $prf '$this->BF_Output(chr($this->dataset[$this->dataptr]));' "\n\n";
                break;

                case 
self::OP_DBG:
                
$out .= $prf '$this->Dump_Debug_Data();' "\n\n";
                break;

            }

        }

        return 
$out;

    }

    private function 
Find_Matching_JNZ($k) {

        
$lvl 0;

        
$c count($this->op_array);

        for (
$a $k$a $c$a++) {

            if (
$this->op_array[$a][0] == self::OP_JZ) {

                ++
$lvl;

            } else if (
$this->op_array[$a][0] == self::OP_JNZ) {

                --
$lvl;

            }

            if (
$lvl == 0) return $a;

        }

        throw new 
Exception("unmatched OP_JZ in internal BPH code at offset {$k}");

    }

    private function 
Has_Op($op) {

        foreach (
$this->op_array as $oa) if ($oa[0] == $op) return true;

        return 
false;

    }

    public function 
Compile($type=false$align=1) {

        if (
$type === false) {

            if (
PHP_OS == "Linux") {

                
$type self::COMPILE_AOUT;

            } else {

                
$type self::COMPILE_COM;

            }

        }

        
$link_sub_in $this->Has_Op(self::OP_IN);
        
$link_sub_out $this->Has_Op(self::OP_OUT);

        if (
$type == self::COMPILE_COM) {

            
$out  chr(0x53) . chr(0x49) . chr(0x58) . chr(0x1a) . chr(0x00);
            
$out .= chr(0x1e);                            // push ds
            
$out .= chr(0x07);                            // pop es
            
$out .= chr(0xBF) . chr(0x00) . chr(0x80);    // mov di,0x8000
            
$out .= chr(0xB9) . chr(0xFF) . chr(0x3F);    // mov cx,0x3FFF
            
$out .= chr(0x31) . chr(0xC0);                // xor ax,ax
            
$out .= chr(0xF3) . chr(0xAB);                // rep stosw
            
$out .= chr(0xBF) . chr(0x00) . chr(0x80);    // mov di,0x8000
            
$out .= chr(0xEB) . "?";                    // jmp ??

        
} else if ($type == self::COMPILE_AOUT) {

            
$out .= chr(0xBF) . "????";                    // mov edi, start_of_bss
            
$out .= chr(0xEB) . "?";                    // jmp ??

        
}

        
$e_jmp_off strlen($out) - 1;

        if (
$link_sub_in) {

            while (
strlen($out)%$align$out .= chr(0x90);

            
$sub_in_addr strlen($out);                    // sub_in:

            
if ($type == self::COMPILE_COM) {

                
$out .= chr(0xB4) . chr(0x01);                // mov ah, 1
                
$out .= chr(0xCD) . chr(0x21);                // int 21h
                
$out .= chr(0x88) . chr(0x05);                // mov [di], al
                
$out .= chr(0xC3);                            // ret

            
} else if ($type == self::COMPILE_AOUT) {

                
$out .= chr(0x53);                            // push ebx
                
$out .= chr(0x31) . chr(0xD2);                // xor edx, edx
                
$out .= chr(0x42);                            // inc edx
                
$out .= chr(0x89) . chr(0xF9);                // mov ecx, edi
                
$out .= chr(0x31) . chr(0xDB);                // xor ebx, ebx
                
$out .= chr(0xB8) . chr(0x03) . chr(0x00) . chr(0x00) . chr(0x00);    // mov eax, 3
                
$out .= chr(0xCD) . chr(0x80);                // int 80h
                
$out .= chr(0x5B);                            // pop ebx
                
$out .= chr(0xC3);                            // ret

            
}

        }

        if (
$link_sub_out) {

            while (
strlen($out)%$align$out .= chr(0x90);

            
$sub_out_addr strlen($out);                    // sub_out:

            
if ($type == self::COMPILE_COM) {

                
$out .= chr(0x8A) . chr(0x15);                // mov dl, [di]
                
$out .= chr(0xB4) . chr(0x02);                // mov ah, 2
                
$out .= chr(0xCD) . chr(0x21);                // int 21h
                
$out .= chr(0xC3);                            // ret

            
} else if ($type == self::COMPILE_AOUT) {

                if (
$link_sub_in$out .= chr(0x53);        // [push ebx]

                
$out .= chr(0x31) . chr(0xD2);                // xor edx, edx
                
$out .= chr(0x42);                            // inc edx
                
$out .= chr(0x89) . chr(0xF9);                // mov ecx, edi
                
$out .= chr(0x89) . chr(0xD3);                // mov ebx, edx
                
$out .= chr(0xB8) . chr(0x04) . chr(0x00) . chr(0x00) . chr(0x00);    // mov eax, 4
                
$out .= chr(0xCD) . chr(0x80);                // int 80h

                
if ($link_sub_in$out .= chr(0x5B);        // [pop ebx]

                
$out .= chr(0xC3);                            // ret

            
}

        }

        
$out{$e_jmp_off} = chr(strlen($out) - $e_jmp_off 1);

        if (
$type == self::COMPILE_COM) {

            if (
$link_sub_in$out .= chr(0xBB) . chr($sub_in_addr) . chr(0x01);    // mov bx, sub_in + 0100h
            
if ($link_sub_out$out .= chr(0xBE) . chr($sub_out_addr) . chr(0x01);    // mov si, sub_out + 0100h

        
} else if ($type == self::COMPILE_AOUT) {

            if (
$link_sub_in$out .= chr(0xBB) . pack("l"$sub_in_addr);            // mov ebx, sub_in
            
if ($link_sub_out$out .= chr(0xBE) . pack("l"$sub_out_addr);        // mov esi, sub_out

        
}

        
$iaddr strlen($out);

        foreach (
$this->op_array as $k => $oa) {

            switch (
$oa[0]) {

                case 
self::OP_INCV:

                if (
$type == self::COMPILE_COM) {

                    
// inc byte ptr [di]
                    
$out .= chr(0xFE) . chr(0x05);
                    
$iaddr += 2;

                } else if (
$type == self::COMPILE_AOUT) {

                    
// inc byte ptr [edi]
                    
$out .= chr(0xFE) . chr(0x07);
                    
$iaddr += 2;

                }

                break;

                case 
self::OP_INCP:
                
// inc [e]di
                
$out .= chr(0x47);
                ++
$iaddr;
                break;

                case 
self::OP_DECV:

                if (
$type == self::COMPILE_COM) {

                    
// dec byte ptr [di]
                    
$out .= chr(0xFE) . chr(0x0D);
                    
$iaddr += 2;

                } else if (
$type == self::COMPILE_AOUT) {

                    
// dec byte ptr [edi]
                    
$out .= chr(0xFE) . chr(0x0F);
                    
$iaddr += 2;

                }

                break;

                case 
self::OP_DECP:
                
// dec [e]di
                
$out .= chr(0x4F);
                ++
$iaddr;
                break;

                case 
self::OP_ADDV:

                if (
$type == self::COMPILE_COM) {

                    
// add byte ptr [di], oa[1]
                    
$out .= chr(0x80) . chr(0x05) .  chr($oa[1] & 0xFF);
                    
$iaddr += 3;

                } else if (
$type == self::COMPILE_AOUT) {

                    
// add byte ptr [edi], oa[1]
                    
$out .= chr(0x80) . chr(0x07) .  chr($oa[1] & 0xFF);
                    
$iaddr += 3;

                }

                break;

                case 
self::OP_ADDP:
                
// add [e]di, oa[1]

                
if ($oa[1] < 127) {

                    
$out .= chr(0x83) . chr(0xC7) . chr($oa[1]);
                    
$iaddr += 3;

                } else {

                    
$out .= chr(0x81) . chr(0xC7) . chr($oa[1] & 0xFF) . chr($oa[1] >> 8);
                    
$iaddr += 4;

                }

                break;

                case 
self::OP_SUBV:

                if (
$type == self::COMPILE_COM) {

                    
// sub byte ptr [di], oa[1]
                    
$out .= chr(0x80) . chr(0x2D) . chr($oa[1] & 0xFF);
                    
$iaddr += 3;

                } else if (
$type == self::COMPILE_AOUT) {

                    
// sub byte ptr [edi], oa[1]
                    
$out .= chr(0x80) . chr(0x2F) . chr($oa[1] & 0xFF);
                    
$iaddr += 3;

                }

                break;

                case 
self::OP_SUBP:
                
// sub [e]di, oa[1]

                
if ($oa[1] < 127) {

                    
$out .= chr(0x83) . chr(0xEF) . chr($oa[1]);
                    
$iaddr += 3;

                } else {

                    
$out .= chr(0x81) . chr(0xEF) . chr($oa[1] & 0xFF) . chr($oa[1] >> 8);
                    
$iaddr += 4;

                }

                break;

                case 
self::OP_ZERO:

                if (
$type == self::COMPILE_COM) {

                    
// mov byte ptr [di], 0
                    
$out .= chr(0xC6) . chr(0x05) . chr(0x00);
                    
$iaddr += 3;

                } else if (
$type == self::COMPILE_AOUT) {

                    
// mov byte ptr [edi], 0
                    
$out .= chr(0xC6) . chr(0x07) . chr(0x00);
                    
$iaddr += 3;

                }

                break;

                case 
self::OP_SETV:

                if (
$type == self::COMPILE_COM) {

                    
// mov byte ptr [di], oa[1]
                    
$out .= chr(0xC6) . chr(0x05) . chr($oa[1] & 0xFF);
                    
$iaddr += 3;

                } else if (
$type == self::COMPILE_AOUT) {

                    
// mov byte ptr [edi], oa[1]
                    
$out .= chr(0xC6) . chr(0x07) . chr($oa[1] & 0xFF);
                    
$iaddr += 3;

                }

                break;


                case 
self::OP_JZ:

                
$last_op $this->op_array[$k 1][0];

                if (
$last_op != self::OP_JZ) {

                    if ((
$last_op != self::OP_INCV) && ($last_op != self::OP_DECV) && ($last_op != self::OP_ADDV) && ($last_op != self::OP_SUBV)) {

                        if (
$type == self::COMPILE_COM) {

                            
// cmp byte ptr [di], 0
                            
$out .= chr(0x80) . chr(0x3D) . chr(0x00);
                            
$iaddr += 3;

                        } else if (
$type == self::COMPILE_AOUT) {

                            
// cmp byte ptr [edi], 0
                            
$out .= chr(0x80) . chr(0x3F) . chr(0x00);
                            
$iaddr += 3;

                        }

                    }

                    if (
$type == self::COMPILE_COM) {

                        
// jnz +3
                        // jmp oa[1]

                        
$out .= chr(0x75) . chr(0x03);

                        
$j_off strlen($out) - 1;

                        
$out .= chr(0xE9) . "??";
                        
$iaddr += 5;

                    } else if (
$type == self::COMPILE_AOUT) {

                        
// jnz +5
                        // jmp oa[1]

                        
$out .= chr(0x75) . chr(0x05);

                        
$j_off strlen($out) - 1;

                        
$out .= chr(0xE9) . "????";
                        
$iaddr += 7;

                    }

                    
$align_offset 0;

                    while (
$iaddr%$align) {

                        
// nop
                        
$out .= chr(0x90);

                        ++
$iaddr;
                        ++
$align_offset;

                    }

                    
$out{$j_off} = chr(ord($out{$j_off}) + $align_offset);

                }

                
$rjmp[$this->Find_Matching_JNZ($k)] = array($iaddr$align_offset);

                break;


                case 
self::OP_JNZ:

                
$last_op $this->op_array[$k 1][0];

                if (
$last_op != self::OP_JNZ) {

                    if ((
$last_op != self::OP_INCV) && ($last_op != self::OP_DECV) && ($last_op != self::OP_ADDV) && ($last_op != self::OP_SUBV)) {

                        if (
$type == self::COMPILE_COM) {

                            
// cmp byte ptr [di], 0
                            
$out .= chr(0x80) . chr(0x3D) . chr(0x00);
                            
$iaddr += 3;

                        } else if (
$type == self::COMPILE_AOUT) {

                            
// cmp byte ptr [edi], 0
                            
$out .= chr(0x80) . chr(0x3F) . chr(0x00);
                            
$iaddr += 3;

                        }

                    }

                    
$rel_addr $rjmp[$k][0] - $iaddr;

                    if (((
$rel_addr 2) < -128) || (($rel_addr 2) > 127)) {

                        if (
$type == self::COMPILE_COM) {

                            
// jz +3
                            // jmp oa[1]
                            
$out .= chr(0x74) . chr(0x03);
                            
$out .= chr(0xE9) . pack("s"$rel_addr 5);
                            
$iaddr += 5;

                        } else if (
$type == self::COMPILE_AOUT) {

                            
// jz +5
                            // jmp oa[1]
                            
$out .= chr(0x74) . chr(0x05);
                            
$out .= chr(0xE9) . pack("l"$rel_addr 7);
                            
$iaddr += 7;

                        }

                    } else {

                        
// jnz oa[1]
                        
$out .= chr(0x75) . pack("c"$rel_addr 2);
                        
$iaddr += 2;

                    }

                }

                if (
$type == self::COMPILE_COM) {

                    
$revj_pck pack("s"$iaddr $rjmp[$k][0] + $rjmp[$k][1]);

                    
$out{$rjmp[$k][0] - $rjmp[$k][1] - 2} = $revj_pck{0};
                    
$out{$rjmp[$k][0] - $rjmp[$k][1] - 1} = $revj_pck{1};

                } else if (
$type == self::COMPILE_AOUT) {

                    
$revj_pck pack("l"$iaddr $rjmp[$k][0] + $rjmp[$k][1]);

                    
$out{$rjmp[$k][0] - $rjmp[$k][1] - 4} = $revj_pck{0};
                    
$out{$rjmp[$k][0] - $rjmp[$k][1] - 3} = $revj_pck{1};
                    
$out{$rjmp[$k][0] - $rjmp[$k][1] - 2} = $revj_pck{2};
                    
$out{$rjmp[$k][0] - $rjmp[$k][1] - 1} = $revj_pck{3};

                }

                break;

                case 
self::OP_IN:
                
// call [e]bx
                
$out .= chr(0xFF) . chr(0xD3);
                
$iaddr += 2;
                break;

                case 
self::OP_OUT:
                
// call [e]si
                
$out .= chr(0xFF) . chr(0xD6);
                
$iaddr += 2;
                break;

                case 
self::OP_RET:

                if (
$type == self::COMPILE_COM) {

                    
// ret
                    
$out .= chr(0xC3);
                    ++
$iaddr;

                } else if (
$type == self::COMPILE_AOUT) {

                    
// xor eax,eax
                    // inc eax
                    // int 80h
                    
$out .= chr(0x31) . chr(0xC0);
                    
$out .= chr(0x40);
                    
$out .= chr(0xCD) . chr(0x80);
                    
$iaddr += 5;

                }

                break;

                case 
self::OP_DBG:
                
// int 3h
                
$out .= chr(0xCC);
                ++
$iaddr;
                break;

            }

        }

        if (
$type == self::COMPILE_AOUT) {

            
$plen pack("L"strlen($out));

            for (
$a 0$a <= 3$a++) $out{$a 1} = $plen{$a};

            
$hdr  chr(0x07) . chr(0x01) . chr(0x64) . chr(0x00);        // unsigned long a_midmag = OMAGIC | M_386
            
$hdr .= $plen;                                                // unsigned long a_text
            
$hdr .= chr(0x00) . chr(0x00) . chr(0x00) . chr(0x00);        // unsigned long a_data = 0
            
$hdr .= chr(0x00) . chr(0x00) . chr(0x02) . chr(0x00);        // unsigned long a_bss = 131072
            
$hdr .= chr(0x00) . chr(0x00) . chr(0x00) . chr(0x00);        // unsigned long a_syms = 0
            
$hdr .= chr(0x00) . chr(0x00) . chr(0x00) . chr(0x00);        // unsigned long a_entry = 0
            
$hdr .= chr(0x00) . chr(0x00) . chr(0x00) . chr(0x00);        // unsigned long a_trsize = 0
            
$hdr .= chr(0x00) . chr(0x00) . chr(0x00) . chr(0x00);        // unsigned long a_drsize = 0

            
$out $hdr $out;

        } else if (
$type == self::COMPILE_COM) {

            
$dlen floor((0x10000 - (strlen($out) + 0x100)) / 2);

            
$plen pack("S"strlen($out) + 0x100);
            
$dlen pack("S"$dlen);

            
$out{8} = $out{18} = $plen{0};
            
$out{9} = $out{19} = $plen{1};

            
$out{11} = $dlen{0};
            
$out{12} = $dlen{1};

        }

        return 
$out;

    }

}

?>