CompilerOld.php
<?php
namespace Phad;
/**
* This thing is ... kinda trash ... but i just wanna keep it around for historical purposes ... i don't think it will ever be useful again ... but it will be cool to look at a few years from now & see how bad my code was lol
* - Reed (feb 4, 2022)
*
*/
class Compiler {
protected string $source;
protected array $output = [
'view'=>null,
'routes'=>null,
// 'idk what else'=>null,
];
protected array $routes = [
];
public function __construct(string $source){
$this->source = $source;
$this->compile();
}
protected function getPropNodes($item){
if ($item->tagName=='form')return $item->ownerDocument->xpath('descendant::*[@name]',$item);
else return $item->ownerDocument->xpath('descendant::*[@prop]', $item);
}
protected function getAccessNodes($item){
return $item->ownerDocument->xpath('descendant::access',$item);
}
protected function getStatusNodes($item){
return $item->ownerDocument->xpath('descendant::on', $item);
}
protected function getErrorNodes($item){
return $item->ownerDocument->xpath('descendant::error', $item);
}
protected function indent(string $str, int $numSpaces, $padFirstLine = true){
$str = explode("\n", $str);
$pad = str_pad('',$numSpaces);
$str = implode("\n$pad",$str);
if ($padFirstLine)$str = $pad.$str;
return $str;
}
public function routes(){
return $this->routes;
}
public function compile(){
$source = $this->source;
$phtml = new \Taeluf\PHTML($source);
//build sitemap array from sitemap nodes
$sitemapNodeList = $phtml->xpath('//sitemap');
$sitemaps = [];
foreach ($sitemapNodeList as $sn){
$pattern = $sn->parentNode->pattern;
// $sitemap = ['pattern'=>$pattern];
$sitemal = [];
foreach ($sn->attributes() as $attr){
$sitemap[$attr->name] = $attr->value;
}
$sitemap['pattern']=$pattern;
$sitemaps[$pattern] = $sitemap;
$sn->parentNode->removeChild($sn);
}
// var_export sitemaps array into the compiled output & conditionally return the sitemap data
$code = "<?php if ((\$phad_mode??null)==='get_sitemap_data'):";
$code .= "\n return ".var_export($sitemaps,true).';';
$code .= "\nendif; // close get_sitemap_data section ?>";
// $code = '<?php $this->sitemap = '.var_export($sitemaps,true).'; ';
// $code .= "\nif ((\$phad_mode??null)==='get_sitemap_data')return \$this->routes;";
$phtml->insertCodeBefore($phtml->childNodes[0]->childNodes[0]??null, $code);
// build routes array from route nodes
$routeNodeList = $phtml->xpath('//route');
$routes = [];
foreach ($routeNodeList as $rn){
$route = [];
foreach ($rn->attributes() as $attr){
$route[$attr->name] = $attr->value;
}
$routes[] = $route;
$rn->parentNode->removeChild($rn);
}
$this->routes = $routes;
// var_export routes array into the compiled output & conditionally return the routes
$code = '<?php $this->routes = '.var_export($routes,true).'; ';
$code .= "\nif ((\$phad_mode??null)==='get_routes')return \$this->routes;";
$code .= "\n?>";
$phtml->insertCodeBefore($phtml->childNodes[0]->childNodes[0]??null, $code);
//compile item nodes
$itemNodeList = $phtml->xpath('//*[@item]');
$limit = 50;
$iter = 0;
while ($iter++<$limit&&count($itemNodeList)>0){
foreach ($itemNodeList as $index=>$itemNode){
$children = $phtml->xpath('descendant::*[@item]', $itemNode);
//@bugfix(jan 18, 2022) form's with nested items were NOT getting their <onsubmit> included when using this strategy for handling child items, so I added the ->tagName!=form check. This means forms' nested items are handled differently & this MAY be problematic.
if (count($children) > 0
&& $itemNode->tagName!='form'
)continue;
unset($itemNodeList[$index]);
$this->compileItemNode($itemNode);
}
}
// compile individual nodes with access like `<a href="/whatever/" access="role:admin">...</a>`
$accessNodeList = $phtml->xpath('//*[@access]');
foreach ($accessNodeList as $accessNode){
$this->compileNodeWithAccess($accessNode);
}
$this->output['view'] = $phtml.'';
}
public function output(){
return $this->output;
}
protected function compileNodeWithAccess($node){
$node_info = $node->attributesAsArray();
$node_info['tagName'] = $node->tagName;
$node_info = var_export($node_info,true);
$node->doc->insertCodeBefore($node, "<?php if (\$phad->can_read_node($node_info)): ?>\n");
$node->doc->insertCodeAfter($node, "\n<?php endif; ?>");
unset($node->access);
}
protected function compileItemNode($itemNode){
// convenience vars
$phtml = $itemNode->ownerDocument;
$phpClose = '?>'; // stops my editor from being upset
$phpOpen = '<?php'; //just mildly convenient
$itemName = $itemNode->item;
$itemListVar = "\$${itemName}List";
$itemVar = "\$${itemName}";
$itemDataVar = "\$${itemName}Item";
$accessVar = "\$${itemName}Access";
$accessListVar = "\$${itemName}AccessList";
$code = [];
// special nodes
$propNodes = $this->getPropNodes($itemNode);
$accessNodes = $this->getAccessNodes($itemNode);
$statusNodes = $this->getStatusNodes($itemNode);
$errorNodes = $this->getErrorNodes($itemNode);
foreach ($errorNodes as $en){
$errorNodeCode = "<?=is_array(\$phad->failed_submit_columns) ? \n"
." \$phad->validationErrorMessage(\$phad->failed_submit_columns) : ''?>";
$en->doc->insertCodeBefore($en, $errorNodeCode);
}
// itemdata code
$code[] = $this->getItemDataCode($itemDataVar, $itemName, $itemNode, $accessNodes);
$isForm = $itemNode->is('form');
// if ($isForm){
// }
// props code
$itemDataProperties = [];
$form_has_file_input = false;
foreach ($propNodes as $pn){
if ($isForm){
if ($pn->tagName=='input'&&$pn->type=='file'){
$form_has_file_input = true;
}
$propsData = $pn->attributesAsArray();
unset($propsData['prop']);
unset($propsData['name']);
$propsData['tagName'] = $pn->tagName;
if ($pn->is('select')){
foreach($pn->xpath('option') as $optNode){
if (!$optNode->hasAttribute('value'))continue;
$propsData['options'][] = $optNode->value;
}
}
$itemDataProperties[$pn->prop ?? $pn->name] = $propsData;
}
$this->compilePropNode($itemNode, $pn);
}
if ($isForm){
$itemNode->action = $itemNode->action ?? '';
$itemNode->method = $itemNode->method ?? 'POST';
if ($form_has_file_input){
$itemNode->enctype = "multipart/form-data";
}
if (!isset($itemDataProperties['id'])){
$itemDataProperties['id'] = ['tagName'=>'input', 'type'=>'hidden'];
}
$itemDataPropertiesExported = var_export($itemDataProperties, true);
$code[] = $this->indent("${itemDataVar}->properties = $itemDataPropertiesExported;",4);
}
$code[] = <<<PHP
if (${itemDataVar}->phad_mode == 'get_item_data'){
return ${itemDataVar};
}
\$phad->resolveAccess($itemDataVar);
PHP;
if ($isForm&&isset($itemNode->deleteable)){
$code[] = <<<PHP
if (${itemDataVar}->phad_mode == 'delete'){
\$phad->delete(${itemDataVar});
return;
}
PHP;
}
$code[] =
" \$phad->itemListStarted($itemDataVar);"
;
$onSubmitForm = '';
if ($isForm){
$onSubmitForm = '//<onsubmit> code goes here';
$onSubmitNode = $itemNode->xpath('onsubmit')[0] ?? null;
if ($onSubmitNode!==null){
$onSubmitCode = $onSubmitNode->innerHTML;
$placeholder = $onSubmitNode->innerHTML;
$placeheldCode= $onSubmitNode->doc->codeFromPlaceholder($placeholder);
if ($placeheldCode!==null){
$onSubmitCode = $placeheldCode;
}
$onSubmitForm = $phpClose.trim($onSubmitCode).$phpOpen;
$onSubmitForm = trim($this->indent($onSubmitForm, 8));
}
}
// display code (dealing with access index & status)
$submit_code = <<<PHP
if ({$itemDataVar}->phad_mode == 'submit'){
$onSubmitForm
if ({$itemDataVar}->phad_mode=='submit'
&& \$phad->submit($itemDataVar, ${itemVar}Row) )return;
}
PHP;
if ($itemNode->tagName!='form')$submit_code = '';
$foreachDisplayCode = <<<PHP
foreach(${itemDataVar}->list as ${itemVar}Row_Index=>${itemVar}Row):
$itemVar = \$phad->objectFromRow($itemDataVar, ${itemVar}Row);
if (!\$phad->hasRowAccess($itemDataVar, $itemVar))continue;
{$submit_code}
\$phad->rowStarted($itemDataVar, $itemVar);
$phpClose
PHP;
$statusNodeData = $this->getStatusNodeData($statusNodes, $foreachDisplayCode, $phtml);
$this->cleanupNodes($itemNode, $propNodes, $accessNodes, $statusNodes);
$this->getDisplayLogicCode($beforeCode, $afterCode, $statusNodeData, $itemName, $phtml);
$code[] = $beforeCode;
$code = implode("\n", $code);
$phtml->insertCodeBefore($itemNode, $code);
$phtml->insertCodeAfter($itemNode, $afterCode);
}
protected function compilePropNode($item, $prop){
$phtml = $item->ownerDocument;
$itemName = $item->item;
$p = $prop;
$propName = $p->hasAttribute('prop') ? $p->prop : $p->name;
$propCode = '$'.$itemName.'->'.$propName;
$phpCode = null;
if ($p->hasAttribute('filter')){
$filterCode = var_export($p->filter,true);
$phpCode = '<?=$phad->filter('.$filterCode.','.$propCode.')?>';
} else {
$phpCode = '<?='.$propCode.'?>';
}
if ($p->tagName == 'input' && $p->type == 'backend'){
} elseif ($p->tagName=='input'){
$p->value = $phtml->phpPlaceholder($phpCode);
} else if ($p->tagName=='select'){
$options = $phtml->xpath('descendant::option', $p);
foreach ($options as $opt){
$optVal = var_export($opt->value,true);
$code = "<?=($optVal==$propCode)? ' selected=\"\" ' : ' '?>";
$phtml->addPhpToTag($opt, $code);
}
}
else {
$p->innerHTML=$phtml->phpPlaceholder($phpCode);
}
unset($p->filter);
unset($p->prop);
}
protected function cleanupNodes($itemNode, $propNodes, $accessNodes, $statusNodes){
unset($itemNode->item);
if ($itemNode->tagName=='x-item'){
$itemNode->hideOwnTag = true;
} else if ($itemNode->is('form')){
unset($itemNode->target);
unset($itemNode->deleteable);
}
// prop nodes are cleaned up earlier on... when prop nodes are compiled
// foreach ($propNodes as $p){
// unset($p->prop);
// }
foreach ($accessNodes as $a){
$a->parentNode->removeChild($a);
}
foreach ($statusNodes as $s){
$s->parentNode->removeChild($s);
}
foreach ($propNodes as $p){
if ($p->tagName=='input' && $p->type == 'backend'){
$p->parentNode->removeChild($p);
}
}
foreach ($itemNode->xpath('//onsubmit') as $node){
$node->parentNode->removeChild($node);
}
foreach ($itemNode->xpath('//error') as $errorNode){
$errorNode->parentNode->removeChild($errorNode);
}
foreach ($itemNode->xpath('//x-prop') as $xPropNode){
$xPropNode->hideOwnTag = true;
}
}
protected function getItemDataCode($itemDataVar, $itemName, $itemNode, $accessNodes){
$code = ['<?php '];
/** init the the item data var */
$formTarget = (function() use ($itemNode){
if (!$itemNode->is('form')){ //|| $itemNode->target==null){
// return ', "uhoh"=>false';
return ", 'item_type'=>'view'";
// return '';
}
$export = var_export($itemNode->target, true);
$targetCode = ", 'target'=> $export, 'item_type'=>'form'";
return $targetCode;
})();
$deleteable = '';
if (isset($itemNode->deleteable)&&$itemNode->is('form')){
$deleteable =
", 'deleteable'=> "
. var_export($itemNode->deleteable, true);
}
$code[] = $this->indent("$itemDataVar = (object)['accessList'=>[], 'args'=>\$args, 'list'=>[], 'name'=>'$itemName', 'accessStatus'=>false, 'phad_mode'=>\$phad_mode??'display'${formTarget}${deleteable}];",4);
/** [200 => [accessIndex => $codeToExecute]] */
$statusCode = [];
$accessIndex = -1;
foreach ($accessNodes as $a){
$accessIndex++;
$statusNodes = $a->ownerDocument->xpath('descendant::on',$a);
$accessArray = [];
foreach ($a->attributes() as $index=>$attr){
$accessArray[$attr->name] = $attr->value;
}
$accessExported = var_export($accessArray,true);
$accessExported = $this->indent($accessExported, 4, false);
$code[] = <<<PHP
${itemDataVar}->accessList[] = $accessExported;
PHP;
// the status node needs this, later
$a->setAttribute('accessIndex', $accessIndex);
}
return implode("\n", $code);
}
protected function getStatusNodeData($statusNodes, $displayCode, $phtml){
// The target format:
// [
// 200 => [
// [2]=>$theActualCode, // 2 is the accessIndex
// ['else']=>$actualCode,
// ['then']=>$theForeachCode,
// ],
//
// ];
$statusNodeData = [];
foreach ($statusNodes as $sn){
if (strtolower($sn->parentNode->tagName)=='access'){
$statusNodeData[$sn->getAttribute('s')][$sn->parentNode->accessIndex] = $phtml->placeholder($sn->innerHTML);
} else {
$statusNodeData[$sn->getAttribute('s')]['else'] = $phtml->placeholder($sn->innerHTML);
}
}
$statusNodeData[200]['then'] = $displayCode;
$snd = $statusNodeData;
foreach($snd as $status=>$list){
uasort($list, function($a, $b){
if (is_string($a))return -1;
else if (is_string($b))return 1;
});
$statusNodeData[$status] = $list;
}
return $statusNodeData;
}
public function getDisplayLogicCode(&$beforeCode, &$afterCode, $statusNodeData, $itemName, $phtml){
$itemDataVar = "\$${itemName}Item";
$itemVar = "\$${itemName}";
$closeTag = '?>';
$if = 'if';
$beforeCode = [];
$beforeCode[] = <<<PHP
if (${itemDataVar}->accessStatus == 200):
PHP;
$hasIf = false;
foreach ($statusNodeData[200] as $accessIndex=>$actualCode){
if (is_numeric($accessIndex)){
$hasIf = true;
$beforeCode[] = <<<PHP
${if} (${itemDataVar}->accessIndex == $accessIndex): $closeTag
$actualCode
<?php
PHP;
$if = 'elseif';
} else if ($accessIndex=='else'){
if ($hasIf){
$beforeCode[] = <<<PHP
else: $closeTag
$actualCode
<?php
PHP;
} else {
$beforeCode[] = <<<PHP
if (true): $closeTag
$actualCode
<?php
PHP;
}
$hasIf = true;
} else if ($accessIndex=='then'){
if ($hasIf){
$beforeCode[] = $this->indent('endif; //close access status = 200 & access index = 0', 8);
}
$beforeCode[] = $actualCode;
continue;
}
}
$beforeCode = implode("\n", $beforeCode);
$afterCode = [];
$afterCode[] = <<<PHP
<?php
\$phad->rowFinished($itemDataVar, $itemVar);
endforeach;
PHP;
$hasAccessIf = false;
foreach ($statusNodeData as $accessStatus=>$accessIndexList){
if ($accessStatus==200)continue;
$afterCode[] = <<<PHP
elseif (${itemDataVar}->accessStatus == $accessStatus):
PHP;
$if = 'if';
// $count = count($accessIndexList);
// $iters = 0;
$if_iters = 0;
foreach ($accessIndexList as $accessIndex=>$actualCode){
// $iters++;
if (is_numeric($accessIndex)){
$if_iters++;
// $endif = $iters < $count
// ? 'endif; //close if (\$NamedItem->accessIndex == ###)'
// : '// no endif, expecting else';
$afterCode[] = <<<PHP
${if} (${itemDataVar}->accessIndex == $accessIndex): $closeTag
$actualCode
<?php
PHP;
$afterCode[] = ' endif; // added to close accessIndex if()';
$if = 'elseif';
$hasAccessIf = true;
} else if ($accessIndex=='else'){
if ($if_iters>0)array_pop($afterCode);
$else = $hasAccessIf ? 'else: ' : '';
$afterCode[] = <<<PHP
$else $closeTag
$actualCode
<?php
endif; // close else: within access branches
PHP;
// $hasAccessIf = true;
}
}
}
$accessIf = $hasAccessIf ? 'endif; //idk' : '';
$accessIf = '//endif; removed';
$afterCode[] =
<<<PHP
$accessIf
endif; // close sequence of elseif (\$NamedItem->accessStatus == ###):
\$phad->itemListFinished($itemDataVar);
PHP
;
$afterCode[] = '?>';
$afterCode = implode("\n", $afterCode);
}
}