<?php
/**********************************************
* 파일명: class.SkyTemplate_withDaddy.php
* 설  명: Sky Template Compiler
* 버  젼: v1.4.0
* 작성자: 하늘아부지 (daddy@withsky.net)
***********************************************/
/*
    LICENSE : LGPL

    # v1.4.0
        2005-07-30
*/

class SkyTemplate_withDaddy
{
    var $TPL          = null;
    var $webRoot      = "";
    var $tplRoot      = "";
	var $delimiter    = null;
    var $rewriteRoot  = "";
    var $tplRewrite   = false;
    var $divReal      = true;
	var $strict       = true;
    var $debug        = false;
    var $preFilter    = "";
    var $postFilter   = "";
	var $safeMode     = false;

    // 내부적으로 사용하는 것들
    var $version      = "";
    var $timeOffset   = 0;
	var $dlm          = null;
	var $dlmQuoted    = array();
    var $File         = array();
    var $Src          = array();
    var $Code         = array();
    var $Divide       = array();
    var $Target       = array();
	var $safeModeList = array();
	var $safeModeFlag = true;

    function SkyTemplate_withDaddy($TPL=null)
    {
        if ($TPL) {
            $this->setInfo(&$TPL);
        }
    }

    function setInfo($TPL)
    {
        if (is_object($TPL)) {
            $this->webRoot     = &$TPL->webRoot;
            $this->tplRoot     = &$TPL->tplRoot;
			$this->delimiter   = &$TPL->delimiter;
            $this->rewriteRoot = &$TPL->rewriteRoot;
            $this->tplRewrite  = &$TPL->tplRewrite;
            $this->divReal     = &$TPL->divReal;
			$this->strict      = &$TPL->strict;
            $this->debug       = &$TPL->debug;
            $this->preFilter   = &$TPL->preFilter;
            $this->postFilter  = &$TPL->postFilter;
            $this->safeMode    = &$TPL->safeMode;
            $this->version     = &$TPL->version;
            $this->timeOffset  = &$TPL->timeOffset;
        } else {
            $this->error("setInfo : No SkyTemplate Object!!", true);
            exit;
        }

		$this->dlm = explode(",", $this->delimiter);
		for($i=0; $i<2; $i++) {
			$this->dlmQuoted[$i] = preg_quote($this->dlm[$i], "/");
		}
        $this->rewriteRoot  = str_replace("{tplRoot}", $this->tplRoot, $this->rewriteRoot);
        if (!is_dir($this->tplRoot) && !$this->makeDir($this->tplRoot)) {
            $this->error("setInfo : Directory Not Exists! (".$this->tplRoot.")", true);
            exit;
        } else if (!is_writable($this->tplRoot)) {
            $this->error("setInfo : Directory Permission Error! (".$this->tplRoot.")", true);
            exit;
		}
		if ($this->safeMode) {
	        $safeModeFile = dirname(__FILE__)."/SkyTpl/setup.ini.SafeMode.php";
			if (is_file($safeModeFile)) {
				$type = "";
				$tmp = file($safeModeFile);
				for ($i=0, $c=count($tmp); $i<$c; $i++) {
					list($line) = explode(";", preg_replace("/[\s]/", "", $tmp[$i]), 2);
					if ($line) {
						if (preg_match("/^\[(control|var|func)\.(allow|deny)\]$/", $line)) {
							$type = substr($line, 1, -1);
							$this->safeModeList[$type] = array();
						} else {
							$this->safeModeList[$type][$line] = true;
						}
					}
				}
			} else {
				$this->error("setInfo : SafeMode Setup File Not Exists!  (".$safeModeFile.")", true);
				exit;
			}
		}
    }

    function compile($var, $file, $tfile)
    {
        if (isset($this->File[$var])) return true;

        if ($fp = @fopen($file, "rb")) {
            $this->Src[$var] = "";
            do { $this->Src[$var] .= @fread($fp, 100000); } while (!feof($fp));
            fclose($fp);

            // file info
            $this->File[$var] = $file;
            $this->Target[$var] = ($pos=strpos($tfile, "#")) ? substr($tfile, 0, $pos).".php" : $tfile;

            // prefilter
            if ($this->preFilter) $this->filter($var, "pre");

            // template rewrite
            if ($this->tplRewrite) $this->tplRewrite($var);

            // remove comment
            $this->comment($var);

            // parse template
			$this->safeModeFlag = true;
            $this->parse($var);
			if (!$this->safeModeFlag) {
				return $this->error("compile : Denied Expression Error! ($file)", true);
			}

            // postfilter
            if ($this->postFilter) $this->filter($var, "post");

            // finalize
			return $this->save($var, @filemtime($file)+$this->timeOffset);

        } else {
            return $this->error("compile : File Not Exists! ($file)", true);
        }
    }

    // apply filter
    function filter($var, $mode="pre")
    {
        static $filterList=array();

		$filter = ($mode == "post") ? $this->postFilter : $this->preFilter;

		if (isset($filterList[$mode])) {
            foreach ($filterList[$mode] as $func=>$args) {
                $args[1] = $var;
				call_user_func_array($func, $args);
            }
        } else {
            $tmp1 = $this->parseQuote(trim($filter), false, false);
            $tmp = array();
            $z = 0;
            for ($i=0, $c1=count($tmp1); $i<$c1; $i+=2) {
                $tmp2 = preg_split("/([,\(\);])/i", $tmp1[$i], -1, PREG_SPLIT_DELIM_CAPTURE);
                for ($j=0, $c2=count($tmp2); $j<$c2; $j++) {
                    $tmp2[$j] = trim($tmp2[$j]);
                    if ($tmp2[$j] != "") {
                        if ($tmp2[$j] == ";") {
                            $z++;
                        } else {
                            $tmp[$z][] = $tmp2[$j];
                        }
                    }
                }
                if (isset($tmp1[$i+1])) $tmp[$z][] = $tmp1[$i+1];
            }

            $filterList[$mode] = array();
            for ($i=0, $c=count($tmp); $i<$c; $i++) {
                $func = array_shift($tmp[$i]);
				$filterList[$mode][$func] = array();
                $args = &$filterList[$mode][$func];
				$args[] = &$this;
				$args[] = $var;
                for ($j=1, $ci=count($tmp[$i]); $j<$ci; $j+=2) {
                    switch (strtolower($tmp[$i][$j])) {
                        case "true" : $args[] = true; break;
                        case "false" : $args[] = false; break;
                        case "null" : $args[] = null; break;
                        default : $args[] = preg_replace("/^['\\\"]|['\\\"]$/", "", $tmp[$i][$j]);
                    }
                }
                $file = dirname(__FILE__)."/SkyTpl/plugin.$mode.$func.php";
                if (is_file($file)) {
                    include_once $file;
                    if (function_exists($func)) {
                        call_user_func_array($func, $args);
                    } else {
                        $this->error("filter : Plugin Function Not Exists! ($func)");
                    }
                } else {
                    $this->error("filter : Plugin File Not Exists! ($file)");
                }
            }
        }
    }

    // template file rewrite
    function tplRewrite($var)
    {
        if ($this->preFilter) {
            $r = $this->save($var, "", "rewrite");
            if ($this->tplRewrite === "overwrite" && !preg_match("/^http:\/\//", $this->File[$var])) {
                $this->error("tplRewrite : Rewrite Original Template File : ".($r ? "Success" : "Fail")." (".$this->File[$var].")", true);
            }
            return $r;
        } else {
            return true;
        }
    }

    // multi line 주석 {* *}
    function comment($var)
    {
        $src = &$this->Src[$var];

		$src = preg_replace(array("/<![-]{2,}".$this->dlmQuoted[0]."/", "/".$this->dlmQuoted[1]."[-]{2,}>/"), $this->dlm, $src); // HTML주석 제거
        $tmp = preg_split("/(".$this->dlmQuoted[0]."\*|\*".$this->dlmQuoted[1].")/", $src, -1, PREG_SPLIT_DELIM_CAPTURE);
        $step = array();
        for ($i=1, $c=count($tmp); $i<$c; $i+=2) {
            if ($tmp[$i] == $this->dlm[0]."*") {
                $step[] = $i;
            } else if ($tmp[$i] == "*".$this->dlm[1]) {
                if ($step) {
                    for ($j=array_pop($step); $j<=$i; $j++) {
                        $tmp[$j] = ""; // unset하면 안됨
                    }
                }
            }
        }
        $src = implode("", $tmp);
    }

