/**
* ConfigfileParser
*
* This is the config file parser
* 
* 
* @package Smarty
* @subpackage Config
* @author Uwe Tews
*/
%name TPC_
%declare_class {

namespace Smarty\Parser;

use \Smarty\Lexer\ConfigfileLexer as Lexer;
use \Smarty\Compiler\Configfile as Configfile;

/**
* Smarty Internal Plugin Configfileparse
*
* This is the config file parser.
* It is generated from the ConfigfileParser.y file
* @package Smarty
* @subpackage Compiler
* @author Uwe Tews
*/
class ConfigfileParser
}
%include_class
{
    /**
     * result status
     *
     * @var bool
     */
    public $successful = true;
    /**
     * return value
     *
     * @var mixed
     */
    public $retvalue = 0;
    /**
     * @var
     */
    public $yymajor;
    /**
     * lexer object
     *
     * @var Lexer
     */
    private $lex;
    /**
     * internal error flag
     *
     * @var bool
     */
    private $internalError = false;
    /**
     * compiler object
     *
     * @var Configfile
     */
    public $compiler = null;
    /**
     * smarty object
     *
     * @var Smarty
     */
    public $smarty = null;
    /**
     * copy of config_overwrite property
     *
     * @var bool
     */
    private $configOverwrite = false;
    /**
     * copy of config_read_hidden property
     *
     * @var bool
     */
    private $configReadHidden = false;
    /**
     * helper map
     *
     * @var array
     */
    private static $escapes_single = array('\\' => '\\',
                                           '\'' => '\'');

    /**
     * constructor
     *
     * @param Lexer      $lex
     * @param Configfile $compiler
     */
    public function __construct(Lexer $lex, Configfile $compiler)
    {
        $this->lex = $lex;
        $this->smarty = $compiler->getSmarty();
        $this->compiler = $compiler;
        $this->configOverwrite = $this->smarty->config_overwrite;
        $this->configReadHidden = $this->smarty->config_read_hidden;
    }

    /**
     * parse optional boolean keywords
     *
     * @param string $str
     *
     * @return bool
     */
    private function parse_bool($str)
    {
        $str = strtolower($str);
        if (in_array($str, array('on', 'yes', 'true'))) {
            $res = true;
        } else {
            $res = false;
        }
        return $res;
    }

    /**
     * parse single quoted string
     *  remove outer quotes
     *  unescape inner quotes
     *
     * @param string $qstr
     *
     * @return string
     */
    private static function parse_single_quoted_string($qstr)
    {
        $escaped_string = substr($qstr, 1, strlen($qstr) - 2); //remove outer quotes

        $ss = preg_split('/(\\\\.)/', $escaped_string, - 1, PREG_SPLIT_DELIM_CAPTURE);

        $str = '';
        foreach ($ss as $s) {
            if (strlen($s) === 2 && $s[0] === '\\') {
                if (isset(self::$escapes_single[$s[1]])) {
                    $s = self::$escapes_single[$s[1]];
                }
            }
            $str .= $s;
        }
        return $str;
    }

    /**
     * parse double quoted string
     *
     * @param string $qstr
     *
     * @return string
     */
    private static function parse_double_quoted_string($qstr)
    {
        $inner_str = substr($qstr, 1, strlen($qstr) - 2);
        return stripcslashes($inner_str);
    }

    /**
     * parse triple quoted string
     *
     * @param string $qstr
     *
     * @return string
     */
    private static function parse_tripple_double_quoted_string($qstr)
    {
        return stripcslashes($qstr);
    }

    /**
     * set a config variable in target array
     *
     * @param array $var
     * @param array $target_array
     */
    private function set_var(array $var, array &$target_array)
    {
        $key = $var['key'];
        $value = $var['value'];

        if ($this->configOverwrite || !isset($target_array['vars'][$key])) {
            $target_array['vars'][$key] = $value;
        } else {
            settype($target_array['vars'][$key], 'array');
            $target_array['vars'][$key][] = $value;
        }
    }

    /**
     * add config variable to global vars
     *
     * @param array $vars
     */
    private function add_global_vars(array $vars)
    {
        if (!isset($this->compiler->config_data['vars'])) {
            $this->compiler->config_data['vars'] = array();
        }
        foreach ($vars as $var) {
            $this->set_var($var, $this->compiler->config_data);
        }
    }

    /**
     * add config variable to section
     *
     * @param string $section_name
     * @param array  $vars
     */
    private function add_section_vars($section_name, array $vars)
    {
        if (!isset($this->compiler->config_data['sections'][$section_name]['vars'])) {
            $this->compiler->config_data['sections'][$section_name]['vars'] = array();
        }
        foreach ($vars as $var) {
            $this->set_var($var, $this->compiler->config_data['sections'][$section_name]);
        }
    }
}

%token_prefix TPC_

%parse_accept
{
    $this->successful = !$this->internalError;
    $this->internalError = false;
    $this->retvalue = $this->_retvalue;
}

%syntax_error
{
    $this->internalError = true;
    $this->yymajor = $yymajor;
    $this->compiler->trigger_config_file_error();
}

%stack_overflow
{
            $this->internalError = true;
            $this->compiler->trigger_config_file_error('Stack overflow in configfile parser');
}

// Complete config file
start(res) ::= global_vars sections. {
    res = null;
}

// Global vars
global_vars(res) ::= var_list(vl). {
    $this->add_global_vars(vl);
    res = null;
}

// Sections
sections(res) ::= sections section. {
    res = null;
}

sections(res) ::= . {
    res = null;
}

section(res) ::= OPENB SECTION(i) CLOSEB newline var_list(vars). {
    $this->add_section_vars(i, vars);
    res = null;
}

section(res) ::= OPENB DOT SECTION(i) CLOSEB newline var_list(vars). {
    if ($this->configReadHidden) {
        $this->add_section_vars(i, vars);
    }
    res = null;
}

// Var list
var_list(res) ::= var_list(vl) newline. {
    res = vl;
}

var_list(res) ::= var_list(vl) var(v). {
    res = array_merge(vl, array(v));
}

var_list(res) ::= . {
    res = array();
}


// Var
var(res) ::= ID(id) EQUAL value(v). {
    res = array('key' => id, 'value' => v);
}


value(res) ::= FLOAT(i). {
    res = (float) i;
}

value(res) ::= INT(i). {
    res = (int) i;
}

value(res) ::= BOOL(i). {
    res = $this->parse_bool(i);
}

value(res) ::= SINGLE_QUOTED_STRING(i). {
    res = self::parse_single_quoted_string(i);
}

value(res) ::= DOUBLE_QUOTED_STRING(i). {
    res = self::parse_double_quoted_string(i);
}

value(res) ::= TRIPPLE_QUOTES(i) TRIPPLE_TEXT(c) TRIPPLE_QUOTES_END(ii). {
    res = self::parse_tripple_double_quoted_string(c);
}

value(res) ::= TRIPPLE_QUOTES(i) TRIPPLE_QUOTES_END(ii). {
    res = '';
}

value(res) ::= NAKED_STRING(i). {
    res = i;
}

// NOTE: this is not a valid rule
// It is added hier to produce a usefull error message on a missing '=';
value(res) ::= OTHER(i). {
    res = i;
}


// Newline and comments
newline(res) ::= NEWLINE. {
    res = null;
}

newline(res) ::= COMMENTSTART NEWLINE. {
    res = null;
}

newline(res) ::= COMMENTSTART NAKED_STRING NEWLINE. {
    res = null;
}
