namespace Ice\Mvc\View\Engine\Sleet;
use Ice\Exception;
/**
* Sleet file parser.
*
* @package Ice/View
* @category Component
* @author Ice Team
* @copyright (c) 2014-2025 Ice Team
* @license http://iceframework.org/license
*/
class Parser
{
protected functions = [
"content": "$this->getContent",
"partial": "$this->partial",
"load": "$this->load",
"dump": "$this->dump->vars",
"version": "Ice\\Version::get"
];
protected filters = [
"capitalize": "ucfirst"
];
protected env = [];
const NORMAL = 0;
const SHORTIF = 1;
const INARRAY = 2;
/**
* Sleet parser constructor. Fetch Ice\Tag methods.
*/
public function __construct()
{
var tag, methods, functions, method;
let tag = new \ReflectionClass("Ice\\Tag"),
methods = tag->getMethods(\ReflectionMethod::IS_PUBLIC),
functions = [];
for method in methods {
switch method->name {
case "__construct":
continue;
default:
let functions[method->name] = "$this->tag->" . method->name,
functions[uncamelize(method->name)] = "$this->tag->" . method->name;
}
}
let this->functions = array_merge(this->functions, functions),
this->env[] = Parser::NORMAL;
}
/**
* Parse text.
*
* @param string text
* @return string Parsed text
*/
public function text(string text) -> string
{
var pos, start, parsedText, end, ch;
int i;
let pos = 0,
start = strpos(text, "{"),
parsedText = "";
while start !== false {
let i = start + 1,
ch = text[i];
switch ch {
case '{':
// append string before tokens, search close-symbol of the tag
let parsedText .= substr(text, pos, (int) (start - pos)),
end = strpos(text, "}}", start + 2);
if end === false {
// If unexpected end of template
throw new Exception(sprintf("Unclosed echo on the line %d", substr_count(substr(text, 0, start), PHP_EOL) + 1));
}
let end = end + 2,
parsedText .= this->parse(substr(text, start, (int) (end - start)));
break;
case '%':
// append string before tokens, search close-symbol of the tag
let parsedText .= substr(text, pos, (int) (start - pos)),
end = strpos(text, "%}", start + 2);
if end === false {
// If unexpected end of template
throw new Exception(sprintf("Unclosed tag on the line %d", substr_count(substr(text, 0, start), PHP_EOL) + 1));
}
let end = end + 2,
parsedText .= this->parse(substr(text, start, (int) (end - start)));
break;
case '#':
// append string before comment, search close-symbol of the comment
let parsedText .= substr(text, pos, (int) (start - pos)),
end = strpos(text, "#}", start + 2);
if end === false {
// If unexpected end of template
throw new Exception(sprintf("Unclosed comment block on the line %d", substr_count(substr(text, 0, start), PHP_EOL) + 1));
}
let end = end + 2;
break;
default:
// Ignore the tag
let parsedText .= substr(text, pos, (int) (start - pos + 1)),
end = start + 1;
break;
}
// next tokens
let pos = end,
start = strpos(text, "{", pos);
}
// append string after tokens
let parsedText .= substr(text, pos);
return parsedText;
}
/**
* Parse one sleet expression.
*
* @param string expression
* @return string
*/
public function parse(string expression) -> string
{
var php, tokenized, tokens, token, first;
if starts_with(expression, "{{") {
let php = "";
case T_BREAK:
return "";
case T_ELSE:
case T_DEFAULT:
return "";
case T_SWITCH:
case T_CASE:
case T_IF:
case T_ELSEIF:
case T_DO:
case T_FOR:
case T_WHILE:
case T_FOREACH:
return this->parseControl(first[1], tokens);
case T_ECHO:
return this->parseEcho(tokens);
case T_VAR:
return this->parseSet(tokens);
case T_STRING:
if first[1] == "set" {
return this->parseSet(tokens);
}
break;
case T_USE:
return this->parseUse(tokens);
}
return "";
}
/**
* Parse control expression.
*
* @param string control Control structure
* @param array expression Tokens
* @return string
*/
private function parseControl(control, expression) -> string
{
return "doParse(expression) . "): ?>";
}
/**
* Parse echo expression.
*
* @param array expression Tokens
* @return string
*/
private function parseEcho($expression) -> string
{
return "doParse(expression) . " ?>";
}
/**
* Parse set expression.
*
* @param array expression Tokens
* @return string
*/
private function parseSet($expression) -> string
{
return "doParse(expression) . "; ?>";
}
/**
* Parse use expression.
*
* @param array expression Tokens
* @return string
*/
private function parseUse($expression) -> string
{
return "doParse(expression) . "; ?>";
}
/**
* Internal tokens parse.
*
* @param array tokens
* @return string
*/
private function doParse(tokens) -> string
{
var i, parsed, prev, next, token, filter, seek, filters;
let i = new \ArrayIterator(tokens),
parsed = "",
prev = "";
while i->valid() {
let token = i->current(),
next = i->offsetExists(i->key() + 1) ? i->offsetGet(i->key() + 1) : null;
if next == "|" {
let seek = i->key() + 2,
filter = i->offsetGet(seek),
filter = isset this->filters[filter[1]] ? this->filters[filter[1]] : filter[1],
filters = ["camelize", "uncamelize", "human", "lower", "upper", "alnum", "alpha", "email", "float", "int", "string", "strip_repeats", "e", "escape", "strip_special", "unescape", "unstrip_special"];
if in_array(filter, filters) {
let parsed .= "$this->filter->sanitize(" . this->token(token, prev, next) . ", '" . filter . "'";
} else {
let parsed .= filter . "(" . this->token(token, prev, next);
}
let next = i->offsetExists(seek + 1) ? i->offsetGet(seek + 1) : null;
if next == "(" {
let parsed .= ", ",
seek++;
} else {
let parsed .= ")";
}
i->seek(seek);
i->next();
continue;
}
let parsed .= this->token(token, prev, next),
prev = token;
i->next();
}
return parsed;
}
/**
* Internal token parse.
*
* @param mixed token
* @param mixed prev
* @param mixed next
* @return mixed
*/
private function token(token, prev = null, next = null)
{
string str;
if typeof token == "array" {
switch token[0] {
case T_AS:
case T_NEW:
case T_INSTANCEOF:
case T_IS_EQUAL:
case T_IS_NOT_EQUAL:
case T_IS_IDENTICAL:
case T_IS_NOT_IDENTICAL:
case T_IS_SMALLER_OR_EQUAL:
case T_IS_GREATER_OR_EQUAL:
return " " . token[1] . " ";
case T_LOGICAL_OR:
return " || ";
case T_LOGICAL_AND:
return " && ";
case T_STRING:
let str = (string) token[1];
if next == "(" && (prev != "." || typeof prev == "array" && prev[0] != T_DOUBLE_COLON) {
return isset this->functions[str] ? this->functions[str] : str;
}
switch str {
case "in":
return " as ";
case "is":
return next == "!" ? " != " : " == ";
case "and":
return " && ";
case "or":
return " || ";
case "not":
return "!";
case "false":
case "true":
case "null":
return str;
default:
if prev == "." || next == "(" || ctype_upper(str[0]) && next != "|" {
return str;
}
return "$" . str;
}
default:
return token[1];
}
} else {
switch token {
case "-":
case "+":
case "*":
case "/":
case "%":
case "=":
case ">":
case "<":
return " " . token . " ";
case "~":
return " . ";
case ",":
return ", ";
case ".":
return "->";
case ":":
switch end(this->env) {
case Parser::SHORTIF:
array_pop(this->env);
return " : ";
default:
return " => ";
}
case "?":
let this->env[] = Parser::SHORTIF;
return " ? ";
case "[":
let this->env[] = Parser::INARRAY;
return token;
case "]":
if end(this->env) == Parser::INARRAY {
array_pop(this->env);
}
return token;
default:
return token;
}
}
}
}