	function parse($var)
    {
        $src = &$this->Src[$var];
        $code = &$this->Code[$var];
        $divide = &$this->Divide[$var];
        $codeDefine = ""; // 전처리용
        $arr = array();
        $index = array();

		$quote = false;
		$quoteType = "";
		$bracket = 0;
		$z = 0;

        $tmp = preg_split("/(['\\\"]|".$this->dlmQuoted[0]."|".$this->dlmQuoted[1].")/", $src, -1, PREG_SPLIT_DELIM_CAPTURE);

		$arr[$z] = $tmp[0];
		for ($i=1, $c=count($tmp); $i<$c; $i+=2) {
			switch($token = $tmp[$i]) {
				case '"' :
				case "'" :
					if ($bracket > 0) {
						if (preg_match("/([\\\\]+)$/", $tmp[$i-1], $match)) {
							$escape = (strlen($match[1]) % 2) ? true : false;
						} else {
							$escape = false;
						}
						if ($escape) {
							$arr[$z] .= $token;
							$arr[$z] .= $tmp[$i+1];
						} else {
							if ($quote) {
								if ($quoteType == $token) {
									$quote = false;
									$quoteType = "";
									$arr[$z] .= $token;
									$arr[$z] .= $tmp[$i+1];
								} else {
									$arr[$z] .= $token;
									$arr[$z] .= $tmp[$i+1];
								}
							} else {
								$quote = true;
								$quoteType = $token;
								$arr[$z] .= $token;
								$arr[$z] .= $tmp[$i+1];
							}
						}
					} else {
						$arr[$z] .= $token;
						$arr[$z] .= $tmp[$i+1];
					}
					break;
				case $this->dlm[0] :
					if ($quote) {
						$arr[$z] .= $token;
						$arr[$z] .= $tmp[$i+1];
					} else {
						if ($bracket > 0) {
							$bracket++;
							$arr[$z] .= $token;
							$arr[$z] .= $tmp[$i+1];
						} else if ($bracket == 0) {
							$tmpKey = ($this->strict) ? $tmp[$i+1] : preg_replace("/(^[\t ]+|[\t ]+$)/", "", $tmp[$i+1]);
							// 명령어 (우선은 명령어인지 아닌지만 체크 ^^:)
							if (preg_match("/^([@%\?:\/\|&#\+\]\\$\\\\!\^]|(=;|=|;)|(LOOP|EACH|IF|ELSE|END|DIVIDE|DIV|REFER|REF|INCLUDE|INC|EXECUTE|EXE|DUMPFILE|DUMP|PHPCODE|PHP|EXPRESSION[;]?|EXP[;]?|ESCAPE|ESC|DEFINE|DEF|COLLECT|COL)([\s]|$))/", $tmpKey)) {
								$bracket++;
								$arr[++$z] = ltrim($tmp[$i+1]);
							} else
							// 변수
							if ($tmp[$i+2] == $this->dlm[1] && preg_match("/^([a-zA-Z0-9_\x7f-\xff]+(\.[0-9]+)?::|[\.])?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\.[a-zA-Z0-9_\x7f-\xff]+)*(->[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\.[a-zA-Z0-9_\x7f-\xff]+)*)?$/", $tmpKey)) {
								$arr[++$z] = "<?php echo ".$this->parseVar($tmpKey)."?>";
								if (preg_match("/^[\r\n]/", $tmp[$i+3])) $arr[$z] .= "\n";
								$arr[++$z] = $tmp[$i+3];
								$i+=2;
							} else
							// 해당사항 없음
							{
								$arr[$z] .= $token;
								$arr[$z] .= $tmp[$i+1];
							}
						} else {
							// error
						}
					}
					break;
				case $this->dlm[1] :
					if ($quote) {
						$arr[$z] .= $token;
						$arr[$z] .= $tmp[$i+1];
					} else {
						if ($bracket > 1) {
							$bracket--;
							$arr[$z] .= $token;
							$arr[$z] .= $tmp[$i+1];
						} else if ($bracket == 1) {
							$tmpKey = ($this->strict) ? $arr[$z] : preg_replace("/^[\t ]+/", "", $arr[$z]);
							// 제어문, 블럭형 주석 : 코드 변환은 루프 추출에서 처리
							if (preg_match("/^([@%\?\:\/\|]|(LOOP|EACH|IF|ELSE|END|DIVIDE|DIV)([\s]|$))/", $tmpKey, $match)) {
								$arr[$z] = substr($tmpKey, strlen($match[0]));
								$index[$z] = trim($match[1]);
							} else
							// 템플릭 블럭 모으기
							if (preg_match("/^(\^|(COLLECT|COL)([\s]|$))/", $tmpKey, $match)) {
								if (strpos($tmpKey, ":")) {
									$arr[$z] = substr($tmpKey, strlen($match[0]));
									$index[$z] = trim($match[1]);
								} else {
									$key = trim(substr($tmpKey, strlen($match[0])));
									$key = trim($this->parseExpression($key));
									$arr[$z] = "<?php \$TPL->collect(".$key."); ?>";
								}
							} else
							// 템플릿 블럭 출력 (참조 출력)
							if (preg_match("/^(&|(REFER|REF)[\s])/", $tmpKey, $match)) {
								$key = trim(substr($tmpKey, strlen($match[0])));
								$key = trim($this->parseExpression($key));
								$arr[$z] = "<?php if (\$TPL->assigned(".$key.")) \$TPL->Info[".$key."]['index']=0; \$TPL->tprint(".$key."); ?>"; // Use tprint() Not call()
							} else
							// 템플릿 파일 include : 경로 변수 적용은 {} 사용
							if (preg_match("/^(#|(INCLUDE|INC)[\s])/", $tmpKey, $match)) {
								$key = trim(substr($tmpKey, strlen($match[0])));
								if (preg_match("/^(([^:'\\\"]+):)/", $key, $match)) {
									$name = trim($match[2]);
									$key = trim($this->parseExpression(substr($key, strlen($match[1]))));
								} else {
									$name = "";
									$key = trim($this->parseExpression($key));
								}
								if ($name) {
									$arr[$z] = "<?php if (@is_array(\$_data['".$name."'])) \$TPL->assign('".$name."',&\$_data['".$name."']); else \$TPL->assign('".$name."',array(&\$_data)); \$TPL->tinclude('".$name."',".$key."); ?>";
								} else {
									$arr[$z] = "<?php \$TPL->tinclude('',".$key.",array(&\$_data)); ?>";
								}
							} else
							// include (normal php file)
							if (preg_match("/^(\+|(EXECUTE|EXE)[\s])/", $tmpKey, $match)) {
								$key = trim(substr($tmpKey, strlen($match[0])));
								$key = trim($this->parseExpression($key));
								$arr[$z] = "<?php \$tmp=parse_url(".$key."); if (isset(\$tmp['query'])) { parse_str(\$tmp['query'],\$args); include \$tmp['path']; } else { include ".$key."; } ?>";
							} else
							// file dump (no parsing)
							if (preg_match("/^(\]|(DUMPFILE|DUMP)[\s])/", $tmpKey, $match)) {
								$key = trim(substr($tmpKey, strlen($match[0])));
								$key = trim($this->parseExpression($key));
								$arr[$z] = "<?php readfile(".$key."); ?>";
							} else
							// php code
							if (preg_match("/^(\\$|(PHPCODE|PHP)[\s])/", $tmpKey, $match)) {
								$key = substr($tmpKey, strlen($match[0]));
								$arr[$z] = "<?php".$key."?>";
							} else
							// expression
							if (preg_match("/^((=;|=|;)|(EXPRESSION|EXP)[;\s])/", $tmpKey, $match)) {
								$key = $this->parseExpression(substr($tmpKey, strlen($match[0])));
								$arr[$z] = (strstr($match[0], ";")) ? "<?php".$key."?>" : "<?php echo ".$key."?>";
							} else
							// escape
							if (preg_match("/^(\\\\|(ESCAPE|ESC)[\s])/", $tmpKey, $match)) {
								$key = substr($tmpKey, strlen($match[0]));
								$arr[$z] = $this->dlm[0].$key.$this->dlm[1];
							} else
							// define (전처리)
							if (preg_match("/^(!|(DEFINE|DEF)[\s])/", $tmpKey, $match)) {
								$codeDefine .= $this->parseExpression(substr($tmpKey, strlen($match[0])), "DEF");
								$arr[$z] = "";
							}
							if (isset($tmp[$i+3]) && preg_match("/^[\r\n]/", $tmp[$i+3])) $arr[$z] .= "\n";
							$bracket--;
							$arr[++$z] = $tmp[$i+1];
						} else if ($bracket == 0) {
							$arr[$z] .= $token;
							$arr[$z] .= $tmp[$i+1];
						} else {
							// error
						}
					}
					break;
			}
		}
		unset($tmp);

        // Src 흔적만 남김
        $src = true;

		// 루프 추출 + 코드 작성
        $code = "";
        if ($index) {
            $codeDevide = array();
            $loop = array();
            $div = array();
			$divOffset = array();
            $step = 0;
            $end = 0;

            foreach ($index as $idx=>$proc) {
				// 루프, EACH 블럭
                if (preg_match("/^([@%\^]|LOOP|EACH|COLLECT|COL)$/", $proc)) {
                    $step++;
                    $loop[$step] = $idx;
                } else
                // DIVIDE
                if (preg_match("/^([\|]|DIVIDE|DIV)$/", $proc)) {
                    $step++;
                    $loop[$step] = $idx;
                    $div[$step] = $idx;
                    if ($this->divReal) {
                        $divOffset[$step] = strlen($code);
                    }
                } else
                // IF
                if (preg_match("/^(\?|IF)$/", $proc)) {
                    $step++;
					$key = $this->parseExpression($arr[$idx]);
                    $arr[$idx] = "<?php if (".$key.") { ?>";
                } else
                // ELSE : 루프, else, else if
                if (preg_match("/^(:|ELSE)$/", $proc)) {
                    if (isset($loop[$step])) {
                        $arr[$idx] = "<?php }\nif (!\$_size) { \$_data=&\$GLOBALS; ?>";
                    } else {
                        $key = trim($this->parseExpression($arr[$idx]));
                        $arr[$idx] = ($key) ? "<?php } elseif (".$key."){ ?>" : "<?php } else { ?>";
                    }
                } else
                // END : 주석, 루프, if
                if (preg_match("/^(\/|END)$/", $proc)) {
                    $end++;
                    if ($step <= 0) {
                        $this->error("parse : Unexpected 'END' Tag in ".$this->File[$var]." (offset:".$end.")", true);
                        exit;
                    }
                    // 루프 end
                    if (isset($loop[$step])) {
                        // DIVIDE
                        if (isset($div[$step])) {
                            $mode = "DIV";
                            $name = trim($this->parseQuote($arr[$loop[$step]], true, false)); // 주석 제거
                            if ($pos = strpos($name, ":")) {
                                $exp = trim(substr($name, $pos+1));
                                $name = trim(substr($name, 0, $pos));
                                $arr[$loop[$step]] = "<?php \$tmp=parse_url(".$exp."); if (isset(\$tmp['query'])) { parse_str(\$tmp['query'],\$args); include \$tmp['path']; } else { include ".$exp."; } ?>";
                            } else {
                                $arr[$loop[$step]] = "";
                            }
                        // 루프
                        } else {
                            $mode = preg_match("/^(%|EACH)$/", $index[$loop[$step]]) ? "EACH" : "LOOP";
                            $name = trim($this->parseQuote($arr[$loop[$step]], true, false)); // 주석 제거
                            if (is_int($pos = strpos($name, ":"))) {
                                $exp = trim(substr($name, $pos+1));
                                $name = trim(substr($name, 0, $pos));
								$name || $name = uniqid("");
								if (preg_match("/^(\^|COLLECT|COL)$/", $index[$loop[$step]])) {
									$collect = $this->parseExpression($exp);
									$codeDefine .= "\n\$this->collect(".$collect.", '".$name."');";
									$arr[$loop[$step]] = "";
								} else {
									$loopData = $this->parseExpression(preg_replace("/;[\s]*$/", "", $exp)); // 끝 세미콜론 제거
									$arr[$loop[$step]] = "<?php if (@is_array(\$_data['".$name."'])) \$TPL->assign('".$name."',&\$_data['".$name."']); else \$TPL->assign('".$name."',".$loopData."); \$TPL->call('".$name."'); ?>";
								}
                            } else {
								$name || $name = uniqid("");
                                $arr[$loop[$step]] = "<?php if (@is_array(\$_data['".$name."'])) \$TPL->assign('".$name."',&\$_data['".$name."']); \$TPL->call('".$name."'); ?>";
                            }
                        }
                        $endName = trim($this->parseQuote($arr[$idx], true, false)); // 주석 제거
                        if ($endName && ($name != $endName)) {
                            $this->error("parse : Invalid 'END' Tag Name in ".$this->File[$var]." (expecting '".$name."' not '".$endName."')", true);
                            exit;
                        }
                        $code .= $this->parseFunction($name, implode("", array_slice($arr, $loop[$step]+1, $idx-$loop[$step]-1)), $mode);
                        for ($i=$loop[$step]+1; $i<=$idx; $i++) $arr[$i] = ""; // unset하면 안됨
                        if (isset($div[$step])) {
                            if ($this->divReal) {
                                $divide[$name] = substr($code, $divOffset[$step]);
								unset($divOffset[$step]);
                            }
                            unset($div[$step]);
                        }
                        unset($loop[$step]);
                    // if end
                    } else {
                        $arr[$idx] = "<?php } ?>";
                    }
                    $step--;
                }
            }
            if ($step > 0) {
                $this->error("parse : Need More 'END' Tag in ".$this->File[$var]." (count:".$step.")", true);
                exit;
            }
        }
        $code .= $this->parseFunction($var, implode("", $arr));

        $codeTop = "/* Created by SkyTemplate $this->version on ".date("Y/m/d H:i:s")." */";
        if ($codeDefine) $codeDefine = "\n" . $codeDefine . "\n"; else $codeDefine = "";
        $code = "<?php " . $codeTop . $codeDefine . $code . "?>";
		if (is_array($divide)) {
            foreach ($divide as $key=>$val) {
                $divide[$key] = "<?php " . $codeTop . $val . "?>";
            }
        }
    }

    // 함수화
    function parseFunction($name, $code, $type="LOOP")
    {
        if ($type == "EACH") {
            $code = "\nfunction ".SKY_FUNC_PREFIX.$name." (&\$TPL,&\$DATA,&\$_index,\$_size,\$_col,\$_mod,\$_stop) { "
                  . "if (!is_array(\$DATA)) \$DATA=array(); "
                  . "foreach (\$DATA as \$_index=>\$_data) { "
                  . "?>\n".$code."\n<?php "
                  . "} }\n";
        } else {
            $code = "\nfunction ".SKY_FUNC_PREFIX.$name." (&\$TPL,&\$DATA,&\$_index,\$_size,\$_col,\$_mod,\$_stop) { "
                  . "\$_to=\$_index+\$_col; if (\$_stop && (\$_to>\$_size)) \$_to=\$_size; else \$_mod=0; "
                  . "for (;\$_index<\$_to;\$_index++) { "
                  . "\$_data=&\$DATA[\$_index]; "
                  . "?>\n".$code."\n<?php "
                  . "} }\n";
        }
        return $code;
    }

    // 변수 처리 : 일반 변수
    function parseVar($key)
    {
        $key = preg_replace("/[\.]{2,}/", ".", $key);
		if ($this->safeMode) $this->parseSafeMode($key, "var"); // safeMode : 변수
        if (!preg_match("/^(ELSE|END)$/", $key)) {
            $tmp = explode("->", $key);
            $key = $tmp[0];
            $var = isset($tmp[1]) ? $tmp[1] : null;
            // object
            if ($var) {
				$key = $this->parseVarProcObject($key);
				$tmp = explode(".", preg_replace("/^[\.]/", "", $var));
                if (count($tmp) > 1) $var = $tmp[0]."['".implode("']['", array_slice($tmp, 1))."']";
                $key .= "->" . $var;
            // normal var
            } else {
				$key = $this->parseVarProcNormal($key);
			}
        }
        return $key;
    }

    // 변수 처리 for 표현식
    function parseExpressionVar($key, $p="", $n="", $quote="")
    {
		if ($quote) {
			// escape
			if (substr($p, -1) == "\\") {
				return substr($p, 0, 1) . $key . $n;
			} else {
				$p = str_replace("{", $quote.".", $p);
				$n = str_replace("}", ".".$quote, $n);
			}
        } else {
			$p = preg_replace("/^\\\['\\\"]/", "\"", $p); // 왜 따옴표에 역슬러시가 붙어나오는 거징? ㅜ.ㅜ
            $n = preg_replace("/\\\['\\\"]$/", "\"", $n);
            // .(dot) 연산자 처리
            if (preg_match("/^[\.]{2,}/", $key) || (preg_match("/['\\\"][\s]*$/", $p) && preg_match("/^[\.]/", $key))) {
                $p .= ".";
                $key = substr($key, 1);
            }
        }

        if (!preg_match("/^(if|else|elseif|do|while|for|foreach|as|break|continue|switch|case|default|echo|print|true|false|null|define|declare|include|include_once|require|require_once)$/i", $key)) {
			$tmp = explode("->", $key);
            $key = $tmp[0];
            $var = isset($tmp[1]) ? $tmp[1] : null;
            // object
            if ($var) {
				if ($this->safeMode) $this->parseSafeMode($key, "var"); // safeMode : 변수
				$key = $this->parseVarProcObject($key);
                if (!preg_match("/^[\s]*\(/", $n)) {
                    $tmp = explode(".", preg_replace("/^[\.]/", "", $var));
                    if (count($tmp) > 1) $var = $tmp[0]."['".implode("']['", array_slice($tmp, 1))."']";
                }
                $key .= "->" . $var;
            // normal var
            } else {
                if (!preg_match("/^[\s]*\(/", $n)) {
					if ($this->safeMode) $this->parseSafeMode($key, "var"); // safeMode : 변수
					$key = $this->parseVarProcNormal($key);
                } else {
					if ($this->safeMode) $this->parseSafeMode($key, "func"); // safeMode : 함수
                }
            }
        } else {
			if ($this->safeMode) $this->parseSafeMode($key, "control"); // safeMode : 제어문
		}
        return $p . $key . $n;
    }

