BarefootJS

 view release on metacpan or  search on metacpan

lib/BarefootJS/Evaluator.pm  view on Meta::CPAN

    if (_is_string($l) && _is_string($r)) {
        $c = $l lt $r ? -1 : $l gt $r ? 1 : 0;
    }
    else {
        my $ln = _to_number($l);
        my $rn = _to_number($r);
        return _bool(0) if $ln != $ln || $rn != $rn;    # NaN → false
        $c = $ln < $rn ? -1 : $ln > $rn ? 1 : 0;
    }
    return _bool($c < 0)  if $op eq '<';
    return _bool($c <= 0) if $op eq '<=';
    return _bool($c > 0)  if $op eq '>';
    return _bool($c >= 0) if $op eq '>=';
    return _bool(0);
}

sub _strict_eq ($l, $r) {
    # Strict `===`: equal JS type and value, no coercion.
    my $ln = _is_number($l);
    my $rn = _is_number($r);
    if ($ln && $rn) {
        my ($lf, $rf) = ($l + 0, $r + 0);
        return 0 if $lf != $lf || $rf != $rf;    # NaN
        return $lf == $rf ? 1 : 0;
    }
    return 0 if $ln != $rn;    # one numeric, one not
    if (!defined $l) { return !defined $r ? 1 : 0 }
    return 0 if !defined $r;
    my $lb = ref $l eq 'JSON::PP::Boolean';
    my $rb = ref $r eq 'JSON::PP::Boolean';
    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);
}

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} // '';
    }
    if ($kind eq 'member' && !$callee->{computed}) {
        my $obj = $callee->{object};
        return '' unless ref $obj eq 'HASH' && ($obj->{kind} // '') eq 'identifier';
        return ($obj->{name} // '') . '.' . ($callee->{property} // '');
    }
    return '';
}

# _math_round: half rounds toward +Infinity (JS Math.round: 2.5→3, -2.5→-2),
# matching the existing round helper rather than half-away-from-zero.
sub _math_round ($n) {
    return POSIX::floor($n + 0.5);
}

sub _call_builtin ($name, $args) {
    if ($name eq 'Math.max') {
        my $m = -(9**9**9);    # JS Math.max() with no args is -Infinity
        for my $a (@$args) {
            my $n = _to_number($a);
            return $n if $n != $n;    # any NaN argument ⇒ NaN (JS / Go)
            $m = $n if $n > $m;
        }
        return $m;
    }
    if ($name eq 'Math.min') {
        my $m = 9**9**9;       # JS Math.min() with no args is +Infinity
        for my $a (@$args) {
            my $n = _to_number($a);
            return $n if $n != $n;    # any NaN argument ⇒ NaN (JS / Go)
            $m = $n if $n < $m;
        }
        return $m;
    }
    return abs(_to_number($args->[0]))          if $name eq 'Math.abs';
    return POSIX::floor(_to_number($args->[0])) if $name eq 'Math.floor';
    return POSIX::ceil(_to_number($args->[0]))  if $name eq 'Math.ceil';
    return _math_round(_to_number($args->[0]))  if $name eq 'Math.round';
    return _to_string($args->[0])              if $name eq 'String';
    return _to_number($args->[0])              if $name eq 'Number';
    return _bool(_truthy($args->[0]))          if $name eq 'Boolean';
    # Any other callee is outside the subset (refused upstream).
    return undef;
}

# ---------------------------------------------------------------------------
# Member / index access
# ---------------------------------------------------------------------------

sub _read_property ($obj, $key) {
    return undef unless defined $obj;



( run in 0.573 second using v1.01-cache-2.11-cpan-a5162978ef8 )