@barefootjs/php 0.18.3 → 0.18.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/src/BarefootJS.php
CHANGED
|
@@ -680,6 +680,33 @@ final class BarefootJS
|
|
|
680
680
|
return (is_nan($n) || is_infinite($n)) ? $n : floor($n + 0.5);
|
|
681
681
|
}
|
|
682
682
|
|
|
683
|
+
/** `Math.min(a, b)` / `Math.max(a, b)` -- two-arg forms only (#2168
|
|
684
|
+
* math-methods). JS returns NaN if either operand is NaN. */
|
|
685
|
+
public function min($a, $b): float
|
|
686
|
+
{
|
|
687
|
+
$x = $this->number($a);
|
|
688
|
+
$y = $this->number($b);
|
|
689
|
+
if (is_nan($x)) return $x;
|
|
690
|
+
if (is_nan($y)) return $y;
|
|
691
|
+
return $x < $y ? $x : $y;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
public function max($a, $b): float
|
|
695
|
+
{
|
|
696
|
+
$x = $this->number($a);
|
|
697
|
+
$y = $this->number($b);
|
|
698
|
+
if (is_nan($x)) return $x;
|
|
699
|
+
if (is_nan($y)) return $y;
|
|
700
|
+
return $x > $y ? $x : $y;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/** `Math.abs()` (#2168 math-methods). */
|
|
704
|
+
public function abs($value): float
|
|
705
|
+
{
|
|
706
|
+
$n = $this->number($value);
|
|
707
|
+
return is_nan($n) ? $n : abs($n);
|
|
708
|
+
}
|
|
709
|
+
|
|
683
710
|
// -----------------------------------------------------------------
|
|
684
711
|
// Array / String method helpers (#1448 Tier A)
|
|
685
712
|
// -----------------------------------------------------------------
|
|
@@ -852,6 +879,46 @@ final class BarefootJS
|
|
|
852
879
|
return mb_strlen($this->scalarOrEmpty($recv), 'UTF-8');
|
|
853
880
|
}
|
|
854
881
|
|
|
882
|
+
/** True for a JS *object* value under the canonical value convention
|
|
883
|
+
* (`Evaluator::isJsArray`'s docstring): a `stdClass` (the live
|
|
884
|
+
* representation for a `json_decode()`-sourced object prop) or a
|
|
885
|
+
* non-list PHP array (a compiler-baked object literal, `toPhpLiteral`'s
|
|
886
|
+
* `stdClass`-cast form notwithstanding -- defensive, in case a bare
|
|
887
|
+
* assoc array ever reaches here directly). */
|
|
888
|
+
private function isJsObject($recv): bool
|
|
889
|
+
{
|
|
890
|
+
return $recv instanceof \stdClass || (is_array($recv) && !Evaluator::isJsArray($recv));
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* `Object.entries(x)` (#2168 object-entries-map) -- returns a PHP assoc
|
|
895
|
+
* array (`key => value`) so the generated Twig/Blade `for`/`@foreach`
|
|
896
|
+
* unpacks key + value directly via the language's own 2-variable
|
|
897
|
+
* iteration form. `(array)` on a `stdClass` (or a bare assoc array)
|
|
898
|
+
* preserves the object's OWN declaration/insertion order -- a PHP
|
|
899
|
+
* array's iteration order IS its insertion order by language
|
|
900
|
+
* guarantee, unlike e.g. a Go `map[string]T` or a Perl hash, which
|
|
901
|
+
* have no such guarantee (see `IRLoop.objectIteration`'s docstring,
|
|
902
|
+
* `packages/jsx/src/types.ts`, for why those adapters take a
|
|
903
|
+
* different, sorted-order approach instead).
|
|
904
|
+
*/
|
|
905
|
+
public function entries($recv): array
|
|
906
|
+
{
|
|
907
|
+
return $this->isJsObject($recv) ? (array) $recv : [];
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/** `Object.keys(x)` -- see `entries()`. */
|
|
911
|
+
public function keys($recv): array
|
|
912
|
+
{
|
|
913
|
+
return $this->isJsObject($recv) ? array_keys((array) $recv) : [];
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/** `Object.values(x)` -- see `entries()`. */
|
|
917
|
+
public function values($recv): array
|
|
918
|
+
{
|
|
919
|
+
return $this->isJsObject($recv) ? array_values((array) $recv) : [];
|
|
920
|
+
}
|
|
921
|
+
|
|
855
922
|
private function arrayIndexOf($recv, $elem, bool $reverse): int
|
|
856
923
|
{
|
|
857
924
|
if (!Evaluator::isJsArray($recv)) {
|
|
@@ -914,9 +981,23 @@ final class BarefootJS
|
|
|
914
981
|
return $out;
|
|
915
982
|
}
|
|
916
983
|
|
|
917
|
-
/** `Array.prototype.slice(start, end?)
|
|
918
|
-
|
|
919
|
-
|
|
984
|
+
/** `Array.prototype.slice(start, end?)` AND `String.prototype.slice`
|
|
985
|
+
* (the `string-slice` divergence) -- the adapter emits the same
|
|
986
|
+
* `slice(recv, start, end)` call for both receiver shapes (it can't
|
|
987
|
+
* disambiguate string vs. array at compile time), so this dispatches
|
|
988
|
+
* on the receiver's PHP type, mirroring `includes` above. String
|
|
989
|
+
* length/positions use `mb_strlen`/`mb_substr` (UTF-8 code points,
|
|
990
|
+
* not bytes), matching this runtime's other string helpers. */
|
|
991
|
+
public function slice($recv, $start, $end)
|
|
992
|
+
{
|
|
993
|
+
if (is_string($recv)) {
|
|
994
|
+
$len = mb_strlen($recv, 'UTF-8');
|
|
995
|
+
[$s, $e] = $this->clampSliceRange($len, $start, $end);
|
|
996
|
+
if ($s >= $e) {
|
|
997
|
+
return '';
|
|
998
|
+
}
|
|
999
|
+
return mb_substr($recv, (int) $s, (int) ($e - $s), 'UTF-8');
|
|
1000
|
+
}
|
|
920
1001
|
if (!Evaluator::isJsArray($recv)) {
|
|
921
1002
|
return [];
|
|
922
1003
|
}
|
|
@@ -925,6 +1006,16 @@ final class BarefootJS
|
|
|
925
1006
|
if ($len === 0) {
|
|
926
1007
|
return [];
|
|
927
1008
|
}
|
|
1009
|
+
[$s, $e] = $this->clampSliceRange($len, $start, $end);
|
|
1010
|
+
if ($s >= $e) {
|
|
1011
|
+
return [];
|
|
1012
|
+
}
|
|
1013
|
+
return array_slice($arr, (int) $s, (int) ($e - $s));
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/** Shared bounds arithmetic for both `slice` branches above. */
|
|
1017
|
+
private function clampSliceRange(int $len, $start, $end): array
|
|
1018
|
+
{
|
|
928
1019
|
$s = $start ?? 0;
|
|
929
1020
|
if ($s < 0) {
|
|
930
1021
|
$s += $len;
|
|
@@ -937,10 +1028,7 @@ final class BarefootJS
|
|
|
937
1028
|
}
|
|
938
1029
|
$e = max($e, 0);
|
|
939
1030
|
$e = min($e, $len);
|
|
940
|
-
|
|
941
|
-
return [];
|
|
942
|
-
}
|
|
943
|
-
return array_slice($arr, (int) $s, (int) ($e - $s));
|
|
1031
|
+
return [$s, $e];
|
|
944
1032
|
}
|
|
945
1033
|
|
|
946
1034
|
public function reverse($recv): array
|
|
@@ -1117,6 +1205,25 @@ final class BarefootJS
|
|
|
1117
1205
|
return (string) preg_replace('/^\s+|\s+$/u', '', $this->string($recv));
|
|
1118
1206
|
}
|
|
1119
1207
|
|
|
1208
|
+
/** `String.prototype.trimStart()` / `.trimEnd()` -- the one-sided
|
|
1209
|
+
* siblings of `trim` above (#2183 follow-up), same `\s` /u regex
|
|
1210
|
+
* restricted to one side. */
|
|
1211
|
+
public function trim_start($recv): string
|
|
1212
|
+
{
|
|
1213
|
+
if ($recv === null || is_array($recv) || $recv instanceof \stdClass) {
|
|
1214
|
+
return '';
|
|
1215
|
+
}
|
|
1216
|
+
return (string) preg_replace('/^\s+/u', '', $this->string($recv));
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
public function trim_end($recv): string
|
|
1220
|
+
{
|
|
1221
|
+
if ($recv === null || is_array($recv) || $recv instanceof \stdClass) {
|
|
1222
|
+
return '';
|
|
1223
|
+
}
|
|
1224
|
+
return (string) preg_replace('/\s+$/u', '', $this->string($recv));
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1120
1227
|
/** `Number.prototype.toFixed(digits)` -- JS rounds the scaled integer
|
|
1121
1228
|
* half toward +Infinity (the spec's "pick the larger n" tie-break); a
|
|
1122
1229
|
* bare `sprintf('%.*f')` would round half-to-even, diverging. */
|
|
@@ -1208,6 +1315,29 @@ final class BarefootJS
|
|
|
1208
1315
|
return substr($s, 0, $i) . $n . substr($s, $i + strlen($o));
|
|
1209
1316
|
}
|
|
1210
1317
|
|
|
1318
|
+
/** `String.prototype.replaceAll(pattern, replacement)` -- string-pattern
|
|
1319
|
+
* form only (#2182), replacing EVERY occurrence (the all-occurrences
|
|
1320
|
+
* sibling of `replace` above). A non-empty pattern uses PHP's own
|
|
1321
|
+
* `str_replace`, which is global-by-default and treats both the pattern
|
|
1322
|
+
* and the replacement literally (no backreference interpolation) --
|
|
1323
|
+
* matching JS. PHP's `str_replace("", ...)` is a no-op, unlike JS,
|
|
1324
|
+
* which inserts the replacement at every boundary (including before
|
|
1325
|
+
* the first and after the last character,
|
|
1326
|
+
* `"abc".replaceAll("", "X")` -> "XaXbXcX") -- so the empty pattern is
|
|
1327
|
+
* special-cased with `mb_str_split` (UTF-8 code points, matching this
|
|
1328
|
+
* runtime's other string helpers). */
|
|
1329
|
+
public function replace_all($recv, $pattern, $replacement): string
|
|
1330
|
+
{
|
|
1331
|
+
$s = $this->scalarOrEmpty($recv);
|
|
1332
|
+
$o = $this->string($pattern);
|
|
1333
|
+
$n = $this->string($replacement);
|
|
1334
|
+
if ($o === '') {
|
|
1335
|
+
$chars = $s === '' ? [] : mb_str_split($s, 1, 'UTF-8');
|
|
1336
|
+
return implode($n, array_merge([''], $chars, ['']));
|
|
1337
|
+
}
|
|
1338
|
+
return str_replace($o, $n, $s);
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1211
1341
|
/** `queryHref(base, {...})` (#2042) -- build `"$base?k=v&..."` from a
|
|
1212
1342
|
* flat (guard, key, value) triple sequence. A pair is included iff its
|
|
1213
1343
|
* guard is JS-truthy AND its value is a non-empty string; an array value
|
|
@@ -146,13 +146,19 @@ function bfv_call(BarefootJS $bf, string $fn, array $args)
|
|
|
146
146
|
case 'floor': return $bf->floor($args[0]);
|
|
147
147
|
case 'ceil': return $bf->ceil($args[0]);
|
|
148
148
|
case 'round': return $bf->round($args[0]);
|
|
149
|
+
case 'min': return $bf->min($args[0], $args[1]);
|
|
150
|
+
case 'max': return $bf->max($args[0], $args[1]);
|
|
151
|
+
case 'abs': return $bf->abs($args[0]);
|
|
149
152
|
case 'to_fixed': return $bf->to_fixed(...$args);
|
|
150
153
|
case 'lower': return $bf->lc($args[0]);
|
|
151
154
|
case 'upper': return $bf->uc($args[0]);
|
|
152
155
|
case 'trim': return $bf->trim($args[0]);
|
|
156
|
+
case 'trim_start': return $bf->trim_start($args[0]);
|
|
157
|
+
case 'trim_end': return $bf->trim_end($args[0]);
|
|
153
158
|
case 'starts_with': return $bf->starts_with(...$args);
|
|
154
159
|
case 'ends_with': return $bf->ends_with(...$args);
|
|
155
160
|
case 'replace': return $bf->replace(...$args);
|
|
161
|
+
case 'replace_all': return $bf->replace_all(...$args);
|
|
156
162
|
case 'repeat': return $bf->repeat(...$args);
|
|
157
163
|
case 'pad_start': return $bf->pad_start(...$args);
|
|
158
164
|
case 'pad_end': return $bf->pad_end(...$args);
|
|
@@ -188,6 +188,19 @@ bf_test('slice mutation isolation and clamping', function () use ($bf) {
|
|
|
188
188
|
bf_assert_eq($src, ['a', 'b', 'c']);
|
|
189
189
|
});
|
|
190
190
|
|
|
191
|
+
bf_test('slice string receiver', function () use ($bf) {
|
|
192
|
+
// The `string-slice` divergence (#2182): a string receiver used to
|
|
193
|
+
// fall through the array-only branch and return an empty array
|
|
194
|
+
// instead of a substring.
|
|
195
|
+
$word = 'barefootjs';
|
|
196
|
+
bf_assert_eq($bf->slice($word, 0, 4), 'bare');
|
|
197
|
+
bf_assert_eq($bf->slice($word, -4, null), 'otjs');
|
|
198
|
+
bf_assert_eq($bf->slice($word, 4, null), 'footjs');
|
|
199
|
+
bf_assert_eq($bf->slice($word, 5, 2), '');
|
|
200
|
+
// Multi-byte: index by character, not byte.
|
|
201
|
+
bf_assert_eq($bf->slice('héllo', 0, 2), 'hé');
|
|
202
|
+
});
|
|
203
|
+
|
|
191
204
|
bf_test('reverse mutation isolation', function () use ($bf) {
|
|
192
205
|
bf_assert_eq($bf->reverse(['a', 'b', 'c']), ['c', 'b', 'a']);
|
|
193
206
|
bf_assert_eq($bf->reverse([]), []);
|
|
@@ -211,6 +224,33 @@ bf_test('trim', function () use ($bf) {
|
|
|
211
224
|
bf_assert_eq($bf->trim(42), '42');
|
|
212
225
|
});
|
|
213
226
|
|
|
227
|
+
// `trim_start` / `trim_end` -- the one-sided siblings of `trim` above
|
|
228
|
+
// (#2183 follow-up). Padding BOTH sides of the flagship input so a
|
|
229
|
+
// swapped side (or a routed-through-both-sides regression) fails visibly.
|
|
230
|
+
bf_test('trim_start / trim_end', function () use ($bf) {
|
|
231
|
+
bf_assert_eq($bf->trim_start(' padded '), 'padded ');
|
|
232
|
+
bf_assert_eq($bf->trim_end(' padded '), ' padded');
|
|
233
|
+
|
|
234
|
+
bf_assert_eq($bf->trim_start("\t\nleading"), 'leading');
|
|
235
|
+
bf_assert_eq($bf->trim_end('trailing '), 'trailing');
|
|
236
|
+
|
|
237
|
+
bf_assert_eq($bf->trim_start('no-pad'), 'no-pad');
|
|
238
|
+
bf_assert_eq($bf->trim_end('no-pad'), 'no-pad');
|
|
239
|
+
|
|
240
|
+
bf_assert_eq($bf->trim_start(' '), '');
|
|
241
|
+
bf_assert_eq($bf->trim_end(' '), '');
|
|
242
|
+
|
|
243
|
+
bf_assert_eq($bf->trim_start(''), '');
|
|
244
|
+
bf_assert_eq($bf->trim_end(''), '');
|
|
245
|
+
|
|
246
|
+
bf_assert_eq($bf->trim_start(null), '');
|
|
247
|
+
bf_assert_eq($bf->trim_end(null), '');
|
|
248
|
+
bf_assert_eq($bf->trim_start(['a' => 1]), '');
|
|
249
|
+
bf_assert_eq($bf->trim_end(['a' => 1]), '');
|
|
250
|
+
bf_assert_eq($bf->trim_start(42), '42');
|
|
251
|
+
bf_assert_eq($bf->trim_end(42), '42');
|
|
252
|
+
});
|
|
253
|
+
|
|
214
254
|
bf_test('split', function () use ($bf) {
|
|
215
255
|
bf_assert_eq($bf->split('a,b,c', ','), ['a', 'b', 'c']);
|
|
216
256
|
bf_assert_eq($bf->split('a.b.c', '.'), ['a', 'b', 'c']);
|