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 )