	// 일반 변수, 표현식의 object 공통 처리
	function parseVarProcObject($key)
	{
		if ($pos = strpos($key, "::")) {
			$key = $this->parseVarProcBlock($key, $pos);
		} else if ("." == substr($key, 0, 1)) {
			$key = "\$GLOBALS['".str_replace(".", "']['", substr($key, 1))."']";
		} else if (preg_match("/^(TPL)$/", $key)) {
			$key = "\$".$key;
		} else {
			$key = "\$_data['".str_replace(".", "']['", $key)."']";
		}
		return $key;
	}

	// 일반 변수, 표현식의 normal var 공통 처리
	function parseVarProcNormal($key)
	{
		if ($pos = strpos($key, "::")) {
			$key = $this->parseVarProcBlock($key, $pos);
		} else if ("." == substr($key, 0, 1)) {
			$key = "\$GLOBALS['".str_replace(".", "']['", substr($key, 1))."']";
		} else if (preg_match("/^c\.[a-zA-Z0-9_\x7f-\xff]+$/", $key)) {
			$key = substr($key, 2);
		} else if (preg_match("/^((DATA|_data|_value|GLOBALS|_SERVER|_ENV|_SESSION|_COOKIE|_REQUEST|_POST|_GET)(\.[a-zA-Z0-9_\x7f-\xff]+)*|(_size|_col|_mod|_index|_key)(\.[a-zA-Z0-9_\x7f-\xff]+)?|TPL)$/", $key)) {
			$tmp = explode(".", $key);
			$tmp[0] = str_replace(array("_key","_value"), array("_index","_data"), $tmp[0]);
			if (count($tmp) > 1) {
				$key = (preg_match("/^(_size|_col|_mod|_index)$/", $tmp[0], $match)) ? "\$TPL->Info['".$tmp[1]."']['".substr($match[1],1)."']" : "\$".$tmp[0]."['".implode("']['", array_slice($tmp, 1))."']";
			} else {
				$key = "\$".$tmp[0];
			}
		} else {
			$key = "\$_data['".str_replace(".", "']['", $key)."']";
		}
		return $key;
	}

	// 블럭 변수 참조 처리 분리 (라인수를 줄이기 위한 몸부림 ㅜ.ㅡ;;)
	function parseVarProcBlock($key, $pos, $type="EXP")
	{
		$obj = ($type == "DEF") ? "\$this" : "\$TPL";
		$block = preg_replace("/^[\.]+/", "", substr($key, 0, $pos));
		if ($pos2 = strpos($block, ".")) {
			$index = substr($block, $pos2+1);
			$block = substr($block, 0, $pos2);
		} else {
			$index = $obj."->Info['".$block."']['index']";
		}
		$var = str_replace(".", "']['", substr($key, $pos+2));
		$key = $obj."->Info['".$block."']['data'][".$index."]['".$var."']";
		return $key;
	}

    // 변수 처리 for 전처리
    function parseDefineVar($key, $p="", $n="", $quote="")
    {
		if ($quote) {
			// escape
			if (substr($p, -1) == "\\") {
				return substr($p, 0, 1) . $key . $n;
			} else {
				$p = str_replace("{", $quote.".", $p);
				$n = str_replace("}", ".".$quote, $n);
			}
        } else {
            $p = preg_replace("/^\\\['\\\"]/", "\"", $p); // 왜 따옴표에 역슬러시가 붙어나오는 거징? ㅜ.ㅜ
            $n = preg_replace("/\\\['\\\"]$/", "\"", $n);
            // .(dot) 연산자 처리
            if (preg_match("/^[\.]{2,}/", $key) || (preg_match("/['\\\"][\s]*$/", $p) && preg_match("/^[\.]/", $key))) {
                $p .= ".";
                $key = substr($key, 1);
            }
        }

		if (!preg_match("/^(if|else|elseif|do|while|for|foreach|as|break|continue|switch|case|default|echo|print|true|false|null|define|declare|include|include_once|require|require_once)$/i", $key)) {
            $tmp = explode("->", $key);
            $key = preg_replace("/^[\.]/", "", $tmp[0]);
            $var = isset($tmp[1]) ? $tmp[1] : null;
            // object
            if ($var) {
				if ($this->safeMode) $this->parseSafeMode($key, "var"); // safeMode : 변수
                if ($pos = strpos($key, "::")) {
					$key = $this->parseVarProcBlock($key, $pos, "DEF");
				} else if (preg_match("/^(this)$/", $key)) {
                    $key = "\$".$key;
                } else {
                    $key = "\$GLOBALS['".str_replace(".", "']['", $key)."']";
                }
                if (!preg_match("/^[\s]*\(/", $n)) {
                    $tmp = explode(".", preg_replace("/^[\.]/", "", $var));
                    if (count($tmp) > 1) $var = $tmp[0]."['".implode("']['", array_slice($tmp, 1))."']";
                }
                $key .= "->" . $var;
            // normal var
            } else {
                if (!preg_match("/^[\s]*\(/", $n)) {
					if ($this->safeMode) $this->parseSafeMode($key, "var"); // safeMode : 변수
                    if ($pos = strpos($key, "::")) {
						$key = $this->parseVarProcBlock($key, $pos, "DEF");
					} else if (preg_match("/^c\.[a-zA-Z0-9_\x7f-\xff]+$/", $key)) {
                        $key = substr($key, 2);
                    } else if (preg_match("/^(GLOBALS|_SERVER|_ENV|_SESSION|_COOKIE|_REQUEST|_POST|_GET)(\.[a-zA-Z0-9_\x7f-\xff]+)*$/", $key)) {
						$tmp = explode(".", $key);
						if (count($tmp) > 1) {
							$key = "\$".$tmp[0]."['".implode("']['", array_slice($tmp, 1))."']";
						} else {
							$key = "\$".$key;
						}
					} else {
                        $key = "\$GLOBALS['".str_replace(".", "']['", $key)."']";
                    }
                } else {
					if ($this->safeMode) $this->parseSafeMode($key, "func"); // safeMode : 함수
                }
            }
        } else {
			if ($this->safeMode) $this->parseSafeMode($key, "control"); // safeMode : 제어문
		}
        return $p . $key . $n;
    }

