BarefootJS
view release on metacpan or search on metacpan
lib/BarefootJS/Evaluator.pm view on Meta::CPAN
# (packages/adapter-tests/helper-vectors/eval-vectors.json), shared with the Go
# evaluator (bf.go) â same input â same output.
#
# The coercion below is JS-faithful (ToNumber / ToString / ToBoolean, strict
# equality) and deliberately distinct from the divergent bf->string / number
# helpers in BarefootJS.pm, so the contract is unambiguous and the two
# template adapters stay byte-equal with each other and with Go.
# evaluate($node, $env)
#
# Evaluate a decoded ParsedExpr node (a hashref keyed by `kind`) against the
# environment hashref ($env), returning a Perl value (number, string,
# JSON::PP::Boolean, undef for null, arrayref, hashref). The matching JSON
# entry point is eval_json() below.
sub evaluate ($node, $env) {
return undef unless ref $node eq 'HASH';
my $kind = $node->{kind} // '';
if ($kind eq 'literal') {
return $node->{value};
}
lib/BarefootJS/Evaluator.pm view on Meta::CPAN
return () unless $prop eq 'map' || $prop eq 'filter';
my $args = $node->{args} // [];
return () unless @$args;
my $arrow = $args->[0];
return () unless ref $arrow eq 'HASH' && ($arrow->{kind} // '') eq 'arrow';
return ($prop, $callee->{object}, $arrow);
}
# _array_callback($method, $object_node, $arrow_node, $env): evaluate the
# receiver (`$object_node`) against $env, then map/filter it through the
# arrow's body. `params` in the decoded JSON is an array ref of plain
# strings (e.g. `["t"]` or `["t","i"]`) â 1 param binds the element, 2
# params bind (element, index). The child env is a COPY of the parent env
# (never mutated in place across sibling iterations), with the param
# name(s) bound per call â matches Go's per-call `inner` env copy.
sub _array_callback ($method, $object_node, $arrow_node, $env) {
my $arr = evaluate($object_node, $env);
return undef unless ref $arr eq 'ARRAY';
my @params = @{ $arrow_node->{params} // [] };
my $body = $arrow_node->{body};
my $call_cb = sub {
lib/BarefootJS/Evaluator.pm view on Meta::CPAN
# eval_json($json, $env): decode a ParsedExpr JSON string and evaluate it.
# Mirrors the Go EvalExpr entry point. Requires JSON::PP only when used.
sub eval_json ($json, $env) {
require JSON::PP;
my $node = JSON::PP->new->decode($json);
return evaluate($node, $env);
}
# ---------------------------------------------------------------------------
# JS value classification. JSON-decoded strings carry the string flag (POK)
# but no numeric flag; JSON-decoded numbers carry IOK/NOK. This lets the
# evaluator tell the JS *string* "10" from the JS *number* 10 â essential for
# the `+` overload and relational comparison â which looks_like_number alone
# cannot (it is true for both).
# ---------------------------------------------------------------------------
sub _is_string ($v) {
return 0 if !defined $v || ref $v;
my $f = B::svref_2object(\$v)->FLAGS;
return (($f & B::SVf_POK) && !($f & (B::SVf_IOK | B::SVf_NOK))) ? 1 : 0;
}
lib/BarefootJS/Evaluator.pm view on Meta::CPAN
if ($lb || $rb) {
return 0 unless $lb && $rb;
return ((!!$l) == (!!$r)) ? 1 : 0;
}
return ($l eq $r ? 1 : 0) if _is_string($l) && _is_string($r);
return 0;
}
# _same_value_zero: `Array.prototype.includes` membership test â `===`
# except `NaN` equals itself (and +0/-0 are not distinguished, which the
# JSON-decoded values here can't represent anyway). Reuses `_strict_eq`'s
# type/value rules and only special-cases the two-NaN case that `_strict_eq`
# (deliberately, for `===`) reports as unequal.
sub _same_value_zero ($l, $r) {
if (_is_number($l) && _is_number($r)) {
my ($lf, $rf) = ($l + 0, $r + 0);
return 1 if $lf != $lf && $rf != $rf; # NaN sameValueZero NaN
}
return _strict_eq($l, $r);
}
lib/BarefootJS/Evaluator.pm view on Meta::CPAN
my @out;
for my $item (@arr) {
$env{$param} = $item;
push @out, evaluate($proj, \%env);
}
return \@out;
}
# ---------------------------------------------------------------------------
# JSON-string seams â the adapters emit `bf->filter_eval($recv, '<json>', â¦)`;
# the predicate body arrives as a JSON string here, decoded then handed to the
# helper above (mirroring fold_json / sort_by_json).
# ---------------------------------------------------------------------------
sub filter_json ($items, $pred_json, $param, $base_env = undef) {
require JSON::PP;
return filter($items, JSON::PP->new->decode($pred_json), $param, $base_env);
}
sub every_json ($items, $pred_json, $param, $base_env = undef) {
require JSON::PP;
lib/BarefootJS/SearchParams.pm view on Meta::CPAN
# adapters' `?? â //` lowering: get() returns the first value for a key, or
# `undef` when the key is absent. Perl's `//` (defined-or) coalesces only
# `undef`, so an absent key falls back to the author's default while a
# present-but-empty value (`?sort=`) keeps the empty string â the same
# distinction JS `??` draws between `null` and `''`. (This is a closer match
# than the Go adapter, whose `or` lowering also coalesces the empty string.)
# new($class, $query = '')
#
# Parse a raw query string into the reader. A leading '?' is tolerated, '+'
# decodes to a space, and %XX escapes are decoded â mirroring URLSearchParams's
# application/x-www-form-urlencoded parsing. A malformed pair never dies; it
# simply contributes nothing, matching the browser's lenient parsing.
sub new ($class, $query = '') {
$query //= '';
$query =~ s/\A\?//;
my %values;
for my $pair (split /[&;]/, $query) {
next if $pair eq '';
my ($key, $val) = split /=/, $pair, 2;
$key = _decode($key);
t/helper_vectors.t view on Meta::CPAN
# ARGS are lowered to 1/0 before reaching a binding. Expects keep their
# boolean identity (vector_ok compares those by truthiness).
sub normalize_arg {
my ($v) = @_;
return [ map { normalize_arg($_) } @$v ] if ref $v eq 'ARRAY';
return { map { $_ => normalize_arg($v->{$_}) } keys %$v } if ref $v eq 'HASH';
return JSON::PP::is_bool($v) ? ($v ? 1 : 0) : $v;
}
# _match: boolean form of the spec's value-compat comparison against a
# JSON-decoded expect â sentinel hashes, booleans by truthiness,
# numbers numerically, arrays/hashes recursively.
sub _match {
my ($got, $expect) = @_;
return !defined $got if !defined $expect;
if (ref $expect eq 'HASH' && exists $expect->{'$num'}) {
my $kind = $expect->{'$num'};
return 0 unless defined $got && looks_like_number($got);
return $got != $got ? 1 : 0 if $kind eq 'NaN';
my $inf = 9**9**9;
return $got == ($kind eq 'Infinity' ? $inf : -$inf) ? 1 : 0;
t/search_params.t view on Meta::CPAN
my $empty = BarefootJS->search_params('sort=');
is(($empty->get('sort') // 'none'), '', 'present-but-empty value is kept, NOT defaulted');
};
# Percent-encoded UTF-8 decodes to characters via the core `utf8::decode`
# builtin (no URI / URI::Escape dependency) â kept here rather than in the
# ASCII-only shared vectors to avoid cross-harness byte/char encoding skew.
subtest 'UTF-8 percent-decoding (core utf8::decode, no URI dep)' => sub {
my $sp = BarefootJS->search_params('q=%E2%9C%93');
is $sp->get('q'), "\x{2713}", 'percent-encoded UTF-8 â decoded character (â)';
};
# Malformed input must degrade, never die (SSR survives junk query strings).
subtest 'lenient parsing never dies' => sub {
ok lives { BarefootJS->search_params(undef) }, 'undef query';
ok lives { BarefootJS->search_params('&&&')->get('x') }, 'only separators';
ok lives { BarefootJS->search_params('=novalue')->get('x') }, 'empty key pair';
};
done_testing;
( run in 1.868 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )