Skip to content

Commit 377e3b9

Browse files
committed
feat: add parser
1 parent fde4bba commit 377e3b9

1 file changed

Lines changed: 226 additions & 0 deletions

File tree

src/UI/Parser.php

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
<?php
2+
3+
namespace Leaf\UI;
4+
5+
use Leaf\UI;
6+
use MatthiasMullie\Minify\CSS;
7+
8+
/**
9+
* This class handles the internal parsing of Leaf UI components
10+
*/
11+
class Parser {
12+
/**
13+
* Compile Leaf UI Template
14+
* @param string $rawText The template to compile
15+
*/
16+
public static function compileTemplate(string $rawText, array $state = []): string
17+
{
18+
$compiled = preg_replace_callback('/{{(.*?)}}/', function ($matches) use ($state) {
19+
return $state[ltrim(trim($matches[1]), '$')] ?? trigger_error($matches[1] . ' is not defined', E_USER_ERROR);
20+
}, $rawText);
21+
22+
$compiled = preg_replace_callback('/<style.*?>(.*?)<\/style>/is', function ($matches) {
23+
$newCSS = (new CSS())->add($matches[1])->minify();
24+
return str_replace($matches[1], $newCSS, $matches[0]);
25+
}, $compiled);
26+
27+
$compiled = preg_replace_callback('/\$eval\((.*?)\)/', function ($matches) use ($state) {
28+
$compiledWithVars = preg_replace_callback('/\$([a-zA-Z0-9_]+)/', function ($matches) use ($state) {
29+
return $state[$matches[1]] ?? trigger_error($matches[1] . ' is not defined', E_USER_ERROR);
30+
}, $matches[1]);
31+
32+
return eval("return $compiledWithVars;");
33+
}, $compiled);
34+
35+
$compiled = preg_replace_callback('/@loop\([\s\S]*?\)\s*[\s\S]*@endloop\s*/', function ($matches) use ($state) {
36+
$rendered = '';
37+
$loopMatches = null;
38+
39+
preg_match('/@loop\((.*?)\)/', $matches[0], $loopMatches);
40+
41+
$dataToLoop = "return $loopMatches[1];";
42+
43+
if (strpos($loopMatches[1], '$') !== false) {
44+
$loopMatches[1] = $state[ltrim(trim($loopMatches[1]), '$')] ?? trigger_error($loopMatches[1] . ' is not defined', E_USER_ERROR);
45+
$dataToLoop = 'return json_decode(\'' . json_encode($loopMatches[1]) . '\', true);';
46+
}
47+
48+
static::loop(eval($dataToLoop), function ($value, $key) use ($matches, &$rendered, $state) {
49+
$regex = '/@loop\((.*?)\)([\s\S]*?)@endloop/';
50+
preg_match($regex, $matches[0], $regexLoopMatches);
51+
52+
preg_match('/@key/', $regexLoopMatches[2], $keyMatches);
53+
preg_match('/@value/', $regexLoopMatches[2], $valueMatches);
54+
55+
$renderedString = str_replace(
56+
['@key', '@value', "\""],
57+
[$key, '$value', "'"],
58+
preg_replace(
59+
'/@value\[[\'"][^\'"]*[\'"]\]/',
60+
'{$0}',
61+
preg_replace_callback(
62+
'/@if\((.*?)\)/',
63+
function ($ifStatementMatches) {
64+
$compiledIf = '';
65+
66+
if (strpos($ifStatementMatches[1], '@value[') !== false) {
67+
$compiledIf = preg_replace('/@value\[[\'"]([^\'"]*)[\'"]\]/', '\'@value[\'$1\']\'', $ifStatementMatches[0]);
68+
}
69+
70+
if (strpos($compiledIf, '@key') !== false) {
71+
$compiledIf = str_replace('@key', '\'@key\'', $compiledIf);
72+
}
73+
74+
return $compiledIf;
75+
},
76+
$regexLoopMatches[2]
77+
)
78+
)
79+
);
80+
81+
$rendered .= eval("\$value = json_decode('" . json_encode($value) . "', true); return \"$renderedString\";");
82+
});
83+
84+
return $rendered;
85+
}, $compiled);
86+
87+
$compiled = preg_replace_callback('/@if\([\s\S]*?\)\s*[\s\S]*?(\s*@endif\s*)/', function ($matches) use ($state) {
88+
$renderedData = '';
89+
$compiledWithParsedConditions = preg_replace_callback('/\$([a-zA-Z0-9_]+)/', function ($matches) use ($state) {
90+
return $state[$matches[1]] ?? trigger_error($matches[1] . ' is not defined', E_USER_ERROR);
91+
}, $matches[0]);
92+
93+
preg_match('/@if\((.*?)\)/', $compiledWithParsedConditions, $condition);
94+
95+
if (eval("return $condition[1];") === true) {
96+
preg_match(
97+
'/@if\([\s\S]*?\)\s*[\s\S]*?(?:\s*@elseif\([\s\S]*?\)\s*[\s\S]*?|\s*@else\s*[\s\S]*?|\s*@endif\s*)/',
98+
$compiledWithParsedConditions,
99+
$ifConditionMatches
100+
);
101+
102+
$renderedData = preg_replace('/@if\([\s\S]*?\)\s*[\s\S]*?/', '', $ifConditionMatches[0]);
103+
$renderedData = preg_replace('/\s*@elseif\([\s\S]*?\)\s*[\s\S]*?/', '', $renderedData);
104+
$renderedData = preg_replace('/\s*@else\s*[\s\S]*?/', '', $renderedData);
105+
} else {
106+
if (strpos($compiledWithParsedConditions, '@elseif') !== false) {
107+
preg_match('/@elseif\((.*?)\)/', $compiledWithParsedConditions, $elseifCondition);
108+
109+
if (eval("return $elseifCondition[1];") === true) {
110+
preg_match(
111+
'/@elseif\([\s\S]*?\)\s*[\s\S]*?(?:\s*@elseif\([\s\S]*?\)\s*[\s\S]*?|\s*@else\s*[\s\S]*?|\s*@endif\s*)/',
112+
$compiledWithParsedConditions,
113+
$elseifConditionMatches
114+
);
115+
116+
$renderedData = preg_replace('/@elseif\([\s\S]*?\)\s*[\s\S]*?/', '', $elseifConditionMatches[0]);
117+
$renderedData = preg_replace('/\s*@else\s*[\s\S]*?/', '', $renderedData);
118+
} else if (strpos($compiledWithParsedConditions, '@else') !== false) {
119+
preg_match('/@else\s*(.*?)\s*@endif/', $compiledWithParsedConditions, $elseConditionMatches);
120+
$renderedData = $elseConditionMatches[1];
121+
}
122+
} else {
123+
if (strpos($compiledWithParsedConditions, '@else') !== false) {
124+
preg_match('/@else\s*(.*?)\s*@endif/', $compiledWithParsedConditions, $elseConditionMatches);
125+
$renderedData = $elseConditionMatches[1];
126+
}
127+
}
128+
}
129+
130+
return $renderedData;
131+
}, $compiled);
132+
133+
$compiled = preg_replace_callback('/@php\s*([\s\S]+?)\s*@endphp/', function ($matches) {
134+
return eval($matches[1]);
135+
}, $compiled);
136+
137+
$compiled = preg_replace_callback('/@include\((.*?)\)/', function ($matches) use ($state) {
138+
$viewToInclude = trim($matches[1], '"\'\`');
139+
140+
$compiledWithVars = preg_replace_callback('/\$([a-zA-Z0-9_]+)/', function ($matches) use ($state) {
141+
return $state[$matches[1]] ?? trigger_error($matches[1] . ' is not defined', E_USER_ERROR);
142+
}, $viewToInclude);
143+
144+
return UI::view($compiledWithVars);
145+
}, $compiled);
146+
147+
$compiled = preg_replace_callback('/@component\((.*?)\)/', function ($matches) {
148+
$paramsArray = preg_split('/,\s*/', $matches[1]);
149+
$props = preg_replace('/\s+/', '', $paramsArray[1] ?? '[]');
150+
return UI::component($paramsArray[0], eval("return $props;"));
151+
}, $compiled);
152+
153+
return $compiled;
154+
}
155+
156+
/**
157+
* Loop over an array of items
158+
*
159+
* @param array|string|int $array The array to loop through
160+
* @param callable $handler Call back function to run per iteration
161+
*/
162+
public static function loop($array, callable $handler)
163+
{
164+
$element = "";
165+
166+
if (!is_array($array)) {
167+
$array = explode(',', str_repeat(',', (int) $array - 1));
168+
}
169+
170+
if (is_callable($handler)) {
171+
foreach ($array as $key => $value) {
172+
$element .= call_user_func($handler, $value, $key);
173+
}
174+
}
175+
176+
return $element;
177+
}
178+
179+
public static function parseStyles(array $styles): string
180+
{
181+
$parsedStyles = '';
182+
183+
foreach ($styles as $key => $value) {
184+
if (is_numeric($key)) {
185+
$value = rtrim($value, ';');
186+
$parsedStyles .= "$value;";
187+
} else if (is_string($value)) {
188+
$value = rtrim($value, ';');
189+
190+
if (strpos($value, ':') !== false) {
191+
$parsedStyles .= "$key { $value; }";
192+
} else {
193+
$parsedStyles .= "$key: $value;";
194+
}
195+
} else {
196+
$parsedStyles .= "$key {";
197+
198+
foreach ($value as $selector => $styling) {
199+
if (is_array($styling)) {
200+
if (is_string($selector)) {
201+
$parsedStyles .= self::parseStyles([$selector => $styling]);
202+
} else {
203+
$parsedStyles .= self::parseStyles($styling);
204+
}
205+
} else {
206+
$styling = rtrim($styling, ';');
207+
208+
if (is_numeric($selector)) {
209+
$parsedStyles .= self::parseStyles(["$styling;"]);
210+
} else {
211+
if (strpos($styling, ':') !== false) {
212+
$parsedStyles .= "$selector { $styling; }";
213+
} else {
214+
$parsedStyles .= "$selector: $styling;";
215+
}
216+
}
217+
}
218+
}
219+
220+
$parsedStyles .= '}';
221+
}
222+
}
223+
224+
return $parsedStyles;
225+
}
226+
}

0 commit comments

Comments
 (0)