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; } } } }