BarefootJS
view release on metacpan or search on metacpan
lib/BarefootJS.pm view on Meta::CPAN
my ($y, $mon, $day, $h, $min, $s, $ms) = ($1, $2, $3, $4, $5, $6, $7 // 0);
my $epoch_s = Time::Local::timegm_modern($s, $min, $h, $day, $mon - 1, $y);
return $epoch_s * 1000 + $ms;
}
# `format_date($recv, $pattern, $tz, $names)` -- pure, explicit-timezone
# date formatter (#2324, #2334, spec entry "format_date"). Same receiver
# contract as `date` (a `BarefootJS::Date`, an ISO-8601 string, or
# undef/unparseable -> ''), then pure pattern substitution over the
# shifted instant. Mirrors packages/client/src/format-date.ts
# byte-for-byte -- deterministic, no locale, no host TZ, no `time()`.
#
# `$tz` (#2344) is 'UTC', a range-valid fixed offset `±HH:MM` (hours
# 00-23, minutes 00-59), or a canonical IANA zone name ('Asia/Tokyo')
# resolved through tzdata via DateTime::TimeZone -- the zone's UTC
# offset AT THE INSTANT being formatted (DST-aware,
# historical-transition-aware, seconds precision: pre-standard LMT
# offsets like Tokyo's +09:18:59 count). Anything unresolvable -- an
# unknown zone, a malformed or out-of-range offset ('+9:00', '+25:00'
# fall through to the zone lookup), DateTime::TimeZone's implicit
# 'local'/'floating' specials -- DIES, aborting the render loudly: the
lib/BarefootJS.pm view on Meta::CPAN
# the key here must match the actual hash key the
# user populated.
# compare_type => 'numeric' | 'string' | 'auto'
# direction => 'asc' | 'desc'
#
# Accepted comparator catalogue (gated upstream at parse time â
# anything outside refuses with BF101 before reaching this helper):
#
# (a,b) => a.f - b.f â field, numeric
# (a,b) => a - b â self, numeric
# (a,b) => a[.f].localeCompare(b[.f]) â field|self, string
# (a,b) => a.f > b.f ? 1 : -1 â field|self, auto
# any of the above ||-chained â multi-key tie-breaks
# (and reversed-operand variants for `desc`).
#
# `auto` (relational-ternary lowering) compares numerically when both
# keys `looks_like_number`, else lexically â Go's `bf_sort` applies the
# same rule so the two template adapters stay byte-equal.
#
# A future `nulls => 'first' | 'last'` knob can land per key without
# churn â the opts hash is the right place to grow.
# Evaluator-driven sort / reduce (#2018): the comparator / reducer body rides
# as a serialized-ParsedExpr JSON string and is evaluated per element, delegating
# to the shared BarefootJS::Evaluator. The adapter emits `bf->sort_eval(...)` /
# `bf->reduce_eval(...)` for any pure comparator / reducer body; a body it can't
# model (e.g. localeCompare) keeps the legacy `bf->sort` / `bf->reduce` path.
sub sort_eval ($self, $recv, $cmp_json, $param_a, $param_b, $base_env = {}) {
return BarefootJS::Evaluator::sort_by_json($recv, $cmp_json, $param_a, $param_b, $base_env);
}
sub reduce_eval ($self, $recv, $body_json, $acc_name, $item_name, $init, $direction = 'left', $base_env = {}) {
return BarefootJS::Evaluator::fold_json($recv, $body_json, $acc_name, $item_name, $init, $direction, $base_env);
}
# Evaluator-driven higher-order predicates (#2018, P2): the predicate body
# rides as a serialized-ParsedExpr JSON string evaluated per element, delegating
lib/BarefootJS/Evaluator.pm view on Meta::CPAN
sub _unary ($op, $v) {
return _bool(!_truthy($v)) if $op eq '!';
return -_to_number($v) if $op eq '-';
return _to_number($v) if $op eq '+';
return undef;
}
# ---------------------------------------------------------------------------
# Built-in calls (the deterministic allowlist). Locale-sensitive builtins
# (localeCompare) are deliberately excluded to keep the backends isomorphic.
# ---------------------------------------------------------------------------
# _builtin_name: resolve a `call` callee to its builtin name (e.g.
# "Math.max"), or '' when the callee is not an allowlisted builtin reference.
sub _builtin_name ($callee) {
return '' unless ref $callee eq 'HASH';
my $kind = $callee->{kind} // '';
if ($kind eq 'identifier') {
return $callee->{name} // '';
}
t/evaluator.t view on Meta::CPAN
# directions.
subtest 'fold: direction is observable for string concat' => sub {
my $body = nbin('+', nid('acc'), nid('item'));
my $items = [ 'a', 'b', 'c' ];
is(BarefootJS::Evaluator::fold($items, $body, 'acc', 'item', '', 'left'), 'abc', 'left');
is(BarefootJS::Evaluator::fold($items, $body, 'acc', 'item', '', 'right'), 'cba', 'right');
};
# sort_by lifts bf_sort's comparator pattern restriction: a comparator that
# calls Math.abs on each operand's field is outside the subtraction /
# localeCompare / relational-ternary catalogue, but is just another pure
# expression to the evaluator.
subtest 'sort_by: arbitrary comparator body (abs-of-field difference)' => sub {
my $cmp = nbin('-',
ncall_math('abs', nmem(nid('a'), 'v')),
ncall_math('abs', nmem(nid('b'), 'v')),
);
my $items = [ { v => -5 }, { v => 3 }, { v => -1 } ];
my $sorted = BarefootJS::Evaluator::sort_by($items, $cmp, 'a', 'b');
is_deeply([ map { $_->{v} } @$sorted ], [ -1, 3, -5 ], 'ascending by |v|');
};
t/template_primitives.t view on Meta::CPAN
# Numeric field, descending â reverse direction.
is $bf->sort($items, { keys => [{ key_kind => 'field', key => 'price', compare_type => 'numeric', direction => 'desc' }] }),
[ { name => 'c', price => 30 }, { name => 'b', price => 20 }, { name => 'a', price => 10 } ],
'numeric field desc';
# Primitive numeric â `(a, b) => a - b` shape (key_kind=self).
is $bf->sort([3, 1, 2], { keys => [{ key_kind => 'self', compare_type => 'numeric', direction => 'asc' }] }),
[1, 2, 3],
'numeric self asc';
# Primitive string â `(a, b) => a.localeCompare(b)`. The Perl
# helper falls back to plain `cmp` (byte-ordering); within the
# same case it matches lexicographic order.
is $bf->sort(['charlie', 'alice', 'bob'], { keys => [{ key_kind => 'self', compare_type => 'string', direction => 'asc' }] }),
['alice', 'bob', 'charlie'],
'string self asc';
is $bf->sort(['charlie', 'alice', 'bob'], { keys => [{ key_kind => 'self', compare_type => 'string', direction => 'desc' }] }),
['charlie', 'bob', 'alice'],
'string self desc';
t/template_primitives.t view on Meta::CPAN
is $bf->sort('not an array', { keys => [{ key_kind => 'self', compare_type => 'numeric', direction => 'asc' }] }), [], 'scalar receiver â []';
is $bf->sort({a => 1}, { keys => [{ key_kind => 'self', compare_type => 'numeric', direction => 'asc' }] }), [], 'hash ref receiver â []';
# Empty array short-circuits.
is $bf->sort([], { keys => [{ key_kind => 'field', key => 'price', compare_type => 'numeric', direction => 'asc' }] }), [], 'empty array â []';
};
# Multi-key (`||`-chained) comparator: a tie on the primary key falls
# through to the next key. (#1448 Tier B follow-up.)
subtest 'sort â multi-key (||-chain) tie-breaks' => sub {
# `(a,b) => a.p - b.p || a.name.localeCompare(b.name)`.
my $items = [
{ p => 1, name => 'b' },
{ p => 1, name => 'a' },
{ p => 0, name => 'c' },
];
is $bf->sort($items, { keys => [
{ key_kind => 'field', key => 'p', compare_type => 'numeric', direction => 'asc' },
{ key_kind => 'field', key => 'name', compare_type => 'string', direction => 'asc' },
] }),
[ { p => 0, name => 'c' }, { p => 1, name => 'a' }, { p => 1, name => 'b' } ],
( run in 0.694 second using v1.01-cache-2.11-cpan-a5162978ef8 )