    // 표현식 처리
    function parseExpression($src, $type="EXP")
    {
		if ($type == "DEF") {
            $func = "parseDefineVar";
        } else {
            $func = "parseExpressionVar";
        }

        // javascript 스타일 배열 (중괄호대신 대괄호 사용, 2차원까지만 지원 ^^:)
        if (preg_match_all("/((^|[=\s])(\[[^;]+))(;|$)/", $src, $match)) {
            for ($i=0, $ci=count($match[0]); $i<$ci; $i++) {
                $p = trim($match[3][$i]);
                if (preg_match("/^\[.+\]$/s", $p)) {
                    $e = substr($p, 1, -1);
                    if (preg_match_all("/\[([\s]*)((([0-9]+|['\\\"][^'\\\"]*['\\\"])([\s]*:[\s]*))?([0-9]+|['\\\"][^'\\\"]*['\\\"])([\s]*,[\s]*))*(([0-9]+|['\\\"][^'\\\"]*['\\\"])([\s]*:[\s]*))?([0-9]+|['\\\"][^'\\\"]*['\\\"])([\s]*[,]?[\s]*)\]/", $e, $m)) {
                        for ($j=0, $cj=count($m[0]); $j<$cj; $j++) {
                            $e = str_replace($m[0][$j], "array(".$m[1][$j].$m[4][$j].str_replace(":","=>",$m[5][$j]).$m[6][$j].$m[7][$j].$m[9][$j].str_replace(":","=>",$m[10][$j]).$m[11][$j].$m[12][$j].")", $e);
                        }
                    }
                    $e = preg_replace("/([0-9]+|['\\\"][^'\\\"]*['\\\"])([\s]*:[\s]*)([0-9]+|['\\\"][^'\\\"]*['\\\"])/", "\\1=>\\3", $e);
                    $p = "array(".$e.")";
                }
                $src = str_replace($match[3][$i], $p, $src);
            }
        }

		$tmp = $this->parseQuote($src, false, false, true);
		$count = count($tmp);

        // 따옴표 밖
        for ($i=0; $i<$count; $i+=2) {
            $tmp[$i] = preg_replace("/([^a-zA-Z0-9_\x7f-\xff\.\s]*[\s]*)(([\.]?[a-zA-Z0-9_\x7f-\xff]+(\.[0-9]+)?::|[\.]{1,2})?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\.[a-zA-Z0-9_\x7f-\xff]+)*((->|::)[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\.[a-zA-Z0-9_\x7f-\xff]+)*)?)([\s]*[^a-zA-Z0-9_\x7f-\xff\.\s]*)/e", "\$this->".$func."('\\2', '\\1', '\\9');", $tmp[$i]);
        }
        // 큰따옴표 안 : {변수명} 형식 적용 (작은따옴표는 적용 안됨)
        for ($i=1; $i<$count; $i+=2) {
            $quote = substr($tmp[$i-1], -1);
            if ($quote == "\"") $tmp[$i] = preg_replace("/(\{[\\\\]?)(([a-zA-Z0-9_\x7f-\xff]+(\.[0-9]+)?::|[\.])?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\.[a-zA-Z0-9_\x7f-\xff]+)*((->|::)[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\.[a-zA-Z0-9_\x7f-\xff]+)*)?)(\})/e", "\$this->".$func."('\\2', '\\1', '\\9', '$quote');", $tmp[$i]);
        }
        return implode("", $tmp);
    }

	// safeMode 처리
	function parseSafeMode($src, $type)
	{
		$tmp = preg_split("/([\.:]+|->)/", $src);
		foreach ($tmp as $dummy=>$key) {
			if (!isset($this->safeModeList[$type.'.allow']['ALL']) && !isset($this->safeModeList[$type.'.allow'][$key])) {
				if (isset($this->safeModeList[$type.'.deny']['ALL']) || isset($this->safeModeList[$type.'.deny'][$key])) {
					$this->error("parseSafeMode : Denied Expression [".$type."] : ".$key, true);
					$this->safeModeFlag = false;
				}
			}
		}
	}

    // 따옴표, 주석 처리 (PHP style not template)
    function parseQuote($src, $toString=false, $allowComment=false, $switchQuote=false)
    {
        $arr = array();
        $comment = false;
        $commentType = "";
        $quote = false;
        $quoteType = "";
        $z = 0;

        $tmp = preg_split("/(['\\\"\n]|\/\*|\*\/|\/\/)/", $src, -1, PREG_SPLIT_DELIM_CAPTURE);
        $arr[$z] = $tmp[0];

        for ($i=1, $c=count($tmp); $i<$c; $i++) {
            switch ($token = $tmp[$i]) {
                case "\"" :
                case "'" :
                    if ($comment) break; // comment인 경우 무시

                    $escape = false;
                    if (preg_match("/([\\\\]+)$/", $tmp[$i-1], $match)) {
                        if (strlen($match[1]) % 2) $escape = true;
                    }
                    if ($escape) {
                        $arr[$z] .= $token;
                    } else {
                        if ($quote) {
                            if ($quoteType == $token) {
                                $quote = false;
                                $quoteType = "";
                                if ($switchQuote) {
                                    $arr[++$z] = $token;
                                } else {
                                    $arr[$z++] .= $token;
                                    $arr[$z] = "";
                                }
                            } else {
                                $arr[$z] .= $token;
                            }
                        } else {
                            $quote = true;
                            $quoteType = $token;
                            if ($switchQuote) {
                                $arr[$z++] .= $token;
                                $arr[$z] = "";
                            } else {
                                $arr[++$z] = $token;
                            }
                        }
                    }
                    break;
                case "/*" :
                    if ($quote) {
                        $arr[$z] .= $token;
                    } else {
                        if (!$comment) {
                            $comment = true;
                            $commentType = "MULTI";
                        }
                        if ($allowComment) $arr[$z] .= $token;
                    }
                    break;
                case "*/" :
                    if ($comment) {
                        if ($commentType == "MULTI") {
                            $comment = false;
                            $commentType = "";
                        }
                        if ($allowComment) $arr[$z] .= $token;
                    } else {
                        $arr[$z] .= $token;
                    }
                    break;
                case "//" :
                    if ($quote) {
                        $arr[$z] .= $token;
                    } else {
                        if (!$comment) {
                            $comment = true;
                            $commentType = "SINGLE";
                        }
                        if ($allowComment) $arr[$z] .= $token;
                    }
                    break;
                case "\n" :
                    if ($comment) {
                        if ($commentType == "SINGLE") {
                            $comment = false;
                            $commentType = "";
                            $arr[$z] .= $token; // single 일때는 남김
                        }
                    } else {
                        $arr[$z] .= $token;
                    }
                    break;
                default :
                    if ($quote || !$comment || $allowComment) {
                        $arr[$z] .= $token;
                    }
                    break;
            }
        }

        if ($toString) {
            return implode("", $arr);
        } else {
            return $arr;
        }
    }

    function save($var, $timeStamp=0, $type="compile")
    {
        if ($type == "compile") {
            $str = &$this->Code[$var];
            $path = $this->tplRoot."/".$this->Target[$var];
        } else if ($type == "rewrite") {
            $str = &$this->Src[$var];
            if ($this->tplRewrite === "overwrite" && !preg_match("/^http:\/\//", $this->File[$var])) {
                $path = $this->File[$var];
            } else {
                $path = (preg_match("/^http:%%/", $this->Target[$var])) ? $this->Target[$var] : str_replace("%", "/", $this->Target[$var]);
                $path = $this->rewriteRoot."/".preg_replace("/\.php$/", "", $path);
            }
        } else {
            return $this->error("save : Invalid Save Type! (".$type.")");
        }
        $dir = dirname($path);
        if (!is_dir($dir)) {
            if (!$this->makeDir($dir)) {
                $this->error("save : Directory Create Error! (".$dir.")", true);
                exit;
            }
        }
        // 파일 쓰기
        if ($fp = @fopen($path, "wb")) {
            @fwrite($fp, $str);
            fclose($fp);
            @chmod($path, 0606);
            if ($timeStamp) @touch($path, $timeStamp);
        } else {
            return $this->error("save : File Write Error! (".$path.")", true);
        }
        if ($type == "compile") {
            $this->Code[$var] = true; // 흔적만 남김
            if ($this->divReal && is_array($this->Divide[$var])) $this->saveDivide($var, $timeStamp);
        }
        return $var;
    }

    function saveDivide($var, $timeStamp=0)
    {
        $target = $this->tplRoot."/".substr($this->Target[$var], 0, -4);
        foreach ($this->Divide[$var] as $key=>$str) {
            $path = $target."#".$key.".php";
            if ($fp = @fopen($path, "wb")) {
                @fwrite($fp, $str);
                fclose($fp);
                @chmod($path, 0606);
                if ($timeStamp) @touch($path, $timeStamp);
            } else {
                return $this->error("saveDivide : File Write Error! (".$path.")", true);
            }
        }
        $this->Divide[$var] = true; // 흔적만 남김
    }

    function makeDir($path, $mod=0777)
    {
        $token = explode("/", $path);
        $dir = array_shift($token)."/";
        if (is_array($token)) {
            foreach ($token as $val) {
                if (!$val) continue;
                $dir .= $val."/";
                if (is_dir($dir)) continue;
                @mkdir($dir, $mod);
                @chmod($dir, $mod);
                if (!is_dir($dir)) return false;
            }
        }
        return true;
    }

    // 에러
    function error($msg, $debug=false)
    {
        if ($debug || $this->debug) {
			echo "\n<font color='green'><b>Compiler</b> :: ".$msg."</font><br>\n";
		}
        return false;
    }
}

?>