Skip to content

Instantly share code, notes, and snippets.

@LionsAd
Last active November 25, 2020 00:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LionsAd/77b3bf4e606b37e1bfab492efa288e64 to your computer and use it in GitHub Desktop.
Save LionsAd/77b3bf4e606b37e1bfab492efa288e64 to your computer and use it in GitHub Desktop.
Preact and template literals in PHP
<?php
require_once "tl.php";
function html($string) {
return tl('html', $string);
}
$tabs = [
(object) ['tray' => TRUE, 'url' => '/', 'title' => 'A tray item'],
(object) ['tray' => NULL, 'url' => '/admin', 'title' => 'Admin'],
];
$Tab = function($props) {
$href = $props->href;
return htm(eval(html('<div><a href="${$href}">Some Text</a></div>')));
};
$tabItems = array_map(function($tab, $i) use ($Tab) {
$onMouseDown = '';
$isActive = 0;
$href = $tab->tray == null ? $tab->url : null;
return htm(eval(html('
<${$Tab} index=${$i} key=${$tab->url} href=${$href} onMouseDown=${$onMouseDown} isActive=${$isActive}>
${$tab->title}
<//>
')));
}, $tabs, array_keys($tabs));
$items = [];
$items[] = htm(eval(html('<span class="item">An item</span>')));
$SubNav = 'x-subnav';
$PositionToggle = 'x-toggle';
$orientation = 'left';
function cn($x) {
return $x;
}
$navBar = htm(eval(html('<${$SubNav} orientation=${$orientation}>
${$items}
<div className=${cn(
($orientation === "left" ? "left" : "top") .
" hidden lg:flex"
)}>
<${$PositionToggle}
orientation=${$orientation}
/>
</div>
<//>')));
$x = htm(eval(html(' <div>
<div
className=${cn(
"overflow-x-auto scrolling-touch relative"
)}
>
${$tabItems}
</div>
<div>
${$navBar}
</div>
</div>')));
print_r($x);
return $x;
<?php
class Component {
protected $name = null;
protected $callback = null;
function __construct($name, $callback) {
$this->name = $name;
$this->callback = $callback;
}
function getName() {
return $this->name;
}
function getCallback() {
return $this->callback;
}
};
define('MODE_SLASH', 0);
define('MODE_TEXT', 1);
define('MODE_WHITESPACE', 2);
define('MODE_TAGNAME', 3);
define('MODE_COMMENT', 4);
define('MODE_PROP_SET', 5);
define('MODE_PROP_APPEND', 6);
define('CHILD_APPEND', 0);
define('CHILD_RECURSE', 2);
define('TAG_SET', 3);
define('PROPS_ASSIGN', 4);
define('PROP_SET', MODE_PROP_SET);
define('PROP_APPEND', MODE_PROP_APPEND);
/*
define('CHILD_APPEND', 'CHILD_APPEND');
define('CHILD_RECURSE', 'CHILD_RECURSE');
define('TAG_SET', 'TAG_SET');
define('PROPS_ASSIGN', 'PROPS_ASSIGN');
define('PROP_SET', 'PROP_SET');
define('PROP_APPEND', 'PROP_APPEND');
*/
define('MINI', FALSE);
function htm($args) {
$statics = $args[1];
$fields = $args[2];
array_unshift($fields, NULL);
if (MINI) {
return htm_html($statics, $fields);
}
$built = htm_html($statics, $fields);
//print_r([$built, $fields]);
//$tree = htm_treeify($built, $fields);
//print_r([$tree]);
$vdom = htm_evaluate($built, $fields);
//print_r([$vdom]);
return $vdom;
}
function component($name, $type, $args = [], $children = '') {
/* if (is_callable($type)) {
return <<<EOF
<-- <$name> -->
{$type((object)$args[0])};
<!-- </$name> -->
EOF;
}*/
$props_string = '';
foreach ($args as $key => $value) {
$props_string .= " $key=\"$value\"";
}
return "<{$name}{$props_string}>" . $children . "</$name>";
}
function preact_render_children($children) {
$child_string = '';
foreach ($children as $child) {
if (is_array($child)) {
$child_string .= preact_render_children($child);
}
else {
$child_string .= $child;
}
$child_string .= PHP_EOL;
}
return $child_string;
}
function preact_h($type, $props, ...$children) {
if (is_array($type) && is_callable($type[1])) {
$cb = $type[1];
return call_user_func($cb, (object) $props);
}
if (is_array($type) && $type[1] instanceof Component) {
$cb = $type[1];
$name = $cb->getName();
$cb = $cb->getCallback();
$args = [];
$props_string = '';
foreach ($props as $key => $value) {
$value = $value[1];
$args[$key] = $value;
if (!is_string($value)) {
$value = gettype($value);
}
$props_string .= " $key=\"$value\"";
}
$args['children'] = $children;
$result = call_user_func($cb, (object) $args);
$result = ["<!-- <{$name}{$props_string}> -->", $result, "<!-- </$name> -->"];
return $result;
}
return (object) [
'type' => $type,
'props' => $props,
'children' => $children
];
if (!is_array($props)) {
$props = [];
}
if (is_callable($type)) {
$x = '<!-- <Foo> -->';
$props['children'] = $children;
$x .= $type((object) $props);
$x .= '<!-- </Foo> -->';
return $x;
}
//$child_string = count($children) == 1 ? $children[0] : var_export($children, TRUE);
$child_string = preact_render_children($children);
return component($type, NULL, $props, $child_string);
return (object) [
'tag' => $type,
'props' => $props,
'children' => $children
];
}
function htm_evaluate($built, $fields, $args = []) {
$built[0] = 0;
for ($i = 1; $i < count($built); $i++) {
$type = $built[$i++];
// $value = $built[$i] ? [(($built[0] |= $type) ? 1 : 2), $fields[$built[$i++]]] : $built[++$i];
if ($built[$i]) {
$value = [(($built[0] |= $type) ? 1 : 2), $fields[$built[$i++]]];
}
else {
$i++;
$value = $built[$i];
}
if ($type === TAG_SET) {
$args[0] = $value;
}
else if ($type === PROPS_ASSIGN) {
if (empty($args[1])) {
$args[1] = [];
}
$args[1] = $args[1] + $value;
}
else if ($type === PROP_SET) {
$i++;
$args[1][$built[$i]] = $value;
}
else if ($type === PROP_APPEND) {
$i++;
$args[1][$built[$i]] .= ($value . '');
}
else if ($type) { // type === CHILD_RECURSE
$tmp = call_user_func_array('preact_h', htm_evaluate($value, $fields, ['', null]));
array_push($args, $tmp);
if ($value[0]) {
// Set the 2nd lowest bit it the child element is dynamic.
$built[0] |= 2;
} else {
// Rewrite the operation list in-place if the child element is static.
// The currently evaluated piece `CHILD_RECURSE, 0, [...]` becomes
// `CHILD_APPEND, 0, tmp`.
// Essentially the operation list gets optimized for potential future
// re-evaluations.
$built[$i-2] = CHILD_APPEND;
$built[$i] = $tmp;
}
}
else { // CHILD_APPEND
array_push($args, $value);
}
}
return $args;
}
function htm_treeify($built, $fields) {
$_treeify = function($built) use (&$fields, &$_treeify) {
$tag = "";
$currentProps = [];
$props = [];
$children = [];
for ($i = 1; $i < count($built); $i++) {
$type = $built[$i++];
$value = $built[$i] ? $fields[$built[$i++] - 1] : $built[++$i];
if ($type === TAG_SET) {
$tag = $value;
} else if ($type === PROPS_ASSIGN) {
array_push($props, $value);
$currentProps = FALSE;
} else if ($type === PROP_SET) {
if (!$currentProps) {
$currentProps = [];
array_push($props, $currentProps);
}
$currentProps[$built[++$i]] = [$value];
} else if ($type === PROP_APPEND) {
array_push($currentProps[$built[++$i]], $value);
} else if ($type === CHILD_RECURSE) {
array_push($children, $_treeify($value));
} else if ($type === CHILD_APPEND) {
array_push($children, $value);
}
}
return [$tag, $props, $children];
};
$children = $_treeify($built)[2];
return count($children) > 1 ? $children : $children[0];
}
function htm_html($statics, $fields) {
$mode = MODE_TEXT;
$buffer = "";
$quote = "";
$current = [0];
$char = NULL;
$propName = NULL;
$commit = function($field = 0) use (&$mode, &$buffer, &$current, &$fields, &$propName) {
if ($mode === MODE_TEXT && ($field || ($buffer = trim(preg_replace("/^\\s*\\n\\s*|\\s*\\n\\s*\$/", '', $buffer))))) {
if (MINI) {
array_push($current, $field ? $fields[$field] : $buffer);
} else {
array_push($current, CHILD_APPEND, $field, $buffer);
}
} else if ($mode === MODE_TAGNAME && ($field || $buffer)) {
if (MINI) {
$current[1] = $field ? $fields[$field] : $buffer;
} else {
array_push($current, TAG_SET, $field, $buffer);
}
$mode = MODE_WHITESPACE;
} else if ($mode === MODE_WHITESPACE && $buffer === "..." && $field) {
if (MINI) {
$tmp_array = [];
if (!empty($current[2])) {
$tmp_array = $current[2];
}
$current[2] = $fields[$field] + $tmp_array;
} else {
array_push($current, PROPS_ASSIGN, $field, 0);
}
} else if ($mode === MODE_WHITESPACE && $buffer && !$field) {
if (MINI) {
$current[2][$buffer] = TRUE;
} else {
array_push($current, PROP_SET, 0, TRUE, $buffer);
}
} else if ($mode >= MODE_PROP_SET) {
if (MINI) {
if ($mode === MODE_PROP_SET) {
$current[2][$propName] = $field ? ($buffer ? ($buffer . $fields[$field]) : $fields[$field]) : $buffer;
$mode = MODE_PROP_APPEND;
} else if ($field || $buffer) {
$current[2][$propName] .= $field ? ($buffer . $fields[$field]) : $buffer;
}
} else {
if ($buffer || (!isset($field) && $mode === MODE_PROP_SET)) {
array_push($current, $mode, 0, $buffer, $propName);
$mode = MODE_PROP_APPEND;
}
if ($field) {
array_push($current, $mode, $field, 0, $propName);
$mode = MODE_PROP_APPEND;
}
}
}
$buffer = "";
};
for ($i = 0; $i < count($statics); $i++) {
if ($i) {
if ($mode === MODE_TEXT) {
$commit();
}
$commit($i);
}
for ($j = 0; $j < strlen($statics[$i]); $j++) {
$char = $statics[$i][$j];
if ($mode === MODE_TEXT) {
if ($char === "<") {
$commit();
if (MINI) {
$current = [$current, '', NULL];
} else {
$current = [$current];
}
$mode = MODE_TAGNAME;
} else {
$buffer .= $char;
}
} else if ($mode === MODE_COMMENT) {
if ($buffer === "--" && $char === ">") {
$mode = MODE_TEXT;
$buffer = '';
} else {
$buffer = $char . $buffer[0];
}
} else if ($quote) {
if ($char === $quote) {
$quote = '';
} else {
$buffer .= $char;
}
} else if ($char === '"' || $char === "'") {
$quote = $char;
} else if ($char === ">") {
$commit();
$mode = MODE_TEXT;
} else if ($mode == 0) {
// Ignore everything until the tag ends
} else if ($char === "=") {
$mode = MODE_PROP_SET;
$propName = $buffer;
$buffer = "";
} else if ($char === "/" && ($mode < MODE_PROP_SET || $statics[$i][$j+1] === ">")) {
$commit();
if ($mode === MODE_TAGNAME) {
$current = $current[0];
}
// Mode scope is overwritten here.
$_mode = $current;
if (MINI) {
$current = $current[0];
$vdom_node = call_user_func_array('preact_h', array_slice($_mode, 1));
array_push($current, $vdom_node);
} else {
$current = $current[0];
$_mode[0] = [];
array_push($current, CHILD_RECURSE, 0, $_mode);
}
$mode = MODE_SLASH;
} else if ($char === ' ' || $char === "\t" || $char === "\n" || $char === "\r") {
// <a disabled>
$commit();
$mode = MODE_WHITESPACE;
} else {
$buffer .= $char;
}
if ($mode === MODE_TAGNAME && $buffer === "!--") {
$mode = MODE_COMMENT;
$current = $current[0];
}
}
}
$commit();
if (MINI) {
return count($current) > 2 ? array_slice($current, 1) : $current[1];
}
return $current;
}
function process_tokens(&$tokens) {
$strings = [];
$placeholders = [];
$buffer = '';
$in_tag = FALSE;
while (!empty($tokens)) {
$token_var = array_shift($tokens);
$token = T_INLINE_HTML;
$token_string = $token_var;
if (is_array($token_var)) {
$token = $token_var[0];
$token_string = $token_var[1];
}
if ($token == T_OPEN_TAG_WITH_ECHO) {
$in_tag = TRUE;
$strings[] = $buffer;
$buffer = '';
continue;
}
elseif ($token == T_CLOSE_TAG) {
$in_tag = FALSE;
$placeholders[] = "$buffer";
$buffer = '';
continue;
}
$buffer .= $token_string;
}
if (!empty($buffer)) {
$strings[] = $buffer;
}
return [$strings, $placeholders];
}
function tl($func, $str) {
$str = str_replace('${', '<?=', $str);
$str = str_replace('}', '?>', $str);
$tokens = token_get_all($str);
$processed = process_tokens($tokens);
$statics = var_export($processed[0], TRUE);
$fields = [];
foreach ($processed[1] as $field) {
$fields[] = $field;
}
$fields_string = '[' . implode(',' . PHP_EOL, $fields) . ']';
$code =<<<EOF
return [
'$func',
$statics,
$fields_string,
];
EOF;
return $code;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment