diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..ba223e2 --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,11 @@ +((nil . ((indent-tabs-mode . nil) + (css-indent-offset . 2) + (js-indent-level . 2) + (c-basic-offset . 2) + (fill-column . 80)))) + +;; (defun my-indent-setup () +;; (c-set-offset 'innamespace [0]) +;; (c-set-offset 'statement-cont 0) +;; (c-set-offset 'case-label '+) +;; (c-set-offset 'arglist-intro '++)) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0a40189 --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2013, Julian Scheid +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..38a1fa8 --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +capnproto-js +============ + +A port of the [Cap'n Proto](http://kentonv.github.io/capnproto/) runtime to +JavaScript. Includes a schema compiler that generates JavaScript. + +Status +------ + +This is work in progress. It is currently based on an [outdated +version](https://github.com/kentonv/capnproto/commit/798ff87b1b7e0a2da96355a34fa6e0aa10aaae85) +of Cap'n Proto, which means it doesn't include the recent object capability and +RPC additions or any recent bugfixes. Also, dynamic access (at runtime, based +on schemas) is not implemented and probably will never be, as JavaScript is +itself a dynamic language. + +It includes a mostly complete port of the Cap'n Proto test suite with over 100 +test cases passing. + +There are a number of open issues, including the following: + +* This isn't very optimized yet and might be terribly slow. + +* Currently you need to use Google Closure Compiler to link your code. + +* Functions are distributed over nested namespaces, which makes client code + (pre-link) somewhat verbose. + +* Functions don't check their argument types and are mostly lacking Google + Closure parameter annotations, which means it's pretty easy to shoot yourself + in the foot. + +* Generated code is overly verbose and in places uses inconsistent naming. + +* Some of the code is a bit messy and redundant, in particular primitive + getter/setters and capnp_list.js. + +* No support for Mozilla's int64 datatype or any of the common JavaScript bignum + libraries. int64 are currently represented as an array of two int32s. + +Getting Started +--------------- + +The build isn't very straighforward at the moment. You will need: + +* The capnproto source code, to be safe checkout the correct version (798ff87b1b7e0a2da96355a34fa6e0aa10aaae85) +* A corresponding capnproto installation or build tree +* [Google Closure Compiler](https://developers.google.com/closure/compiler/) +* [Google Closure Library](https://developers.google.com/closure/library/) + +You need to edit the following files and adjust paths manually: + +* javascript/build-tests.sh +* javascript/tests/all_tests.html + +Then try this: + +``` +capnproto_js=/path/to/capnproto-js +capnproto=/path/to/capnproto +capnproto_build=/path/to/capnproto-build +$capnproto_js/c++/configure \ + --with-capnp-source=$capnproto/c++ \ + --with-capnp=$capnproto_build/capnp \ + --with-capnp-libdir=$capnproto_build/.libs +make +$capnproto_js/javascript/build-tests.sh +open test/all_tests.html # in your browser +``` + +Compatibility +------------- + +All tests pass in current versions of Chrome (31), Firefox (25) and Safari (7). +Other browsers have not been tested yet. diff --git a/build-aux/tap-driver.pl b/build-aux/tap-driver.pl new file mode 100755 index 0000000..aca65fe --- /dev/null +++ b/build-aux/tap-driver.pl @@ -0,0 +1,564 @@ +#! /usr/bin/env perl +# Copyright (C) 2011-2013 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# This file is maintained in Automake, please report +# bugs to or send patches to +# . + +# ---------------------------------- # +# Imports, static data, and setup. # +# ---------------------------------- # + +use warnings FATAL => 'all'; +use strict; +use Getopt::Long (); +use TAP::Parser; + +my $VERSION = '2012-02-01.19'; # UTC + +my $ME = "tap-driver.pl"; + +my $USAGE = <<'END'; +Usage: + tap-driver --test-name=NAME --log-file=PATH --trs-file=PATH + [--expect-failure={yes|no}] [--color-tests={yes|no}] + [--enable-hard-errors={yes|no}] [--ignore-exit] + [--diagnostic-string=STRING] [--merge|--no-merge] + [--comments|--no-comments] [--] TEST-COMMAND +The `--test-name', `--log-file' and `--trs-file' options are mandatory. +END + +my $HELP = "$ME: TAP-aware test driver for Automake testsuite harness." . + "\n" . $USAGE; + +# Keep this in sync with `lib/am/check.am:$(am__tty_colors)'. +my %COLOR = ( + red => "\e[0;31m", + grn => "\e[0;32m", + lgn => "\e[1;32m", + blu => "\e[1;34m", + mgn => "\e[0;35m", + brg => "\e[1m", + std => "\e[m", +); + +# It's important that NO_PLAN evaluates "false" as a boolean. +use constant NO_PLAN => 0; +use constant EARLY_PLAN => 1; +use constant LATE_PLAN => 2; + +# ------------------- # +# Global variables. # +# ------------------- # + +my $testno = 0; # Number of test results seen so far. +my $bailed_out = 0; # Whether a "Bail out!" directive has been seen. +my $parser; # TAP parser object (will be initialized later). + +# Whether the TAP plan has been seen or not, and if yes, which kind +# it is ("early" is seen before any test result, "late" otherwise). +my $plan_seen = NO_PLAN; + +# ----------------- # +# Option parsing. # +# ----------------- # + +my %cfg = ( + "color-tests" => 0, + "expect-failure" => 0, + "merge" => 0, + "comments" => 0, + "ignore-exit" => 0, +); + +my $test_script_name = undef; +my $log_file = undef; +my $trs_file = undef; +my $diag_string = "#"; + +Getopt::Long::GetOptions + ( + 'help' => sub { print $HELP; exit 0; }, + 'version' => sub { print "$ME $VERSION\n"; exit 0; }, + 'test-name=s' => \$test_script_name, + 'log-file=s' => \$log_file, + 'trs-file=s' => \$trs_file, + 'color-tests=s' => \&bool_opt, + 'expect-failure=s' => \&bool_opt, + 'enable-hard-errors=s' => sub {}, # No-op. + 'diagnostic-string=s' => \$diag_string, + 'comments' => sub { $cfg{"comments"} = 1; }, + 'no-comments' => sub { $cfg{"comments"} = 0; }, + 'merge' => sub { $cfg{"merge"} = 1; }, + 'no-merge' => sub { $cfg{"merge"} = 0; }, + 'ignore-exit' => sub { $cfg{"ignore-exit"} = 1; }, + ) or exit 1; + +# ------------- # +# Prototypes. # +# ------------- # + +sub add_test_result ($); +sub bool_opt ($$); +sub colored ($$); +sub copy_in_global_log (); +sub decorate_result ($); +sub extract_tap_comment ($); +sub finish (); +sub get_global_test_result (); +sub get_test_exit_message (); +sub get_test_results (); +sub handle_tap_bailout ($); +sub handle_tap_plan ($); +sub handle_tap_result ($); +sub is_null_string ($); +sub main (@); +sub must_recheck (); +sub report ($;$); +sub setup_io (); +sub setup_parser (@); +sub stringify_result_obj ($); +sub testsuite_error ($); +sub trap_perl_warnings_and_errors (); +sub write_test_results (); +sub yn ($); + +# -------------- # +# Subroutines. # +# -------------- # + +sub bool_opt ($$) +{ + my ($opt, $val) = @_; + if ($val =~ /^(?:y|yes)\z/i) + { + $cfg{$opt} = 1; + } + elsif ($val =~ /^(?:n|no)\z/i) + { + $cfg{$opt} = 0; + } + else + { + die "$ME: invalid argument '$val' for option '$opt'\n"; + } +} + +# If the given string is undefined or empty, return true, otherwise +# return false. This function is useful to avoid pitfalls like: +# if ($message) { print "$message\n"; } +# which wouldn't print anything if $message is the literal "0". +sub is_null_string ($) +{ + my $str = shift; + return ! (defined $str and length $str); +} + +# Convert a boolean to a "yes"/"no" string. +sub yn ($) +{ + my $bool = shift; + return $bool ? "yes" : "no"; +} + +TEST_RESULTS : +{ + my (@test_results_list, %test_results_seen); + + sub add_test_result ($) + { + my $res = shift; + push @test_results_list, $res; + $test_results_seen{$res} = 1; + } + + sub get_test_results () + { + return @test_results_list; + } + + # Whether the test script should be re-run by "make recheck". + sub must_recheck () + { + return grep { !/^(?:XFAIL|PASS|SKIP)$/ } (keys %test_results_seen); + } + + # Whether the content of the log file associated to this test should + # be copied into the "global" test-suite.log. + sub copy_in_global_log () + { + return grep { not $_ eq "PASS" } (keys %test_results_seen); + } + + # FIXME: this can certainly be improved ... + sub get_global_test_result () + { + return "ERROR" + if $test_results_seen{"ERROR"}; + return "FAIL" + if $test_results_seen{"FAIL"} || $test_results_seen{"XPASS"}; + return "SKIP" + if scalar keys %test_results_seen == 1 && $test_results_seen{"SKIP"}; + return "PASS"; + } + +} + +sub write_test_results () +{ + open RES, ">", $trs_file or die "$ME: opening $trs_file: $!\n"; + print RES ":global-test-result: " . get_global_test_result . "\n"; + print RES ":recheck: " . yn (must_recheck) . "\n"; + print RES ":copy-in-global-log: " . yn (copy_in_global_log) . "\n"; + foreach my $result (get_test_results) + { + print RES ":test-result: $result\n"; + } + close RES or die "$ME: closing $trs_file: $!\n"; +} + +sub trap_perl_warnings_and_errors () +{ + $SIG{__WARN__} = $SIG{__DIE__} = sub + { + # Be sure to send the warning/error message to the original stderr + # (presumably the console), not into the log file. + open STDERR, ">&OLDERR"; + die @_; + } +} + +sub setup_io () +{ + # Redirect stderr and stdout to a temporary log file. Save the + # original stdout stream, since we need it to print testsuite + # progress output. Save original stderr stream, so that we can + # redirect warning and error messages from perl there. + open LOG, ">", $log_file or die "$ME: opening $log_file: $!\n"; + open OLDOUT, ">&STDOUT" or die "$ME: duplicating stdout: $!\n"; + open OLDERR, ">&STDERR" or die "$ME: duplicating stdout: $!\n"; + *OLDERR = *OLDERR; # To pacify a "used only once" warning. + trap_perl_warnings_and_errors; + open STDOUT, ">&LOG" or die "$ME: redirecting stdout: $!\n"; + open STDERR, ">&LOG" or die "$ME: redirecting stderr: $!\n"; +} + +sub setup_parser (@) +{ + local $@ = ''; + eval { $parser = TAP::Parser->new ({exec => \@_, merge => $cfg{merge}}) }; + if ($@ ne '') + { + # Don't use the error message in $@ as set by TAP::Parser, since + # currently it's both too generic (at the point of being basically + # useless) and quite long. + report "ERROR", "- couldn't execute test script"; + finish; + } +} + +sub get_test_exit_message () +{ + my $wstatus = $parser->wait; + # Watch out for possible internal errors. + die "$ME: couldn't get the exit status of the TAP producer" + unless defined $wstatus; + # Return an undefined value if the producer exited with success. + return unless $wstatus; + # Otherwise, determine whether it exited with error or was terminated + # by a signal. + use POSIX qw (WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG); + if (WIFEXITED ($wstatus)) + { + return sprintf "exited with status %d", WEXITSTATUS ($wstatus); + } + elsif (WIFSIGNALED ($wstatus)) + { + return sprintf "terminated by signal %d", WTERMSIG ($wstatus); + } + else + { + return "terminated abnormally"; + } +} + +sub stringify_result_obj ($) +{ + my $result_obj = shift; + my $COOKED_PASS = $cfg{"expect-failure"} ? "XPASS": "PASS"; + my $COOKED_FAIL = $cfg{"expect-failure"} ? "XFAIL": "FAIL"; + if ($result_obj->is_unplanned || $result_obj->number != $testno) + { + return "ERROR"; + } + elsif ($plan_seen == LATE_PLAN) + { + return "ERROR"; + } + elsif (!$result_obj->directive) + { + return $result_obj->is_ok ? $COOKED_PASS: $COOKED_FAIL; + } + elsif ($result_obj->has_todo) + { + return $result_obj->is_actual_ok ? "XPASS" : "XFAIL"; + } + elsif ($result_obj->has_skip) + { + return $result_obj->is_ok ? "SKIP" : $COOKED_FAIL; + } + die "$ME: INTERNAL ERROR"; # NOTREACHED +} + +sub colored ($$) +{ + my ($color_name, $text) = @_; + return $COLOR{$color_name} . $text . $COLOR{'std'}; +} + +sub decorate_result ($) +{ + my $result = shift; + return $result unless $cfg{"color-tests"}; + my %color_for_result = + ( + "ERROR" => 'mgn', + "PASS" => 'grn', + "XPASS" => 'red', + "FAIL" => 'red', + "XFAIL" => 'lgn', + "SKIP" => 'blu', + ); + if (my $color = $color_for_result{$result}) + { + return colored ($color, $result); + } + else + { + return $result; # Don't colorize unknown stuff. + } +} + +sub report ($;$) +{ + my ($msg, $result, $explanation) = (undef, @_); + if ($result =~ /^(?:X?(?:PASS|FAIL)|SKIP|ERROR)/) + { + $msg = ": $test_script_name"; + add_test_result $result; + } + elsif ($result eq "#") + { + $msg = " $test_script_name:"; + } + else + { + die "$ME: INTERNAL ERROR"; # NOTREACHED + } + $msg .= " $explanation" if defined $explanation; + $msg .= "\n"; + # Output on console might be colorized. + print OLDOUT decorate_result ($result) . $msg; + # Log the result in the log file too, to help debugging (this is + # especially true when said result is a TAP error or "Bail out!"). + print $result . $msg; +} + +sub testsuite_error ($) +{ + report "ERROR", "- $_[0]"; +} + +sub handle_tap_result ($) +{ + $testno++; + my $result_obj = shift; + + my $test_result = stringify_result_obj $result_obj; + my $string = $result_obj->number; + + my $description = $result_obj->description; + $string .= " $description" + unless is_null_string $description; + + if ($plan_seen == LATE_PLAN) + { + $string .= " # AFTER LATE PLAN"; + } + elsif ($result_obj->is_unplanned) + { + $string .= " # UNPLANNED"; + } + elsif ($result_obj->number != $testno) + { + $string .= " # OUT-OF-ORDER (expecting $testno)"; + } + elsif (my $directive = $result_obj->directive) + { + $string .= " # $directive"; + my $explanation = $result_obj->explanation; + $string .= " $explanation" + unless is_null_string $explanation; + } + + report $test_result, $string; +} + +sub handle_tap_plan ($) +{ + my $plan = shift; + if ($plan_seen) + { + # Error, only one plan per stream is acceptable. + testsuite_error "multiple test plans"; + return; + } + # The TAP plan can come before or after *all* the TAP results; we speak + # respectively of an "early" or a "late" plan. If we see the plan line + # after at least one TAP result has been seen, assume we have a late + # plan; in this case, any further test result seen after the plan will + # be flagged as an error. + $plan_seen = ($testno >= 1 ? LATE_PLAN : EARLY_PLAN); + # If $testno > 0, we have an error ("too many tests run") that will be + # automatically dealt with later, so don't worry about it here. If + # $plan_seen is true, we have an error due to a repeated plan, and that + # has already been dealt with above. Otherwise, we have a valid "plan + # with SKIP" specification, and should report it as a particular kind + # of SKIP result. + if ($plan->directive && $testno == 0) + { + my $explanation = is_null_string ($plan->explanation) ? + undef : "- " . $plan->explanation; + report "SKIP", $explanation; + } +} + +sub handle_tap_bailout ($) +{ + my ($bailout, $msg) = ($_[0], "Bail out!"); + $bailed_out = 1; + $msg .= " " . $bailout->explanation + unless is_null_string $bailout->explanation; + testsuite_error $msg; +} + +sub extract_tap_comment ($) +{ + my $line = shift; + if (index ($line, $diag_string) == 0) + { + # Strip leading `$diag_string' from `$line'. + $line = substr ($line, length ($diag_string)); + # And strip any leading and trailing whitespace left. + $line =~ s/(?:^\s*|\s*$)//g; + # Return what is left (if any). + return $line; + } + return ""; +} + +sub finish () +{ + write_test_results; + close LOG or die "$ME: closing $log_file: $!\n"; + exit 0; +} + +sub main (@) +{ + setup_io; + setup_parser @_; + + while (defined (my $cur = $parser->next)) + { + # Verbatim copy any input line into the log file. + print $cur->raw . "\n"; + # Parsing of TAP input should stop after a "Bail out!" directive. + next if $bailed_out; + + if ($cur->is_plan) + { + handle_tap_plan ($cur); + } + elsif ($cur->is_test) + { + handle_tap_result ($cur); + } + elsif ($cur->is_bailout) + { + handle_tap_bailout ($cur); + } + elsif ($cfg{comments}) + { + my $comment = extract_tap_comment ($cur->raw); + report "#", "$comment" if length $comment; + } + } + # A "Bail out!" directive should cause us to ignore any following TAP + # error, as well as a non-zero exit status from the TAP producer. + if (!$bailed_out) + { + if (!$plan_seen) + { + testsuite_error "missing test plan"; + } + elsif ($parser->tests_planned != $parser->tests_run) + { + my ($planned, $run) = ($parser->tests_planned, $parser->tests_run); + my $bad_amount = $run > $planned ? "many" : "few"; + testsuite_error (sprintf "too %s tests run (expected %d, got %d)", + $bad_amount, $planned, $run); + } + if (!$cfg{"ignore-exit"}) + { + my $msg = get_test_exit_message (); + testsuite_error $msg if $msg; + } + } + finish; +} + +# ----------- # +# Main code. # +# ----------- # + +main @ARGV; + +# Local Variables: +# perl-indent-level: 2 +# perl-continued-statement-offset: 2 +# perl-continued-brace-offset: 0 +# perl-brace-offset: 0 +# perl-brace-imaginary-offset: 0 +# perl-label-offset: -2 +# cperl-indent-level: 2 +# cperl-brace-offset: 0 +# cperl-continued-brace-offset: 0 +# cperl-label-offset: -2 +# cperl-extra-newline-before-brace: t +# cperl-merge-trailing-else: nil +# cperl-continued-statement-offset: 2 +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "my $VERSION = " +# time-stamp-format: "'%:y-%02m-%02d.%02H'" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/build-aux/tap-driver.sh b/build-aux/tap-driver.sh new file mode 100644 index 0000000..19aa531 --- /dev/null +++ b/build-aux/tap-driver.sh @@ -0,0 +1,652 @@ +#! /bin/sh +# Copyright (C) 2011-2013 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# This file is maintained in Automake, please report +# bugs to or send patches to +# . + +scriptversion=2011-12-27.17; # UTC + +# Make unconditional expansion of undefined variables an error. This +# helps a lot in preventing typo-related bugs. +set -u + +me=tap-driver.sh + +fatal () +{ + echo "$me: fatal: $*" >&2 + exit 1 +} + +usage_error () +{ + echo "$me: $*" >&2 + print_usage >&2 + exit 2 +} + +print_usage () +{ + cat < + # + trap : 1 3 2 13 15 + if test $merge -gt 0; then + exec 2>&1 + else + exec 2>&3 + fi + "$@" + echo $? + ) | LC_ALL=C ${AM_TAP_AWK-awk} \ + -v me="$me" \ + -v test_script_name="$test_name" \ + -v log_file="$log_file" \ + -v trs_file="$trs_file" \ + -v expect_failure="$expect_failure" \ + -v merge="$merge" \ + -v ignore_exit="$ignore_exit" \ + -v comments="$comments" \ + -v diag_string="$diag_string" \ +' +# FIXME: the usages of "cat >&3" below could be optimized when using +# FIXME: GNU awk, and/on on systems that supports /dev/fd/. + +# Implementation note: in what follows, `result_obj` will be an +# associative array that (partly) simulates a TAP result object +# from the `TAP::Parser` perl module. + +## ----------- ## +## FUNCTIONS ## +## ----------- ## + +function fatal(msg) +{ + print me ": " msg | "cat >&2" + exit 1 +} + +function abort(where) +{ + fatal("internal error " where) +} + +# Convert a boolean to a "yes"/"no" string. +function yn(bool) +{ + return bool ? "yes" : "no"; +} + +function add_test_result(result) +{ + if (!test_results_index) + test_results_index = 0 + test_results_list[test_results_index] = result + test_results_index += 1 + test_results_seen[result] = 1; +} + +# Whether the test script should be re-run by "make recheck". +function must_recheck() +{ + for (k in test_results_seen) + if (k != "XFAIL" && k != "PASS" && k != "SKIP") + return 1 + return 0 +} + +# Whether the content of the log file associated to this test should +# be copied into the "global" test-suite.log. +function copy_in_global_log() +{ + for (k in test_results_seen) + if (k != "PASS") + return 1 + return 0 +} + +# FIXME: this can certainly be improved ... +function get_global_test_result() +{ + if ("ERROR" in test_results_seen) + return "ERROR" + if ("FAIL" in test_results_seen || "XPASS" in test_results_seen) + return "FAIL" + all_skipped = 1 + for (k in test_results_seen) + if (k != "SKIP") + all_skipped = 0 + if (all_skipped) + return "SKIP" + return "PASS"; +} + +function stringify_result_obj(result_obj) +{ + if (result_obj["is_unplanned"] || result_obj["number"] != testno) + return "ERROR" + + if (plan_seen == LATE_PLAN) + return "ERROR" + + if (result_obj["directive"] == "TODO") + return result_obj["is_ok"] ? "XPASS" : "XFAIL" + + if (result_obj["directive"] == "SKIP") + return result_obj["is_ok"] ? "SKIP" : COOKED_FAIL; + + if (length(result_obj["directive"])) + abort("in function stringify_result_obj()") + + return result_obj["is_ok"] ? COOKED_PASS : COOKED_FAIL +} + +function decorate_result(result) +{ + color_name = color_for_result[result] + if (color_name) + return color_map[color_name] "" result "" color_map["std"] + # If we are not using colorized output, or if we do not know how + # to colorize the given result, we should return it unchanged. + return result +} + +function report(result, details) +{ + if (result ~ /^(X?(PASS|FAIL)|SKIP|ERROR)/) + { + msg = ": " test_script_name + add_test_result(result) + } + else if (result == "#") + { + msg = " " test_script_name ":" + } + else + { + abort("in function report()") + } + if (length(details)) + msg = msg " " details + # Output on console might be colorized. + print decorate_result(result) msg + # Log the result in the log file too, to help debugging (this is + # especially true when said result is a TAP error or "Bail out!"). + print result msg | "cat >&3"; +} + +function testsuite_error(error_message) +{ + report("ERROR", "- " error_message) +} + +function handle_tap_result() +{ + details = result_obj["number"]; + if (length(result_obj["description"])) + details = details " " result_obj["description"] + + if (plan_seen == LATE_PLAN) + { + details = details " # AFTER LATE PLAN"; + } + else if (result_obj["is_unplanned"]) + { + details = details " # UNPLANNED"; + } + else if (result_obj["number"] != testno) + { + details = sprintf("%s # OUT-OF-ORDER (expecting %d)", + details, testno); + } + else if (result_obj["directive"]) + { + details = details " # " result_obj["directive"]; + if (length(result_obj["explanation"])) + details = details " " result_obj["explanation"] + } + + report(stringify_result_obj(result_obj), details) +} + +# `skip_reason` should be empty whenever planned > 0. +function handle_tap_plan(planned, skip_reason) +{ + planned += 0 # Avoid getting confused if, say, `planned` is "00" + if (length(skip_reason) && planned > 0) + abort("in function handle_tap_plan()") + if (plan_seen) + { + # Error, only one plan per stream is acceptable. + testsuite_error("multiple test plans") + return; + } + planned_tests = planned + # The TAP plan can come before or after *all* the TAP results; we speak + # respectively of an "early" or a "late" plan. If we see the plan line + # after at least one TAP result has been seen, assume we have a late + # plan; in this case, any further test result seen after the plan will + # be flagged as an error. + plan_seen = (testno >= 1 ? LATE_PLAN : EARLY_PLAN) + # If testno > 0, we have an error ("too many tests run") that will be + # automatically dealt with later, so do not worry about it here. If + # $plan_seen is true, we have an error due to a repeated plan, and that + # has already been dealt with above. Otherwise, we have a valid "plan + # with SKIP" specification, and should report it as a particular kind + # of SKIP result. + if (planned == 0 && testno == 0) + { + if (length(skip_reason)) + skip_reason = "- " skip_reason; + report("SKIP", skip_reason); + } +} + +function extract_tap_comment(line) +{ + if (index(line, diag_string) == 1) + { + # Strip leading `diag_string` from `line`. + line = substr(line, length(diag_string) + 1) + # And strip any leading and trailing whitespace left. + sub("^[ \t]*", "", line) + sub("[ \t]*$", "", line) + # Return what is left (if any). + return line; + } + return ""; +} + +# When this function is called, we know that line is a TAP result line, +# so that it matches the (perl) RE "^(not )?ok\b". +function setup_result_obj(line) +{ + # Get the result, and remove it from the line. + result_obj["is_ok"] = (substr(line, 1, 2) == "ok" ? 1 : 0) + sub("^(not )?ok[ \t]*", "", line) + + # If the result has an explicit number, get it and strip it; otherwise, + # automatically assing the next progresive number to it. + if (line ~ /^[0-9]+$/ || line ~ /^[0-9]+[^a-zA-Z0-9_]/) + { + match(line, "^[0-9]+") + # The final `+ 0` is to normalize numbers with leading zeros. + result_obj["number"] = substr(line, 1, RLENGTH) + 0 + line = substr(line, RLENGTH + 1) + } + else + { + result_obj["number"] = testno + } + + if (plan_seen == LATE_PLAN) + # No further test results are acceptable after a "late" TAP plan + # has been seen. + result_obj["is_unplanned"] = 1 + else if (plan_seen && testno > planned_tests) + result_obj["is_unplanned"] = 1 + else + result_obj["is_unplanned"] = 0 + + # Strip trailing and leading whitespace. + sub("^[ \t]*", "", line) + sub("[ \t]*$", "", line) + + # This will have to be corrected if we have a "TODO"/"SKIP" directive. + result_obj["description"] = line + result_obj["directive"] = "" + result_obj["explanation"] = "" + + if (index(line, "#") == 0) + return # No possible directive, nothing more to do. + + # Directives are case-insensitive. + rx = "[ \t]*#[ \t]*([tT][oO][dD][oO]|[sS][kK][iI][pP])[ \t]*" + + # See whether we have the directive, and if yes, where. + pos = match(line, rx "$") + if (!pos) + pos = match(line, rx "[^a-zA-Z0-9_]") + + # If there was no TAP directive, we have nothing more to do. + if (!pos) + return + + # Let`s now see if the TAP directive has been escaped. For example: + # escaped: ok \# SKIP + # not escaped: ok \\# SKIP + # escaped: ok \\\\\# SKIP + # not escaped: ok \ # SKIP + if (substr(line, pos, 1) == "#") + { + bslash_count = 0 + for (i = pos; i > 1 && substr(line, i - 1, 1) == "\\"; i--) + bslash_count += 1 + if (bslash_count % 2) + return # Directive was escaped. + } + + # Strip the directive and its explanation (if any) from the test + # description. + result_obj["description"] = substr(line, 1, pos - 1) + # Now remove the test description from the line, that has been dealt + # with already. + line = substr(line, pos) + # Strip the directive, and save its value (normalized to upper case). + sub("^[ \t]*#[ \t]*", "", line) + result_obj["directive"] = toupper(substr(line, 1, 4)) + line = substr(line, 5) + # Now get the explanation for the directive (if any), with leading + # and trailing whitespace removed. + sub("^[ \t]*", "", line) + sub("[ \t]*$", "", line) + result_obj["explanation"] = line +} + +function get_test_exit_message(status) +{ + if (status == 0) + return "" + if (status !~ /^[1-9][0-9]*$/) + abort("getting exit status") + if (status < 127) + exit_details = "" + else if (status == 127) + exit_details = " (command not found?)" + else if (status >= 128 && status <= 255) + exit_details = sprintf(" (terminated by signal %d?)", status - 128) + else if (status > 256 && status <= 384) + # We used to report an "abnormal termination" here, but some Korn + # shells, when a child process die due to signal number n, can leave + # in $? an exit status of 256+n instead of the more standard 128+n. + # Apparently, both behaviours are allowed by POSIX (2008), so be + # prepared to handle them both. See also Austing Group report ID + # 0000051 + exit_details = sprintf(" (terminated by signal %d?)", status - 256) + else + # Never seen in practice. + exit_details = " (abnormal termination)" + return sprintf("exited with status %d%s", status, exit_details) +} + +function write_test_results() +{ + print ":global-test-result: " get_global_test_result() > trs_file + print ":recheck: " yn(must_recheck()) > trs_file + print ":copy-in-global-log: " yn(copy_in_global_log()) > trs_file + for (i = 0; i < test_results_index; i += 1) + print ":test-result: " test_results_list[i] > trs_file + close(trs_file); +} + +BEGIN { + +## ------- ## +## SETUP ## +## ------- ## + +'"$init_colors"' + +# Properly initialized once the TAP plan is seen. +planned_tests = 0 + +COOKED_PASS = expect_failure ? "XPASS": "PASS"; +COOKED_FAIL = expect_failure ? "XFAIL": "FAIL"; + +# Enumeration-like constants to remember which kind of plan (if any) +# has been seen. It is important that NO_PLAN evaluates "false" as +# a boolean. +NO_PLAN = 0 +EARLY_PLAN = 1 +LATE_PLAN = 2 + +testno = 0 # Number of test results seen so far. +bailed_out = 0 # Whether a "Bail out!" directive has been seen. + +# Whether the TAP plan has been seen or not, and if yes, which kind +# it is ("early" is seen before any test result, "late" otherwise). +plan_seen = NO_PLAN + +## --------- ## +## PARSING ## +## --------- ## + +is_first_read = 1 + +while (1) + { + # Involutions required so that we are able to read the exit status + # from the last input line. + st = getline + if (st < 0) # I/O error. + fatal("I/O error while reading from input stream") + else if (st == 0) # End-of-input + { + if (is_first_read) + abort("in input loop: only one input line") + break + } + if (is_first_read) + { + is_first_read = 0 + nextline = $0 + continue + } + else + { + curline = nextline + nextline = $0 + $0 = curline + } + # Copy any input line verbatim into the log file. + print | "cat >&3" + # Parsing of TAP input should stop after a "Bail out!" directive. + if (bailed_out) + continue + + # TAP test result. + if ($0 ~ /^(not )?ok$/ || $0 ~ /^(not )?ok[^a-zA-Z0-9_]/) + { + testno += 1 + setup_result_obj($0) + handle_tap_result() + } + # TAP plan (normal or "SKIP" without explanation). + else if ($0 ~ /^1\.\.[0-9]+[ \t]*$/) + { + # The next two lines will put the number of planned tests in $0. + sub("^1\\.\\.", "") + sub("[^0-9]*$", "") + handle_tap_plan($0, "") + continue + } + # TAP "SKIP" plan, with an explanation. + else if ($0 ~ /^1\.\.0+[ \t]*#/) + { + # The next lines will put the skip explanation in $0, stripping + # any leading and trailing whitespace. This is a little more + # tricky in truth, since we want to also strip a potential leading + # "SKIP" string from the message. + sub("^[^#]*#[ \t]*(SKIP[: \t][ \t]*)?", "") + sub("[ \t]*$", ""); + handle_tap_plan(0, $0) + } + # "Bail out!" magic. + # Older versions of prove and TAP::Harness (e.g., 3.17) did not + # recognize a "Bail out!" directive when preceded by leading + # whitespace, but more modern versions (e.g., 3.23) do. So we + # emulate the latter, "more modern" behaviour. + else if ($0 ~ /^[ \t]*Bail out!/) + { + bailed_out = 1 + # Get the bailout message (if any), with leading and trailing + # whitespace stripped. The message remains stored in `$0`. + sub("^[ \t]*Bail out![ \t]*", ""); + sub("[ \t]*$", ""); + # Format the error message for the + bailout_message = "Bail out!" + if (length($0)) + bailout_message = bailout_message " " $0 + testsuite_error(bailout_message) + } + # Maybe we have too look for dianogtic comments too. + else if (comments != 0) + { + comment = extract_tap_comment($0); + if (length(comment)) + report("#", comment); + } + } + +## -------- ## +## FINISH ## +## -------- ## + +# A "Bail out!" directive should cause us to ignore any following TAP +# error, as well as a non-zero exit status from the TAP producer. +if (!bailed_out) + { + if (!plan_seen) + { + testsuite_error("missing test plan") + } + else if (planned_tests != testno) + { + bad_amount = testno > planned_tests ? "many" : "few" + testsuite_error(sprintf("too %s tests run (expected %d, got %d)", + bad_amount, planned_tests, testno)) + } + if (!ignore_exit) + { + # Fetch exit status from the last line. + exit_message = get_test_exit_message(nextline) + if (exit_message) + testsuite_error(exit_message) + } + } + +write_test_results() + +exit 0 + +} # End of "BEGIN" block. +' + +# TODO: document that we consume the file descriptor 3 :-( +} 3>"$log_file" + +test $? -eq 0 || fatal "I/O or internal error" + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/c++/LICENSE.txt b/c++/LICENSE.txt new file mode 100644 index 0000000..0a40189 --- /dev/null +++ b/c++/LICENSE.txt @@ -0,0 +1,23 @@ +Copyright (c) 2013, Julian Scheid +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/c++/Makefile.am b/c++/Makefile.am new file mode 100644 index 0000000..f702ab4 --- /dev/null +++ b/c++/Makefile.am @@ -0,0 +1,90 @@ +## Process this file with automake to produce Makefile.in + +ACLOCAL_AMFLAGS = -I m4 + +AUTOMAKE_OPTIONS = foreign subdir-objects + +AM_CXXFLAGS = -I$(srcdir)/src -I$(builddir)/src $(PTHREAD_CFLAGS) + +AM_LDFLAGS = $(PTHREAD_CFLAGS) + +EXTRA_DIST = \ + README.txt \ + LICENSE.txt + +CLEANFILES = $(test_capnpc_outputs) test_capnpc_middleman + +# Deletes all the files generated by autoreconf. +MAINTAINERCLEANFILES = \ + $(top_srcdir)/aclocal.m4 \ + $(top_srcdir)/config.guess \ + $(top_srcdir)/config.sub \ + $(top_srcdir)/configure \ + $(top_srcdir)/depcomp \ + $(top_srcdir)/install-sh \ + $(top_srcdir)/ltmain.sh \ + $(top_srcdir)/Makefile.in \ + $(top_srcdir)/missing \ + $(top_srcdir)/mkinstalldirs \ + $(top_srcdir)/config.h.in \ + $(top_srcdir)/config.h.in~ \ + $(top_srcdir)/stamp.h.in \ + $(top_srcdir)/m4/ltsugar.m4 \ + $(top_srcdir)/m4/libtool.m4 \ + $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 \ + $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/Makefile.in + +maintainer-clean-local: + -rm -rf $(top_srcdir)/build-aux + +includecapnpdir = $(includedir)/capnp + +dist_includecapnp_DATA = $(public_capnpc_inputs) + +bin_PROGRAMS = capnpc-js + +capnpc_js_CXXFLAGS = -I$(CAPNP_SOURCE)/src +capnpc_js_LDADD = $(CAPNP_LIBDIR)/libcapnp.la $(CAPNP_LIBDIR)/libkj.la $(PTHREAD_LIBS) +capnpc_js_SOURCES = src/capnp/compiler/capnpc-js.c++ + +# Source files intentionally not included in the dist at this time: +# src/capnp/serialize-snappy* +# src/capnp/benchmark/... +# src/capnp/compiler/... + +# Tests ============================================================== + +test_capnpc_inputs = \ + $(CAPNP_SOURCE)/src/capnp/schema.capnp \ + $(CAPNP_SOURCE)/src/capnp/test.capnp \ + $(CAPNP_SOURCE)/src/capnp/test-import.capnp \ + $(CAPNP_SOURCE)/src/capnp/test-import2.capnp + +test_capnpc_outputs = \ + src/capnp/schema.capnp.js \ + src/capnp/test.capnp.js \ + src/capnp/test-import.capnp.js \ + src/capnp/test-import2.capnp.js + +$(test_capnpc_outputs): test_capnpc_middleman + +test_capnpc_middleman: capnpc-js$(EXEEXT) $(test_capnpc_inputs) + echo $^ | (read CAPNPC_JS SOURCES && $(CAPNP) compile --src-prefix=$(CAPNP_SOURCE)/src -o./$$CAPNPC_JS:src -I$(CAPNP_SOURCE)/src $$SOURCES) + touch test_capnpc_middleman + + +BUILT_SOURCES = $(test_capnpc_outputs) + +LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) \ + $(top_srcdir)/build-aux/tap-driver.sh + +AM_TESTS_ENVIRONMENT = \ + export CAPNP=@CAPNP@; \ + export CAPNP_SOURCE=@CAPNP_SOURCE@; \ + export NODE_PATH=@NODE_PATH@; + +TESTS = ../javascript/run-tests.sh + +../javascript/run-tests.sh: $(test_capnpc_outputs) diff --git a/c++/configure.ac b/c++/configure.ac new file mode 100644 index 0000000..5a4aa34 --- /dev/null +++ b/c++/configure.ac @@ -0,0 +1,64 @@ +## Process this file with autoconf to produce configure. + +AC_INIT([Capn Proto for JavaScript],[0.4-dev],[julian37@gmail.com],[capnproto-js]) + +AC_CONFIG_SRCDIR([src/capnp/compiler/capnpc-js.c++]) +AC_CONFIG_AUX_DIR([build-aux]) +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_MACRO_DIR([m4]) +AC_REQUIRE_AUX_FILE([tap-driver.sh]) + +# autoconf's default CXXFLAGS are usually "-g -O2". A far more reasonable +# default is -O2 -NDEBUG. +AS_IF([test "x${ac_cv_env_CFLAGS_set}" = "x"], + [CFLAGS="-O2 -DNDEBUG"]) +AS_IF([test "x${ac_cv_env_CXXFLAGS_set}" = "x"], + [CXXFLAGS="-O2 -DNDEBUG"]) + +AM_INIT_AUTOMAKE([tar-ustar]) + +AC_ARG_WITH([capnp], + [AS_HELP_STRING([--with-capnp], + [use the given capnp binary])], + [external_capnp=yes], + [AC_MSG_FAILURE([--with-capnp is currently mandatory])]) + +AC_ARG_WITH([capnp-source], + [AS_HELP_STRING([--with-capnp-source], + [use the given capnp source root])], + [have_capnp_source=yes], + [AC_MSG_FAILURE([--with-capnp-source is currently mandatory])]) + +AC_ARG_WITH([capnp-libdir], + [AS_HELP_STRING([--with-capnp-libdir], + [use the given directory for locating capnp libraries])], + [have_capnp_libdir=yes], + [AC_MSG_FAILURE([--with-capnp-libdir is currently mandatory])]) + +# Checks for programs. +AC_PROG_CC +AC_PROG_CXX +AC_LANG([C++]) +AX_CXX_COMPILE_STDCXX_11 +ACX_PTHREAD +AC_PROG_LIBTOOL +AC_PROG_AWK + +CAPNP="$with_capnp" +AC_SUBST([CAPNP]) + +CAPNP_SOURCE="$with_capnp_source" +AC_SUBST([CAPNP_SOURCE]) + +CAPNP_LIBDIR="$with_capnp_libdir" +AC_SUBST([CAPNP_LIBDIR]) + +AC_SUBST([NODE_PATH]) + +AC_SEARCH_LIBS(sched_yield, rt) + +LIBS="$PTHREAD_LIBS $LIBS" +CXXFLAGS="$CXXFLAGS $PTHREAD_CFLAGS" + +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT diff --git a/c++/m4/acx_pthread.m4 b/c++/m4/acx_pthread.m4 new file mode 100644 index 0000000..7fe1406 --- /dev/null +++ b/c++/m4/acx_pthread.m4 @@ -0,0 +1,366 @@ +# This file was copied to Cap'n Proto from the Protocol Buffers distribution, +# version 2.3.0. + +# This was retrieved from +# http://svn.0pointer.de/viewvc/trunk/common/acx_pthread.m4?revision=1277&root=avahi +# See also (perhaps for new versions?) +# http://svn.0pointer.de/viewvc/trunk/common/acx_pthread.m4?root=avahi +# +# We've rewritten the inconsistency check code (from avahi), to work +# more broadly. In particular, it no longer assumes ld accepts -zdefs. +# This caused a restructing of the code, but the functionality has only +# changed a little. + +dnl @synopsis ACX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) +dnl +dnl @summary figure out how to build C programs using POSIX threads +dnl +dnl This macro figures out how to build C programs using POSIX threads. +dnl It sets the PTHREAD_LIBS output variable to the threads library and +dnl linker flags, and the PTHREAD_CFLAGS output variable to any special +dnl C compiler flags that are needed. (The user can also force certain +dnl compiler flags/libs to be tested by setting these environment +dnl variables.) +dnl +dnl Also sets PTHREAD_CC to any special C compiler that is needed for +dnl multi-threaded programs (defaults to the value of CC otherwise). +dnl (This is necessary on AIX to use the special cc_r compiler alias.) +dnl +dnl NOTE: You are assumed to not only compile your program with these +dnl flags, but also link it with them as well. e.g. you should link +dnl with $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS +dnl $LIBS +dnl +dnl If you are only building threads programs, you may wish to use +dnl these variables in your default LIBS, CFLAGS, and CC: +dnl +dnl LIBS="$PTHREAD_LIBS $LIBS" +dnl CFLAGS="$CFLAGS $PTHREAD_CFLAGS" +dnl CC="$PTHREAD_CC" +dnl +dnl In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute +dnl constant has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to +dnl that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX). +dnl +dnl ACTION-IF-FOUND is a list of shell commands to run if a threads +dnl library is found, and ACTION-IF-NOT-FOUND is a list of commands to +dnl run it if it is not found. If ACTION-IF-FOUND is not specified, the +dnl default action will define HAVE_PTHREAD. +dnl +dnl Please let the authors know if this macro fails on any platform, or +dnl if you have any other suggestions or comments. This macro was based +dnl on work by SGJ on autoconf scripts for FFTW (www.fftw.org) (with +dnl help from M. Frigo), as well as ac_pthread and hb_pthread macros +dnl posted by Alejandro Forero Cuervo to the autoconf macro repository. +dnl We are also grateful for the helpful feedback of numerous users. +dnl +dnl @category InstalledPackages +dnl @author Steven G. Johnson +dnl @version 2006-05-29 +dnl @license GPLWithACException +dnl +dnl Checks for GCC shared/pthread inconsistency based on work by +dnl Marcin Owsiany + + +AC_DEFUN([ACX_PTHREAD], [ +AC_REQUIRE([AC_CANONICAL_HOST]) +AC_LANG_SAVE +AC_LANG_C +acx_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on True64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) + AC_TRY_LINK_FUNC(pthread_join, acx_pthread_ok=yes) + AC_MSG_RESULT($acx_pthread_ok) + if test x"$acx_pthread_ok" = xno; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items starting with a "-" are +# C compiler flags, and other items are library names, except for "none" +# which indicates that we try without any flags at all, and "pthread-config" +# which is a program returning the flags for the Pth emulation library. + +acx_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) +# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) +# -pthreads: Solaris/gcc +# -mthreads: Mingw32/gcc, Lynx/gcc +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads too; +# also defines -D_REENTRANT) +# ... -mt is also the pthreads flag for HP/aCC +# pthread: Linux, etcetera +# --thread-safe: KAI C++ +# pthread-config: use pthread-config program (for GNU Pth library) + +case "${host_cpu}-${host_os}" in + *solaris*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (We need to link with -pthreads/-mt/ + # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather + # a function called by this macro, so we could check for that, but + # who knows whether they'll stub that too in a future libc.) So, + # we'll just look for -pthreads and -lpthread first: + + acx_pthread_flags="-pthreads pthread -mt -pthread $acx_pthread_flags" + ;; +esac + +if test x"$acx_pthread_ok" = xno; then +for flag in $acx_pthread_flags; do + + case $flag in + none) + AC_MSG_CHECKING([whether pthreads work without any flags]) + ;; + + -*) + AC_MSG_CHECKING([whether pthreads work with $flag]) + PTHREAD_CFLAGS="$flag" + ;; + + pthread-config) + AC_CHECK_PROG(acx_pthread_config, pthread-config, yes, no) + if test x"$acx_pthread_config" = xno; then continue; fi + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + AC_MSG_CHECKING([for the pthreads library -l$flag]) + PTHREAD_LIBS="-l$flag" + ;; + esac + + save_LIBS="$LIBS" + save_CFLAGS="$CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + AC_TRY_LINK([#include ], + [pthread_t th; pthread_join(th, 0); + pthread_attr_init(0); pthread_cleanup_push(0, 0); + pthread_create(0,0,0,0); pthread_cleanup_pop(0); ], + [acx_pthread_ok=yes]) + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + AC_MSG_RESULT($acx_pthread_ok) + if test "x$acx_pthread_ok" = xyes; then + break; + fi + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi + +# Various other checks: +if test "x$acx_pthread_ok" = xyes; then + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. + AC_MSG_CHECKING([for joinable pthread attribute]) + attr_name=unknown + for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do + AC_TRY_LINK([#include ], [int attr=$attr; return attr;], + [attr_name=$attr; break]) + done + AC_MSG_RESULT($attr_name) + if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then + AC_DEFINE_UNQUOTED(PTHREAD_CREATE_JOINABLE, $attr_name, + [Define to necessary symbol if this constant + uses a non-standard name on your system.]) + fi + + AC_MSG_CHECKING([if more special flags are required for pthreads]) + flag=no + case "${host_cpu}-${host_os}" in + *-aix* | *-freebsd* | *-darwin*) flag="-D_THREAD_SAFE";; + *solaris* | *-osf* | *-hpux*) flag="-D_REENTRANT";; + esac + AC_MSG_RESULT(${flag}) + if test "x$flag" != xno; then + PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" + fi + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + # More AIX lossage: must compile with xlc_r or cc_r + if test x"$GCC" != xyes; then + AC_CHECK_PROGS(PTHREAD_CC, xlc_r cc_r, ${CC}) + else + PTHREAD_CC=$CC + fi + + # The next part tries to detect GCC inconsistency with -shared on some + # architectures and systems. The problem is that in certain + # configurations, when -shared is specified, GCC "forgets" to + # internally use various flags which are still necessary. + + # + # Prepare the flags + # + save_CFLAGS="$CFLAGS" + save_LIBS="$LIBS" + save_CC="$CC" + + # Try with the flags determined by the earlier checks. + # + # -Wl,-z,defs forces link-time symbol resolution, so that the + # linking checks with -shared actually have any value + # + # FIXME: -fPIC is required for -shared on many architectures, + # so we specify it here, but the right way would probably be to + # properly detect whether it is actually required. + CFLAGS="-shared -fPIC -Wl,-z,defs $CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + CC="$PTHREAD_CC" + + # In order not to create several levels of indentation, we test + # the value of "$done" until we find the cure or run out of ideas. + done="no" + + # First, make sure the CFLAGS we added are actually accepted by our + # compiler. If not (and OS X's ld, for instance, does not accept -z), + # then we can't do this test. + if test x"$done" = xno; then + AC_MSG_CHECKING([whether to check for GCC pthread/shared inconsistencies]) + AC_TRY_LINK(,, , [done=yes]) + + if test "x$done" = xyes ; then + AC_MSG_RESULT([no]) + else + AC_MSG_RESULT([yes]) + fi + fi + + if test x"$done" = xno; then + AC_MSG_CHECKING([whether -pthread is sufficient with -shared]) + AC_TRY_LINK([#include ], + [pthread_t th; pthread_join(th, 0); + pthread_attr_init(0); pthread_cleanup_push(0, 0); + pthread_create(0,0,0,0); pthread_cleanup_pop(0); ], + [done=yes]) + + if test "x$done" = xyes; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + fi + fi + + # + # Linux gcc on some architectures such as mips/mipsel forgets + # about -lpthread + # + if test x"$done" = xno; then + AC_MSG_CHECKING([whether -lpthread fixes that]) + LIBS="-lpthread $PTHREAD_LIBS $save_LIBS" + AC_TRY_LINK([#include ], + [pthread_t th; pthread_join(th, 0); + pthread_attr_init(0); pthread_cleanup_push(0, 0); + pthread_create(0,0,0,0); pthread_cleanup_pop(0); ], + [done=yes]) + + if test "x$done" = xyes; then + AC_MSG_RESULT([yes]) + PTHREAD_LIBS="-lpthread $PTHREAD_LIBS" + else + AC_MSG_RESULT([no]) + fi + fi + # + # FreeBSD 4.10 gcc forgets to use -lc_r instead of -lc + # + if test x"$done" = xno; then + AC_MSG_CHECKING([whether -lc_r fixes that]) + LIBS="-lc_r $PTHREAD_LIBS $save_LIBS" + AC_TRY_LINK([#include ], + [pthread_t th; pthread_join(th, 0); + pthread_attr_init(0); pthread_cleanup_push(0, 0); + pthread_create(0,0,0,0); pthread_cleanup_pop(0); ], + [done=yes]) + + if test "x$done" = xyes; then + AC_MSG_RESULT([yes]) + PTHREAD_LIBS="-lc_r $PTHREAD_LIBS" + else + AC_MSG_RESULT([no]) + fi + fi + if test x"$done" = xno; then + # OK, we have run out of ideas + AC_MSG_WARN([Impossible to determine how to use pthreads with shared libraries]) + + # so it's not safe to assume that we may use pthreads + acx_pthread_ok=no + fi + + CFLAGS="$save_CFLAGS" + LIBS="$save_LIBS" + CC="$save_CC" +else + PTHREAD_CC="$CC" +fi + +AC_SUBST(PTHREAD_LIBS) +AC_SUBST(PTHREAD_CFLAGS) +AC_SUBST(PTHREAD_CC) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test x"$acx_pthread_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,1,[Define if you have POSIX threads libraries and header files.]),[$1]) + : +else + acx_pthread_ok=no + $2 +fi +AC_LANG_RESTORE +])dnl ACX_PTHREAD diff --git a/c++/m4/ax_cxx_compile_stdcxx_11.m4 b/c++/m4/ax_cxx_compile_stdcxx_11.m4 new file mode 100644 index 0000000..e669cea --- /dev/null +++ b/c++/m4/ax_cxx_compile_stdcxx_11.m4 @@ -0,0 +1,179 @@ +# ============================================================================ +# http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_11.html +# Additionally modified to detect -stdlib by Kenton Varda. +# ============================================================================ +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX_11([ext|noext]) +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the C++11 +# standard; if necessary, add switches to CXXFLAGS to enable support. +# Errors out if no mode that supports C++11 baseline syntax can be found. +# The argument, if specified, indicates whether you insist on an extended +# mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. -std=c++11). +# If neither is specified, you get whatever works, with preference for an +# extended mode. +# +# Additionally, check if the standard library supports C++11. If not, +# try adding -stdlib=libc++ to see if that fixes it. This is needed e.g. +# on Mac OSX 10.8, which ships with a very old libstdc++ but a relatively +# new libc++. +# +# Both flags are actually added to CXX rather than CXXFLAGS to work around +# a bug in libtool: -stdlib is stripped from CXXFLAGS when linking dynamic +# libraries because it is not recognized. A patch was committed to mainline +# libtool in February 2012 but as of June 2013 there has not yet been a +# release containing this patch. +# http://git.savannah.gnu.org/gitweb/?p=libtool.git;a=commit;h=c0c49f289f22ae670066657c60905986da3b555f +# +# LICENSE +# +# Copyright (c) 2008 Benjamin Kosnik +# Copyright (c) 2012 Zack Weinberg +# Copyright (c) 2013 Kenton Varda +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 1 + +m4_define([_AX_CXX_COMPILE_STDCXX_11_testbody], [ + template + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + typedef check> right_angle_brackets; + + int a; + decltype(a) b; + + typedef check check_type; + check_type c; + check_type&& cr = static_cast(c); + + // GCC 4.7 introduced __float128 and makes reference to it in type_traits. + // Clang doesn't implement it, so produces an error. Using -std=c++11 + // instead of -std=gnu++11 works around the problem. But on some + // platforms we need -std=gnu++11. So we want to make sure the test of + // -std=gnu++11 fails only where this problem is present, and we hope that + // -std=c++11 is always an acceptable fallback in these cases. Complicating + // matters, though, is that we don't want to fail here if the platform is + // completely missing a C++11 standard library, because we want to probe that + // in a later test. It happens, though, that Clang allows us to check + // whether a header exists at all before we include it. + // + // So, if we detect that __has_include is available (which it is on Clang), + // and we use it to detect that (a C++11 header) exists, then + // we go ahead and #include it to see if it breaks. In all other cases, we + // don't #include it at all. + #ifdef __has_include + #if __has_include() + #include + #endif + #endif +]) + +m4_define([_AX_CXX_COMPILE_STDCXX_11_testbody_lib], [ + #include + #include + #include + #include +]) + +AC_DEFUN([AX_CXX_COMPILE_STDCXX_11], [dnl + m4_if([$1], [], [], + [$1], [ext], [], + [$1], [noext], [], + [m4_fatal([invalid argument `$1' to AX_CXX_COMPILE_STDCXX_11])])dnl + AC_LANG_ASSERT([C++])dnl + ac_success=no + AC_CACHE_CHECK(whether $CXX supports C++11 features by default, + ax_cv_cxx_compile_cxx11, + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], + [ax_cv_cxx_compile_cxx11=yes], + [ax_cv_cxx_compile_cxx11=no])]) + if test x$ax_cv_cxx_compile_cxx11 = xyes; then + ac_success=yes + fi + + m4_if([$1], [noext], [], [dnl + if test x$ac_success = xno; then + for switch in -std=gnu++11 -std=gnu++0x; do + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch, + $cachevar, + [ac_save_CXX="$CXX" + CXX="$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXX="$ac_save_CXX"]) + if eval test x\$$cachevar = xyes; then + CXX="$CXX $switch" + ac_success=yes + break + fi + done + fi]) + + m4_if([$1], [ext], [], [dnl + if test x$ac_success = xno; then + for switch in -std=c++11 -std=c++0x; do + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch, + $cachevar, + [ac_save_CXX="$CXX" + CXX="$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXX="$ac_save_CXX"]) + if eval test x\$$cachevar = xyes; then + CXX="$CXX $switch" + ac_success=yes + break + fi + done + fi]) + + if test x$ac_success = xno; then + AC_MSG_ERROR([*** A compiler with support for C++11 language features is required.]) + else + ac_success=no + AC_CACHE_CHECK(whether $CXX supports C++11 library features by default, + ax_cv_cxx_compile_cxx11_lib, + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody_lib])], + [ax_cv_cxx_compile_cxx11_lib=yes], + [ax_cv_cxx_compile_cxx11_lib=no]) + ]) + if test x$ax_cv_cxx_compile_cxx11_lib = xyes; then + ac_success=yes + else + # Try with -stdlib=libc++ + AC_CACHE_CHECK(whether $CXX supports C++11 library features with -stdlib=libc++, + ax_cv_cxx_compile_cxx11_lib_libcxx, + [ac_save_CXX="$CXX" + CXX="$CXX -stdlib=libc++" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody_lib])], + [eval ax_cv_cxx_compile_cxx11_lib_libcxx=yes], + [eval ax_cv_cxx_compile_cxx11_lib_libcxx=no]) + CXX="$ac_save_CXX"]) + if eval test x$ax_cv_cxx_compile_cxx11_lib_libcxx = xyes; then + CXX="$CXX -stdlib=libc++" + ac_success=yes + break + fi + fi + + if test x$ac_success = xno; then + AC_MSG_ERROR([*** A C++ library with support for C++11 features is required.]) + fi + fi +]) diff --git a/c++/regenerate-bootstraps.sh b/c++/regenerate-bootstraps.sh new file mode 100755 index 0000000..9467d46 --- /dev/null +++ b/c++/regenerate-bootstraps.sh @@ -0,0 +1,9 @@ +#! /bin/bash + +set -euo pipefail + +export PATH=$PWD/bin:$PWD:$PATH + +capnp compile -Isrc --no-standard-import --src-prefix=src -oc++:src \ + src/capnp/c++.capnp src/capnp/schema.capnp \ + src/capnp/compiler/lexer.capnp src/capnp/compiler/grammar.capnp diff --git a/c++/setup-autotools.sh b/c++/setup-autotools.sh new file mode 100755 index 0000000..099cb8a --- /dev/null +++ b/c++/setup-autotools.sh @@ -0,0 +1,10 @@ +#! /bin/bash + +set -euo pipefail + +echo "================================================================================" +echo "Done" +echo "================================================================================" +echo +echo "Ready to run autoreconf. For example:" +echo " autoreconf -i && ./configure && make -j6 check && sudo make install" diff --git a/c++/src/capnp/compiler/capnpc-js.c++ b/c++/src/capnp/compiler/capnpc-js.c++ new file mode 100644 index 0000000..69f9d41 --- /dev/null +++ b/c++/src/capnp/compiler/capnpc-js.c++ @@ -0,0 +1,1451 @@ +// Copyright (c) 2013, Julian Scheid +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// This program is a code generator plugin for `capnp compile` which generates JavaScript code. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#ifndef VERSION +#define VERSION "(unknown)" +#endif + +namespace capnp { +namespace { + +static constexpr uint64_t NAMESPACE_ANNOTATION_ID = 0xb9c6f99ebf805f2cull; +static constexpr uint64_t JS_NAMESPACE_ANNOTATION_ID = 0x8db73c0d097e6e8bull; + +static kj::String indent(int depth) { + return kj::strTree(kj::repeat(' ', depth * 2)).flatten(); +} + +void enumerateDeps(schema::Type::Reader type, std::set& deps) { + switch (type.which()) { + case schema::Type::STRUCT: + deps.insert(type.getStruct().getTypeId()); + break; + case schema::Type::ENUM: + deps.insert(type.getEnum().getTypeId()); + break; + case schema::Type::INTERFACE: + deps.insert(type.getInterface().getTypeId()); + break; + case schema::Type::LIST: + enumerateDeps(type.getList().getElementType(), deps); + break; + default: + break; + } +} + +void enumerateDeps(schema::Node::Reader node, std::set& deps) { + switch (node.which()) { + case schema::Node::STRUCT: { + auto structNode = node.getStruct(); + for (auto field: structNode.getFields()) { + switch (field.which()) { + case schema::Field::SLOT: + enumerateDeps(field.getSlot().getType(), deps); + break; + case schema::Field::GROUP: + deps.insert(field.getGroup().getTypeId()); + break; + } + } + if (structNode.getIsGroup()) { + deps.insert(node.getScopeId()); + } + break; + } + case schema::Node::INTERFACE: + for (auto method: node.getInterface().getMethods()) { + for (auto param: method.getParams()) { + enumerateDeps(param.getType(), deps); + } + enumerateDeps(method.getReturnType(), deps); + } + break; + default: + break; + } +} + +struct OrderByName { + template + inline bool operator()(const T& a, const T& b) const { + return a.getProto().getName() < b.getProto().getName(); + } +}; + +template +kj::Array makeMembersByName(MemberList&& members) { + auto sorted = KJ_MAP(member, members) { return member; }; + std::sort(sorted.begin(), sorted.end(), OrderByName()); + return KJ_MAP(member, sorted) { return member.getIndex(); }; +} + +kj::StringPtr baseName(kj::StringPtr path) { + KJ_IF_MAYBE(slashPos, path.findLast('/')) { + return path.slice(*slashPos + 1); + } else { + return path; + } +} + +// ======================================================================================= + +class CapnpcJavaScriptMain { +public: + CapnpcJavaScriptMain(kj::ProcessContext& context): context(context) {} + + kj::MainFunc getMain() { + return kj::MainBuilder(context, "Cap'n Proto JavaScript plugin version " VERSION, + "This is a Cap'n Proto compiler plugin which generates JavaScript code. " + "It is meant to be run using the Cap'n Proto compiler, e.g.:\n" + " capnp compile -ojs foo.capnp") + .callAfterParsing(KJ_BIND_METHOD(*this, run)) + .build(); + } + +private: + kj::ProcessContext& context; + SchemaLoader schemaLoader; + std::unordered_set usedImports; + + kj::StringTree cppFullName(schema::CodeGeneratorRequest::RequestedFile::Reader request, Schema schema) { + auto node = schema.getProto(); + if (node.getScopeId() == 0) { + usedImports.insert(node.getId()); + for (auto annotation: node.getAnnotations()) { + if (annotation.getId() == JS_NAMESPACE_ANNOTATION_ID) { + return kj::strTree("capnp_generated_", kj::hex(node.getId())); + } + } + if (request.getId() == node.getId()) { + return kj::strTree("module"); + } + else { + return kj::strTree("import_", kj::hex(node.getId())); + } + } else { + Schema parent = schemaLoader.get(node.getScopeId()); + for (auto nested: parent.getProto().getNestedNodes()) { + if (nested.getId() == node.getId()) { + return kj::strTree(cppFullName(request, parent), ".", nested.getName()); + } + } + KJ_FAIL_REQUIRE("A schema Node's supposed scope did not contain the node as a NestedNode."); + } + } + + kj::String toUpperCase(kj::StringPtr name) { + kj::Vector result(name.size() + 4); + + for (char c: name) { + if ('a' <= c && c <= 'z') { + result.add(c - 'a' + 'A'); + } else if (result.size() > 0 && 'A' <= c && c <= 'Z') { + result.add('_'); + result.add(c); + } else { + result.add(c); + } + } + + result.add('\0'); + + return kj::String(result.releaseAsArray()); + } + + kj::String toTitleCase(kj::StringPtr name) { + kj::String result = kj::heapString(name); + if ('a' <= result[0] && result[0] <= 'z') { + result[0] = result[0] - 'a' + 'A'; + } + return kj::mv(result); + } + + kj::StringTree typeNameShort(schema::Type::Which type) { + switch (type) { + case schema::Type::BOOL: return kj::strTree("bool"); + case schema::Type::INT8: return kj::strTree("int8"); + case schema::Type::INT16: return kj::strTree("int16"); + case schema::Type::INT32: return kj::strTree("int32"); + case schema::Type::INT64: return kj::strTree("int64"); + case schema::Type::UINT8: return kj::strTree("uint8"); + case schema::Type::UINT16: return kj::strTree("uint16"); + case schema::Type::UINT32: return kj::strTree("uint32"); + case schema::Type::UINT64: return kj::strTree("uint64"); + case schema::Type::FLOAT32: return kj::strTree("float32"); + case schema::Type::FLOAT64: return kj::strTree("float64"); + case schema::Type::ENUM: return kj::strTree("uint16"); + default: return kj::strTree(""); + } + } + + kj::StringTree typeNameShort(schema::Type::Reader type) { + return typeNameShort(type.which()); + } + + kj::StringTree typeName(schema::CodeGeneratorRequest::RequestedFile::Reader request, schema::Type::Reader type) { + switch (type.which()) { + case schema::Type::VOID: return kj::strTree("capnp.prim.Void"); + + case schema::Type::BOOL: return kj::strTree("capnp.prim.bool"); + case schema::Type::INT8: return kj::strTree("capnp.prim.int8_t"); + case schema::Type::INT16: return kj::strTree("capnp.prim.int16_t"); + case schema::Type::INT32: return kj::strTree("capnp.prim.int32_t"); + case schema::Type::INT64: return kj::strTree("capnp.prim.int64_t"); + case schema::Type::UINT8: return kj::strTree("capnp.prim.uint8_t"); + case schema::Type::UINT16: return kj::strTree("capnp.prim.uint16_t"); + case schema::Type::UINT32: return kj::strTree("capnp.prim.uint32_t"); + case schema::Type::UINT64: return kj::strTree("capnp.prim.uint64_t"); + case schema::Type::FLOAT32: return kj::strTree("capnp.prim.float32_t"); + case schema::Type::FLOAT64: return kj::strTree("capnp.prim.float64_t"); + + case schema::Type::TEXT: return kj::strTree("capnp.blob.Text"); + case schema::Type::DATA: return kj::strTree("capnp.blob.Data"); + + case schema::Type::ENUM: + return cppFullName(request, schemaLoader.get(type.getEnum().getTypeId())); + case schema::Type::STRUCT: + return cppFullName(request, schemaLoader.get(type.getStruct().getTypeId())); + case schema::Type::INTERFACE: + return cppFullName(request, schemaLoader.get(type.getInterface().getTypeId())); + + case schema::Type::LIST: + switch (type.getList().getElementType().which()) { + case schema::Value::STRUCT: + case schema::Value::INTERFACE: + case schema::Value::OBJECT: + return kj::strTree("capnp.list.ListOfStructs(", typeName(request, type.getList().getElementType()), ")"); + case schema::Value::LIST: + return kj::strTree("capnp.list.ListOfLists(", typeName(request, type.getList().getElementType()), ")"); + case schema::Value::TEXT: + return kj::strTree("capnp.list.ListOfBlobs(capnp.blob.Text)"); + case schema::Value::DATA: + return kj::strTree("capnp.list.ListOfBlobs(capnp.blob.Data)"); + case schema::Value::ENUM: + return kj::strTree("capnp.list.ListOfEnums(", typeName(request, type.getList().getElementType()), ")"); + default: + return kj::strTree("capnp.list.ListOfPrimitives(", typeName(request, type.getList().getElementType()), ")"); + } + + case schema::Type::OBJECT: + // Not used. + return kj::strTree(); + } + KJ_UNREACHABLE; + } + + kj::StringTree literalValue(schema::CodeGeneratorRequest::RequestedFile::Reader request, schema::Type::Reader type, schema::Value::Reader value) { + switch (value.which()) { + case schema::Value::VOID: return kj::strTree("undefined"); + case schema::Value::BOOL: return kj::strTree(value.getBool() ? "true" : "false"); + case schema::Value::INT8: return kj::strTree(value.getInt8()); + case schema::Value::INT16: return kj::strTree(value.getInt16()); + case schema::Value::INT32: return kj::strTree(value.getInt32()); + case schema::Value::UINT8: return kj::strTree(value.getUint8()); + case schema::Value::UINT16: return kj::strTree(value.getUint16()); + case schema::Value::UINT32: return kj::strTree(value.getUint32()); + case schema::Value::INT64: + return kj::strTree("[", value.getInt64() >> 32, ", ", value.getInt64() & 0xffffffff, "]"); + case schema::Value::UINT64: + return kj::strTree("[", value.getUint64() >> 32, ", ", value.getUint64() & 0xffffffff, "]"); + case schema::Value::FLOAT32: return kj::strTree(value.getFloat32()); + case schema::Value::FLOAT64: return kj::strTree(value.getFloat64()); + case schema::Value::ENUM: { + EnumSchema schema = schemaLoader.get(type.getEnum().getTypeId()).asEnum(); + if (value.getEnum() < schema.getEnumerants().size()) { + return kj::strTree( + cppFullName(request, schema), ".", + toUpperCase(schema.getEnumerants()[value.getEnum()].getProto().getName())); + } else { + return kj::strTree("static_cast<", cppFullName(request, schema), ">(", value.getEnum(), ")"); + } + } + + case schema::Value::TEXT: + case schema::Value::DATA: + case schema::Value::STRUCT: + case schema::Value::INTERFACE: + case schema::Value::LIST: + case schema::Value::OBJECT: + KJ_FAIL_REQUIRE("literalValue() can only be used on primitive types."); + } + KJ_UNREACHABLE; + } + + // ----------------------------------------------------------------- + // Code to deal with "slots" -- determines what to zero out when we clear a group. + + static uint typeSizeBits(schema::Type::Which whichType) { + switch (whichType) { + case schema::Type::BOOL: return 1; + case schema::Type::INT8: return 8; + case schema::Type::INT16: return 16; + case schema::Type::INT32: return 32; + case schema::Type::INT64: return 64; + case schema::Type::UINT8: return 8; + case schema::Type::UINT16: return 16; + case schema::Type::UINT32: return 32; + case schema::Type::UINT64: return 64; + case schema::Type::FLOAT32: return 32; + case schema::Type::FLOAT64: return 64; + case schema::Type::ENUM: return 16; + + case schema::Type::VOID: + case schema::Type::TEXT: + case schema::Type::DATA: + case schema::Type::LIST: + case schema::Type::STRUCT: + case schema::Type::INTERFACE: + case schema::Type::OBJECT: + KJ_FAIL_REQUIRE("Should only be called for data types."); + } + KJ_UNREACHABLE; + } + + enum class Section { + NONE, + DATA, + POINTERS + }; + + static Section sectionFor(schema::Type::Which whichType) { + switch (whichType) { + case schema::Type::VOID: + return Section::NONE; + case schema::Type::BOOL: + case schema::Type::INT8: + case schema::Type::INT16: + case schema::Type::INT32: + case schema::Type::INT64: + case schema::Type::UINT8: + case schema::Type::UINT16: + case schema::Type::UINT32: + case schema::Type::UINT64: + case schema::Type::FLOAT32: + case schema::Type::FLOAT64: + case schema::Type::ENUM: + return Section::DATA; + case schema::Type::TEXT: + case schema::Type::DATA: + case schema::Type::LIST: + case schema::Type::STRUCT: + case schema::Type::INTERFACE: + case schema::Type::OBJECT: + return Section::POINTERS; + } + KJ_UNREACHABLE; + } + + struct Slot { + schema::Type::Which whichType; + uint offset; + + bool isSupersetOf(Slot other) const { + auto section = sectionFor(whichType); + if (section != sectionFor(other.whichType)) return false; + switch (section) { + case Section::NONE: + return true; // all voids overlap + case Section::DATA: { + auto bits = typeSizeBits(whichType); + auto start = offset * bits; + auto otherBits = typeSizeBits(other.whichType); + auto otherStart = other.offset * otherBits; + return start <= otherStart && otherStart + otherBits <= start + bits; + } + case Section::POINTERS: + return offset == other.offset; + } + KJ_UNREACHABLE; + } + + bool operator<(Slot other) const { + // Sort by section, then start position, and finally size. + + auto section = sectionFor(whichType); + auto otherSection = sectionFor(other.whichType); + if (section < otherSection) { + return true; + } else if (section > otherSection) { + return false; + } + + switch (section) { + case Section::NONE: + return false; + case Section::DATA: { + auto bits = typeSizeBits(whichType); + auto start = offset * bits; + auto otherBits = typeSizeBits(other.whichType); + auto otherStart = other.offset * otherBits; + if (start < otherStart) { + return true; + } else if (start > otherStart) { + return false; + } + + // Sort larger sizes before smaller. + return bits > otherBits; + } + case Section::POINTERS: + return offset < other.offset; + } + KJ_UNREACHABLE; + } + }; + + void getSlots(StructSchema schema, kj::Vector& slots) { + auto structProto = schema.getProto().getStruct(); + if (structProto.getDiscriminantCount() > 0) { + slots.add(Slot { schema::Type::UINT16, structProto.getDiscriminantOffset() }); + } + + for (auto field: schema.getFields()) { + auto proto = field.getProto(); + switch (proto.which()) { + case schema::Field::SLOT: { + auto slot = proto.getSlot(); + slots.add(Slot { slot.getType().which(), slot.getOffset() }); + break; + } + case schema::Field::GROUP: + getSlots(schema.getDependency(proto.getGroup().getTypeId()).asStruct(), slots); + break; + } + } + } + + kj::Array getSortedSlots(StructSchema schema) { + // Get a representation of all of the field locations owned by this schema, e.g. so that they + // can be zero'd out. + + kj::Vector slots(schema.getFields().size()); + getSlots(schema, slots); + std::sort(slots.begin(), slots.end()); + + kj::Vector result(slots.size()); + + // All void slots are redundant, and they sort towards the front of the list. By starting out + // with `prevSlot` = void, we will end up skipping them all, which is what we want. + Slot prevSlot = { schema::Type::VOID, 0 }; + for (auto slot: slots) { + if (prevSlot.isSupersetOf(slot)) { + // This slot is redundant as prevSlot is a superset of it. + continue; + } + + // Since all sizes are power-of-two, if two slots overlap at all, one must be a superset of + // the other. Since we sort slots by starting position, we know that the only way `slot` + // could be a superset of `prevSlot` is if they have the same starting position. However, + // since we sort slots with the same starting position by descending size, this is not + // possible. + KJ_DASSERT(!slot.isSupersetOf(prevSlot)); + + result.add(slot); + + prevSlot = slot; + } + + return result.releaseAsArray(); + } + + // ----------------------------------------------------------------- + + struct DiscriminantChecks { + kj::String has; + kj::String check; + kj::String set; + kj::StringTree readerIsDecl; + kj::StringTree builderIsDecl; + }; + + DiscriminantChecks makeDiscriminantChecks(kj::StringPtr scope, + uint16_t discrimValue, + kj::StringPtr memberName, + StructSchema containingStruct, + int outerIndent) { + auto discrimOffset = containingStruct.getProto().getStruct().getDiscriminantOffset(); + kj::String titleCase = toTitleCase(memberName); + kj::String upperCase = toUpperCase(memberName); + + return DiscriminantChecks { + kj::str( + " if (this.which() != ", discrimValue, ") return false;\n"), + kj::str( + " if (this.which() != ", discrimValue, ") throw new Error(\"Must check which() before get()ing a union member.\");\n"), + kj::str("_builder.setDataField_uint16(", discrimOffset, ", ", discrimValue, ");\n"), + kj::strTree(indent(outerIndent), "this.is", titleCase, " = function() { return this.which() === ", scope, upperCase, "; };\n"), + kj::strTree(indent(outerIndent), "this.is", titleCase, " = function() { return this.which() === ", scope, upperCase, "; };\n") + }; + } + + // ----------------------------------------------------------------- + + struct FieldText { + kj::StringTree readerMethodDecls; + kj::StringTree builderMethodDecls; + }; + + enum class FieldKind { + PRIMITIVE, + BLOB, + STRUCT, + LIST, + INTERFACE, + OBJECT + }; + + FieldText makeFieldText(schema::CodeGeneratorRequest::RequestedFile::Reader request, kj::StringPtr scope, StructSchema::Field field, int outerIndent) { + auto proto = field.getProto(); + kj::String titleCase = toTitleCase(proto.getName()); + auto fullName = kj::str(scope, titleCase); + + DiscriminantChecks unionDiscrim; + if (proto.hasDiscriminantValue()) { + unionDiscrim = makeDiscriminantChecks(scope, proto.getDiscriminantValue(), proto.getName(), field.getContainingStruct(), outerIndent); + } + + switch (proto.which()) { + case schema::Field::SLOT: + // Continue below. + break; + + case schema::Field::GROUP: { + auto slots = getSortedSlots(schemaLoader.get( + field.getProto().getGroup().getTypeId()).asStruct()); + return FieldText { + kj::strTree( + kj::mv(unionDiscrim.readerIsDecl), + indent(outerIndent), "this.has", titleCase, " = function() {\n", + indent(outerIndent + 2), "return ", + + kj::StringTree(KJ_MAP(slot, slots) { + kj::String suffix = typeNameShort(slot.whichType).flatten(); + switch (sectionFor(slot.whichType)) { + case Section::NONE: + return kj::strTree(); + case Section::DATA: + return kj::strTree("_reader.hasDataField_", suffix, "(", slot.offset, ")"); + case Section::POINTERS: + return kj::strTree( + "!_reader.isPointerFieldNull(", slot.offset, ")"); + } + KJ_UNREACHABLE; + }, kj::strTree("\n", indent(outerIndent + 2), " || ").flatten()), + ";\n", + indent(outerIndent), "};\n", + indent(outerIndent), "this.get", titleCase, " = function() { return new module.", fullName, ".Reader(_reader); };\n", + "\n"), + + kj::strTree( + kj::mv(unionDiscrim.builderIsDecl), + indent(outerIndent), "this.has", titleCase, " = function() {\n", + indent(outerIndent + 2), "return ", + + kj::StringTree(KJ_MAP(slot, slots) { + kj::String suffix = typeNameShort(slot.whichType).flatten(); + switch (sectionFor(slot.whichType)) { + case Section::NONE: + return kj::strTree(); + case Section::DATA: + return kj::strTree("_builder.hasDataField_", suffix, "(", slot.offset, ")"); + case Section::POINTERS: + return kj::strTree( + "!_builder.isPointerFieldNull(", slot.offset, ")"); + } + KJ_UNREACHABLE; + }, kj::strTree("\n", indent(outerIndent + 2), " || ").flatten()), + ";\n", + indent(outerIndent), "};\n", + indent(outerIndent), "this.get", titleCase, " = function() { return new module.", fullName, ".Builder(_builder); };\n", + indent(outerIndent), "this.init", titleCase, " = function() {\n", + + indent(outerIndent + 2), unionDiscrim.set, "\n", + indent(outerIndent + 2), + kj::StringTree(KJ_MAP(slot, slots) { + kj::String suffix = typeNameShort(slot.whichType).flatten(); + switch (sectionFor(slot.whichType)) { + case Section::NONE: + return kj::strTree(); + case Section::DATA: + return kj::strTree("_builder.setDataField_", suffix, "(", slot.offset, ", 0)"); + case Section::POINTERS: + return kj::strTree( + "_builder.clearPointerField(", slot.offset, ");"); + } + KJ_UNREACHABLE; + }, kj::strTree("\n", indent(outerIndent + 2), "").flatten()), + "\n", + indent(outerIndent + 2), "return new module.", fullName, ".Builder(_builder);\n", + indent(outerIndent), "};\n", + "\n"), + }; + } + } + + auto slot = proto.getSlot(); + + FieldKind kind = FieldKind::PRIMITIVE; + kj::String ownedType; + kj::String type = typeName(request, slot.getType()).flatten(); + kj::String suffix = typeNameShort(slot.getType()).flatten(); + kj::String defaultMask; // primitives only + size_t defaultOffset = 0; // pointers only: offset of the default value within the schema. + size_t defaultSize = 0; // blobs only: byte size of the default value. + + auto typeBody = slot.getType(); + auto defaultBody = slot.getDefaultValue(); + switch (typeBody.which()) { + case schema::Type::VOID: + kind = FieldKind::PRIMITIVE; + break; + +#define HANDLE_PRIMITIVE(discrim, typeName, defaultName, suffix) \ + case schema::Type::discrim: \ + kind = FieldKind::PRIMITIVE; \ + if (defaultBody.get##defaultName() != 0) { \ + defaultMask = kj::str(defaultBody.get##defaultName() /*, #suffix*/); \ + } \ + break; + + HANDLE_PRIMITIVE(BOOL, bool, Bool, ); + HANDLE_PRIMITIVE(INT8 , ::int8_t , Int8 , ); + HANDLE_PRIMITIVE(INT16, ::int16_t, Int16, ); + HANDLE_PRIMITIVE(INT32, ::int32_t, Int32, ); + HANDLE_PRIMITIVE(UINT8 , ::uint8_t , Uint8 , u); + HANDLE_PRIMITIVE(UINT16, ::uint16_t, Uint16, u); + HANDLE_PRIMITIVE(UINT32, ::uint32_t, Uint32, u); +#undef HANDLE_PRIMITIVE + + case schema::Type::INT64: + kind = FieldKind::PRIMITIVE; + if (defaultBody.getInt64() != 0) { + int32_t hi = (defaultBody.getInt64() >> 32); + int32_t lo = static_cast(defaultBody.getUint64() & 0xffffffff); + defaultMask = kj::strTree("[", hi, ", ", lo, "]").flatten(); + } + break; + + case schema::Type::UINT64: + kind = FieldKind::PRIMITIVE; + if (defaultBody.getUint64() != 0) { + defaultMask = kj::strTree("[", (defaultBody.getUint64() >> 32), ", ", (defaultBody.getUint64() & 0xffffffff), "]").flatten(); + } + break; + + case schema::Type::FLOAT32: + kind = FieldKind::PRIMITIVE; + if (defaultBody.getFloat32() != 0) { + uint32_t mask; + float value = defaultBody.getFloat32(); + static_assert(sizeof(mask) == sizeof(value), "bug"); + memcpy(&mask, &value, sizeof(mask)); + defaultMask = kj::str(mask); + } + break; + + case schema::Type::FLOAT64: + kind = FieldKind::PRIMITIVE; + if (defaultBody.getFloat64() != 0) { + uint64_t mask; + double value = defaultBody.getFloat64(); + static_assert(sizeof(mask) == sizeof(value), "bug"); + memcpy(&mask, &value, sizeof(mask)); + defaultMask = kj::strTree("[", (mask >> 32), ", ", (mask & 0xffffffff), "]").flatten(); + } + break; + + case schema::Type::TEXT: + kind = FieldKind::BLOB; + if (defaultBody.hasText()) { + defaultOffset = field.getDefaultValueSchemaOffset(); + defaultSize = defaultBody.getText().size(); + } + break; + case schema::Type::DATA: + kind = FieldKind::BLOB; + if (defaultBody.hasData()) { + defaultOffset = field.getDefaultValueSchemaOffset(); + defaultSize = defaultBody.getData().size(); + } + break; + + case schema::Type::ENUM: + kind = FieldKind::PRIMITIVE; + if (defaultBody.getEnum() != 0) { + defaultMask = kj::str(defaultBody.getEnum()); + } + type = kj::str("capnp.uint16_t"); + break; + + case schema::Type::STRUCT: + kind = FieldKind::STRUCT; + if (defaultBody.hasStruct()) { + defaultOffset = field.getDefaultValueSchemaOffset(); + } + break; + case schema::Type::LIST: + kind = FieldKind::LIST; + if (defaultBody.hasList()) { + defaultOffset = field.getDefaultValueSchemaOffset(); + } + break; + case schema::Type::INTERFACE: + kind = FieldKind::INTERFACE; + break; + case schema::Type::OBJECT: + kind = FieldKind::OBJECT; + if (defaultBody.hasObject()) { + defaultOffset = field.getDefaultValueSchemaOffset(); + } + break; + } + + kj::String defaultMaskParam; + kj::String defaultMaskSuffix; + if (defaultMask.size() > 0) { + defaultMaskParam = kj::str(", ", defaultMask); + defaultMaskSuffix = kj::str("_masked"); + } + + uint offset = slot.getOffset(); + + if (kind == FieldKind::PRIMITIVE) { + + kj::String hasGetter; + kj::String builderHasGetter; + kj::String getter; + kj::String builderGetter; + kj::String setter; + + switch (slot.getType().which()) { + + case schema::Type::VOID: + hasGetter = kj::strTree("this.has", titleCase, " = function() { ", unionDiscrim.has, "return false; };\n").flatten(); + builderHasGetter = kj::strTree("this.has", titleCase, " = function() { ", unionDiscrim.has, "return false; };\n").flatten(); + getter = kj::strTree("this.get", titleCase, " = function() { ", unionDiscrim.check, "return undefined; };\n").flatten(); + builderGetter = kj::strTree("this.get", titleCase, " = function() { ", unionDiscrim.check, "return undefined; };\n").flatten(); + setter = kj::strTree("this.set", titleCase, " = function(val) { ", unionDiscrim.set, " };\n").flatten(); + break; + + case schema::Type::ENUM: + case schema::Type::INT8: + case schema::Type::INT16: + case schema::Type::INT32: + case schema::Type::UINT8: + case schema::Type::UINT16: + case schema::Type::UINT32: + case schema::Type::FLOAT32: + case schema::Type::FLOAT64: + case schema::Type::INT64: + case schema::Type::UINT64: + case schema::Type::BOOL: + hasGetter = kj::strTree("this.has", titleCase, " = function() { ", unionDiscrim.has, "return _reader.hasDataField_", suffix, defaultMaskSuffix, "(", offset, "); };\n").flatten(); + builderHasGetter = kj::strTree("this.has", titleCase, " = function() { ", unionDiscrim.has, "return _builder.hasDataField_", suffix, defaultMaskSuffix, "(", offset, "); };\n").flatten(); + getter = kj::strTree("this.get", titleCase, " = function() { ", unionDiscrim.check, "return _reader.getDataField_", suffix, defaultMaskSuffix, "(", offset, defaultMaskParam, "); };\n").flatten(); + builderGetter = kj::strTree("this.get", titleCase, " = function() { ", unionDiscrim.check, "return _builder.getDataField_", suffix, defaultMaskSuffix, "(", offset, defaultMaskParam, "); };\n").flatten(); + setter = kj::strTree("this.set", titleCase, " = function(value) { ", unionDiscrim.set, "_builder.setDataField_", suffix, defaultMaskSuffix, "(", offset, defaultMaskParam, ", value); };\n").flatten(); + + break; + + default: + hasGetter = kj::strTree("this.has", titleCase, " = function() { ", unionDiscrim.has, "return seg.getUint32(", offset, ") !== 0 && seg.getUint32(", offset + 4, ") !== 0; };\n").flatten(); + builderHasGetter = kj::strTree("this.has", titleCase, " = function() { ", unionDiscrim.has, "return seg.getUint32(", offset, ") !== 0 && seg.getUint32(", offset + 4, ") !== 0; };\n").flatten(); + getter = kj::strTree("this.get", titleCase, " = function() { ", unionDiscrim.check, "return ", type, ".Reader(msg, seg, ofs + ", (offset * 8), defaultMaskParam, "); };\n").flatten(); + setter = kj::strTree("this.set", titleCase, " = function(value) { ", unionDiscrim.set, type, ".Builder(msg, seg, ofs + ", (offset * 8), defaultMaskParam, ").set(value); };\n").flatten(); + builderGetter = kj::strTree("this.get", titleCase, " = function() { return new ", type, ".Builder(_builder); };\n").flatten(); + } + + + return FieldText { + kj::strTree( + kj::mv(unionDiscrim.readerIsDecl), + indent(outerIndent), hasGetter, + indent(outerIndent), getter, + "\n"), + + kj::strTree( + kj::mv(unionDiscrim.builderIsDecl), + indent(outerIndent), builderHasGetter, + indent(outerIndent), builderGetter, + indent(outerIndent), setter, + "\n"), + }; + + } else if (kind == FieldKind::INTERFACE) { + // Not implemented. + return FieldText { kj::strTree(), kj::strTree() }; + + } else if (kind == FieldKind::OBJECT) { + return FieldText { + kj::strTree( + kj::mv(unionDiscrim.readerIsDecl), + indent(outerIndent), "this.has", titleCase, " = function() { ", unionDiscrim.has, "return !_reader.isPointerFieldNull(", offset, "); };\n", + indent(outerIndent), "this.get", titleCase, " = function(type) { return capnp.genhelper.objectGetFromReader(type, _reader, ", offset, "); },\n", + "\n"), + + kj::strTree( + kj::mv(unionDiscrim.builderIsDecl), + indent(outerIndent), "this.has", titleCase, " = function() { ", unionDiscrim.has, "return !_builder.isPointerFieldNull(", offset, "); };\n", + indent(outerIndent), "this.get", titleCase, " = function(type) { return capnp.genhelper.objectGetFromBuilder(type, _builder, ", offset, "); };\n", + indent(outerIndent), "this.set", titleCase, " = function(type, value) { capnp.genhelper.objectSet(type, _builder, ", offset, ", value); };\n", + indent(outerIndent), "this.init", titleCase, " = function(type, arg /* , arg... */) { return capnp.genhelper.objectInit(_builder, ", offset, ", arguments); };\n", + indent(outerIndent), "this.adopt", titleCase, " = function(type, value) { return capnp.genhelper.objectAdopt(type, _builder, ", offset, ", value); };\n", + indent(outerIndent), "this.disown", titleCase, " = function(type) { return capnp.genhelper.objectDisown(type, _builder, ", offset, "); };\n", + "\n") + }; + + } else { + // Blob, struct, or list. These have only minor differences. + + uint64_t typeId = field.getContainingStruct().getProto().getId(); + + kj::String defaultParam = defaultOffset == 0 ? kj::str() : kj::str(", new Uint8Array(schemas['", kj::hex(typeId), "']).buffer.slice(", defaultOffset * 8, ")", defaultSize == 0 ? kj::strTree() : kj::strTree(", ", defaultSize)); + + kj::String elementReaderType; + bool isStructList = false; + if (kind == FieldKind::LIST) { + bool primitiveElement = false; + switch (typeBody.getList().getElementType().which()) { + case schema::Type::VOID: + case schema::Type::BOOL: + case schema::Type::INT8: + case schema::Type::INT16: + case schema::Type::INT32: + case schema::Type::INT64: + case schema::Type::UINT8: + case schema::Type::UINT16: + case schema::Type::UINT32: + case schema::Type::UINT64: + case schema::Type::FLOAT32: + case schema::Type::FLOAT64: + case schema::Type::ENUM: + primitiveElement = true; + break; + + case schema::Type::TEXT: + case schema::Type::DATA: + case schema::Type::LIST: + case schema::Type::INTERFACE: + case schema::Type::OBJECT: + primitiveElement = false; + break; + + case schema::Type::STRUCT: + isStructList = true; + primitiveElement = false; + break; + } + elementReaderType = kj::str( + typeName(request, typeBody.getList().getElementType()), + primitiveElement ? "" : "::Reader"); + } + + return FieldText { + kj::strTree( + kj::mv(unionDiscrim.readerIsDecl), + indent(outerIndent), "this.has", titleCase, " = function() { return !_reader.isPointerFieldNull(", offset, "); };\n", + kind == FieldKind::STRUCT ? + kj::strTree(indent(outerIndent), "this.get", titleCase, " = function() { return new ", type, ".Reader(_reader.getStructField(", offset, defaultParam, ")); };\n") + : + kj::strTree(indent(outerIndent), "this.get", titleCase, " = function() { return ", type, ".getReader(_reader, ", offset, defaultParam, "); };\n"), + "\n"), + + kj::strTree( + kj::mv(unionDiscrim.builderIsDecl), + indent(outerIndent), "this.has", titleCase, " = function() { return !_builder.isPointerFieldNull(", offset, "); };\n", + kind == FieldKind::STRUCT + ? kj::strTree(indent(outerIndent), "this.get", titleCase, " = function() { return new ", type, ".Builder(_builder.getStructField(", offset, ", module.", scope, "STRUCT_SIZE", defaultParam, ")); };\n") + : kj::strTree(indent(outerIndent), "this.get", titleCase, " = function() { return ", type, ".getBuilder(_builder, ", offset, defaultParam, "); };\n"), + + indent(outerIndent), "this.set", titleCase, " = function(val) { ", unionDiscrim.set, + + kind == FieldKind::BLOB + ? ((slot.getType().which() == schema::Type::TEXT) ? kj::strTree("capnp.genhelper.textBlobSet(_builder, ", offset, ", val); };\n") : kj::strTree("capnp.genhelper.dataBlobSet(_builder, ", offset, ", val); };\n")) + : (kind == FieldKind::LIST + ? kj::strTree("capnp.genhelper.listSet(", type, ", _builder, ", offset, ", val); };\n") + : kj::strTree("capnp.genhelper.structSet(", type, ", _builder, ", offset, ", val); };\n")), + + kind == FieldKind::LIST && !isStructList + ? kj::strTree() + : kj::strTree(), + + kind == FieldKind::STRUCT + ? kj::strTree(indent(outerIndent), "this.init", titleCase, " = function(size) {\n", + indent(outerIndent + 2), "return new ", type, ".Builder(_builder.initStructField(", offset, ", ", type, ".STRUCT_SIZE));\n", + indent(outerIndent), "};\n") + : isStructList ? kj::strTree(indent(outerIndent), "this.init", titleCase, " = function(size) { return ", type, ".initBuilder(_builder, ", offset, ", size); };\n") : + + kj::strTree(indent(outerIndent), "this.init", titleCase, " = function(size) { return new ", type, ".initBuilder(_builder, ", offset, ", size); };\n"), + indent(outerIndent), "this.adopt", titleCase, " = function(val) { capnp.genhelper.structAdopt(", type, ", _builder, ", offset, ", val); };;\n", + + indent(outerIndent), "this.disown", titleCase, " = function() { ", + kind == FieldKind::BLOB + ? ((slot.getType().which() == schema::Type::TEXT) ? kj::strTree("return capnp.genhelper.textBlobDisown(_builder, ", offset, "); };\n") : kj::strTree("return capnp.genhelper.dataBlobDisown(_builder, ", offset, "); };\n")) + : (kind == FieldKind::LIST + ? kj::strTree("return capnp.genhelper.listDisown(", type, ", _builder, ", offset, "); };\n") + : kj::strTree("return capnp.genhelper.structDisown(", type, ", _builder, ", offset, "); };\n")), + + "\n") + }; + } + } + + // ----------------------------------------------------------------- + + kj::StringTree makeReaderDef(Schema schema, kj::StringPtr fullName, kj::StringPtr unqualifiedParentType, + bool isUnion, kj::Array&& methodDecls, kj::Array& fieldNames, kj::StringPtr name, int outerIndent) { + auto structNode = schema.asStruct().getProto().getStruct(); + return kj::strTree( + "\n", + indent(outerIndent), "STRUCT_SIZE: new capnp.genhelper.StructSize(", structNode.getDataWordCount(), ", ", structNode.getPointerCount(), ", ", static_cast(structNode.getPreferredListEncoding()), "),\n", + indent(outerIndent), "ELEMENT_SIZE: 7, // FieldSize::INLINE_COMPOSITE\n", + indent(outerIndent), "FIELD_LIST: [", kj::StringTree(KJ_MAP(n, fieldNames) { return kj::strTree("\"", n, "\""); }, ", ").flatten(), "],\n", + "\n", + indent(outerIndent), "toString: function() { return '", fullName, "'; },\n", + + indent(outerIndent), "getOrphanReader: function(builder) { return new this.Reader(builder.asStructReader(this.STRUCT_SIZE)); },\n", + indent(outerIndent), "getOrphan: function(builder) { return new this.Builder(builder.asStruct(this.STRUCT_SIZE)); },\n", + + indent(outerIndent), "copyOrphan: capnp.layout.OrphanBuilder.copyStruct,\n", + + indent(outerIndent), "Reader: function(_reader) {\n", + indent(outerIndent + 1), "if (_reader === undefined) _reader = capnp.genhelper.NullStructReader;\n", + indent(outerIndent + 1), "//return {\n" + "\n", + isUnion ? kj::strTree(indent(outerIndent + 2), "this.which = function() { return _reader.getDataField_uint16(", structNode.getDiscriminantOffset() ,"); };\n") : kj::strTree(), + kj::mv(methodDecls), + indent(outerIndent+2), "this._getParentType = function() { return module.", fullName, "; };\n", + indent(outerIndent+2), "this._getInnerReader = function() { return _reader; };\n", + indent(outerIndent+2), "this.totalSizeInWords = function() { return _reader.totalSize(); };\n", + indent(outerIndent+2), "this._getReader = function() { return _reader; };\n", + indent(outerIndent+2), "this.GET_MEMBER = [", kj::StringTree(KJ_MAP(n, fieldNames) { return kj::strTree("this.get", toTitleCase(n)); }, ", ").flatten(), "];\n", + indent(outerIndent+2), "this.HAS_MEMBER = [", kj::StringTree(KJ_MAP(n, fieldNames) { return kj::strTree("this.has", toTitleCase(n)); }, ", ").flatten(), "];\n", + indent(outerIndent+2), "this.toString = function() { return capnp.genhelper.ToStringHelper(this, \"", name, ".Reader\", module.", fullName, ".FIELD_LIST, this.HAS_MEMBER, this.GET_MEMBER", + isUnion? kj::strTree(", this.which()") : kj::strTree(""), + "); }\n", + indent(outerIndent + 1), "//};\n", + indent(outerIndent), "},\n" + "\n"); + } + + kj::StringTree makeBuilderDef(Schema schema, kj::StringPtr fullName, kj::StringPtr unqualifiedParentType, + bool isUnion, kj::Array&& methodDecls, kj::Array& fieldNames, kj::StringPtr name, int outerIndent) { + auto structNode = schema.asStruct().getProto().getStruct(); + return kj::strTree( + indent(outerIndent), "Builder: function(_builder) {\n", + indent(outerIndent+1), "//return {\n", + isUnion ? kj::strTree(indent(outerIndent + 2), "this.which = function() { return _builder.getDataField_uint16(", structNode.getDiscriminantOffset() ,"); };\n") : kj::strTree(), + kj::mv(methodDecls), + indent(outerIndent+2), "this.asReader = function() { return new module.", fullName, ".Reader(_builder.asReader()); };\n", + // + indent(outerIndent+2), "this.getReader = function() { return _builder.asReader(); };\n", + indent(outerIndent+2), "this.totalSizeInWords = function() { return this.asReader().totalSizeInWords(); };\n", + indent(outerIndent+2), "this.GET_MEMBER = [", kj::StringTree(KJ_MAP(n, fieldNames) { return kj::strTree("this.get", toTitleCase(n)); }, ", ").flatten(), "];\n", + indent(outerIndent+2), "this.HAS_MEMBER = [", kj::StringTree(KJ_MAP(n, fieldNames) { return kj::strTree("this.has", toTitleCase(n)); }, ", ").flatten(), "];\n", + indent(outerIndent+2), "this.toString = function() { return capnp.genhelper.ToStringHelper(this, \"", name, ".Builder\", module.", fullName, ".FIELD_LIST, this.HAS_MEMBER, this.GET_MEMBER", + isUnion? kj::strTree(", this.which()") : kj::strTree(""), + "); }\n", + indent(outerIndent+1), "//};\n", + indent(outerIndent), "},\n" + "\n"); + } + + // ----------------------------------------------------------------- + + struct ConstText { + bool needsSchema; + kj::StringTree decl; + kj::StringTree def; + }; + + ConstText makeConstText(schema::CodeGeneratorRequest::RequestedFile::Reader request, kj::StringPtr scope, kj::StringPtr name, ConstSchema schema, int outerIndent) { + auto proto = schema.getProto(); + auto constProto = proto.getConst(); + auto type = constProto.getType(); + auto typeName_ = typeName(request, type).flatten(); + auto upperCase = toUpperCase(name); + + switch (type.which()) { + case schema::Value::VOID: + case schema::Value::BOOL: + case schema::Value::INT8: + case schema::Value::INT16: + case schema::Value::INT32: + case schema::Value::UINT8: + case schema::Value::UINT16: + case schema::Value::UINT32: + case schema::Value::FLOAT32: + case schema::Value::FLOAT64: + case schema::Value::ENUM: + return ConstText { + false, + kj::strTree(((scope.size() == 0) ? + kj::strTree( + indent(outerIndent + 1), "module.", upperCase, " = ", + literalValue(request, constProto.getType(), constProto.getValue()), + ";\n") + : + kj::strTree(indent(outerIndent), upperCase, ": ", + literalValue(request, constProto.getType(), constProto.getValue()), + ",\n"))), + scope.size() == 0 ? kj::strTree() : kj::strTree( + "constexpr ", typeName_, ' ', scope, upperCase, ";\n") + }; + + case schema::Value::INT64: + case schema::Value::UINT64: + return ConstText { + false, + kj::strTree(((scope.size() == 0) ? + kj::strTree(indent(outerIndent + 1), "module.", upperCase, " = ", + literalValue(request, constProto.getType(), constProto.getValue()), ";\n") + : + kj::strTree(indent(outerIndent), upperCase, ": ", + literalValue(request, constProto.getType(), constProto.getValue()), ",\n"))), + scope.size() == 0 ? kj::strTree() : kj::strTree( + "constexpr ", typeName_, ' ', scope, upperCase, ";\n") + }; + + case schema::Value::TEXT: { + return ConstText { + true, + + (scope.size() == 0) + ? kj::strTree(indent(outerIndent + 1), "module.", upperCase, + " = new capnp.genhelper.ConstText(new Uint8Array(schemas['", kj::hex(proto.getId()), "']).buffer, ", + schema.getValueSchemaOffset(), ", ", schema.as().size(),");\n") + : kj::strTree(indent(outerIndent), upperCase, + ": new capnp.genhelper.ConstText(new Uint8Array(schemas['", kj::hex(proto.getId()), "']).buffer, ", + schema.getValueSchemaOffset(), ", ", schema.as().size(),"),\n"), + kj::strTree() + }; + } + + case schema::Value::DATA: { + return ConstText { + true, + + (scope.size() == 0) + ? kj::strTree(indent(outerIndent + 1), "module.", upperCase, + " = new capnp.genhelper.ConstData(new Uint8Array(schemas['", kj::hex(proto.getId()), "']).buffer, ", + schema.getValueSchemaOffset(), ", ", schema.as().size(),");\n") + : kj::strTree(indent(outerIndent), upperCase, ": new capnp.genhelper.ConstData(new Uint8Array(schemas['", kj::hex(proto.getId()), "']).buffer, ", + schema.getValueSchemaOffset(), ", ", schema.as().size(),"),\n"), + kj::strTree() + }; + } + + case schema::Value::STRUCT: { + return ConstText { + true, + + (scope.size() == 0) + ? kj::strTree(indent(outerIndent + 1), "module.", upperCase, " = new capnp.genhelper.ConstStruct(", typeName_, + ", new Uint8Array(schemas['", kj::hex(proto.getId()), "']).buffer, ", + schema.getValueSchemaOffset(), ");\n") + : kj::strTree(indent(outerIndent), upperCase, ": new capnp.genhelper.ConstStruct(", typeName_, + ", new Uint8Array(schemas['", kj::hex(proto.getId()), "']).buffer, ", schema.getValueSchemaOffset(), "),\n"), + kj::strTree() + }; + } + + case schema::Value::LIST: { + + return ConstText { + true, + + (scope.size() == 0) + ? kj::strTree(indent(outerIndent + 1), "module.", upperCase, " = new capnp.genhelper.ConstList(", + typeName(request, type.getList().getElementType()), + ", new Uint8Array(schemas['", kj::hex(proto.getId()), "']).buffer, ", + schema.getValueSchemaOffset(), ");\n") + : kj::strTree(indent(outerIndent), upperCase, ": new capnp.genhelper.ConstList(capnp.list.List(", + typeName(request, type.getList().getElementType()), + "), new Uint8Array(schemas['", kj::hex(proto.getId()), "']).buffer, ", + schema.getValueSchemaOffset(), "),\n"), + kj::strTree() + }; + } + + case schema::Value::OBJECT: + case schema::Value::INTERFACE: + return ConstText { false, kj::strTree(), kj::strTree() }; + } + + KJ_UNREACHABLE; + } + + // ----------------------------------------------------------------- + + struct NodeText { + kj::StringTree outerTypeDecl; + kj::StringTree outerTypeDef; + kj::StringTree capnpSchemaDefs; + }; + + NodeText makeNodeText(schema::CodeGeneratorRequest::RequestedFile::Reader request, + kj::StringPtr namespace_, kj::StringPtr scope, + kj::StringPtr name, Schema schema, int outerIndent) { + auto proto = schema.getProto(); + auto fullName = kj::str(scope, name); + auto subScope = kj::str(fullName, "."); + auto hexId = kj::hex(proto.getId()); + + // Compute nested nodes, including groups. + kj::Vector nestedTexts(proto.getNestedNodes().size()); + for (auto nested: proto.getNestedNodes()) { + nestedTexts.add(makeNodeText(request, + namespace_, subScope, nested.getName(), schemaLoader.get(nested.getId()), + outerIndent + 1)); + }; + + if (proto.isStruct()) { + for (auto field: proto.getStruct().getFields()) { + if (field.isGroup()) { + nestedTexts.add(makeNodeText(request, + namespace_, subScope, toTitleCase(field.getName()), + schemaLoader.get(field.getGroup().getTypeId()), + outerIndent + 1)); + } + } + } + + // Convert the encoded schema to a literal byte array. + kj::ArrayPtr rawSchema = schema.asUncheckedMessage(); + auto schemaLiteral = kj::StringTree(KJ_MAP(w, rawSchema) { + const byte* bytes = reinterpret_cast(&w); + + return kj::strTree(KJ_MAP(i, kj::range(0, sizeof(word))) { + auto text = kj::toCharSequence(kj::implicitCast(bytes[i])); + return kj::strTree(kj::repeat(' ', 4 - text.size()), text, ((i < sizeof(word)-1) || (&w != rawSchema.end() - 1)) ? "," : ""); + }); + }, kj::strTree("\n", indent(outerIndent + 4)).flatten()); + + std::set deps; + enumerateDeps(proto, deps); + + kj::Array membersByName; + kj::Array membersByDiscrim; + switch (proto.which()) { + case schema::Node::STRUCT: { + auto structSchema = schema.asStruct(); + membersByName = makeMembersByName(structSchema.getFields()); + auto builder = kj::heapArrayBuilder(structSchema.getFields().size()); + for (auto field: structSchema.getUnionFields()) { + builder.add(field.getIndex()); + } + for (auto field: structSchema.getNonUnionFields()) { + builder.add(field.getIndex()); + } + membersByDiscrim = builder.finish(); + break; + } + case schema::Node::ENUM: + membersByName = makeMembersByName(schema.asEnum().getEnumerants()); + break; + case schema::Node::INTERFACE: + membersByName = makeMembersByName(schema.asInterface().getMethods()); + break; + default: + break; + } + + auto schemaDef = kj::strTree(indent(outerIndent + 3), "schemas['", kj::hex(proto.getId()),"'] = [\n", + indent(outerIndent + 4), kj::mv(schemaLiteral), "\n", + indent(outerIndent + 3), "];\n", + "\n"); + + auto declaration = (scope.size() == 0) ? kj::strTree("module.", name, " = ").flatten() : kj::strTree(name, ": ").flatten(); + auto declEnd = (scope.size() == 0) ? kj::str("}}();") : kj::strTree("}}(),").flatten(); + + switch (proto.which()) { + case schema::Node::FILE: + KJ_FAIL_REQUIRE("This method shouldn't be called on file nodes."); + + case schema::Node::STRUCT: { + auto fieldTexts = + KJ_MAP(f, schema.asStruct().getFields()) { return makeFieldText(request, subScope, f, outerIndent + 4); }; + + auto fieldNames = + KJ_MAP(f, schema.asStruct().getFields()) { return kj::strTree(f.getProto().getName()).flatten(); }; + + auto structNode = proto.getStruct(); + uint discrimOffset = structNode.getDiscriminantOffset(); + + return NodeText { + kj::str(), + + kj::strTree( + indent(outerIndent + 1), + declaration, "function() {\n", + "\n", + + indent(outerIndent + 2), "return {\n", + structNode.getDiscriminantCount() == 0 ? kj::strTree() : kj::strTree( + KJ_MAP(f, structNode.getFields()) { + if (f.hasDiscriminantValue()) { + return kj::strTree(indent(outerIndent + 3), toUpperCase(f.getName()), ": ", f.getDiscriminantValue(), ",\n"); + } else { + return kj::strTree(); + } + }), + KJ_MAP(n, nestedTexts) { return kj::mv(n.outerTypeDecl); }, + KJ_MAP(n, nestedTexts) { return kj::mv(n.outerTypeDef); }, + + + makeReaderDef(schema, fullName, name, structNode.getDiscriminantCount() != 0, + KJ_MAP(f, fieldTexts) { return kj::mv(f.readerMethodDecls); }, fieldNames, name, outerIndent + 3), + makeBuilderDef(schema, fullName, name, structNode.getDiscriminantCount() != 0, + KJ_MAP(f, fieldTexts) { return kj::mv(f.builderMethodDecls); }, fieldNames, name, outerIndent + 3), + + indent(outerIndent + 2), declEnd, "\n", + "\n"), + + kj::strTree( + kj::mv(schemaDef), + KJ_MAP(n, nestedTexts) { return kj::mv(n.capnpSchemaDefs); }), + + }; + } + + case schema::Node::ENUM: { + auto enumerants = schema.asEnum().getEnumerants(); + + return NodeText { + scope.size() == 0 + ? kj::strTree() + : kj::strTree( + indent(outerIndent+1), declaration, "function() {\n", + indent(outerIndent+2), "return {\n", + KJ_MAP(e, enumerants) { + return kj::strTree(indent(outerIndent+3), toUpperCase(e.getProto().getName()), ": ", e.getOrdinal(), ",\n"); + }, + indent(outerIndent+1), declEnd, "\n" + "\n"), + + scope.size() > 0 + ? kj::strTree() + : kj::strTree( + indent(outerIndent+1), declaration, "function() {\n", + indent(outerIndent+2), "return {\n", + KJ_MAP(e, enumerants) { + return kj::strTree(indent(outerIndent+3), toUpperCase(e.getProto().getName()), ": ", e.getOrdinal(), ",\n"); + }, + indent(outerIndent+1), declEnd, "\n" + "\n"), + + kj::mv(schemaDef), + + }; + } + + case schema::Node::INTERFACE: { + return NodeText { + kj::strTree(), + kj::strTree(), + + kj::mv(schemaDef), + + }; + } + + case schema::Node::CONST: { + auto constText = makeConstText(request, scope, name, schema.asConst(), outerIndent); + + return NodeText { + scope.size() == 0 ? kj::strTree() : kj::strTree(" ", kj::mv(constText.decl)), + scope.size() > 0 ? kj::strTree() : kj::mv(constText.decl), + + constText.needsSchema ? kj::mv(schemaDef) : kj::strTree(), + + }; + } + + case schema::Node::ANNOTATION: { + return NodeText { + kj::strTree(), + kj::strTree(), + + kj::mv(schemaDef), + + }; + } + } + + KJ_UNREACHABLE; + } + + struct FileText { + kj::StringTree javascript; + }; + + FileText makeFileText(Schema schema, + schema::CodeGeneratorRequest::RequestedFile::Reader request) { + usedImports.clear(); + + auto node = schema.getProto(); + auto displayName = node.getDisplayName(); + + kj::Vector> namespaceParts; + kj::String namespacePrefix; + + kj::String fileNamespace; + + for (auto annotation: node.getAnnotations()) { + if (annotation.getId() == JS_NAMESPACE_ANNOTATION_ID) { + fileNamespace = kj::str(annotation.getValue().getText()); + break; + } + } + + auto nodeTexts = KJ_MAP(nested, node.getNestedNodes()) { + return makeNodeText(request, namespacePrefix, "", nested.getName(), schemaLoader.get(nested.getId()), 0); + }; + + kj::Vector includes; + for (auto import: request.getImports()) { + if (usedImports.count(import.getId()) > 0) { + includes.add(import.getName()); + } + } + + return FileText { + kj::strTree( + "// Generated by Cap'n Proto compiler, DO NOT EDIT\n" + "// source: ", baseName(displayName), "\n" + "\n" + "goog.provide('capnp_generated_", kj::hex(node.getId()), "');\n", + (fileNamespace.size() > 0 ? kj::strTree("goog.provide('", fileNamespace, "');\n") : kj::strTree()), + "\n" + "goog.require('capnp.genhelper');\n" + "goog.require('goog.object');\n" + "\n" + "(function() {\n" + "\n", + indent(1), "var module = capnp_generated_", kj::hex(node.getId()), ";\n", + indent(1), "var schemas = {};\n" + "\n", + KJ_MAP(includedFile, request.getImports()) { + if (usedImports.count(includedFile.getId()) > 0) { + kj::String filename = kj::strTree(includedFile.getName()).flatten(); + return kj::strTree("goog.require('capnp_generated_", kj::hex(includedFile.getId()), "');\n"); + } + else { + return kj::strTree(); + } + }, + KJ_MAP(n, nodeTexts) { return kj::mv(n.capnpSchemaDefs); }, + KJ_MAP(n, nodeTexts) { return kj::mv(n.outerTypeDef); }, + + "\n", + (fileNamespace.size() > 0 ? kj::strTree(indent(1), "goog.object.extend(", fileNamespace, ", capnp_generated_", kj::hex(node.getId()), ");\n") : kj::strTree()), + "})();\n") + }; + } + + // ----------------------------------------------------------------- + + void makeDirectory(kj::StringPtr path) { + KJ_IF_MAYBE(slashpos, path.findLast('/')) { + // Make the parent dir. + makeDirectory(kj::str(path.slice(0, *slashpos))); + } + + if (mkdir(path.cStr(), 0777) < 0) { + int error = errno; + if (error != EEXIST) { + KJ_FAIL_SYSCALL("mkdir(path)", error, path); + } + } + } + + void writeFile(kj::StringPtr filename, const kj::StringTree& text) { + KJ_IF_MAYBE(slashpos, filename.findLast('/')) { + // Make the parent dir. + makeDirectory(kj::str(filename.slice(0, *slashpos))); + } + + int fd; + KJ_SYSCALL(fd = open(filename.cStr(), O_CREAT | O_WRONLY | O_TRUNC, 0666), filename); + kj::FdOutputStream out((kj::AutoCloseFd(fd))); + + text.visit( + [&](kj::ArrayPtr text) { + out.write(text.begin(), text.size()); + }); + } + + kj::MainBuilder::Validity run() { + ReaderOptions options; + options.traversalLimitInWords = 1 << 30; // Don't limit. + StreamFdMessageReader reader(STDIN_FILENO, options); + auto request = reader.getRoot(); + + for (auto node: request.getNodes()) { + schemaLoader.load(node); + } + + kj::FdOutputStream rawOut(STDOUT_FILENO); + kj::BufferedOutputStreamWrapper out(rawOut); + + for (auto requestedFile: request.getRequestedFiles()) { + auto schema = schemaLoader.get(requestedFile.getId()); + auto fileText = makeFileText(schema, requestedFile); + + writeFile(kj::str(schema.getProto().getDisplayName(), ".js"), fileText.javascript); + } + + return true; + } +}; + +} // namespace +} // namespace capnp + +KJ_MAIN(capnp::CapnpcJavaScriptMain); diff --git a/javascript/build-tests.sh b/javascript/build-tests.sh new file mode 100755 index 0000000..015aefa --- /dev/null +++ b/javascript/build-tests.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +#-------------------- edit here >>> +closure_library=../capnproto-js/thirdparty/closure-library +compiler_jar=../capnproto-js/thirdparty/compiler.jar +capnproto_js=../capnproto-js +#-------------------- <<< edit here + +set -exuo pipefail + +builddir=$PWD +srcdir=$(dirname $0) + +python $closure_library/closure/bin/build/closurebuilder.py --root=$capnproto_js/javascript/lib/ --root=$closure_library/ --namespace='capnp.runtime' --output_mode=compiled --compiler_jar=$compiler_jar --compiler_flags="--compilation_level=ADVANCED_OPTIMIZATIONS" > dist/capnp_runtime.js + +python $closure_library/closure/bin/build/closurebuilder.py \ + --root=$capnproto_js/javascript/lib/ \ + --root=$capnproto_js/javascript/tests/ \ + --root=$closure_library/ \ + --root=src/capnp \ + --namespace='capnp.tests.encoding' \ + --namespace='capnp.tests.serialize' \ + --namespace='capnp.tests.packed' \ + --namespace='capnp.tests.layout' \ + --namespace='capnp.tests.stringify' \ + --namespace='capnp.tests.orphans' \ + --output_mode=compiled \ + --compiler_jar=$compiler_jar \ + --compiler_flags="--compilation_level=SIMPLE_OPTIMIZATIONS" \ + --compiler_flags="--language_in=ECMASCRIPT5" \ + --compiler_flags="--use_types_for_optimization" \ + --compiler_flags="--formatting=PRETTY_PRINT" \ + > dist/capnp_tests.js + + #--compiler_flags="--warning_level=VERBOSE" \ + +#export NODE_PATH=${NODEPATH}:${builddir}/src/capnp:${builddir}/src:${builddir}/dist +#mocha --bail ${srcdir}/../javascript/tests/ diff --git a/javascript/lib/capnp_arena.js b/javascript/lib/capnp_arena.js new file mode 100644 index 0000000..c2147f5 --- /dev/null +++ b/javascript/lib/capnp_arena.js @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2013, Julian Scheid + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +goog.provide('capnp.arena'); + +goog.require('goog.asserts'); + +goog.require('kj.util'); + + +/** + * @constructor + */ +capnp.arena.ReadLimiter = function() { + this.unread = function(amount) { }; + this.toString = function() { return 'ReadLimiter{...}'; }; + return this; +}; + +/** + * @constructor + */ +capnp.arena.SegmentReader = function(arena, id, segment, readLimiter) { + + goog.asserts.assert(kj.util.isDataView(segment), 'expected dataView but got ' + segment); + + var uint8Array = null; + + this.getArena = function() { return arena; }; + + this.createWirePointerAt = function(offset) { + goog.asserts.assert(kj.util.isRegularNumber(offset) && offset >= 0); + return new capnp.layout.WirePointer(offset, this.createSubDataView(offset, 8)); + }; + + this.containsInterval = function(start, end) { + kj.util.warnOnce('containsInterval not yet implemented'); + return true; + }; + + this.toString = function() { + return 'SegmentReader{id=' + id + '}'; + }; + + this.unread = function(amount) { + readLimiter.unread(amount); + }; + + this.getDataView = function() { + return segment; + }; + + this.createSubDataView = function(offset, length) { + goog.asserts.assert(offset + length <= segment.byteLength); + return new DataView(segment.buffer, segment.byteOffset + offset, length); + }; + + this.getUint8Array = function() { + if (uint8Array === null) { + uint8Array = new Uint8Array(segment.buffer, segment.byteOffset, segment.byteLength); + } + return uint8Array; + }; +}; + + +/** + * @constructor + */ +capnp.arena.SegmentBuilder = function(arena, id, segment, readLimiter) { + + capnp.arena.SegmentReader.call(this, arena, id, segment, readLimiter); + + goog.asserts.assert(arena, 'SegmentBuilder constructor got invalid arena: ' + arena); + goog.asserts.assert(typeof(id) === 'number', 'SegmentBuilder constructor got invalid id: ' + id); + goog.asserts.assert(segment, 'SegmentBuilder constructor got invalid segment: ' + segment); + goog.asserts.assert(readLimiter, 'SegmentBuilder constructor got invalid readLimiter: ' + readLimiter); + + goog.asserts.assert(segment instanceof DataView, 'SegmentBuilder constructor got invalid segment: ' + segment); + + this.getSegmentId = function() { return id; }; + this.getArena = function() { return arena; }; + + var pos = 0; + + this.allocate = function(amount) { + + goog.asserts.assert(amount % 1 == 0, 'SegmentBuilder.allocate asked to allocate fractional amount: ' + amount); + goog.asserts.assert(amount >= 0, 'SegmentBuilder.allocate asked to zero or negative amount: ' + amount); + + var newPos = pos + amount * capnp.common.BYTES_PER_WORD; + + if (newPos <= segment.byteLength) { + var result = pos; + pos = newPos; + return result >>> 3; + } + else { + // Not enough space in the segment for this allocation. + return null; + } + }; + + this.validateIntegrity = function() { + + var uint8Array = this.getUint8Array(); + for (var i = pos; i < segment.byteLength; ++i) { + if (uint8Array[i] != 0) { + throw new Error('free space unclean at ' + i); + } + } + }; + + this.currentlyAllocated = function() { + return this.createSubDataView(0, pos); + }; + + this.toString = function() { + return 'SegmentBuilder{id=' + id + '}'; + }; + + return this; +}; +capnp.arena.SegmentBuilder.prototype = Object.create(capnp.arena.SegmentReader.prototype); + + +/** + * @constructor + */ +capnp.arena.BuilderArena = function(message) { + + goog.asserts.assert(message, 'BuilderArena constructor got invalid message: ' + message); + + var segment0 = null; + var builders = []; + var dummyLimiter = new capnp.arena.ReadLimiter(); + + this.tryGetSegment = function(id) { + if (id == 0) { + return segment0; + } + else if (id <= builders.length) { + return builders[id - 1]; + } + else { + return null; + } + }; + + this.getSegment = function(id) { + // This method is allowed to fail if the segment ID is not valid. + if (id == 0) { + return segment0; + } else { + kj.debug.REQUIRE(id - 1 < builders.length, 'invalid segment id ' + id + ' (have ' + (1 + builders.length) + ' segments)'); + // TODO(cleanup): Return a const SegmentBuilder and tediously constify all SegmentBuilder + // pointers throughout the codebase. + return builders[id - 1]; + } + }; + + this.allocate = function(amount) { + + if (!segment0) { + // We're allocating the first segment. + + var ptr = message.allocateSegment(amount); + segment0 = new capnp.arena.SegmentBuilder(this, 0, ptr, dummyLimiter); + return { segment: segment0, words: segment0.allocate(amount) }; + } + else { + // Check if there is space in the first segment. We can do this without locking. + var attempt = segment0.allocate(amount); + if (attempt !== null) { + return { segment: segment0, words: attempt }; + } + + // Need to fall back to additional segments. + + if (builders.length > 0) { + attempt = builders[builders.length - 1].allocate(amount); + if (attempt !== null) { + return { segment: builders[builders.length - 1], words: attempt }; + } + } + + var newBuilder = new capnp.arena.SegmentBuilder(this, builders.length + 1, message.allocateSegment(amount), dummyLimiter); + builders.push(newBuilder); + + // Allocating from the new segment is guaranteed to succeed since no other thread could have + // received a pointer to it yet (since we still hold the lock). + return { segment: newBuilder, words: newBuilder.allocate(amount) }; + } + }; + + this.getSegmentsForOutput = function() { + var result = [segment0.currentlyAllocated()]; + for (var i = 0, len = builders.length; i < len; i++) { + result.push(builders[i].currentlyAllocated()); + } + return result; + }; + + this.toString = function() { return 'BuilderArena{...}; ' }; + + this.getSegment0 = function() { return segment0; }; + + return this; +}; + +/** + * @constructor + */ +capnp.arena.ReaderArena = function(message) { + + if (!kj.util.isDataView(message.getSegment(0))) { + throw new Error('not a dataview: ' + message); + } + + var readLimiter = new capnp.arena.ReadLimiter(); + var segment0 = new capnp.arena.SegmentReader(this, 0, message.getSegment(0), readLimiter); + var segments = {}; + + this.tryGetSegment = function(id) { + + if (id == 0) { + return segment0; + } + else if (segments.hasOwnProperty(id)) { + return segments[id]; + } + else { + var newSegment = message.getSegment(id); + if (newSegment === null) { + return null; + } + if (newSegment === undefined) { + goog.asserts.assert(false); + } + + var segment = new capnp.arena.SegmentReader(this, id, newSegment, readLimiter); + segments[id] = segment; + return segment; + } + }; + + this.reportReadLimitReached = function() { + }; + +}; diff --git a/javascript/lib/capnp_blob.js b/javascript/lib/capnp_blob.js new file mode 100644 index 0000000..7a6b672 --- /dev/null +++ b/javascript/lib/capnp_blob.js @@ -0,0 +1,336 @@ +/* + * Copyright (c) 2013, Julian Scheid + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +goog.provide('capnp.blob'); + +goog.require('kj.util'); + +capnp.blob.Text = { + + copyOrphan: capnp.layout.OrphanBuilder.copyText, + + getReaderAsElement: function(reader, index) { + return reader.getTextBlobElement(index); + }, + + getBuilderAsElement: function(builder, index) { + var result = builder.getTextBlobElement(index); + return result; + }, + + setElement: function(builder, index, value) { + builder.setTextBlobElement(index, value); + }, + + getReader: function(reader, index, defaultValue, defaultBytes) { + if (defaultValue === undefined) { defaultBytes = 0; } + if (defaultBytes === undefined) { defaultBytes = 0; } + return reader.getTextBlobField(index, defaultValue, defaultBytes); + }, + + getBuilder: function(builder, index, defaultValue, defaultBytes) { + if (defaultValue === undefined) { defaultBytes = 0; } + if (defaultBytes === undefined) { defaultBytes = 0; } + return builder.getTextBlobField(index, defaultValue, defaultBytes); + }, + + Builder: + /** + * @constructor + */ + function(segment, ptr, size) { + + this.toDebugString = function() { + return 'Text.Builder{ptr=' + ptr + ', size=' + size + + ', str="' + this.str() + '"}'; + }; + + this.toString = function() { return this.str(); }; + + this.begin = function() { return ptr * 8; }; + + this.size = function() { return size; }; + + this.str = function() { + + var pad = function(n) { + return n.length < 2 ? '0' + n : n; + }; + + var str = ''; + var dataView = segment.getUint8Array(); + for (var i = ptr * 8, end = ptr * 8 + size; i < end; ++i) { + str += ('%' + pad(dataView[i].toString(16))); + } + + return decodeURIComponent(str); + }; + + + this.asUint8Array = function() { + return segment.getUint8Array().subarray(ptr * 8, + ptr * 8 + size); + }; + + this.getOrphan = function(builder) { + return builder.asText(); + }; + + this.getOrphanReader = function(builder) { + return builder.asTextReader(); + }; + + this.getNewOrphanList = function(arena, size) { + return capnp.layout.OrphanBuilder.initText(arena, size); + }; + + return this; + }, + + Reader: + /** + * @constructor + */ + function(segment, offset, numElements) { + + goog.asserts.assert(kj.util.isRegularNumber(numElements), 'invalid Reader.numElements: ' + numElements); + + this.totalSizeInWords = 1; + + goog.asserts.assert(numElements === 0 || segment !== null); + + return new (function() { + this.str = function() { + + if (numElements === 0) { + return ''; + } + else { + var pad = function(n) { + return n.length < 2 ? '0' + n : n; + }; + + var str = ''; + var dataView = segment.getUint8Array(); + for (var i = offset * 8, + end = offset * 8 + numElements; i < end; ++i) { + str += ('%' + pad(dataView[i].toString(16))); + } + + return decodeURIComponent(str); + } + }; + + this.raw = function() { + var dataView = segment; + return new DataView(dataView.buffer, + dataView.byteOffset + offset, + numElements); + }; + + this.toString = function() { + return this.str(); + }; + this.toDebugString = function() { + return 'Text{offset=' + offset + + ', numElements=' + numElements + + ', str="' + this.str() + '"}'; + }; + + this._getParentType = function() { + return capnp.blob.Text; + }; + + this._getInnerReader = function() { + return this; + }; + + return this; + }); + } +}; + +capnp.blob.Data = { + + copyOrphan: capnp.layout.OrphanBuilder.copyData, + + getReaderAsElement: function(reader, index) { + return reader.getDataBlobElement(index); + }, + + getBuilderAsElement: function(builder, index) { + return builder.getDataBlobElement(index); + }, + + setElement: function(builder, index, value) { + builder.setDataBlobElement(index, value); + }, + + getReader: function(reader, index, defaultValue, defaultBytes) { + if (defaultValue === undefined) { + defaultValue = null; + defaultBytes = 0; + } + if (defaultBytes === undefined) { + defaultBytes = 0; + } + return reader.getDataBlobField(index, defaultValue, defaultBytes); + }, + + getBuilder: function(builder, index, defaultValue, defaultBytes) { + if (defaultValue === undefined) { + defaultValue = null; defaultBytes = 0; } + if (defaultBytes === undefined) { + defaultBytes = 0; + } + return builder.getDataBlobField(index, defaultValue, defaultBytes); + }, + + getOrphan: function(builder) { + return builder.asData(); + }, + + getOrphanReader: function(builder) { + return builder.asDataReader(); + }, + + getNewOrphanList: function(arena, size) { + return capnp.layout.OrphanBuilder.initData(arena, size); + }, + + Builder: + /** + * @constructor + */ + function(segment, ptr, size) { + + this.begin = function() { return ptr * 8; }; + + this.size = function() { return size; }; + + this.toDebugString = function() { + return 'Data.Builder{ptr=' + ptr + + ', size=' + size + + ', arr=""}'; + }; + + this.toString = function() { return '[...]'; }; // FIXME + + this.asArray = function() { + var bytes = []; + var arr = this.asUint8Array(); + for (var i = 0, len = this.size(); i < len; i++) { + bytes.push(arr[i]); + } + return bytes; + }; + + this.asUint8Array = function() { + return segment.getUint8Array().subarray(ptr * 8, + ptr * 8 + size); + }; + + return this; + }, + + Reader: + /** + * @constructor + */ + function(uint8array, numElements) { + + goog.asserts.assert(kj.util.isRegularNumber(numElements), + 'numElements not a regular number: ' + numElements); + + return new (function() { + this.size = function() { + return numElements; + }; + + this.asUint8Array = function() { + return uint8array; + }; + + this.asArray = function() { + var bytes = []; + var arr = this.asUint8Array(); + for (var i = 0, len = this.size(); i < len; i++) { + bytes.push(arr[i]); + } + return bytes; + }; + + this.equals = function(other) { + if (this.size() !== other.size()) { + return false; + } + var arr1 = this.asUint8Array(); + var arr2 = this.asUint8Array(); + for (var i = 0, len = this.size(); i < len; i++) { + if (arr1[i] != arr2[i]) { + return false; + } + } + return true; + }; + + this.toString = function() { + return '[ ' + this.asArray().join(', ') + ' ]'; + }; + + this._getParentType = function() { + return capnp.blob.Data; + }; + + this._getInnerReader = function() { + return this; + }; + + return this; + }); + } +}; + +/** + * @constructor + */ +capnp.blob.StringTextReader = function(str) { + var strUtf8 = unescape(encodeURIComponent(str)); + var ab = new Uint8Array(strUtf8.length); + for (var i = 0; i < strUtf8.length; i++) { + ab[i] = strUtf8.charCodeAt(i); + } + + this.asUint8Array = function() { return ab; }; + this.size = function() { return ab.byteLength; }; + this._getParentType = function() { + return capnp.blob.Text; + }; + this._getInnerReader = function() { + return this; + }; + + return this; +}; diff --git a/javascript/lib/capnp_common.js b/javascript/lib/capnp_common.js new file mode 100644 index 0000000..d11ec35 --- /dev/null +++ b/javascript/lib/capnp_common.js @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2013, Julian Scheid + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +goog.provide('capnp.common'); + +/** @const */ capnp.common.BITS_PER_BYTE = 8; +/** @const */ capnp.common.BITS_PER_WORD = 64; +/** @const */ capnp.common.BYTES_PER_WORD = 8; +/** @const */ capnp.common.BITS_PER_POINTER = 64; +/** @const */ capnp.common.BYTES_PER_POINTER = 8; +/** @const */ capnp.common.WORDS_PER_POINTER = 1; +/** @const */ capnp.common.POINTER_SIZE_IN_WORDS = 1; + +capnp.common.roundBitsUpToBytes = function(bitCount) { + return ((bitCount + 7) / capnp.common.BITS_PER_BYTE) >>> 0; +}; + +capnp.common.roundBitsUpToWords = function(bitCount) { + return ((bitCount + 63) / capnp.common.BITS_PER_WORD) >>> 0; +}; + +capnp.common.roundBytesUpToWords = function(bytes) { + return ((bytes + 7) / capnp.common.BYTES_PER_WORD) >>> 0; +}; diff --git a/javascript/lib/capnp_genhelper.js b/javascript/lib/capnp_genhelper.js new file mode 100644 index 0000000..24e31f1 --- /dev/null +++ b/javascript/lib/capnp_genhelper.js @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2013, Julian Scheid + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +goog.provide('capnp.genhelper') + +goog.require('capnp.blob') +goog.require('capnp.list') +goog.require('capnp.layout') +goog.require('capnp.orphan') + +goog.require('kj.util') + +/** + * @param {capnp.layout.StructBuilder} builder + * @param {number} index + */ +capnp.genhelper.textBlobSet = function(builder, index, value) { + if (kj.util.isString(value)) { + builder.setTextBlobField(index, new capnp.blob.StringTextReader(value)); + } + else { + builder.setTextBlobField(index, value); + } +}; + +capnp.genhelper.textBlobDisown = function(builder, index) { + return new capnp.orphan.Orphan(capnp.blob.Text, builder.disownTextBlobField(index)); +}; + +/** + * @param {capnp.layout.StructBuilder} builder + * @param {number} index + */ +capnp.genhelper.dataBlobSet = function(builder, index, value) { + if (kj.util.isString(value)) { + builder.setDataBlobField(index, new capnp.blob.StringTextReader(value)); + } + else { + builder.setDataBlobField(index, value); + } +}; + +capnp.genhelper.dataBlobDisown = function(builder, index) { + return new capnp.orphan.Orphan(capnp.blob.Data, builder.disownDataBlobField(index)); +}; + + +/** + * @param {number} index + * @param {number} size + */ +capnp.genhelper.listInit = function(listClass, builder, index, size) { + return new listClass.Builder(listClass.initBuilderAsFieldOf(builder, index, size)); +}; + +/** + * @param {capnp.layout.StructBuilder} builder + * @param {number} index + */ +capnp.genhelper.listSet = function(listClass, builder, index, value) { + if (goog.isArray(value)) { + + var len = value.length; + var l = capnp.genhelper.listInit(listClass, builder, index, len); + for (var i = 0; i < len; ++i) { + l.set(i, value[i]); + } + } + else { + builder.setListField(index, value.getReader()); + } +}; + +capnp.genhelper.listDisown = function(listClass, builder, index) { + return new capnp.orphan.Orphan(listClass, builder.disownListField(index)); +}; + +/** + * @param {capnp.layout.StructBuilder} builder + * @param {number} index + */ +capnp.genhelper.structSet = function(structClass, builder, index, value) { + builder.setStructField(index, value._getReader()); +}; + +capnp.genhelper.structAdopt = function(structClass, builder, index, value) { + builder.adoptStructField(index, value.builder); +} + +capnp.genhelper.structDisown = function(structClass, builder, index) { + return new capnp.orphan.Orphan(structClass, builder.disownStructField(index)); +}; + + +/** + * @param {number} index + */ +capnp.genhelper.objectInit = function(builder, index, _args) { + var args = Array.prototype.slice.call(_args, 0); + var type = args.shift(); + if (type instanceof capnp.list.List) { + var size = args.shift(); + return new type.Builder(type.initBuilderAsFieldOf(builder, index, size)); + } + else if (type.STRUCT_SIZE) { + return new type.Builder(builder.initStructField(index, type.STRUCT_SIZE)); + } + else { + throw new Error('unsupported type: ' + type); // FIXME + } +}; + +/** + * @param {number} index + */ +capnp.genhelper.objectSet = function(type, builder, index, value) { + if (type instanceof capnp.list.List) { + if (goog.isArray(value)) { + var len = value.length; + var l = capnp.genhelper.objectInit(builder, index, [type, len]); + for (var i = 0; i < len; ++i) { + l.set(i, value[i]); + } + } + else { + builder.setListField(index, value.getReader()); + } + } + else if (type === capnp.blob.Text) { + if (kj.util.isString(value)) { + value = new capnp.blob.StringTextReader(value); + } + builder.setTextBlobField(index, value); + } + else if (type === capnp.blob.Data) { + builder.setDataBlobField(index, value); + } + else { + capnp.genhelper.structSet(type, builder, index, value); + } +}; + +/** + * @param {number} index + * @param {number} defaultBytes + */ +capnp.genhelper.objectGetFromBuilder = function(type, builder, index, defaultValue, defaultBytes) { + if (type instanceof capnp.list.List) { + return new type.Builder(type.getBuilderAsFieldOf(builder, index, defaultValue)); + } + else if (type === capnp.blob.Text) { + if (!defaultValue) { defaultValue = null; defaultBytes = 0; } + else if (!defaultBytes) { defaultBytes = 0; } + return builder.getTextBlobField(index, defaultValue, defaultBytes); + } + else if (type === capnp.blob.Data) { + if (!defaultValue) { defaultValue = null; defaultBytes = 0; } + else if (!defaultBytes) { defaultBytes = 0; } + return builder.getDataBlobField(index, defaultValue, defaultBytes); + } + else { + return new type.Builder(builder.getStructField(index, type.STRUCT_SIZE)); + } +}; + +/** + * @param {number} index + */ +capnp.genhelper.objectGetFromReader = function(type, reader, index, defaultValue) { + if (type === undefined) { + throw new Error('NYI'); + } + else if (type instanceof capnp.list.List) { + return new type.Reader(type.getReaderAsFieldOf(reader, index, defaultValue)); + } + else if (type === capnp.blob.Text) { + var defaultBytes; + if (!defaultValue) { defaultValue = null; defaultBytes = 0; } + else if (!defaultBytes) { defaultBytes = 0; } + return reader.getTextBlobField(index, defaultValue, defaultBytes); + } + else if (type === capnp.blob.Data) { + var defaultBytes; + if (!defaultValue) { defaultValue = null; defaultBytes = 0; } + else if (!defaultBytes) { defaultBytes = 0; } + return reader.getDataBlobField(index, defaultValue, defaultBytes); + } + else { + return new type.Reader(reader.getStructField(index, type.STRUCT_SIZE)); + } +}; + +/** + * @constructor + * @param {number} offset + * @param {number} numElements + */ +capnp.genhelper.ConstText = function(segmentData, offset, numElements) { + + var segment = new capnp.arena.SegmentReader(null, null, new DataView(segmentData), null); + + this.get = function() { + return capnp.blob.Text.Reader(segment, offset, numElements); + }; + return this; +}; + +/** + * @constructor + * @param {number} offset + * @param {number} numElements + */ +capnp.genhelper.ConstData = function(segmentData, offset, numElements) { + + this.get = function() { + return new capnp.blob.Data.Reader(new Uint8Array(segmentData, offset, numElements), numElements); + }; + return this; +}; + +/** + * @constructor + * @param {number} offset + * @param {number} numElements + */ +capnp.genhelper.ConstStruct = function(type, segmentData, offset, numElements) { + + var segment = new capnp.arena.SegmentReader(null, null, new DataView(segmentData), null); + + this.get = function() { + return new type.Reader(capnp.layout.StructReader.readRoot(offset << 3, segment, Number.MAX_VALUE)); + }; + return this; +}; + +/** + * @constructor + * @param {number} offset + * @param {number} numElements + */ +capnp.genhelper.ConstList = function(type, segmentData, offset, numElements) { + + var segment = new capnp.arena.SegmentReader(null, null, new DataView(segmentData), null); + + this.get = function() { + return new type.Reader(capnp.layout.ListReader.readRoot(offset << 3, segment, type.getElementSize())); + }; + return this; +}; + +/** @const */ capnp.genhelper.NullStructReader = new capnp.layout.StructReader(null, 0, 0, 0, 0, 0, Number.MAX_VALUE); + +function safeToString(obj) { + if (obj === null) { + return 'null'; + } + if (obj === undefined) { + return 'undefined'; + } + if (obj instanceof capnp.blob.Text.Reader || + obj instanceof capnp.blob.Text.Builder) { + return '"' + obj + '"'; + } + return obj.toString(); +} + + +capnp.genhelper.ToStringHelper = function(object, className, fieldList, hasList, getList, discriminator) { + if (discriminator !== undefined) { + var hasGetter = hasList[discriminator]; + var getter = getList[discriminator]; + var value = getter.call(object); + if (hasGetter.call(object) || value === 0) { + if (value !== undefined) { + return '(' + fieldList[discriminator] + ' = ' + safeToString(value) + ')'; + } + else { + return '(' + fieldList[discriminator] + ')'; + } + } + else { + return '()'; + } + } + + /* + var result = className + "{"; + for (var i=0, len=fieldList.length; i0) result += ', '; + result += fieldList[i]; + result += '='; + var getter = 'get' + fieldList[i].charAt(0).toUpperCase() + fieldList[i].slice(1); + result += safeToString(object[getter]()); + } + return result + "}"; + */ + + var result = '('; + //result += " [discrim=" + discriminator + " / " + (object.which ? object.which() : "?") + "] "; + var first = true; + for (var i = 0, len = fieldList.length; i < len; ++i) { + var hasGetter = hasList[i]; + var getter = getList[i]; + if (hasGetter.call(object)) { + if (first) { + first = false; + } + else { + result += ', '; + } + result += fieldList[i]; + result += ' = ' + safeToString(getter.call(object)); + } + } + return result + ')'; +}; + +capnp.genhelper.StructSize = capnp.layout.StructSize; diff --git a/javascript/lib/capnp_layout.js b/javascript/lib/capnp_layout.js new file mode 100644 index 0000000..bfd0810 --- /dev/null +++ b/javascript/lib/capnp_layout.js @@ -0,0 +1,3041 @@ +/* + * Copyright (c) 2013, Julian Scheid + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +goog.provide('capnp.layout'); + +goog.require('capnp.arena'); +goog.require('capnp.common'); +goog.require('kj.util'); + +var boundsCheck = function(segment, start, end) { + // If segment is null, this is an unchecked message, so we + // don't do bounds checks. + return segment === null || segment.containsInterval(start, end); +}; + +var checkAlignment = function(segment, ptr) { + // FIXME: + // KJ_DASSERT((uintptr_t)ptr % sizeof(void*) === 0, + // "Pointer section of struct list element not aligned."); + return segment.createWirePointerAt(ptr); +}; + +function memcpy(destSegment, destOffset, srcSegment, srcOffset, numBytes) { + + var destUint8Array = destSegment.getUint8Array(); + var srcUint8Array = srcSegment.getUint8Array(); + + destUint8Array.set(srcUint8Array.subarray( + srcOffset, srcOffset + numBytes), destOffset); +} + +function memclear(destSegment, destOffset, numBytes) { + + goog.asserts.assert(kj.util.isRegularNumber(destOffset)); + goog.asserts.assert(kj.util.isRegularNumber(numBytes)); + + var destUint8Array = destSegment.getUint8Array(); + for (var i = destOffset, end = destOffset + numBytes; i < end; ++i) { + destUint8Array[i] = 0; + } +} + +/** + * @param {capnp.layout.SegmentBuilder} segment + * @param {capnp.layout.WirePointer} ref + */ +var zeroPointerAndFars = function(segment, ref) { + // Zero out the pointer itself and, if it is a far pointer, zero the landing pad as well, but + // do not zero the object body. Used when upgrading. + + if (ref.kind() === capnp.layout.Kind.FAR) { + var padSegment = segment.getArena().getSegment(ref.farRef().segmentId); + var pad = ref.farPositionInSegment(); + memclear(padSegment, pad << 3, capnp.layout.WirePointer.SIZE_IN_BYTES * (1 + (ref.isDoubleFar() ? 1 : 0))); + } + ref.clear(); +}; + +/** + * @param {capnp.layout.SegmentBuilder} segment + * @param {capnp.layout.WirePointer} ref + */ +var zeroObject = function(segment, ref) { + // Zero out the pointed-to object. Use when the pointer is about to be overwritten making the + // target object no longer reachable. + + switch (ref.kind()) { + case capnp.layout.Kind.STRUCT: + zeroObjectTag(segment, ref, ref.target()); + break; + case capnp.layout.Kind.LIST: + zeroObjectTag(segment, ref, ref.target()); + break; + case capnp.layout.Kind.FAR: { + segment = segment.getArena().getSegment(ref.farRef().segmentId); + var pad = segment.createWirePointerAt(ref.farPositionInSegment() << 3); + + if (ref.isDoubleFar()) { + var pad1 = segment.createWirePointerAt((ref.farPositionInSegment() + 1) << 3); + segment = segment.getArena().getSegment(pad.farRef().segmentId); + zeroObject(segment, pad1, pad.farPositionInSegment()); + memclear(segment, pad.getByteOffset(), 0, capnp.layout.WirePointer.SIZE_IN_BYTES * 2); + } else { + zeroObject(segment, pad); + memclear(segment, pad.getByteOffset(), 0, capnp.layout.WirePointer.SIZE_IN_BYTES); + } + break; + } + case capnp.layout.Kind.RESERVED_3: + kj.debug.FAIL_ASSERT("Don't know how to handle RESERVED_3."); + break; + } +}; + +/** + * @param {capnp.layout.SegmentBuilder} segment + * @param {capnp.layout.WirePointer} tag + * @param {Pointer} ptr + */ +var zeroObjectTag = function(segment, tag, ptr) { + goog.asserts.assert(kj.util.isRegularNumber(ptr) && ptr >= 0); + switch (tag.kind()) { + case capnp.layout.Kind.STRUCT: { + var pointerSection = ptr + tag.getStructRef().dataSize(); + var count = tag.getStructRef().ptrCount(); + for (var i = 0; i < count; i++) { + zeroObject(segment, segment.createWirePointerAt((pointerSection + i) << 3)); + } + memclear(segment, ptr << 3, tag.getStructRef().wordSize() * capnp.common.BYTES_PER_WORD); + break; + } + case capnp.layout.Kind.LIST: { + switch (tag.getListRef().elementSize()) { + case capnp.layout.FieldSize.VOID: + // Nothing. + break; + case capnp.layout.FieldSize.BIT: + case capnp.layout.FieldSize.BYTE: + case capnp.layout.FieldSize.TWO_BYTES: + case capnp.layout.FieldSize.FOUR_BYTES: + case capnp.layout.FieldSize.EIGHT_BYTES: + memclear(segment, ptr << 3, + capnp.common.roundBitsUpToWords(tag.getListRef().elementCount() * + capnp.layout.dataBitsPerElement(tag.getListRef().elementSize())) + * capnp.common.BYTES_PER_WORD); + break; + case capnp.layout.FieldSize.POINTER: { + var count = tag.getListRef().elementCount(); + for (var i = 0; i < count; i++) { + zeroObject(segment, segment.createWirePointerAt((ptr + i) << 3)); + } + memclear(segment, ptr << 3, capnp.common.POINTER_SIZE_IN_WORDS * count * capnp.common.BYTES_PER_WORD); + break; + } + case capnp.layout.FieldSize.INLINE_COMPOSITE: { + var elementTag = segment.createWirePointerAt(ptr << 3); + + goog.asserts.assert(elementTag.kind() === capnp.layout.Kind.STRUCT, + "Don't know how to handle non-STRUCT inline composite."); + + var dataSize = elementTag.getStructRef().dataSize(); + var pointerCount = elementTag.getStructRef().ptrCount(); + + var pos = ptr + capnp.common.POINTER_SIZE_IN_WORDS; + var count = elementTag.inlineCompositeListElementCount(); + for (var i = 0; i < count; i++) { + pos += dataSize; + + for (var j = 0; j < pointerCount; j++) { + zeroObject(segment, segment.createWirePointerAt(pos << 3)); + pos += capnp.common.POINTER_SIZE_IN_WORDS; + } + } + + memclear(segment, ptr << 3, (elementTag.getStructRef().wordSize() * count + capnp.common.POINTER_SIZE_IN_WORDS) + * capnp.common.BYTES_PER_WORD); + break; + } + } + break; + } + case capnp.layout.Kind.FAR: + kj.debug.FAIL_ASSERT('Unexpected FAR pointer.'); + break; + case capnp.layout.Kind.RESERVED_3: + kj.debug.FAIL_ASSERT("Don't know how to handle RESERVED_3."); + break; + } +}; + + +var copyStruct = function(dstSegment, dst, srcSegment, src, dataSize, pointerCount) { + + var dstUint8Array = dstSegment.getUint8Array(); + var srcUint8Array = srcSegment.getUint8Array(); + dstUint8Array.set(srcUint8Array.subarray(src << 3, (src << 3) + dataSize * capnp.common.BYTES_PER_WORD), dst << 3); + + for (var i = 0; i < pointerCount; i++) { + var srcRef = srcSegment.createWirePointerAt((src + dataSize + i) << 3); + var dstRef = dstSegment.createWirePointerAt((dst + dataSize + i) << 3); + copyMessage(dstSegment, dstRef, srcSegment, srcRef); + } +}; + +var copyMessage = function(segment, dst, srcSegment, src) { + // Not always-inline because it's recursive. + + goog.asserts.assert(dst.kind() != capnp.layout.Kind.RESERVED_3, 'invalid pointer: ' + dst); + + switch (src.kind()) { + case capnp.layout.Kind.STRUCT: { + if (src.isNull()) { + var uint8Array = segment.getUint8Array(); + for (var i = 0; i < capnp.common.BYTES_PER_WORD; ++i) { + uint8Array[dst.getByteOffset() + i] = 0; + } + return [segment, dst, null]; + } else { + var srcPtr = src.target(); + var allocateResult = capnp.layout.allocate( + dst, segment, src.getStructRef().wordSize(), capnp.layout.Kind.STRUCT, null); + var dstPtr = allocateResult[0]; + segment = allocateResult[1]; + dst = allocateResult[2]; + + copyStruct(segment, dstPtr, srcSegment, srcPtr, src.getStructRef().dataSize(), + src.getStructRef().ptrCount()); + + dst.setStructRef(new capnp.layout.StructSize(src.getStructRef().dataSize(), src.getStructRef().ptrCount())); + return [segment, dst, dstPtr]; + } + } + case capnp.layout.Kind.LIST: { + switch (src.getListRef().elementSize()) { + case capnp.layout.FieldSize.VOID: + case capnp.layout.FieldSize.BIT: + case capnp.layout.FieldSize.BYTE: + case capnp.layout.FieldSize.TWO_BYTES: + case capnp.layout.FieldSize.FOUR_BYTES: + case capnp.layout.FieldSize.EIGHT_BYTES: { + var wordCount = capnp.common.roundBitsUpToWords( + src.getListRef().elementCount() * capnp.layout.dataBitsPerElement(src.getListRef().elementSize())); + var srcPtr = src.target(); + var allocateResult = capnp.layout.allocate(dst, segment, wordCount, capnp.layout.Kind.LIST, null); + var dstPtr = allocateResult[0]; + segment = allocateResult[1]; + dst = allocateResult[2]; + + var dstUint8Array = segment.getUint8Array(); + var srcUint8Array = srcSegment.getUint8Array(); + dstUint8Array.set(srcUint8Array.subarray(srcPtr << 3, (srcPtr << 3) + wordCount * capnp.common.BYTES_PER_WORD), dstPtr << 3); + + dst.setListRef(src.getListRef().elementSize(), src.getListRef().elementCount()); + return [segment, dst, dstPtr]; + } + + case capnp.layout.FieldSize.POINTER: { + + var allocateResult = capnp.layout.allocate(dst, segment, src.getListRef().elementCount() * capnp.common.WORDS_PER_POINTER, + capnp.layout.Kind.LIST, null); + var dstRefs = allocateResult[0]; + segment = allocateResult[1]; + dst = allocateResult[2]; + + var n = src.getListRef().elementCount(); + for (var i = 0; i < n; i++) { + copyMessage(segment, segment.createWirePointerAt((dstRefs + i) << 3), + srcSegment, srcSegment.createWirePointerAt((src.target() + i) << 3)); + } + + dst.setListRef(capnp.layout.FieldSize.POINTER, src.getListRef().elementCount()); + return [segment, dst, dstRefs]; + } + + case capnp.layout.FieldSize.INLINE_COMPOSITE: { + var srcPtr = src.target(); + var allocateResult = capnp.layout.allocate(dst, segment, + src.getListRef().inlineCompositeWordCount() + capnp.common.POINTER_SIZE_IN_WORDS, + capnp.layout.Kind.LIST, null); + var dstPtr = allocateResult[0]; + segment = allocateResult[1]; + dst = allocateResult[2]; + + dst.setListRefInlineComposite(src.getListRef().inlineCompositeWordCount()); + + var dstUint8Array = segment.getUint8Array(); + var srcUint8Array = srcSegment.getUint8Array(); + dstUint8Array.set(srcUint8Array.subarray(srcPtr << 3, (srcPtr << 3) + capnp.common.BYTES_PER_WORD), dstPtr << 3); + + var srcElement = srcPtr + capnp.common.POINTER_SIZE_IN_WORDS; + var dstElement = dstPtr + capnp.common.POINTER_SIZE_IN_WORDS; + + var srcTag = srcSegment.createWirePointerAt(srcPtr << 3); + + goog.asserts.assert(srcTag.kind() === capnp.layout.Kind.STRUCT, + 'INLINE_COMPOSITE of lists is not yet supported.'); + + var n = srcTag.inlineCompositeListElementCount(); + for (var i = 0; i < n; i++) { + copyStruct(segment, dstElement, srcSegment, srcElement, + srcTag.getStructRef().dataSize(), srcTag.getStructRef().ptrCount()); + srcElement += srcTag.getStructRef().wordSize(); + dstElement += srcTag.getStructRef().wordSize(); + } + return [segment, dst, dstPtr]; + } + } + break; + } + default: + kj.debug.FAIL_REQUIRE('Copy source message contained unexpected kind.'); + break; + } + + return [segment, dst, null]; +}; + + +var transferPointerWithSrcPtr = function(dstSegment, dst, srcSegment, srcTag, srcPtr) { + + // Like the other overload, but splits src into a tag and a target. Particularly useful for + // OrphanBuilder. + + if (dstSegment === srcSegment) { + // Same segment, so create a direct pointer. + dst.setKindAndTarget(srcTag.kind(), srcPtr, dstSegment); + // We can just copy the upper 32 bits. (Use memcpy() to comply with aliasing rules.) + dst.setUpper32Bits(srcTag.getUpper32Bits()); + } else { + // Need to create a far pointer. Try to allocate it in the same segment as the source, so + // that it doesn't need to be a double-far. + + var allocateResult = srcSegment.allocate(1); + if (allocateResult === null) { + // Darn, need a double-far. + var allocation = srcSegment.getArena().allocate(2); + var farSegment = allocation.segment; + var landingPad0 = farSegment.createWirePointerAt((allocation.words + 0) << 3); + var landingPad1 = farSegment.createWirePointerAt((allocation.words + 1) << 3); + + landingPad0.setFar(false, srcPtr); + landingPad0.setFarRef(srcSegment.getSegmentId()); + + landingPad1.setKindWithZeroOffset(srcTag.kind()); + landingPad1.setUpper32Bits(srcTag.getUpper32Bits()); + + dst.setFar(true, allocation.words); + dst.setFarRef(farSegment.getSegmentId()); + + } else { + var landingPad = srcSegment.createWirePointerAt(allocateResult << 3); + // Simple landing pad is just a pointer. + landingPad.setKindAndTarget(srcTag.kind(), srcPtr, srcSegment); + landingPad.setUpper32Bits(srcTag.getUpper32Bits()); + + dst.setFar(false, allocateResult); + dst.setFarRef(srcSegment.getSegmentId()); + } + } + +}; + +var transferPointer = function(dstSegment, dst, srcSegment, src) { + // Make *dst point to the same object as *src. Both must reside in the same message, but can + // be in different segments. Not always-inline because this is rarely used. + // + // Caller MUST zero out the source pointer after calling this, to make sure no later code + // mistakenly thinks the source location still owns the object. transferPointer() doesn't do + // this zeroing itself because many callers transfer several pointers in a loop then zero out + // the whole section. + + kj.debug.DASSERT(dst.isNull()); + // We expect the caller to ensure the target is already null so won't leak. + + if (src.isNull()) { + memclear(dstSegment, dst.getByteOffset(), capnp.layout.WirePointer.SIZE_IN_BYTES); + } else if (src.kind() === capnp.layout.Kind.FAR) { + // Far pointers are position-independent, so we can just copy. + memcpy(dstSegment, dst.getByteOffset(), srcSegment, src.getByteOffset(), capnp.layout.WirePointer.SIZE_IN_BYTES); + } else { + transferPointerWithSrcPtr(dstSegment, dst, srcSegment, src, src.target()); + } +}; + + +/** + * @enum {number} + */ +capnp.layout.FieldSize = { + + VOID: 0, + BIT: 1, + BYTE: 2, + TWO_BYTES: 3, + FOUR_BYTES: 4, + EIGHT_BYTES: 5, + + // Indicates that the field lives in the pointer section, not the + // data section. + POINTER: 6, + INLINE_COMPOSITE: 7 +}; + +/** + * @enum {number} + */ +capnp.layout.Kind = { + + // Reference points at / describes a struct. + STRUCT: 0, + + // Reference points at / describes a list. + LIST: 1, + + // Reference is a "far pointer", which points at data located in a + // different segment. The eventual target is one of the other + // kinds. + FAR: 2, + + RESERVED_3: 3 +}; + +/** + * @constructor + * @param {number} dataSize + * @param {number} ptrCount + */ +capnp.layout.StructRef = function(dataSize, ptrCount) { + this.dataSize = function() { return dataSize; }; + this.ptrCount = function() { return ptrCount; }; + this.wordSize = function() { return dataSize + ptrCount * capnp.common.WORDS_PER_POINTER; }; + this.toString = function() { return 'StructRef{dataSize=' + dataSize + ',ptrCount=' + ptrCount + '}'; }; +}; + +/** + * @constructor + * @param {number} elementSize + * @param {number} elementCount + */ +capnp.layout.ListRef = function(elementSize, elementCount) { + this.elementSize = function() { return elementSize; }; + this.elementCount = function() { return elementCount; }; + this.inlineCompositeWordCount = function() { return elementCount; }; +}; + +/** + * @constructor + * @param {number} baseOffset + */ +capnp.layout.WirePointer = function(baseOffset, dataView) { + + goog.asserts.assert(kj.util.isRegularNumber(baseOffset)); + goog.asserts.assert(kj.util.isDataView(dataView), 'WirePointer constructor got invalid dataView: ', dataView); + + this.getByteOffset = function() { + return baseOffset; + }; + + this.asPointer = function() { + return baseOffset >> 3; + }; + + this.getOffsetAndKind = function() { + return dataView.getUint32(0, true); + }; + + this.setOffsetAndKind = function(offsetAndKind) { + dataView.setUint32(0, offsetAndKind, true); + }; + + this.setKindWithZeroOffset = function(kind) { + this.setOffsetAndKind(kind); + }; + + this.getUpper32Bits = function() { + return dataView.getUint32(4, true); + }; + + this.setUpper32Bits = function(upper32bits) { + dataView.setUint32(4, upper32bits, true); + }; + + this.isNull = function() { + return this.getOffsetAndKind() === 0 && this.getUpper32Bits() === 0; + }; + + this.clear = function() { + this.setOffsetAndKind(0); + this.setUpper32Bits(0); + }; + + this.kind = function() { + return this.getOffsetAndKind() & 3; + }; + + this.target = function() { + var offset = this.getOffsetAndKind() >> 2; + return (baseOffset >>> 3) + 1 + offset; + }; + + this.setKindAndTarget = function(kind, target, segment) { + var relOffset = target - (baseOffset >>> 3) - 1; + this.setOffsetAndKind((relOffset << 2) | kind); + }; + + this.setKindAndTargetForEmptyStruct = function() { + this.setOffsetAndKind(0xfffffffc); + }; + + this.setKindForOrphan = function(kind) { + kj.debug.DREQUIRE(kind !== capnp.layout.Kind.FAR); + this.setOffsetAndKind(kind | 0xfffffffc); + }; + + this.setListRefInlineComposite = function(wordCount) { + this.setUpper32Bits((wordCount << 3) | capnp.layout.FieldSize.INLINE_COMPOSITE); + }; + + this.setKindAndInlineCompositeListElementCount = function(kind, elementCount) { + this.setOffsetAndKind((elementCount << 2) | kind); + }; + + this.setStructRef = function(structSize) { + goog.asserts.assert(structSize instanceof capnp.layout.StructSize); + //this.setUpper32Bits(structSize.getDataWordCount() << 16 | structSize.getPointerCount()); + dataView.setUint16(4, structSize.getDataWordCount(), true); + dataView.setUint16(6, structSize.getPointerCount(), true); + }; + + this.setFar = function(isDoubleFar, pos) { + this.setOffsetAndKind((pos << 3) | (isDoubleFar << 2) | capnp.layout.Kind.FAR); + }; + + this.setFarRef = function(segmentId) { + this.setUpper32Bits(segmentId); + }; + + this.farRef = function() { + return { segmentId: this.getUpper32Bits() }; + }; + + this.farPositionInSegment = function() { + kj.debug.DREQUIRE(this.kind() === capnp.layout.Kind.FAR, + 'positionInSegment() should only be called on FAR pointers.'); + return this.getOffsetAndKind() >>> 3; + }; + + this.isDoubleFar = function() { + kj.debug.DREQUIRE(this.kind() === capnp.layout.Kind.FAR, + 'isDoubleFar() should only be called on FAR pointers.'); + return (this.getOffsetAndKind() >> 2) & 1; + }; + + this.getStructRef = function() { + var dataSize = dataView.getUint16(4, true); + var ptrCount = dataView.getUint16(6, true); + return new capnp.layout.StructRef(dataSize, ptrCount); + }; + + this.setListRef = function(fieldSize, elementCount) { + this.setUpper32Bits((elementCount << 3) | fieldSize); + }; + + this.getListRef = function() { + var upper32Bits = this.getUpper32Bits(); + return new capnp.layout.ListRef(upper32Bits & 7, upper32Bits >> 3); + }; + + this.inlineCompositeListElementCount = function() { + return this.getOffsetAndKind() >>> 2; + }; + + this.toString = function() { + return 'WirePointer{byteOffset=' + baseOffset + ', kind=' + this.kind() + ', data=' + kj.util.decimalToHex(this.getOffsetAndKind(), 8) + ' ' + kj.util.decimalToHex(this.getUpper32Bits(), 8) + '}'; + }; + + return this; +}; + +capnp.layout.WirePointer.zero = function() { + + var zeroBuffer = new ArrayBuffer(8); + return new capnp.layout.WirePointer(0, new DataView(zeroBuffer)); +}; + +/** @const */ capnp.layout.WirePointer.SIZE_IN_BYTES = capnp.common.BYTES_PER_WORD; + + +capnp.layout.initStructListPointer = function(ref, segment, elementCount, elementSize, orphanArena) { + + if (elementSize.getPreferredListEncoding() != capnp.layout.FieldSize.INLINE_COMPOSITE) { + // Small data-only struct. Allocate a list of primitives instead. + return capnp.layout.initListPointer(ref, segment, elementCount, elementSize.getPreferredListEncoding(), + orphanArena); + } + + var wordsPerElement = elementSize.getTotal(); + + // Allocate the list, prefixed by a single WirePointer. + var wordCount = elementCount * wordsPerElement; + var allocateResult = capnp.layout.allocate(ref, segment, capnp.common.POINTER_SIZE_IN_WORDS + wordCount, capnp.layout.Kind.LIST, + orphanArena); + var ptr = allocateResult[0]; + segment = allocateResult[1]; + ref = allocateResult[2]; + + // Initialize the pointer. + // INLINE_COMPOSITE lists replace the element count with the word count. + ref.setListRefInlineComposite(wordCount); + + // Initialize the list tag. + var listTag = segment.createWirePointerAt(ptr << 3); + listTag.setKindAndInlineCompositeListElementCount(capnp.layout.Kind.STRUCT, elementCount); + listTag.setStructRef(elementSize); + ptr += capnp.common.POINTER_SIZE_IN_WORDS; + + // Build the ListBuilder. + return new capnp.layout.ListBuilder(segment, ptr, wordsPerElement * capnp.common.BITS_PER_WORD, elementCount, + elementSize.getDataWordCount() * capnp.common.BITS_PER_WORD, elementSize.getPointerCount()); +}; + +/** + * @constructor + */ +capnp.layout.ListBuilder = function(segment, ptr, step, elementCount, structDataSize, structPointerCount) { + + goog.asserts.assert(typeof(elementCount) === 'number', 'elementCount NaN'); + goog.asserts.assert(!isNaN(step)); + + this.segment = segment; + + this.toString = function() { return 'ListBuilder{ptr=' + ptr + ',step=' + step + ',elementCount=' + elementCount + '}'; }; + + this.asReader = function() { + return new capnp.layout.ListReader(segment, ptr, elementCount, step, structDataSize, structPointerCount, Number.MAX_VALUE); + }; + + this.size = function() { return elementCount; }; + + + this.getLocation = function() { + // Get the object's location. Only valid for independently-allocated objects (i.e. not list + // elements). + + if (step <= capnp.common.BITS_PER_WORD) { + return ptr; + } else { + return ptr - capnp.common.POINTER_SIZE_IN_WORDS; + } + } + + this.getStructElement = function(index) { + + var indexBit = index * step; + var structData = (ptr << 3) + (indexBit / capnp.common.BITS_PER_BYTE) >>> 0; + + return new capnp.layout.StructBuilder(segment, + structData, + (structData + (structDataSize / capnp.common.BITS_PER_BYTE) >>> 0) >> 3, + structDataSize, + structPointerCount, + indexBit % capnp.common.BITS_PER_BYTE); + }; + + this.getTextBlobElement = function(index) { + var ref = segment.createWirePointerAt((ptr << 3) + index * step / capnp.common.BITS_PER_BYTE); + return capnp.layout.getWritableTextPointer(ref, ref.target(), segment, '', 0); + }; + + this.getDataBlobElement = function(index) { + var ref = segment.createWirePointerAt((ptr << 3) + index * step / capnp.common.BITS_PER_BYTE); + return capnp.layout.getWritableDataPointer(ref, ref.target(), segment, null, 0); + }; + + this.setTextBlobElement = function(index, value) { + var ref = segment.createWirePointerAt((ptr << 3) + index * step / capnp.common.BITS_PER_BYTE); + capnp.layout.setTextPointer(ref, segment, value); + }; + + this.setDataBlobElement = function(index, value) { + var ref = segment.createWirePointerAt((ptr << 3) + index * step / capnp.common.BITS_PER_BYTE); + capnp.layout.setDataPointer(ref, segment, value); + }; + + this.getListElement = function(index, expectedElementSize) { + var ref = segment.createWirePointerAt((ptr << 3) + index * step / capnp.common.BITS_PER_BYTE); + return capnp.layout.getWritableListPointer(ref, ref.target(), segment, expectedElementSize, null); + }; + + this.setListElement = function(index, value) { + var ref = segment.createWirePointerAt((ptr << 3) + index * step / capnp.common.BITS_PER_BYTE); + capnp.layout.setListPointer(segment, ref, value); + }; + + this.initListElement = function(index, elementSize, elementCount) { + goog.asserts.assert(elementSize !== undefined); + var ref = segment.createWirePointerAt((ptr << 3) + index * step / capnp.common.BITS_PER_BYTE); + return capnp.layout.initListPointer(ref, segment, elementCount, elementSize); + }; + + this.getStructListElement = function(index, elementSize) { + var ref = segment.createWirePointerAt((ptr << 3) + index * step / capnp.common.BITS_PER_BYTE); + return capnp.layout.getWritableStructListPointer(ref, ref.target(), segment, elementSize, null); + }; + + this.initStructListElement = function(index, elementCount, elementSize) { + var ref = segment.createWirePointerAt((ptr << 3) + index * step / capnp.common.BITS_PER_BYTE); + return capnp.layout.initStructListPointer(ref, segment, elementCount, elementSize); + }; + + this.getDataElement = function(clazz, index) { + var offset = (ptr * capnp.common.BITS_PER_WORD) + index * step; + return clazz.getValue(segment.getDataView(), offset); + }; + + this.setDataElement = function(clazz, index, value) { + var offset = (ptr * capnp.common.BITS_PER_WORD) + index * step; + clazz.setValue(segment.getDataView(), offset, value); + }; + + return this; +}; + + +/** + * @constructor + */ +capnp.layout.ListReader = function(segment, ptr, elementCount, step, structDataSize, structPointerCount, nestingLimit) { + + goog.asserts.assert(kj.util.isRegularNumber(nestingLimit)); + + this.ptr = ptr; + this.segment = segment; + this.elementCount = elementCount; + this.step = step; + this.structDataSize = structDataSize; + this.structPointerCount = structPointerCount; + this.nestingLimit = nestingLimit; + + this.getSegment = function() { return segment; }; + this.size = function() { return elementCount; }; + + this.getDataElement = function(clazz, index) { + var offset = (ptr * capnp.common.BITS_PER_WORD) + index * step; + return clazz.getValue(segment.getDataView(), offset); + }; + + this.getStructElement = function(index) { + + var indexBit = index * step; + var structData = (ptr << 3) + ((indexBit / capnp.common.BITS_PER_BYTE) >>> 0); + + return new capnp.layout.StructReader(segment, structData, + (structData >> 3) + (structDataSize / capnp.common.BITS_PER_WORD), + structDataSize, structPointerCount, indexBit % capnp.common.BITS_PER_BYTE, + nestingLimit - 1); + }; + + this.getListElement = function(index, expectedElementSize) { + var ref = checkAlignment(segment, (ptr << 3) + (index * step / capnp.common.BITS_PER_BYTE) >>> 0); + return capnp.layout.readListPointer( + segment, ref, ref.target(), null, expectedElementSize, nestingLimit); + }; + + this.getTextBlobElement = function(index) { + var ref = checkAlignment(segment, (ptr << 3) + (index * step / capnp.common.BITS_PER_BYTE) >>> 0); + return capnp.layout.readTextPointer(segment, ref, ref.target(), '', 0); + }; + + this.getDataBlobElement = function(index) { + var ref = checkAlignment(segment, (ptr << 3) + (index * step / capnp.common.BITS_PER_BYTE) >>> 0); + return capnp.layout.readDataPointer(segment, ref, ref.target(), null, 0); + }; + + this.toString = function() { return 'ListReader{...}'; }; +}; + +capnp.layout.ListReader.readRoot = function(location, segment, elementSize) { + + goog.asserts.assert(segment instanceof capnp.arena.SegmentReader, 'ListReader.readRoot got invalid segment'); + goog.asserts.assert(kj.util.isRegularNumber(elementSize), 'ListReader.readRoot got invalid elementSize'); + + var ref = segment.createWirePointerAt(location); + return capnp.layout.readListPointer(segment, ref, ref.target(), null, elementSize, Number.MAX_VALUE); +}; + +capnp.layout.setTextPointer = function(ref, segment, value, orphanArena) { + var allocation = capnp.layout.initTextPointer(ref, segment, value.size(), orphanArena); + + var target = allocation.value.asUint8Array(); + target.set(value.asUint8Array()); + + return allocation; +}; + + +capnp.layout.initTextPointer = function(ref, segment, size, orphanArena) { + + // The byte list must include a NUL terminator. + var byteSize = size + 1; + + // Allocate the space. + var allocateResult = capnp.layout.allocate(ref, segment, capnp.common.roundBytesUpToWords(byteSize), capnp.layout.Kind.LIST, orphanArena); + var ptr = allocateResult[0]; + segment = allocateResult[1]; + ref = allocateResult[2]; + + // Initialize the pointer. + ref.setListRef(capnp.layout.FieldSize.BYTE, byteSize); + + // Build the Text::Builder. This will initialize the NUL terminator. + return { segment: segment, value: capnp.blob.Text.Builder(segment, ptr, size) }; +}; + + +var BITS_PER_ELEMENT_TABLE = [0, 1, 8, 16, 32, 64, 0, 0]; + +capnp.layout.dataBitsPerElement = function(fieldSize) { + return BITS_PER_ELEMENT_TABLE[fieldSize]; +}; + +capnp.layout.pointersPerElement = function(fieldSize) { + return fieldSize === capnp.layout.FieldSize.POINTER ? 1 : 0; +}; + + +capnp.layout.allocate = function(ref, segment, amount, kind, orphanArena) { + + // Allocate space in the message for a new object, creating far pointers if necessary. + // + // * `ref` starts out being a reference to the pointer which shall be assigned to point at the + // new object. On return, `ref` points to a pointer which needs to be initialized with + // the object's type information. Normally this is the same pointer, but it can change if + // a far pointer was allocated -- in this case, `ref` will end up pointing to the far + // pointer's tag. Either way, `allocate()` takes care of making sure that the original + // pointer ends up leading to the new object. On return, only the upper 32 bit of `*ref` + // need to be filled in by the caller. + // + // * `segment` starts out pointing to the segment containing `ref`. On return, it points to + // the segment containing the allocated object, which is usually the same segment but could + // be a different one if the original segment was out of space. + // + // * `amount` is the number of words to allocate. + // + // * `kind` is the kind of object to allocate. It is used to initialize the pointer. It + // cannot be `FAR` -- far pointers are allocated automatically as needed. + // + // * `orphanArena` is usually null. If it is non-null, then we're allocating an orphan object. + // In this case, `segment` starts out null; the allocation takes place in an arbitrary + // segment belonging to the arena. `ref` will be initialized as a non-far pointer, but its + // target offset will be set to zero. + + goog.asserts.assert(amount % 1 === 0, 'allocate asked to allocate fractional amount: ' + amount); + + if (!orphanArena) { + if (!ref.isNull()) zeroObject(segment, ref); + + if (amount === 0 && kind === capnp.layout.Kind.STRUCT) { + // Note that the check for kind == WirePointer::STRUCT will hopefully cause this whole + // branch to be optimized away from all the call sites that are allocating non-structs. + ref.setKindAndTargetForEmptyStruct(); + return [ref.asPointer(), segment, ref]; + } + + var ptr = segment.allocate(amount); + + if (!ptr) { + // Need to allocate in a new segment. We'll need to allocate an extra pointer worth of + // space to act as the landing pad for a far pointer. + + var amountPlusRef = amount + capnp.common.POINTER_SIZE_IN_WORDS; + var allocation = segment.getArena().allocate(amountPlusRef); + segment = allocation.segment; + ptr = allocation.words; + + goog.asserts.assert(kj.util.isRegularNumber(ptr)); + + // Set up the original pointer to be a far pointer to the new segment. + ref.setFar(false, ptr); + ref.setFarRef(segment.getSegmentId()); + + // Initialize the landing pad to indicate that the data immediately follows the pad. + ref = segment.createWirePointerAt(ptr << 3); //reinterpret_cast(ptr); + ref.setKindAndTarget(kind, ptr + capnp.common.POINTER_SIZE_IN_WORDS, segment); + + // Allocated space follows new pointer. + return [ptr + capnp.common.POINTER_SIZE_IN_WORDS, segment, ref]; + } + else { + goog.asserts.assert(kj.util.isRegularNumber(ptr)); + ref.setKindAndTarget(kind, ptr, segment); + return [ptr, segment, ref]; + } + } + else { + // orphanArena is non-null. Allocate an orphan. + kj.debug.DASSERT(ref.isNull()); + var allocation = orphanArena.allocate(amount); + segment = allocation.segment; + ref.setKindForOrphan(kind); + return [allocation.words, segment, ref]; + } +}; + +capnp.layout.setDataPointer = function(ref, segment, value, orphanArena) { + + var allocation = capnp.layout.initDataPointer(ref, segment, value.size(), orphanArena); + var target = allocation.value.asUint8Array(); + target.set(value.asUint8Array()); + return allocation; +}; + + +capnp.layout.initDataPointer = function(ref, segment, size, orphanArena) { + + // Allocate the space. + var allocateResult = capnp.layout.allocate(ref, segment, capnp.common.roundBytesUpToWords(size), capnp.layout.Kind.LIST, orphanArena); + var ptr = allocateResult[0]; + segment = allocateResult[1]; + ref = allocateResult[2]; + + // Initialize the pointer. + ref.setListRef(capnp.layout.FieldSize.BYTE, size); + + // Build the Data::Builder. + return { segment: segment, value: new capnp.blob.Data.Builder(segment, ptr, size) }; +}; + +capnp.layout.initStructPointer = function(ref, segment, size, orphanArena) { + + // Allocate space for the new struct. Newly-allocated space is automatically zeroed. + var allocateResult = capnp.layout.allocate(ref, segment, size.getTotal(), capnp.layout.Kind.STRUCT, orphanArena); + var ptr = allocateResult[0]; + segment = allocateResult[1]; + ref = allocateResult[2]; + + // Initialize the pointer. + ref.setStructRef(size); + + // Build the StructBuilder. + + goog.asserts.assert(kj.util.isRegularNumber(ptr)); + goog.asserts.assert(kj.util.isRegularNumber(size.getDataWordCount())); + + return new capnp.layout.StructBuilder(segment, (ptr << 3), ptr + size.getDataWordCount(), + size.getDataWordCount() * capnp.common.BITS_PER_WORD, size.getPointerCount(), 0); +}; + +var converter = new DataView(new ArrayBuffer(8)); + +/** + * @constructor + */ +capnp.layout.StructBase = function(segment, data, dataSize, bit0Offset) { + + this.segment = segment; + this.data = data; + this.dataSize = dataSize; + this.seg_dataView = segment ? segment.getDataView() : null; + this.dataSizeBytes = ((dataSize + 7) / capnp.common.BITS_PER_BYTE) >>> 0; + this.bit0Offset = bit0Offset; +}; + +/** + * @param {number} offset + * @return {boolean} + */ +capnp.layout.StructBase.prototype.hasDataField_bool = function(offset) { + return capnp.prim.bool.getValue(this.seg_dataView, this.data * capnp.common.BITS_PER_BYTE + offset) != 0; +}; + +/** + * @param {number} offset + * @return {boolean} + */ +capnp.layout.StructBase.prototype.hasDataField_int8 = function(offset) { + return capnp.prim.int8_t.getValue(this.seg_dataView, (this.data + offset * 1) * capnp.common.BITS_PER_BYTE) != 0; +}; + +/** + * @param {number} offset + * @return {boolean} + */ +capnp.layout.StructBase.prototype.hasDataField_uint8 = function(offset) { + return capnp.prim.uint8_t.getValue(this.seg_dataView, (this.data + offset * 1) * capnp.common.BITS_PER_BYTE) != 0; +}; + +/** + * @param {number} offset + * @return {boolean} + */ +capnp.layout.StructBase.prototype.hasDataField_int16 = function(offset) { + return capnp.prim.int16_t.getValue(this.seg_dataView, (this.data + offset * 2) * capnp.common.BITS_PER_BYTE) != 0; +}; + +/** + * @param {number} offset + * @return {boolean} + */ +capnp.layout.StructBase.prototype.hasDataField_uint16 = function(offset) { + return capnp.prim.uint16_t.getValue(this.seg_dataView, (this.data + offset * 2) * capnp.common.BITS_PER_BYTE) != 0; +}; + +/** + * @param {number} offset + * @return {boolean} + */ +capnp.layout.StructBase.prototype.hasDataField_int32 = function(offset) { + return capnp.prim.int32_t.getValue(this.seg_dataView, (this.data + offset * 4) * capnp.common.BITS_PER_BYTE) != 0; +}; + +/** + * @param {number} offset + * @return {boolean} + */ +capnp.layout.StructBase.prototype.hasDataField_uint32 = function(offset) { + return capnp.prim.uint32_t.getValue(this.seg_dataView, (this.data + offset * 4) * capnp.common.BITS_PER_BYTE) != 0; +}; + +/** + * @param {number} offset + * @return {boolean} + */ +capnp.layout.StructBase.prototype.hasDataField_int64 = function(offset) { + return this.seg_dataView.getUint32(this.data + offset * 8) !== 0 || this.seg_dataView.getUint32(this.data + offset * 8 + 4) !== 0; +}; + +/** + * @param {number} offset + * @return {boolean} + */ +capnp.layout.StructBase.prototype.hasDataField_float32 = function(offset) { + return capnp.prim.float32_t.getValue(this.seg_dataView, (this.data + offset * 4) * capnp.common.BITS_PER_BYTE) != 0; +}; + +/** + * @param {number} offset + * @return {boolean} + */ +capnp.layout.StructBase.prototype.hasDataField_float64 = function(offset) { + return capnp.prim.float64_t.getValue(this.seg_dataView, (this.data + offset * 8) * capnp.common.BITS_PER_BYTE) != 0; +}; + +/** + * @param {number} offset + * @return {Array.} + */ +capnp.layout.StructBase.prototype.getDataField_int64 = function(offset) { + if (offset * 64 < this.dataSize) { + var lo = this.seg_dataView.getInt32(this.data + offset * 8, true); + var hi = this.seg_dataView.getInt32(this.data + offset * 8 + 4, true); + return [hi, lo]; + } + else { + return [0, 0]; + } +}; + +/** + * @param {number} offset + * @param {Array.} mask + * @return {Array.} + */ +capnp.layout.StructBase.prototype.getDataField_int64_masked = function(offset, mask) { + if (offset * 64 < this.dataSize) { + var lo = this.seg_dataView.getInt32(this.data + offset * 8, true); + var hi = this.seg_dataView.getInt32(this.data + offset * 8 + 4, true); + return [hi ^ mask[0], lo ^ mask[1]]; + } + else { + return [mask[0], mask[1]]; + } +}; + +/** + * @param {number} offset + * @return {boolean} + */ +capnp.layout.StructBase.prototype.hasDataField_uint64 = function(offset) { + return this.seg_dataView.getUint32(this.data + offset * 8) !== 0 || this.seg_dataView.getUint32(this.data + offset * 8 + 4) !== 0; +}; + +/** + * @param {number} offset + * @return {Array.} + */ +capnp.layout.StructBase.prototype.getDataField_uint64 = function(offset) { + if (offset * 64 < this.dataSize) { + var lo = this.seg_dataView.getUint32(this.data + offset * 8, true); + var hi = this.seg_dataView.getUint32(this.data + offset * 8 + 4, true); + return [hi, lo]; + } + else { + return [0, 0]; + } +}; + +/** + * @param {number} offset + * @param {Array.} mask + * @return {Array.} + */ +capnp.layout.StructBase.prototype.getDataField_uint64_masked = function(offset, mask) { + var value = this.getDataField_uint64(offset); + return [(value[0] ^ mask[0]) >>> 0, (value[1] ^ mask[1]) >>> 0]; +}; + +/** + * @param {number} offset + * @return {boolean} + */ +capnp.layout.StructBase.prototype.getDataField_bool = function(offset) { + if (offset < this.dataSize) { + return capnp.prim.bool.getValue(this.seg_dataView, this.data * capnp.common.BITS_PER_BYTE + offset + this.bit0Offset); + } + else { + return false; + } +}; + +/** + * @param {number} offset + * @param {boolean} mask + * @return {boolean} + */ +capnp.layout.StructBase.prototype.getDataField_bool_masked = function(offset, mask) { + return !!(this.getDataField_bool(offset) ^ mask); +}; + +/** + * @param {number} offset + * @return {number} + */ +capnp.layout.StructBase.prototype.getDataField_float32 = function(offset) { + if (offset * 32 < this.dataSize) { + return this.seg_dataView.getFloat32(this.data + offset * 4, true); + } + else { + return 0; + } +}; + +/** + * @param {number} offset + * @param {number} mask + * @return {number} + */ +capnp.layout.StructBase.prototype.getDataField_float32_masked = function(offset, mask) { + if (offset * 32 < this.dataSize) { + converter.setUint32(0, this.seg_dataView.getUint32(this.data + offset * 4, true) ^ mask, true); + return converter.getFloat32(0, true); + } + else { + converter.setUint32(0, mask, true); + return converter.getFloat32(0, true); + } +}; + +/** + * @param {number} offset + * @return {number} + */ +capnp.layout.StructBase.prototype.getDataField_float64 = function(offset) { + if (offset * 64 < this.dataSize) { + return this.seg_dataView.getFloat64(this.data + offset * 8, true); + } + else { + return 0; + } +}; + +/** + * @param {number} offset + * @param {number} mask + * @return {number} + */ +capnp.layout.StructBase.prototype.getDataField_float64_masked = function(offset, mask) { + if (offset * 64 < this.dataSize) { + converter.setUint32(0, this.seg_dataView.getUint32(this.data + offset * 8, true) ^ mask[1], true); + converter.setUint32(4, this.seg_dataView.getUint32(this.data + offset * 8 + 4, true) ^ mask[0], true); + return converter.getFloat64(0, true); + } + else { + converter.setUint32(0, mask[1], true); + converter.setUint32(4, mask[0], true); + return converter.getFloat64(0, true); + } +}; + +/** + * @param {number} offset + * @return {number} + */ +capnp.layout.StructBase.prototype.getDataField_uint8 = function(offset) { + if (offset * 8 < this.dataSize) { + return this.seg_dataView.getUint8(this.data + offset); + } + else { + return 0; + } +}; + +/** + * @param {number} offset + * @param {number} mask + * @return {number} + */ +capnp.layout.StructBase.prototype.getDataField_uint8_masked = function(offset, mask) { + return this.getDataField_uint8(offset) ^ mask; +}; + +/** + * @param {number} offset + * @param {number} value + */ +capnp.layout.StructBase.prototype.setDataField_uint8 = function(offset, value) { + this.seg_dataView.setUint8(this.data + offset, value); +}; + +/** + * @param {number} offset + * @param {number} mask + * @return {number} + */ +capnp.layout.StructBase.prototype.setDataField_uint8_masked = function(offset, value, mask) { + this.seg_dataView.setUint8(this.data + offset, value ^ mask); +}; + +/** + * @param {number} offset + * @return {number} + */ +capnp.layout.StructBase.prototype.getDataField_int8 = function(offset) { + if (offset * 8 < this.dataSize) { + return this.seg_dataView.getInt8(this.data + offset); + } + else { + return 0; + } +}; + +capnp.layout.StructBase.prototype.getDataField_int8_masked = function(offset, mask) { + return this.getDataField_int8(offset) ^ mask; +}; + +capnp.layout.StructBase.prototype.setDataField_int8 = function(offset, value) { + this.seg_dataView.setInt8(this.data + offset, value); +}; + +capnp.layout.StructBase.prototype.setDataField_int8_masked = function(offset, value, mask) { + this.seg_dataView.setInt8(this.data + offset, value ^ mask); +}; + +/** + * @param {number} offset + * @return {number} + */ +capnp.layout.StructBase.prototype.getDataField_int16 = function(offset) { + if (offset * 16 < this.dataSize) { + return this.seg_dataView.getInt16(this.data + (offset << 1), true); + } + else { + return 0; + } +}; + +capnp.layout.StructBase.prototype.getDataField_int16_masked = function(offset, mask) { + return this.getDataField_int16(offset) ^ mask; +}; + +capnp.layout.StructBase.prototype.setDataField_int16 = function(offset, value) { + this.seg_dataView.setInt16(this.data + (offset << 1), value, true); +}; + +capnp.layout.StructBase.prototype.setDataField_int16_masked = function(offset, value, mask) { + this.seg_dataView.setInt16(this.data + (offset << 1), value ^ mask, true); +}; + + +capnp.layout.StructBase.prototype.getDataField_uint16 = function(offset) { + if (offset * 16 < this.dataSize) { + return this.seg_dataView.getUint16(this.data + (offset << 1), true); + } + else { + return 0; + } +}; + +capnp.layout.StructBase.prototype.getDataField_uint16_masked = function(offset, mask) { + return this.getDataField_uint16(offset) ^ mask; +}; + +capnp.layout.StructBase.prototype.setDataField_uint16 = function(offset, value) { + this.seg_dataView.setUint16(this.data + (offset << 1), value, true); +}; + +capnp.layout.StructBase.prototype.setDataField_uint16_masked = function(offset, value, mask) { + this.seg_dataView.setUint16(this.data + (offset << 1), value ^ mask, true); +}; + +/** + * @param {number} offset + * @return {number} + */ +capnp.layout.StructBase.prototype.getDataField_int32 = function(offset) { + if (offset * 32 < this.dataSize) { + return this.seg_dataView.getInt32(this.data + (offset << 2), true); + } + else { + return 0; + } +}; + +capnp.layout.StructBase.prototype.getDataField_int32_masked = function(offset, mask) { + return this.getDataField_int32(offset) ^ mask; +}; + +capnp.layout.StructBase.prototype.setDataField_int32 = function(offset, value) { + this.seg_dataView.setInt32(this.data + (offset << 2), value, true); +}; + +capnp.layout.StructBase.prototype.setDataField_int32_masked = function(offset, value, mask) { + this.seg_dataView.setInt32(this.data + (offset << 2), value ^ mask, true); +}; + +/** + * @param {number} offset + * @return {number} + */ +capnp.layout.StructBase.prototype.getDataField_uint32 = function(offset) { + if ((offset << 2) + 4 <= this.dataSizeBytes) { // ((dataSize + 7) / BITS_PER_BYTE)) { // && (offset << 2) + 4 <= dataView.byteLength) { // FIXME -- use byteLength comparison for other getters as well, or see if dataSize can be expressed in bits? + return this.seg_dataView.getUint32(this.data + (offset << 2), true); + } + else { + return 0; + } +}; + +capnp.layout.StructBase.prototype.getDataField_uint32_masked = function(offset, mask) { + return (this.getDataField_uint32(offset) ^ mask) >>> 0; +}; + +capnp.layout.StructBase.prototype.setDataField_uint32 = function(offset, value) { + this.seg_dataView.setUint32(this.data + (offset << 2), value, true); +}; + +capnp.layout.StructBase.prototype.setDataField_uint32_masked = function(offset, value, mask) { + this.seg_dataView.setUint32(this.data + (offset << 2), value ^ mask, true); +}; + + + +capnp.layout.StructBase.prototype.setDataField_int64 = function(offset, value) { + capnp.prim.int64_t.setValue(this.seg_dataView, (this.data + (offset >>> 0) * 8) * capnp.common.BITS_PER_BYTE, value); +}; + +capnp.layout.StructBase.prototype.setDataField_int64_masked = function(offset, value, mask) { + capnp.prim.int64_t.setValue(this.seg_dataView, (this.data + (offset >>> 0) * 8) * capnp.common.BITS_PER_BYTE, [value[0] ^ mask[0], value[1] ^ mask[1]]); +}; + +capnp.layout.StructBase.prototype.setDataField_uint64 = function(offset, value) { + capnp.prim.uint64_t.setValue(this.seg_dataView, (this.data + (offset >>> 0) * 8) * capnp.common.BITS_PER_BYTE, value); +}; + +capnp.layout.StructBase.prototype.setDataField_uint64_masked = function(offset, value, mask) { + capnp.prim.uint64_t.setValue(this.seg_dataView, (this.data + (offset >>> 0) * 8) * capnp.common.BITS_PER_BYTE, [value[0] ^ mask[0], value[1] ^ mask[1]]); +}; + + +capnp.layout.StructBase.prototype.setDataField_float32 = function(offset, value) { + capnp.prim.float32_t.setValue(this.seg_dataView, (this.data + offset * 4) * capnp.common.BITS_PER_BYTE, value); +}; + +capnp.layout.StructBase.prototype.setDataField_float64 = function(offset, value) { + capnp.prim.float64_t.setValue(this.seg_dataView, (this.data + offset * 8) * capnp.common.BITS_PER_BYTE, value); +}; + + +capnp.layout.StructBase.prototype.setDataField_bool = function(offset, value) { + offset += this.bit0Offset; + var byteOffset = offset / capnp.common.BITS_PER_BYTE; + var bitOffset = offset % capnp.common.BITS_PER_BYTE; + this.seg_dataView.setUint8(this.data + byteOffset, this.seg_dataView.getUint8(this.data + byteOffset) & ~(1 << bitOffset) | (value << bitOffset)); +}; + +capnp.layout.StructBase.prototype.setDataField_bool_masked = function(offset, value, mask) { + offset += this.bit0Offset; + var byteOffset = offset / capnp.common.BITS_PER_BYTE; + var bitOffset = offset % capnp.common.BITS_PER_BYTE; + value = (!!value) ^ (!!mask); + this.seg_dataView.setUint8(this.data + byteOffset, this.seg_dataView.getUint8(this.data + byteOffset) & ~(1 << bitOffset) | (value << bitOffset)); +}; + + +/** + * @constructor + */ +capnp.layout.StructBuilder = function(segment, data, pointerOffset, dataSize, pointerCount, bit0Offset) { + + goog.asserts.assert(kj.util.isRegularNumber(pointerOffset)); + + capnp.layout.StructBase.call(this, segment, data, dataSize, bit0Offset); + + goog.asserts.assert(typeof(pointerOffset) === 'number', 'invalid pointerOffset'); + + this.getLocation = function() { return data >> 3; } + + this.getBit0Offset = function() { return bit0Offset; }; + + this.getSegmentBuilder = function() { return segment; }; + this.getPointerOffset = function() { return pointerOffset; }; + this.toString = function() { return 'StructBuilder(...)'; }; + + this.setTextBlobField = function(ptrIndex, value) { + capnp.layout.setTextPointer(segment.createWirePointerAt((pointerOffset + ptrIndex) * capnp.common.BYTES_PER_WORD), segment, value); + }; + + this.setDataBlobField = function(ptrIndex, value) { + capnp.layout.setDataPointer(segment.createWirePointerAt((pointerOffset + ptrIndex) * capnp.common.BYTES_PER_WORD), segment, value); + }; + + this.getTextBlobField = function(ptrIndex, defaultValue, defaultSize) { + var ref = segment.createWirePointerAt((pointerOffset + ptrIndex) << 3); + return capnp.layout.getWritableTextPointer(ref, ref.target(), segment, defaultValue, defaultSize); + }; + + this.disownTextBlobField = function(ptrIndex) { + var ref = segment.createWirePointerAt((pointerOffset + ptrIndex) << 3); + return capnp.layout.disown(segment, ref); + }; + + this.disownDataBlobField = function(ptrIndex) { + var ref = segment.createWirePointerAt((pointerOffset + ptrIndex) << 3); + return capnp.layout.disown(segment, ref); + }; + + this.getDataBlobField = function(ptrIndex, defaultValue, defaultSize) { + var ref = segment.createWirePointerAt((pointerOffset + ptrIndex) << 3); + return capnp.layout.getWritableDataPointer(ref, ref.target(), segment, defaultValue, defaultSize); + }; + + this.getStructField = function(ptrIndex, size, defaultValue) { + var ref = segment.createWirePointerAt((pointerOffset + ptrIndex) << 3); + return capnp.layout.getWritableStructPointer(ref, ref.target(), segment, size, defaultValue); + }; + + this.initStructField = function(ptrIndex, size) { + var ref = segment.createWirePointerAt((pointerOffset + ptrIndex) << 3); + return capnp.layout.initStructPointer(ref, segment, size); + }; + + this.isPointerFieldNull = function(ptrIndex) { + var dataView = segment.getDataView(); + return dataView.getUint32((pointerOffset + ptrIndex) * capnp.common.BYTES_PER_WORD) === 0 + && dataView.getUint32((pointerOffset + ptrIndex) * capnp.common.BYTES_PER_WORD + 4) === 0; + }; + + this.getListField = function(ptrIndex, elementSize, defaultValue) { + var ref = segment.createWirePointerAt((pointerOffset + ptrIndex) << 3); + return capnp.layout.getWritableListPointer(ref, ref.target(), segment, elementSize, defaultValue); + }; + + this.getStructListField = function(ptrIndex, elementSize, defaultValue) { + var ref = segment.createWirePointerAt((pointerOffset + ptrIndex) << 3); + return capnp.layout.getWritableStructListPointer(ref, ref.target(), segment, elementSize, defaultValue); + }; + + this.initStructListField = function(ptrIndex, elementCount, elementSize) { + var ref = segment.createWirePointerAt((pointerOffset + ptrIndex) << 3); + return capnp.layout.initStructListPointer(ref, segment, elementCount, elementSize); + }; + + this.setListField = function(ptrIndex, value) { + var ref = segment.createWirePointerAt((pointerOffset + ptrIndex) << 3); + capnp.layout.setListPointer(segment, ref, value); + }; + + this.initListField = function(ptrIndex, elementSize, elementCount) { + var ref = segment.createWirePointerAt((pointerOffset + ptrIndex) << 3); + return capnp.layout.initListPointer(ref, segment, elementCount, elementSize); + }; + + this.disownListField = function(ptrIndex) { + var ref = segment.createWirePointerAt((pointerOffset + ptrIndex) << 3); + return capnp.layout.disown(segment, ref); + }; + + this.clearPointerField = function(ptrIndex) { + var dataView = segment.getDataView(); + dataView.setUint32((pointerOffset + ptrIndex) * capnp.common.BYTES_PER_WORD, 0); + dataView.setUint32((pointerOffset + ptrIndex) * capnp.common.BYTES_PER_WORD + 4, 0); + }; + + this.setStructField = function(ptrIndex, value) { + goog.asserts.assert(value instanceof capnp.layout.StructReader, 'not a StructReader: ' + value); + var ref = segment.createWirePointerAt((pointerOffset + ptrIndex) << 3); + capnp.layout.setStructPointer(segment, ref, value); + }; + + this.disownStructField = function(ptrIndex) { + var ref = segment.createWirePointerAt((pointerOffset + ptrIndex) << 3); + return capnp.layout.disown(segment, ref); + }; + + this.adoptStructField = function(ptrIndex, value) { + var ref = segment.createWirePointerAt((pointerOffset + ptrIndex) << 3); + return capnp.layout.adopt(segment, ref, value); + }; + + this.asReader = function() { + return new capnp.layout.StructReader(segment, data, pointerOffset, dataSize, pointerCount, bit0Offset, Number.MAX_VALUE); + }; + + this.toString = function() { return 'StructBuilder{segment=' + segment.toString() + ',data=' + data + ', pointerOffset=' + pointerOffset + '}'; }; + + return this; +}; +capnp.layout.StructBuilder.prototype = Object.create(capnp.layout.StructBase.prototype); +capnp.layout.StructBuilder.prototype.constructor = capnp.layout.StructBuilder; + + +capnp.layout.StructBuilder.initRoot = function(segment, location, size) { + return capnp.layout.initStructPointer(segment.createWirePointerAt(location), segment, size); +}; + +capnp.layout.StructBuilder.setRoot = function(segment, location, value) { + capnp.layout.setStructPointer(segment, segment.createWirePointerAt(location), value); +}; + +capnp.layout.StructBuilder.getRoot = function(segment, location, size) { + var ref = segment.createWirePointerAt(location); + return capnp.layout.getWritableStructPointer(ref, ref.target(), segment, size, null); +}; + +/** + * @constructor + */ +capnp.layout.StructReader = function(segment, data, pointers, + dataSize, pointerCount, bit0Offset, + nestingLimit) { + + goog.asserts.assert(kj.util.isRegularNumber(pointers)); + + capnp.layout.StructBase.call(this, segment, data, dataSize, bit0Offset); + + this.segment = segment; + this.data = data; + this.pointers = pointers; + this.dataSize = dataSize; + this.pointerCount = pointerCount; + this.bit0Offset = bit0Offset; + this.nestingLimit = nestingLimit; + + this.getBit0Offset = function() { return bit0Offset; }; + + this.isPointerFieldNull = function(ptrIndex) { + var dataView = segment.getDataView(); + var offsetBytes = (pointers + ptrIndex) * capnp.common.BYTES_PER_WORD; + return dataView.getUint32(offsetBytes) === 0 && dataView.getUint32(offsetBytes + 4) === 0; + }; + + this.getListField = function(ptrIndex, expectedElementSize, defaultValue) { + var ref = ptrIndex >= pointerCount ? capnp.layout.WirePointer.zero() : segment.createWirePointerAt((pointers + ptrIndex) << 3); + return capnp.layout.readListPointer(segment, ref, ref.target(), defaultValue, expectedElementSize, nestingLimit); + }; + + this.getStructField = function(ptrIndex, defaultValue) { + var ref = ptrIndex >= pointerCount ? capnp.layout.WirePointer.zero() : segment.createWirePointerAt((pointers + ptrIndex) << 3); + return capnp.layout.readStructPointer(segment, ref, ref.target(), defaultValue, nestingLimit); + }; + + this.getTextBlobField = function(ptrIndex, defaultValue, defaultSize) { + var ref = ptrIndex >= pointerCount ? capnp.layout.WirePointer.zero() : segment.createWirePointerAt((pointers + ptrIndex) << 3); + return capnp.layout.readTextPointer(segment, ref, ref.target(), defaultValue, defaultSize); + }; + + this.getDataBlobField = function(ptrIndex, defaultValue, defaultSize) { + goog.asserts.assert(kj.util.isRegularNumber(defaultSize), 'defaultSize not a regular number: ' + defaultSize); + var ref = ptrIndex >= pointerCount ? capnp.layout.WirePointer.zero() : segment.createWirePointerAt((pointers + ptrIndex) << 3); + return capnp.layout.readDataPointer(segment, ref, ref.target(), defaultValue, defaultSize); + }; + + this.totalSize = function() { + var result = capnp.common.roundBitsUpToWords(dataSize) + pointerCount * capnp.common.WORDS_PER_POINTER; + + for (var i = 0; i < pointerCount; i++) { + result += capnp.layout.totalSize(segment, segment.createWirePointerAt((pointers + i) << 3), nestingLimit); + } + + if (segment) { + // This traversal should not count against the read limit, because it's highly likely that + // the caller is going to traverse the object again, e.g. to copy it. + segment.unread(result); + } + + return result; + }; + + this.toString = function() { return 'StructReader{...}'; }; +}; +capnp.layout.StructReader.prototype = Object.create(capnp.layout.StructBase.prototype); +capnp.layout.StructReader.prototype.constructor = capnp.layout.StructReader; + +capnp.layout.StructReader.readRoot = function(location, segment, nestingLimit) { + + kj.debug.REQUIRE(boundsCheck(segment, location, location + capnp.common.POINTER_SIZE_IN_WORDS), + 'Root location out-of-bounds.'); + + goog.asserts.assert(segment instanceof capnp.arena.SegmentReader, 'StructReader.readRoot got invalid segment'); + + var ref = segment.createWirePointerAt(location); + + return capnp.layout.readStructPointer(segment, ref, ref.target(), null, nestingLimit); +}; + +capnp.layout.StructReader.readRootUnchecked = function(data) { + var newBuffer = new ArrayBuffer(data.byteLength); + new Uint8Array(newBuffer).set(new Uint8Array(data.buffer).subarray(data.byteOffset, data.byteOffset + data.byteLength)); + return capnp.layout.StructReader.readRoot(0, new capnp.arena.SegmentReader(null, 0, new DataView(newBuffer), new capnp.arena.ReadLimiter()), Number.MAX_VALUE); +}; + + +capnp.layout.initListPointer = function(wirePointer, segmentBuilder, elementCount, elementSize, orphanArena) { + + goog.asserts.assert(elementSize !== undefined); + + var dataSize = capnp.layout.dataBitsPerElement(elementSize); + var pointerCount = capnp.layout.pointersPerElement(elementSize); + var step = (dataSize + pointerCount * capnp.common.BITS_PER_POINTER); + + // Calculate size of the list. + var wordCount = capnp.common.roundBitsUpToWords(elementCount * step); + + // Allocate the list. + var allocateResult = capnp.layout.allocate(wirePointer, segmentBuilder, wordCount, capnp.layout.Kind.LIST, orphanArena); + var ptr = allocateResult[0]; + segmentBuilder = allocateResult[1]; + wirePointer = allocateResult[2]; + + // Initialize the pointer. + wirePointer.setListRef(elementSize, elementCount); + + // Build the ListBuilder. + goog.asserts.assert(!isNaN(step)); + var result = new capnp.layout.ListBuilder(segmentBuilder, ptr, step, elementCount, dataSize, pointerCount); + + return result; +}; + +capnp.layout.getWritableTextPointer = function(ref, refTarget, segment, defaultValue, defaultSize) { + goog.asserts.assert(kj.util.isRegularNumber(defaultSize)); + if (ref.isNull()) { + if (defaultSize === 0) { + return new capnp.blob.Text.Builder(segment, null, 0); + } else { + var initTextPointerResult = capnp.layout.initTextPointer(ref, segment, defaultSize); + var builder = initTextPointerResult.value; + memcpy(initTextPointerResult.segment, builder.begin(), new capnp.arena.SegmentReader(null, null, new DataView(defaultValue), null), 0, defaultSize); + return builder; + } + } else { + var followFarsResult = capnp.layout.followFars(ref, refTarget, segment); + ref = followFarsResult[0]; + segment = followFarsResult[1]; + var ptr = followFarsResult[2]; + + kj.debug.REQUIRE(ref.kind() === capnp.layout.Kind.LIST, + 'Called getText{Field,Element}() but existing pointer is not a list (1).'); + + kj.debug.REQUIRE(ref.getListRef().elementSize() === capnp.layout.FieldSize.BYTE, + 'Called getText{Field,Element}() but existing list pointer is not byte-sized.'); + + // Subtract 1 from the size for the NUL terminator. + return new capnp.blob.Text.Builder(segment, ptr, ref.getListRef().elementCount() - 1); + } +}; + +capnp.layout.followFars = function(ref, refTarget, segment) { + + //assert(segment instanceof SegmentReader, "this is the followFars for a SegmentReader, but got " + segment.toString()); + + if (segment != null && !(segment instanceof capnp.arena.SegmentReader)) { + throw new Error('this is the followFars for a SegmentReader, but got ' + segment.toString()); + } + + // Like the other followFars() but operates on readers. + + // If the segment is null, this is an unchecked message, so there are no FAR pointers. + if (segment != null && ref.kind() === capnp.layout.Kind.FAR) { + + // Look up the segment containing the landing pad. + segment = segment.getArena().tryGetSegment(ref.farRef().segmentId); + kj.debug.REQUIRE(segment !== null, 'Message contains far pointer to unknown segment: ' + ref.farRef().segmentId); + + // Find the landing pad and check that it is within bounds. + var ptr = ref.farPositionInSegment(); + var padWords = (1 + (ref.isDoubleFar() ? 1 : 0)) * capnp.common.POINTER_SIZE_IN_WORDS; + kj.debug.REQUIRE(boundsCheck(segment, ptr, ptr + padWords), + 'Message contains out-of-bounds far pointer.'); + + var pad = segment.createWirePointerAt(ptr << 3); + + // If this is not a double-far then the landing pad is our final pointer. + if (!ref.isDoubleFar()) { + ref = pad; + return [ref, segment, pad.target()]; + } + + // Landing pad is another far pointer. It is followed by a tag describing the pointed-to + // object. + ref = segment.createWirePointerAt((ptr + 1) << 3); + + segment = segment.getArena().tryGetSegment(pad.farRef().segmentId); + kj.debug.REQUIRE(segment !== null, 'Message contains double-far pointer to unknown segment.'); + + return [ref, segment, pad.farPositionInSegment()]; + } else { + return [ref, segment, refTarget]; + } + +}; + +capnp.layout.getWritableDataPointer = function(ref, refTarget, segment, defaultValue, defaultSize) { + if (ref.isNull()) { + if (defaultSize === 0) { + return new capnp.blob.Data.Builder(segment, null, 0); + } else { + var builder = capnp.layout.initDataPointer(ref, segment, defaultSize).value; + builder.asUint8Array().set(defaultValue); + return builder; + } + } else { + var followFarsResult = capnp.layout.followFars(ref, refTarget, segment); + ref = followFarsResult[0]; + segment = followFarsResult[1]; + var ptr = followFarsResult[2]; + + kj.debug.REQUIRE(ref.kind() === capnp.layout.Kind.LIST, + 'Called getData{Field,Element}() but existing pointer is not a list (2).'); + kj.debug.REQUIRE(ref.getListRef().elementSize() === capnp.layout.FieldSize.BYTE, + 'Called getData{Field,Element}() but existing list pointer is not byte-sized.'); + + return new capnp.blob.Data.Builder(segment, ptr, ref.getListRef().elementCount()); + } +}; + +capnp.layout.getWritableStructPointer = function(ref, refTarget, segment, size, defaultValue, orphanArena) { + + if (ref.isNull()) { + if (defaultValue == null) { + return capnp.layout.initStructPointer(ref, segment, size, orphanArena); + } + + var defaultPointer = new capnp.layout.WirePointer(0, new DataView(defaultValue, 0, 8)); + if (defaultPointer.isNull()) { + return capnp.layout.initStructPointer(ref, segment, size, orphanArena); + } + var defaultSegment = new capnp.arena.SegmentReader(null, null, new DataView(defaultValue), null); + + var copyMessageResult = copyMessage(segment, ref, defaultSegment, defaultPointer); + segment = copyMessageResult[0]; + ref = copyMessageResult[1]; + refTarget = copyMessageResult[2]; + defaultValue = null; // If the default value is itself invalid, don't use it again. + } + + var oldRef = ref; + var oldSegment = segment; + var followFarsResult = capnp.layout.followFars(oldRef, refTarget, oldSegment); + oldRef = followFarsResult[0]; + oldSegment = followFarsResult[1]; + var oldPtr = followFarsResult[2]; + + kj.debug.REQUIRE(oldRef.kind() === capnp.layout.Kind.STRUCT, + 'Message contains non-struct pointer where struct pointer was expected.'); + + var oldDataSize = oldRef.getStructRef().dataSize(); + var oldPointerCount = oldRef.getStructRef().ptrCount(); + var oldPointerSection = oldPtr + oldDataSize; + + if (oldDataSize < size.getDataWordCount() || oldPointerCount < size.getPointerCount()) { + // The space allocated for this struct is too small. Unlike with readers, we can't just + // run with it and do bounds checks at access time, because how would we handle writes? + // Instead, we have to copy the struct to a new space now. + + var newDataSize = Math.max(oldDataSize, size.getDataWordCount()); + var newPointerCount = + Math.max(oldPointerCount, size.getPointerCount()); + var totalSize = newDataSize + newPointerCount * capnp.common.WORDS_PER_POINTER; + + // Don't let allocate() zero out the object just yet. + zeroPointerAndFars(segment, ref); + + var allocateResult = capnp.layout.allocate(ref, segment, totalSize, capnp.layout.Kind.STRUCT, orphanArena); + var ptr = allocateResult[0]; + segment = allocateResult[1]; + ref = allocateResult[2]; + ref.setStructRef(new capnp.layout.StructSize(newDataSize, newPointerCount)); + + // Copy data section. + memcpy(segment, ptr << 3, oldSegment, oldPtr << 3, oldDataSize * capnp.common.BYTES_PER_WORD); + + // Copy pointer section. + var newPointerSection = ptr + newDataSize; + for (var i = 0; i < oldPointerCount; i++) { + transferPointer(segment, segment.createWirePointerAt((newPointerSection + i) << 3), + oldSegment, oldSegment.createWirePointerAt((oldPointerSection + i) << 3)); + } + + // Zero out old location. This has two purposes: + // 1) We don't want to leak the original contents of the struct when the message is written + // out as it may contain secrets that the caller intends to remove from the new copy. + // 2) Zeros will be deflated by packing, making this dead memory almost-free if it ever + // hits the wire. + memclear(oldSegment, oldPtr << 3, + (oldDataSize + oldPointerCount * capnp.common.WORDS_PER_POINTER) * capnp.common.BYTES_PER_WORD); + + return new capnp.layout.StructBuilder(segment, (ptr << 3), newPointerSection, newDataSize * capnp.common.BITS_PER_WORD, + newPointerCount, 0); + } else { + return new capnp.layout.StructBuilder(oldSegment, (oldPtr << 3), oldPointerSection, oldDataSize * capnp.common.BITS_PER_WORD, + oldPointerCount, 0); + } +}; + + +capnp.layout.getWritableListPointer = function(origRef, origRefTarget, origSegment, elementSize, defaultValue, orphanArena) { + + if (elementSize === capnp.layout.FieldSize.INLINE_COMPOSITE) { + throw new Error('Use getStructList{Element,Field}() for structs.'); + } + + if (origRef.isNull()) { + if (defaultValue == null) { + return new capnp.layout.ListBuilder(null, null, 0, 0, 0, 0); + } + + var defaultSegment = new capnp.arena.SegmentReader(null, null, new DataView(defaultValue), null); + var defaultPointer = defaultSegment.createWirePointerAt(0); + if (defaultPointer.isNull()) { + return new capnp.layout.ListBuilder(null, null, 0, 0, 0, 0); + } + + var copyMessageResult = copyMessage(origSegment, origRef, defaultSegment, defaultPointer); + origSegment = copyMessageResult[0]; + origRef = copyMessageResult[1]; + origRefTarget = copyMessageResult[2]; + defaultValue = null; // If the default value is itself invalid, don't use it again. + } + + // We must verify that the pointer has the right size. Unlike in + // getWritableStructListReference(), we never need to "upgrade" the data, because this + // method is called only for non-struct lists, and there is no allowed upgrade path *to* + // a non-struct list, only *from* them. + + var ref = origRef; + var segment = origSegment; + var followFarsResult = capnp.layout.followFars(ref, origRefTarget, segment); + ref = followFarsResult[0]; + segment = followFarsResult[1]; + var ptr = followFarsResult[2]; + + kj.debug.REQUIRE(ref.kind() === capnp.layout.Kind.LIST, + 'Called getList{Field,Element}() but existing pointer is not a list (4).'); + + var oldSize = ref.getListRef().elementSize(); + + if (oldSize === capnp.layout.FieldSize.INLINE_COMPOSITE) { + // The existing element size is INLINE_COMPOSITE, which means that it is at least two + // words, which makes it bigger than the expected element size. Since fields can only + // grow when upgraded, the existing data must have been written with a newer version of + // the protocol. We therefore never need to upgrade the data in this case, but we do + // need to validate that it is a valid upgrade from what we expected. + + // Read the tag to get the actual element count. + var tag = segment.createWirePointerAt(ptr << 3); + kj.debug.REQUIRE(tag.kind() === capnp.layout.Kind.STRUCT, + 'INLINE_COMPOSITE list with non-STRUCT elements not supported.'); + ptr += capnp.common.POINTER_SIZE_IN_WORDS; + + var dataSize = tag.getStructRef().dataSize(); + var pointerCount = tag.getStructRef().ptrCount(); + + switch (elementSize) { + case capnp.layout.FieldSize.VOID: + // Anything is a valid upgrade from Void. + break; + + case capnp.layout.FieldSize.BIT: + case capnp.layout.FieldSize.BYTE: + case capnp.layout.FieldSize.TWO_BYTES: + case capnp.layout.FieldSize.FOUR_BYTES: + case capnp.layout.FieldSize.EIGHT_BYTES: + kj.debug.REQUIRE(dataSize >= 1, + 'Existing list value is incompatible with expected type.'); + break; + + case capnp.layout.FieldSize.POINTER: + kj.debug.REQUIRE(pointerCount >= 1, + 'Existing list value is incompatible with expected type.'); + // Adjust the pointer to point at the reference segment. + ptr += dataSize; + break; + + case capnp.layout.FieldSize.INLINE_COMPOSITE: + kj.debug.FAIL_ASSERT("Can't get here."); + break; + } + + // OK, looks valid. + + return new capnp.layout.ListBuilder(segment, ptr, + tag.getStructRef().wordSize() * capnp.common.BITS_PER_WORD, + tag.inlineCompositeListElementCount(), + dataSize * capnp.common.BITS_PER_WORD, pointerCount); + + } else { + var dataSize = capnp.layout.dataBitsPerElement(oldSize); + var pointerCount = capnp.layout.pointersPerElement(oldSize); + + kj.debug.REQUIRE(dataSize >= capnp.layout.dataBitsPerElement(elementSize), + 'Existing list value is incompatible with expected type.'); + + kj.debug.REQUIRE(pointerCount >= capnp.layout.pointersPerElement(elementSize), + 'Existing list value is incompatible with expected type.'); + + var step = dataSize + pointerCount * capnp.common.BITS_PER_POINTER; + return new capnp.layout.ListBuilder(segment, ptr, step, ref.getListRef().elementCount(), + dataSize, pointerCount); + } +}; + +capnp.layout.getWritableStructListPointer = function(origRef, origRefTarget, origSegment, elementSize, defaultValue, orphanArena) { + + var preferredListEncoding = elementSize.getPreferredListEncoding(); + goog.asserts.assert(typeof(preferredListEncoding) === 'number'); + + if (origRef.isNull()) { + if (defaultValue == null) { + return new capnp.layout.ListBuilder(null, null, 0, 0, 0, 0); + } + + var defaultSegment = new capnp.arena.SegmentReader(null, null, new DataView(defaultValue), null); + var defaultPointer = defaultSegment.createWirePointerAt(0); + if (defaultPointer.isNull()) { + return new capnp.layout.ListBuilder(null, null, 0, 0, 0, 0); + } + + var copyMessageResult = copyMessage(origSegment, origRef, defaultSegment, defaultPointer); + origSegment = copyMessageResult[0]; + origRef = copyMessageResult[1]; + origRefTarget = copyMessageResult[2]; + defaultValue = null; // If the default value is itself invalid, don't use it again. + } + + // We must verify that the pointer has the right size and potentially upgrade it if not. + + var oldRef = origRef; + var oldSegment = origSegment; + var followFarsResult = capnp.layout.followFars(oldRef, origRefTarget, oldSegment); + oldRef = followFarsResult[0]; + oldSegment = followFarsResult[1]; + var oldPtr = followFarsResult[2]; + + kj.debug.REQUIRE(oldRef.kind() === capnp.layout.Kind.LIST, + 'Called getList{Field,Element}() ' + + 'but existing pointer is not a list.'); + + var oldSize = oldRef.getListRef().elementSize(); + + if (oldSize === capnp.layout.FieldSize.INLINE_COMPOSITE) { + + // CHECKME -- xxx + + // Existing list is INLINE_COMPOSITE, but we need to verify that the sizes match. + + var oldTag = oldSegment.createWirePointerAt(oldPtr << 3); + oldPtr += capnp.common.POINTER_SIZE_IN_WORDS; + kj.debug.REQUIRE(oldTag.kind() === capnp.layout.Kind.STRUCT, + 'INLINE_COMPOSITE list with non-STRUCT ' + + 'elements not supported.'); + + var oldDataSize = oldTag.getStructRef().dataSize(); + var oldPointerCount = oldTag.getStructRef().ptrCount(); + var oldStep = oldDataSize + oldPointerCount * capnp.common.WORDS_PER_POINTER; + var elementCount = oldTag.inlineCompositeListElementCount(); + + if (oldDataSize >= elementSize.getDataWordCount() && oldPointerCount >= elementSize.getPointerCount()) { + // Old size is at least as large as we need. Ship it. + return new capnp.layout.ListBuilder(oldSegment, oldPtr, oldStep * capnp.common.BITS_PER_WORD, elementCount, + oldDataSize * capnp.common.BITS_PER_WORD, oldPointerCount); + } + + // The structs in this list are smaller than expected, probably written using an older + // version of the protocol. We need to make a copy and expand them. + + var newDataSize = Math.max(oldDataSize, elementSize.getDataWordCount()); + var newPointerCount = Math.max(oldPointerCount, elementSize.getPointerCount()); + var newStep = newDataSize + newPointerCount * capnp.common.WORDS_PER_POINTER; + var totalSize = newStep * elementCount; + + // Don't let allocate() zero out the object just yet. + zeroPointerAndFars(origSegment, origRef); + + var allocateResult = capnp.layout.allocate(origRef, origSegment, totalSize + capnp.common.POINTER_SIZE_IN_WORDS, + capnp.layout.Kind.LIST, orphanArena); + var newPtr = allocateResult[0]; + origSegment = allocateResult[1]; + origRef = allocateResult[2]; + origRef.setListRefInlineComposite(totalSize); + + var newTag = origSegment.createWirePointerAt(newPtr << 3); + newTag.setKindAndInlineCompositeListElementCount(capnp.layout.Kind.STRUCT, elementCount); + newTag.setStructRef(new capnp.layout.StructSize(newDataSize, newPointerCount)); + newPtr += capnp.common.POINTER_SIZE_IN_WORDS; + + goog.asserts.assert(kj.util.isRegularNumber(oldPtr)); + goog.asserts.assert(kj.util.isRegularNumber(newPtr)); + goog.asserts.assert(kj.util.isRegularNumber(oldStep)); + goog.asserts.assert(kj.util.isRegularNumber(newStep)); + goog.asserts.assert(kj.util.isRegularNumber(oldDataSize)); + goog.asserts.assert(kj.util.isRegularNumber(newDataSize)); + goog.asserts.assert(kj.util.isRegularNumber(oldPointerCount)); + goog.asserts.assert(kj.util.isRegularNumber(newPointerCount)); + + var src = oldPtr; + var dst = newPtr; + for (var i = 0; i < elementCount; i++) { + // Copy data section. + memcpy(origSegment, dst << 3, oldSegment, src << 3, oldDataSize * capnp.common.BYTES_PER_WORD); + + // Copy pointer section. + for (var j = 0; j < oldPointerCount; j++) { + var newPointer = origSegment.createWirePointerAt((dst + newDataSize + j) << 3); + var oldPointer = oldSegment.createWirePointerAt((src + oldDataSize + j) << 3); + transferPointer(origSegment, newPointer, oldSegment, oldPointer); + } + + dst += newStep; + src += oldStep; + } + + // Zero out old location. See explanation in getWritableStructPointer(). + memclear(oldSegment, oldPtr << 3, oldStep * elementCount * capnp.common.BYTES_PER_WORD); + + return new capnp.layout.ListBuilder(origSegment, newPtr, newStep * capnp.common.BITS_PER_WORD, elementCount, + newDataSize * capnp.common.BITS_PER_WORD, newPointerCount); + + } else if (oldSize === preferredListEncoding) { + // Old size matches exactly. + + var dataSize = capnp.layout.dataBitsPerElement(oldSize); + var pointerCount = capnp.layout.pointersPerElement(oldSize); + var step = dataSize + pointerCount * capnp.common.BITS_PER_POINTER; + + return new capnp.layout.ListBuilder(oldSegment, oldPtr, step, oldRef.getListRef().elementCount(), + dataSize, pointerCount); + } else { + + switch (preferredListEncoding) { + case capnp.layout.FieldSize.VOID: + // No expectations. + break; + case capnp.layout.FieldSize.POINTER: + kj.debug.REQUIRE(oldSize === capnp.layout.FieldSize.POINTER || oldSize === capnp.layout.FieldSize.VOID, + 'Struct list has incompatible element size.'); + break; + case capnp.layout.FieldSize.INLINE_COMPOSITE: + // Old size can be anything. + break; + case capnp.layout.FieldSize.BIT: + case capnp.layout.FieldSize.BYTE: + case capnp.layout.FieldSize.TWO_BYTES: + case capnp.layout.FieldSize.FOUR_BYTES: + case capnp.layout.FieldSize.EIGHT_BYTES: + // Preferred size is data-only. + kj.debug.REQUIRE(oldSize !== capnp.layout.FieldSize.POINTER, + 'Struct list has incompatible element size.'); + break; + } + + // OK, the old size is compatible with the preferred, but is not exactly the same. We may + // need to upgrade it. + + var oldDataSize = capnp.layout.dataBitsPerElement(oldSize); + var oldPointerCount = capnp.layout.pointersPerElement(oldSize); + var oldStep = (oldDataSize + oldPointerCount * capnp.common.BITS_PER_POINTER); + var elementCount = oldRef.getListRef().elementCount(); + + if (oldSize >= preferredListEncoding) { + // The old size is at least as large as the preferred, so we don't need to upgrade. + return new capnp.layout.ListBuilder(oldSegment, oldPtr, oldStep, elementCount, + oldDataSize, oldPointerCount); + } + + // Upgrade is necessary. + + if (oldSize === capnp.layout.FieldSize.VOID) { + // Nothing to copy, just allocate a new list. + return capnp.layout.initStructListPointer(origRef, origSegment, elementCount, elementSize); + } else if (preferredListEncoding === capnp.layout.FieldSize.INLINE_COMPOSITE) { + // Upgrading to an inline composite list. + + var newDataSize = elementSize.getDataWordCount(); + var newPointerCount = elementSize.getPointerCount(); + + if (oldSize === capnp.layout.FieldSize.POINTER) { + newPointerCount = Math.max(newPointerCount, 1); + } else { + // Old list contains data elements, so we need at least 1 word of data. + newDataSize = Math.max(newDataSize, 1); + } + + var newStep = newDataSize + newPointerCount * capnp.common.WORDS_PER_POINTER; + var totalWords = elementCount * newStep; + + // Don't let allocate() zero out the object just yet. + zeroPointerAndFars(origSegment, origRef); + + var allocateResult = capnp.layout.allocate(origRef, origSegment, totalWords + capnp.common.POINTER_SIZE_IN_WORDS, + capnp.layout.Kind.LIST, orphanArena); + var newPtr = allocateResult[0]; + origSegment = allocateResult[1]; + origRef = allocateResult[2]; + origRef.setListRefInlineComposite(totalWords); + + var tag = origSegment.createWirePointerAt(newPtr << 3); + tag.setKindAndInlineCompositeListElementCount(capnp.layout.Kind.STRUCT, elementCount); + tag.setStructRef(new capnp.layout.StructSize(newDataSize, newPointerCount)); + newPtr += capnp.common.POINTER_SIZE_IN_WORDS; + + if (oldSize === capnp.layout.FieldSize.POINTER) { + var dst = newPtr + newDataSize; + var src = oldPtr; + for (var i = 0; i < elementCount; i++) { + transferPointer(origSegment, origSegment.createWirePointerAt(dst << 3), oldSegment, oldSegment.createWirePointerAt(src << 3)); + dst += (newStep / capnp.common.WORDS_PER_POINTER) >>> 0; + ++src; + } + } else if (oldSize === capnp.layout.FieldSize.BIT) { + var dst = newPtr << 3; + var src = oldPtr << 3; + var dstArray = origSegment.getUint8Array(); + var srcArray = oldSegment.getUint8Array(); + for (var i = 0; i < elementCount; i++) { + dstArray[dst] = (srcArray[src + (i >> 3)] >> (i % 8)) & 1; + dst += newStep * capnp.common.BYTES_PER_WORD; + } + } else { + var dst = newPtr << 3; + var src = oldPtr << 3; + var oldByteStep = (oldDataSize / capnp.common.BITS_PER_BYTE) >>> 0; + for (var i = 0; i < elementCount; i++) { + memcpy(origSegment, dst, oldSegment, src, oldByteStep); + src += oldByteStep; + dst += newStep * capnp.common.BYTES_PER_WORD; + } + } + + // Zero out old location. See explanation in getWritableStructPointer(). + memclear(oldSegment, oldPtr << 3, capnp.common.roundBitsUpToBytes(oldStep * elementCount)); + + return new capnp.layout.ListBuilder(origSegment, newPtr, newStep * capnp.common.BITS_PER_WORD, elementCount, + newDataSize * capnp.common.BITS_PER_WORD, newPointerCount); + + } else { + + // If oldSize were POINTER or EIGHT_BYTES then the preferred size must be + // INLINE_COMPOSITE because any other compatible size would not require an upgrade. + goog.asserts.assert(oldSize < capnp.layout.FieldSize.EIGHT_BYTES); + + // If the preferred size were BIT then oldSize must be VOID, but we handled that case + // above. + goog.asserts.assert(preferredListEncoding >= capnp.layout.FieldSize.BIT); + + // OK, so the expected list elements are all data and between 1 byte and 1 word each, + // and the old element are data between 1 bit and 4 bytes. We're upgrading from one + // primitive data type to another, larger one. + + var newDataSize = + capnp.layout.dataBitsPerElement(preferredListEncoding); + + var totalWords = + capnp.common.roundBitsUpToWords(newDataSize * elementCount); + + // Don't let allocate() zero out the object just yet. + zeroPointerAndFars(origSegment, origRef); + + var allocateResult = capnp.layout.allocate(origRef, origSegment, totalWords, capnp.layout.Kind.LIST, orphanArena); + var newPtr = allocateResult[0]; + origSegment = allocateResult[1]; + origRef = allocateResult[2]; + origRef.setListRef(preferredListEncoding, elementCount); + + var newBytePtr = newPtr << 3; + var oldBytePtr = oldPtr << 3; + var newDataByteSize = (newDataSize / capnp.common.BITS_PER_BYTE) >>> 0; + if (oldSize === capnp.layout.FieldSize.BIT) { + for (var i = 0; i < elementCount; i++) { + origSegment.getUint8Array()[newBytePtr] = (oldSegment.getUint8Array()[oldBytePtr + (i >> 3)] >> (i % 8)) & 1; + newBytePtr += newDataByteSize; + } + } else { + var oldDataByteSize = (oldDataSize / capnp.common.BITS_PER_BYTE) >>> 0; + for (var i = 0; i < elementCount; i++) { + memcpy(origSegment, newBytePtr, oldSegment, oldBytePtr, oldDataByteSize); + oldBytePtr += oldDataByteSize; + newBytePtr += newDataByteSize; + } + } + + // Zero out old location. See explanation in getWritableStructPointer(). + memclear(oldSegment, oldPtr << 3, capnp.common.roundBitsUpToBytes(oldStep * elementCount)); + + return new capnp.layout.ListBuilder(origSegment, newPtr, newDataSize, elementCount, + newDataSize, 0); + } + } +}; + + +capnp.layout.readTextPointer = function(segment, ref, refTarget, defaultValue, defaultSize) { + + if (ref === null || ref.isNull()) { + + if (!defaultValue || !defaultSize) { + return new capnp.blob.Text.Reader(null, 0, 0); + } + + var defaultSegment = new capnp.arena.SegmentReader(null, null, new DataView(defaultValue), null); + return new capnp.blob.Text.Reader(defaultSegment, 0, defaultSize); + } + else { + var followFarsResult = capnp.layout.followFars(ref, refTarget, segment); + ref = followFarsResult[0]; + segment = followFarsResult[1]; + var ptr = followFarsResult[2]; + + var size = ref.getListRef().elementCount(); + + kj.debug.REQUIRE(ref.kind() === capnp.layout.Kind.LIST, + 'Message contains non-list pointer where text was expected.'); + + kj.debug.REQUIRE(ref.getListRef().elementSize() === capnp.layout.FieldSize.BYTE, + 'Message contains list pointer of non-bytes where text was expected.'); + + kj.debug.REQUIRE(boundsCheck(segment, ptr, ptr + capnp.common.roundBytesUpToWords(ref.getListRef().elementCount())), + 'Message contained out-of-bounds text pointer.'); + } + + kj.debug.REQUIRE(size > 0, + 'Message contains text that is not NUL-terminated.'); + + size -= 1; // NUL terminator + + kj.debug.REQUIRE(segment.getUint8Array()[ptr * 8 + size] === 0, + 'Message contains text that is not NUL-terminated.'); + + return new capnp.blob.Text.Reader(segment, ptr, size); +}; + +capnp.layout.readDataPointer = function(segment, ref, refTarget, defaultValue, defaultSize) { + + goog.asserts.assert(kj.util.isRegularNumber(defaultSize), 'defaultSize not a regular number: ' + defaultSize); + + if (ref === null || ref.isNull()) { + return new capnp.blob.Data.Reader(defaultValue, defaultSize); + } else { + var followFarsResult = capnp.layout.followFars(ref, refTarget, segment); + ref = followFarsResult[0]; + segment = followFarsResult[1]; + var ptr = followFarsResult[2]; + + var size = ref.getListRef().elementCount(); + + kj.debug.REQUIRE(ref.kind() === capnp.layout.Kind.LIST, + 'Message contains non-list pointer where data was expected.'); + + kj.debug.REQUIRE(ref.getListRef().elementSize() === capnp.layout.FieldSize.BYTE, + 'Message contains list pointer of non-bytes where data was expected.'); + + kj.debug.REQUIRE(boundsCheck(segment, ptr, ptr + + capnp.common.roundBytesUpToWords(ref.getListRef().elementCount())), + 'Message contained out-of-bounds data pointer.'); + + return new capnp.blob.Data.Reader(segment.getUint8Array().subarray(ptr * 8, ptr * 8 + size), size); + } +}; + +capnp.layout.readStructPointer = function(segment, ref, refTarget, defaultValue, nestingLimit) { + + if (ref == null || ref.isNull()) { + if (defaultValue == null) { + return new capnp.layout.StructReader(null, 0, 0, 0, 0, 0, Number.MAX_VALUE); + } + + var defaultPointer = new capnp.layout.WirePointer(0, new DataView(defaultValue, 0, 8)); + if (defaultPointer.isNull()) { + return new capnp.layout.StructReader(null, 0, 0, 0, 0, 0, Number.MAX_VALUE); + } + + segment = new capnp.arena.SegmentReader(null, null, new DataView(defaultValue), null); + ref = defaultPointer; + refTarget = ref.target(); + defaultValue = null; // If the default value is itself invalid, don't use it again. + } + + kj.debug.REQUIRE(nestingLimit > 0, + 'Message is too deeply-nested or contains cycles. See capnp.ReadOptions.'); + + var followFarsResult = capnp.layout.followFars(ref, refTarget, segment); + ref = followFarsResult[0]; + segment = followFarsResult[1]; + var ptr = followFarsResult[2]; + + kj.debug.REQUIRE(ref.kind() === capnp.layout.Kind.STRUCT, + 'Message contains non-struct pointer where struct pointer was expected. '); + + kj.debug.REQUIRE(boundsCheck(segment, ptr, ptr + ref.getStructRef().wordSize()), + 'Message contained out-of-bounds struct pointer.'); + + return new capnp.layout.StructReader( + segment, (ptr << 3), ptr + ref.getStructRef().dataSize(), + ref.getStructRef().dataSize() * capnp.common.BITS_PER_WORD, + ref.getStructRef().ptrCount(), + 0, + nestingLimit - 1); +}; + +capnp.layout.readListPointer = function(segment, ref, refTarget, defaultValue, expectedElementSize, nestingLimit) { + + if (ref === null || ref.isNull()) { + + if (defaultValue == null) { + return new capnp.layout.ListReader(null, null, 0, 0, 0, 0, Number.MAX_VALUE); + } + + var defaultPointer = new capnp.layout.WirePointer(0, new DataView(defaultValue, 0, 8)); + if (defaultPointer.isNull()) { + return new capnp.layout.ListReader(null, null, 0, 0, 0, 0, Number.MAX_VALUE); + } + + segment = new capnp.arena.SegmentReader(null, null, new DataView(defaultValue), null); + ref = defaultPointer; + refTarget = ref.target(); + defaultValue = null; // If the default value is itself invalid, don't use it again. + } + + kj.debug.REQUIRE(nestingLimit > 0, + 'Message is too deeply-nested or contains cycles. See capnp::ReadOptions.'); + + var followFarsResult = capnp.layout.followFars(ref, refTarget, segment); + ref = followFarsResult[0]; + segment = followFarsResult[1]; + var ptr = followFarsResult[2]; + + kj.debug.REQUIRE(ref.kind() === capnp.layout.Kind.LIST, + 'Message contains non-list pointer where list pointer was expected.'); + + if (ref.getListRef().elementSize() === capnp.layout.FieldSize.INLINE_COMPOSITE) { + + var wordsPerElement; + var size; + + var wordCount = ref.getListRef().inlineCompositeWordCount(); + + // An INLINE_COMPOSITE list points to a tag, which is formatted like a pointer. + var tag = segment.createWirePointerAt(ptr << 3); + ptr += capnp.common.POINTER_SIZE_IN_WORDS; + + kj.debug.REQUIRE(boundsCheck(segment, ptr - capnp.common.POINTER_SIZE_IN_WORDS, ptr + wordCount), + 'Message contains out-of-bounds list pointer.'); + + kj.debug.REQUIRE(tag.kind() === capnp.layout.Kind.STRUCT, + 'INLINE_COMPOSITE lists of non-STRUCT type are not supported.'); + + size = tag.inlineCompositeListElementCount(); + wordsPerElement = tag.getStructRef().wordSize(); + + kj.debug.REQUIRE(size * wordsPerElement <= wordCount, + "INLINE_COMPOSITE list's elements overrun its word count."); + + // If a struct list was not expected, then presumably a non-struct list was upgraded to a + // struct list. We need to manipulate the pointer to point at the first field of the + // struct. Together with the "stepBits", this will allow the struct list to be accessed as + // if it were a primitive list without branching. + + // Check whether the size is compatible. + switch (expectedElementSize) { + case capnp.layout.FieldSize.VOID: + break; + + case capnp.layout.FieldSize.BIT: + case capnp.layout.FieldSize.BYTE: + case capnp.layout.FieldSize.TWO_BYTES: + case capnp.layout.FieldSize.FOUR_BYTES: + case capnp.layout.FieldSize.EIGHT_BYTES: + kj.debug.REQUIRE(tag.getStructRef().dataSize() > 0, + 'Expected a primitive list, but got a list of pointer-only structs.'); + break; + + case capnp.layout.FieldSize.POINTER: + // We expected a list of pointers but got a list of structs. Assuming the first field + // in the struct is the pointer we were looking for, we want to munge the pointer to + // point at the first element's pointer section. + ptr += (tag.getStructRef().dataSize() / capnp.common.BITS_PER_WORD) >>> 0; + kj.debug.REQUIRE(tag.getStructRef().ptrCount() > 0, + 'Expected a pointer list, but got a list of data-only structs.'); + break; + + case capnp.layout.FieldSize.INLINE_COMPOSITE: + break; + } + + return new capnp.layout.ListReader( + segment, ptr, size, wordsPerElement * capnp.common.BITS_PER_WORD, + tag.getStructRef().dataSize() * capnp.common.BITS_PER_WORD, + tag.getStructRef().ptrCount(), nestingLimit - 1); + + } else { + + // This is a primitive or pointer list, but all such lists can also be interpreted as struct + // lists. We need to compute the data size and pointer count for such structs. + var dataSize = capnp.layout.dataBitsPerElement(ref.getListRef().elementSize()); + var pointerCount = + capnp.layout.pointersPerElement(ref.getListRef().elementSize()); + var step = dataSize + pointerCount * capnp.common.BITS_PER_POINTER; + + kj.debug.REQUIRE(boundsCheck(segment, ptr, ptr + + capnp.common.roundBitsUpToWords(ref.getListRef().elementCount() * step)), + 'Message contains out-of-bounds list pointer.'); + + // Verify that the elements are at least as large as the expected type. Note that if we + // expected INLINE_COMPOSITE, the expected sizes here will be zero, because bounds checking + // will be performed at field access time. So this check here is for the case where we + // expected a list of some primitive or pointer type. + + var expectedDataBitsPerElement = + capnp.layout.dataBitsPerElement(expectedElementSize); + var expectedPointersPerElement = + capnp.layout.pointersPerElement(expectedElementSize); + + kj.debug.REQUIRE(expectedDataBitsPerElement <= dataSize, + 'Message contained list with incompatible element type.'); + + kj.debug.REQUIRE(expectedPointersPerElement <= pointerCount, + 'Message contained list with incompatible element type.'); + + return new capnp.layout.ListReader(segment, ptr, ref.getListRef().elementCount(), step, + dataSize, pointerCount, nestingLimit - 1); + } +}; + + +capnp.layout.totalSize = function(segment, ref, nestingLimit) { + // Compute the total size of the object pointed to, not counting far pointer overhead. + + if (ref.isNull()) { + return 0; + } + + kj.debug.REQUIRE(nestingLimit > 0, 'Message is too deeply-nested.'); + --nestingLimit; + + var followFarsResult = capnp.layout.followFars(ref, ref.target(), segment); + ref = followFarsResult[0]; + segment = followFarsResult[1]; + var ptr = followFarsResult[2]; + + var result = 0; + + switch (ref.kind()) { + case capnp.layout.Kind.STRUCT: { + kj.debug.REQUIRE(boundsCheck(segment, ptr, ptr + ref.getStructRef().wordSize()), + 'Message contained out-of-bounds struct pointer.'); + result += ref.getStructRef().wordSize(); + + var pointerSection = ptr + ref.getStructRef().dataSize(); + var count = ref.getStructRef().ptrCount(); + for (var i = 0; i < count; i++) { + result += capnp.layout.totalSize(segment, segment.createWirePointerAt((pointerSection + i) << 3), nestingLimit); + } + break; + } + case capnp.layout.Kind.LIST: { + switch (ref.getListRef().elementSize()) { + case capnp.layout.FieldSize.VOID: + // Nothing. + break; + case capnp.layout.FieldSize.BIT: + case capnp.layout.FieldSize.BYTE: + case capnp.layout.FieldSize.TWO_BYTES: + case capnp.layout.FieldSize.FOUR_BYTES: + case capnp.layout.FieldSize.EIGHT_BYTES: { + var totalWords = capnp.common.roundBitsUpToWords( + ref.getListRef().elementCount() * + capnp.layout.dataBitsPerElement(ref.getListRef().elementSize())); + kj.debug.REQUIRE(boundsCheck(segment, ptr, ptr + totalWords), + 'Message contained out-of-bounds list pointer.'); + result += totalWords; + break; + } + case capnp.layout.FieldSize.POINTER: { + var count = ref.getListRef().elementCount(); + + kj.debug.REQUIRE(boundsCheck(segment, ptr, ptr + count * capnp.common.WORDS_PER_POINTER), + 'Message contained out-of-bounds list pointer.'); + + result += count * capnp.common.WORDS_PER_POINTER; + + for (var i = 0; i < count; i++) { + result += capnp.layout.totalSize(segment, segment.createWirePointerAt((ptr + i) << 3), + nestingLimit); + } + break; + } + case capnp.layout.FieldSize.INLINE_COMPOSITE: { + var wordCount = ref.getListRef().inlineCompositeWordCount(); + kj.debug.REQUIRE(boundsCheck(segment, ptr, ptr + wordCount + capnp.common.POINTER_SIZE_IN_WORDS), + 'Message contained out-of-bounds list pointer.'); + + result += wordCount + capnp.common.POINTER_SIZE_IN_WORDS; + + var elementTag = segment.createWirePointerAt(ptr << 3); + var count = elementTag.inlineCompositeListElementCount(); + + kj.debug.REQUIRE(elementTag.kind() === capnp.layout.Kind.STRUCT, + "Don't know how to handle non-STRUCT inline composite."); + + kj.debug.REQUIRE(elementTag.getStructRef().wordSize() * count <= wordCount, + "Struct list pointer's elements overran size."); + + var dataSize = elementTag.getStructRef().dataSize(); + var pointerCount = elementTag.getStructRef().ptrCount(); + + var pos = ptr + capnp.common.POINTER_SIZE_IN_WORDS; + for (var i = 0; i < count; i++) { + pos += dataSize; + + for (var j = 0; j < pointerCount; j++) { + result += capnp.layout.totalSize(segment, segment.createWirePointerAt(pos << 3), + nestingLimit); + pos += capnp.common.POINTER_SIZE_IN_WORDS; + } + } + break; + } + } + break; + } + case capnp.layout.Kind.FAR: + kj.debug.FAIL_ASSERT('Unexpected FAR pointer.'); + break; + case capnp.layout.Kind.RESERVED_3: + kj.debug.FAIL_REQUIRE("Don't know how to handle RESERVED_3."); + break; + } + + return result; +}; + +capnp.layout.setListPointer = function(segment, ref, value, orphanArena) { + var totalSize = capnp.common.roundBitsUpToWords(value.elementCount * value.step); + + if (value.step <= capnp.common.BITS_PER_WORD) { + + // List of non-structs. + var allocateResult = capnp.layout.allocate(ref, segment, totalSize, capnp.layout.Kind.LIST, orphanArena); + var ptr = allocateResult[0]; + segment = allocateResult[1]; + ref = allocateResult[2]; + + if (value.structPointerCount === 1) { + + // List of pointers. + ref.setListRef(capnp.layout.FieldSize.POINTER, value.elementCount); + for (var i = 0; i < value.elementCount; i++) { + var valueRef = value.segment.createWirePointerAt((value.ptr + i) << 3); + capnp.layout.setObjectPointer(segment, segment.createWirePointerAt((ptr + i) << 3), capnp.layout.readObjectPointer( + value.segment, valueRef, valueRef.target(), null, value.nestingLimit)); + } + + } else { + + // List of data. + var elementSize = capnp.layout.FieldSize.VOID; + switch (value.step) { + case 0: elementSize = capnp.layout.FieldSize.VOID; break; + case 1: elementSize = capnp.layout.FieldSize.BIT; break; + case 8: elementSize = capnp.layout.FieldSize.BYTE; break; + case 16: elementSize = capnp.layout.FieldSize.TWO_BYTES; break; + case 32: elementSize = capnp.layout.FieldSize.FOUR_BYTES; break; + case 64: elementSize = capnp.layout.FieldSize.EIGHT_BYTES; break; + default: + kj.debug.FAIL_ASSERT('invalid list step size', value.step); + break; + } + + ref.setListRef(elementSize, value.elementCount); + + var destUint8Array = segment.getUint8Array(); + var srcUint8Array = value.segment.getUint8Array(); + + destUint8Array.set(srcUint8Array.subarray(value.ptr << 3, (value.ptr << 3) + totalSize * capnp.common.BYTES_PER_WORD), ptr << 3); + } + + return [segment, ptr]; + } else { + + // List of structs. + var allocateResult = capnp.layout.allocate(ref, segment, totalSize + capnp.common.POINTER_SIZE_IN_WORDS, capnp.layout.Kind.LIST, + orphanArena); + var ptr = allocateResult[0]; + segment = allocateResult[1]; + ref = allocateResult[2]; + ref.setListRefInlineComposite(totalSize); + + var dataSize = capnp.common.roundBitsUpToWords(value.structDataSize); + var pointerCount = value.structPointerCount; + + var tag = segment.createWirePointerAt(ptr << 3); + tag.setKindAndInlineCompositeListElementCount(capnp.layout.Kind.STRUCT, value.elementCount); + tag.setStructRef(new capnp.layout.StructSize(dataSize, pointerCount)); + var dst = ptr + capnp.common.POINTER_SIZE_IN_WORDS; + + var src = value.ptr; + for (var i = 0; i < value.elementCount; i++) { + memcpy(segment, dst << 3, value.segment, src << 3, value.structDataSize / capnp.common.BITS_PER_BYTE); + dst += dataSize; + src += dataSize; + + for (var j = 0; j < pointerCount; j++) { + var valueRef = value.segment.createWirePointerAt(src << 3); + capnp.layout.setObjectPointer(segment, segment.createWirePointerAt(dst << 3), + capnp.layout.readObjectPointer(value.segment, valueRef, valueRef.target(), null, + value.nestingLimit)); + dst += capnp.common.POINTER_SIZE_IN_WORDS; + src += capnp.common.POINTER_SIZE_IN_WORDS; + } + } + + return [segment, ptr]; + } +}; + +capnp.layout.readObjectPointer = function(segment, ref, refTarget, defaultValue, nestingLimit) { + // We can't really reuse readStructPointer() and readListPointer() because they are designed + // for the case where we are expecting a specific type, and they do validation around that, + // whereas this method is for the case where we accept any pointer. + // + // Not always-inline because it is called from several places in the copying code, and anyway + // is relatively rarely used. + + goog.asserts.assert(kj.util.isRegularNumber(nestingLimit)); + + if (ref === null || ref.isNull()) { + if (defaultValue == null || segment.createWirePointerAt(defaultValue << 3).isNull()) { + return new capnp.layout.ObjectReader(); + } + segment = null; + ref = segment.createWirePointerAt(defaultValue << 3); + refTarget = ref.target(); + defaultValue = null; // If the default value is itself invalid, don't use it again. + } + + var followFarsResult = capnp.layout.followFars(ref, refTarget, segment); + ref = followFarsResult[0]; + segment = followFarsResult[1]; + var ptr = followFarsResult[2]; + + switch (ref.kind()) { + case capnp.layout.Kind.STRUCT: + kj.debug.REQUIRE(nestingLimit > 0, + 'Message is too deeply-nested or contains cycles. See capnp::ReadOptions.'); + + kj.debug.REQUIRE(boundsCheck(segment, ptr, ptr + ref.getStructRef().wordSize()), + 'Message contained out-of-bounds struct pointer.'); + + return new capnp.layout.ObjectReader( + new capnp.layout.StructReader(segment, ptr << 3, + ptr + ref.getStructRef().dataSize(), + ref.getStructRef().dataSize() * capnp.common.BITS_PER_WORD, + ref.getStructRef().ptrCount(), + 0, nestingLimit - 1)); + case capnp.layout.Kind.LIST: { + var elementSize = ref.getListRef().elementSize(); + + kj.debug.REQUIRE(nestingLimit > 0, + 'Message is too deeply-nested or contains cycles. See capnp::ReadOptions.'); + + if (elementSize === capnp.layout.FieldSize.INLINE_COMPOSITE) { + var wordCount = ref.getListRef().inlineCompositeWordCount(); + var tag = segment.createWirePointerAt(ptr << 3); + ptr += capnp.common.POINTER_SIZE_IN_WORDS; + + kj.debug.REQUIRE(boundsCheck(segment, ptr - capnp.common.POINTER_SIZE_IN_WORDS, ptr + wordCount), + 'Message contains out-of-bounds list pointer.'); + + kj.debug.REQUIRE(tag.kind() === capnp.layout.Kind.STRUCT, + 'INLINE_COMPOSITE lists of non-STRUCT type are not supported.'); + + var elementCount = tag.inlineCompositeListElementCount(); + var wordsPerElement = tag.getStructRef().wordSize(); + + kj.debug.REQUIRE(wordsPerElement * elementCount <= wordCount, + "INLINE_COMPOSITE list's elements overrun its word count."); + + return new capnp.layout.ObjectReader( + new capnp.layout.ListReader(segment, ptr, elementCount, wordsPerElement * capnp.common.BITS_PER_WORD, + tag.getStructRef().dataSize() * capnp.common.BITS_PER_WORD, + tag.getStructRef().ptrCount(), nestingLimit - 1)); + } else { + var dataSize = capnp.layout.dataBitsPerElement(elementSize); + var pointerCount = capnp.layout.pointersPerElement(elementSize); + var step = dataSize + pointerCount * capnp.common.BITS_PER_POINTER; + var elementCount = ref.getListRef().elementCount(); + var wordCount = capnp.common.roundBitsUpToWords(elementCount * step); + + kj.debug.REQUIRE(boundsCheck(segment, ptr, ptr + wordCount), + 'Message contains out-of-bounds list pointer.'); + + return new capnp.layout.ObjectReader( + new capnp.layout.ListReader(segment, ptr, elementCount, step, dataSize, pointerCount, + nestingLimit - 1)); + } + } + default: + kj.debug.FAIL_REQUIRE('Message contained invalid pointer.'); + } +}; + + +/** + * @constructor + */ +capnp.layout.ObjectReader = function(reader) { + + this.getReader = function() { return reader; }; +}; + +capnp.layout.setObjectPointer = function(segment, ref, value) { + var reader = value.getReader(); + if (!reader) { + ref.clear(); + } + else if (reader instanceof capnp.layout.StructReader) { + capnp.layout.setStructPointer(segment, ref, reader); + } + else if (reader instanceof capnp.layout.ListReader) { + capnp.layout.setListPointer(segment, ref, reader); + } +}; + + +capnp.layout.setStructPointer = function(segment, ref, value, orphanArena) { + + var dataSize = capnp.common.roundBitsUpToWords(value.dataSize); + var totalSize = dataSize + value.pointerCount; + + var allocateResult = capnp.layout.allocate(ref, segment, totalSize, capnp.layout.Kind.STRUCT, orphanArena); + var ptr = allocateResult[0]; + segment = allocateResult[1]; + ref = allocateResult[2]; + ref.setStructRef(new capnp.layout.StructSize(dataSize, value.pointerCount)); + + if (value.dataSize === 1) { + throw new Error('NYI'); + // *reinterpret_cast(ptr) = value.getDataField(0 * ELEMENTS); + } else { + memcpy(segment, ptr << 3, value.segment, value.data, value.dataSize / capnp.common.BITS_PER_BYTE); + } + + for (var i = 0; i < value.pointerCount; i++) { + var valueRef = value.segment.createWirePointerAt((value.pointers + i) << 3); + capnp.layout.setObjectPointer(segment, segment.createWirePointerAt((ptr + dataSize + i) << 3), + capnp.layout.readObjectPointer(value.segment, valueRef, valueRef.target(), + null, value.nestingLimit)); + } + + // FIXME: return map instead + return [segment, ptr]; +}; + +/** + * @constructor + */ +capnp.layout.StructSize = function(dataWordCount, pointerCount, preferredListEncoding) { + this.getDataWordCount = function() { return dataWordCount; }; + this.getPointerCount = function() { return pointerCount; }; + this.getPreferredListEncoding = function() { return preferredListEncoding; }; + this.getTotal = function() { return dataWordCount + pointerCount * capnp.common.WORDS_PER_POINTER; }; + this.toString = function() { return 'StructSize(dataWordCount=' + dataWordCount + ',pointerCount=' + pointerCount + ',preferredListEncoding=' + preferredListEncoding + ')'; }; + return this; +}; + +capnp.layout.disown = function(segment, ref) { + + var location; + + if (ref.isNull()) { + location = null; + } else { + var followFarsResult = capnp.layout.followFars(ref, ref.target(), segment); + segment = followFarsResult[1]; + location = followFarsResult[2]; + } + + var result = new capnp.layout.OrphanBuilder(ref, segment, location); + + if (!ref.isNull() && ref.kind() !== capnp.layout.Kind.FAR) { + result.tagAsPtr().setKindForOrphan(ref.kind()); + } + + // Zero out the pointer that was disowned. + ref.clear(); + + return result; +} + +capnp.layout.adopt = function(segment, ref, value) { + + kj.debug.REQUIRE(value.segment === null || value.segment.getArena() === segment.getArena(), + "Adopted object must live in the same message."); + + if (!ref.isNull()) { + capnp.layout.zeroObject(segment, ref); + } + + if (value === null) { + // Set null. + memclear(segment, ref.getByteOffset(), 0, capnp.layout.WirePointer.SIZE_IN_BYTES); + } else if (value.tagAsPtr().kind() === capnp.layout.Kind.FAR) { + // FAR pointers are position-independent, so we can just copy. + ref.setOffsetAndKind(value.tagAsPtr().getOffsetAndKind()); + ref.setUpper32Bits(value.tagAsPtr().getUpper32Bits()); + } else { + transferPointerWithSrcPtr(segment, ref, value.segment, value.tagAsPtr(), value.location); + } + + // Take ownership away from the OrphanBuilder. + value.tagAsPtr().clear(); + value.location = null; + value.segment = null; +} + + +/** + * @constructor + */ +capnp.layout.OrphanBuilder = function(ref, segment, location) { + orphanBuffer = new ArrayBuffer(capnp.layout.WirePointer.SIZE_IN_BYTES); + this.tag = new capnp.layout.WirePointer(0, new DataView(orphanBuffer)); + if (ref) { + this.segment = segment; + this.location = location; + this.tag.setOffsetAndKind(ref.getOffsetAndKind()); + this.tag.setUpper32Bits(ref.getUpper32Bits()); + } + else { + this.segment = null; + this.location = null; + this.tag.clear(); + } +}; + +capnp.layout.OrphanBuilder.prototype.tagAsPtr = function() { + return this.tag; +}; + +capnp.layout.OrphanBuilder.prototype.asStructReader = function(size) { + kj.debug.DASSERT(this.tagAsPtr().isNull() === (this.location === null)); + return capnp.layout.readStructPointer(this.segment, this.tagAsPtr(), this.location, null, Number.MAX_VALUE); +} + +capnp.layout.OrphanBuilder.prototype.asStruct = function(size) { + kj.debug.DASSERT(this.tagAsPtr().isNull() === (this.location === null)); + + var result = capnp.layout.getWritableStructPointer( + this.tagAsPtr(), this.location, this.segment, size, null, this.segment.getArena()); + + // Watch out, the pointer could have been updated if the object had to be relocated. + this.location = result.data >>> 3; + + return result; +} + +capnp.layout.OrphanBuilder.prototype.asList = function(elementSize) { + + kj.debug.DASSERT(this.tagAsPtr().isNull() === (this.location == null)); + + var result = capnp.layout.getWritableListPointer( + this.tagAsPtr(), this.location, this.segment, elementSize, null, this.segment.getArena()); + + // Watch out, the pointer could have been updated if the object had to be relocated. + // (Actually, currently this is not true for primitive lists, but let's not turn into a bug if + // it changes!) + this.location = result.getLocation(); + + return result; +}; + +capnp.layout.OrphanBuilder.prototype.asListReader = function(elementSize) { + kj.debug.DASSERT(this.tagAsPtr().isNull() === (this.location == null)); + return capnp.layout.readListPointer( + this.segment, this.tagAsPtr(), this.location, null, elementSize, Number.MAX_VALUE); +}; + +capnp.layout.OrphanBuilder.prototype.asStructList = function(elementSize) { + kj.debug.DASSERT(this.tagAsPtr().isNull() === (this.location == null)); + + var result = capnp.layout.getWritableStructListPointer( + this.tagAsPtr(), this.location, this.segment, elementSize, null, this.segment.getArena()); + + // Watch out, the pointer could have been updated if the object had to be relocated. + this.location = result.getLocation(); + + return result; +}; + +capnp.layout.OrphanBuilder.prototype.asText = function(elementSize) { + kj.debug.DASSERT(this.tagAsPtr().isNull() === (this.location == null)); + + // Never relocates. + return capnp.layout.getWritableTextPointer(this.tagAsPtr(), this.location, this.segment, null, 0); +}; + +capnp.layout.OrphanBuilder.prototype.asData = function(elementSize) { + kj.debug.DASSERT(this.tagAsPtr().isNull() === (this.location == null)); + + // Never relocates. + return capnp.layout.getWritableDataPointer(this.tagAsPtr(), this.location, this.segment, null, 0); +}; + + +capnp.layout.OrphanBuilder.prototype.asTextReader = function(elementSize) { + kj.debug.DASSERT(this.tagAsPtr().isNull() === (this.location == null)); + return capnp.layout.readTextPointer(this.segment, this.tagAsPtr(), this.location, '', 0); +} + +capnp.layout.OrphanBuilder.prototype.asDataReader = function(elementSize) { + kj.debug.DASSERT(this.tagAsPtr().isNull() === (this.location == null)); + return capnp.layout.readDataPointer(this.segment, this.tagAsPtr(), this.location, null, 0); +} + +capnp.layout.OrphanBuilder.prototype.destroy = function() { + if (this.segment != null) this.euthanize(); +}; + +capnp.layout.OrphanBuilder.prototype.euthanize = function() { + if (this.tagAsPtr().kind() === capnp.layout.Kind.FAR) { + zeroObject(this.segment, this.tagAsPtr()); + } else { + zeroObjectTag(this.segment, this.tagAsPtr(), this.location); + } + + this.tag.clear(); + this.segment = null; + this.location = null; +}; + +capnp.layout.OrphanBuilder.prototype.isNull = function() { + return this.location == null; +}; + +capnp.layout.OrphanBuilder.initStruct = function(arena, size) { + var result = new capnp.layout.OrphanBuilder(); + var builder = capnp.layout.initStructPointer(result.tagAsPtr(), null, size, arena); + result.segment = builder.segment; + result.location = builder.getLocation(); + return result; +} + +capnp.layout.OrphanBuilder.initList = function(arena, elementCount, elementSize) { + var result = new capnp.layout.OrphanBuilder(); + var builder = capnp.layout.initListPointer( + result.tagAsPtr(), null, elementCount, elementSize, arena); + result.segment = builder.segment; + result.location = builder.getLocation(); + return result; +}; + +capnp.layout.OrphanBuilder.initStructList = function(arena, elementCount, elementSize) { + var result = new capnp.layout.OrphanBuilder(); + var builder = capnp.layout.initStructListPointer( + result.tagAsPtr(), null, elementCount, elementSize, arena); + result.segment = builder.segment; + result.location = builder.getLocation(); + return result; +}; + +capnp.layout.OrphanBuilder.initText = function(arena, size) { + var result = new capnp.layout.OrphanBuilder(); + var allocation = capnp.layout.initTextPointer(result.tagAsPtr(), null, size, arena); + result.segment = allocation.segment; + result.location = allocation.value.begin() >>> 3; + return result; +}; + +capnp.layout.OrphanBuilder.initData = function(arena, size) { + var result = new capnp.layout.OrphanBuilder(); + var allocation = capnp.layout.initDataPointer(result.tagAsPtr(), null, size, arena); + result.segment = allocation.segment; + result.location = allocation.value.begin() >>> 3; + return result; +}; + +capnp.layout.OrphanBuilder.copyStruct = function(arena, copyFrom) { + var result = new capnp.layout.OrphanBuilder(); + var allocation = capnp.layout.setStructPointer(null, result.tagAsPtr(), copyFrom, arena); + result.segment = allocation[0]; + result.location = allocation[1]; + return result; +}; + +capnp.layout.OrphanBuilder.copyList = function(arena, copyFrom) { + var result = new capnp.layout.OrphanBuilder(); + var allocation = capnp.layout.setListPointer(null, result.tagAsPtr(), copyFrom, arena); + result.segment = allocation[0]; + result.location = allocation[1]; + return result; +}; + +capnp.layout.OrphanBuilder.copyText = function(arena, copyFrom) { + var result = new capnp.layout.OrphanBuilder(); + var allocation = capnp.layout.setTextPointer( + result.tagAsPtr(), null, copyFrom, arena); + result.segment = allocation.segment; + result.location = allocation.value.begin() >>> 3; + return result; +}; + +capnp.layout.OrphanBuilder.copyData = function(arena, copyFrom) { + var result = new capnp.layout.OrphanBuilder(); + var allocation = capnp.layout.setDataPointer( + result.tagAsPtr(), null, copyFrom, arena); + result.segment = allocation.segment; + result.location = allocation.value.begin() >>> 3; + return result; +}; diff --git a/javascript/lib/capnp_list.js b/javascript/lib/capnp_list.js new file mode 100644 index 0000000..a15d41a --- /dev/null +++ b/javascript/lib/capnp_list.js @@ -0,0 +1,671 @@ +/* + * Copyright (c) 2013, Julian Scheid + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +goog.provide('capnp.list'); + +goog.require('capnp.prim'); +goog.require('capnp.blob'); +goog.require('capnp.layout'); +goog.require('kj.util'); + +var primitiveClasses = [ + capnp.prim.int8_t, + capnp.prim.uint8_t, + capnp.prim.int16_t, + capnp.prim.uint16_t, + capnp.prim.int32_t, + capnp.prim.uint32_t, + capnp.prim.int64_t, + capnp.prim.uint64_t, + capnp.prim.float32_t, + capnp.prim.float64_t, + capnp.prim.bool, + capnp.prim.Void +]; + +var blobClasses = [ + capnp.blob.Text, + capnp.blob.Data +]; + + +/** + * @constructor + */ +capnp.list.List = function(clazz) { + if (clazz) { + if (primitiveClasses.indexOf(clazz) >= 0) { + return capnp.list.ListOfPrimitives(clazz); + } + else if (blobClasses.indexOf(clazz) >= 0) { + return capnp.list.ListOfBlobs(clazz); + } + else if (kj.util.isFunction(clazz) && clazz instanceof this) { + return capnp.list.ListOfLists(clazz); + } + else if (clazz.STRUCT_SIZE) { + return capnp.list.ListOfStructs(clazz); + } + else { + return capnp.list.ListOfEnums(clazz); + } + } +}; + + +capnp.list.List.prototype.getOrphan = function(builder) { + return this.Builder(builder.asList(this.getElementSize())); +}; + +capnp.list.List.prototype.getOrphanReader = function(builder) { + return this.Reader(builder.asListReader(this.getElementSize())); +}; + +capnp.list.List.prototype.getNewOrphanList = function(arena, size) { + return capnp.layout.OrphanBuilder.initList(arena, size, this.getElementSize()); +}; + +capnp.list.ListOfPrimitives = function(clazz, defaultElementSize) { + + /** + * @constructor + */ + var subType = function() { + } + subType.prototype = new capnp.list.List(); + + subType.prototype.getElementSize = function() { + return defaultElementSize || clazz.elementSize; + }; + + subType.prototype.getReaderAsElementOf = function(reader, + index, + defaultValue) { + return reader.getListElement( + index, defaultElementSize || clazz.elementSize); + }; + + subType.prototype.getBuilderAsElementOf = function(builder, + index, + defaultValue) { + return builder.getListElement( + index, defaultElementSize || clazz.elementSize); + }; + + subType.prototype.initBuilderAsElementOf = function(builder, + index, + size) { + return builder.initListElement( + index, defaultElementSize || clazz.elementSize, size); + }; + + subType.prototype.getReaderAsFieldOf = function(reader, + index, + defaultValue) { + return reader.getListField( + index, defaultElementSize || clazz.elementSize, defaultValue); + }; + + subType.prototype.getBuilderAsFieldOf = function(builder, + index, + defaultValue) { + return builder.getListField( + index, defaultElementSize || clazz.elementSize, defaultValue); + }; + + subType.prototype.getBuilder = function(builder, + index, + defaultValue) { + return this.Builder( + this.getBuilderAsFieldOf(builder, index, defaultValue)); + }; + + subType.prototype.initBuilder = function(builder, + index, + size) { + return new this.Builder( + this.initBuilderAsFieldOf(builder, index, size)); + }; + + subType.prototype.initBuilderAsFieldOf = function(builder, index, size) { + var elementSize = defaultElementSize || clazz.elementSize; + return builder.initListField(index, elementSize, size); + }; + + subType.prototype.getReader = function(reader, index, defaultValue) { + return new this.Reader( + this.getReaderAsFieldOf(reader, index, defaultValue)); + }; + + /** + * @constructor + */ + var Reader = function(_reader) { + + this.getReader = function() { + return _reader; + }; + + this._getInnerReader = function() { + return this.getReader(); + } + + this.length = function() { + return _reader.size(); + }; + + this.size = function() { + return _reader.size(); + }; + + this.get = function(index) { + if (index < 0 || index >= _reader.size()) { + throw new RangeError(); + } + return _reader.getDataElement(clazz, index); + }; + + this.toString = function() { + var result = '[ '; + + for (var i = 0, len = this.length(); i < len; ++i) { + if (i > 0) result += ', '; + result += safeToString(this.get(i)); + } + + return result + ' ]'; + }; + + return this; + }; + + /** + * @constructor + */ + subType.prototype.Builder = function(_builder) { + return new (function() { + + this.getReader = function() { + return _builder.asReader(); + }; + + this.length = function() { + return _builder.size(); + }; + + this.size = function() { + return _builder.size(); + }; + + this.get = function(index) { + if (index < 0 || index >= _builder.size()) { + throw new RangeError(); + } + return _builder.getDataElement(clazz, index); + }; + + this.set = function(index, value) { + if (index < 0 || index >= _builder.size()) { + throw new RangeError(); + } + _builder.setDataElement(clazz, index, value); + }; + }); + }; + + subType.prototype.constructor = subType; + subType.prototype.Reader = Reader; + + subType.prototype.copyOrphan = capnp.layout.OrphanBuilder.copyList; + var instance = new subType(); + + Reader.prototype._getParentType = function() { + return instance; + }; + + return instance; +}; + +capnp.list.ListOfEnums = function(clazz) { + return capnp.list.ListOfPrimitives(capnp.prim.uint16_t, capnp.layout.FieldSize.TWO_BYTES); +}; + + +/** + * @constructor + */ +capnp.list.ListOfLists = function(clazz) { + + return { + type: 'ListsOfLists', + + getElementSize: function() { + return capnp.layout.FieldSize.POINTER; + }, + + getReaderAsElementOf: function(reader, index, defaultValue) { + return reader.getListField(index, capnp.layout.FieldSize.POINTER, defaultValue); + }, + + getBuilderAsElementOf: function(builder, index, defaultValue) { + return builder.getListField(index, capnp.layout.FieldSize.POINTER, defaultValue); + }, + + getReaderAsFieldOf: function(reader, index, defaultValue) { + return reader.getListField(index, capnp.layout.FieldSize.POINTER, defaultValue); + }, + + getReader: function(reader, index, defaultValue) { + return new this.Reader(this.getReaderAsFieldOf(reader, index, defaultValue)); + }, + + getBuilderAsFieldOf: function(builder, index, defaultValue) { + return builder.getListField(index, capnp.layout.FieldSize.POINTER, defaultValue); + }, + + initBuilderAsFieldOf: function(builder, index, size) { + return builder.initListField(index, capnp.layout.FieldSize.POINTER, size); + }, + + getBuilder: function(builder, index, defaultValue) { + return new this.Builder(this.getBuilderAsFieldOf(builder, index, defaultValue)); + }, + + initBuilder: function(builder, index, size) { + return new this.Builder(this.initBuilderAsFieldOf(builder, index, size)); + }, + + /** + * @constructor + */ + Reader: function(_reader) { + + this.getReader = function() { + return _reader; + }; + + this.length = function() { + return _reader.size(); + }; + + this.size = function() { + return _reader.size(); + }; + + this.get = function(index) { + if (index < 0 || index >= _reader.size()) { + throw new RangeError(); + } + + return new clazz.Reader(clazz.getReaderAsElementOf(_reader, index)); + }; + + this.toString = function() { + var result = '[ '; + + for (var i = 0, len = this.length(); i < len; ++i) { + if (i > 0) result += ', '; + result += safeToString(this.get(i)); + } + + return result + ' ]'; + }; + + return this; + }, + + /** + * @constructor + */ + Builder: function(_builder) { + + this.getReader = function() { + return _builder.asReader(); + }; + + this.length = function() { + return _builder.size(); + }; + + this.size = function() { + return _builder.size(); + }; + + this.get = function(index) { + if (index < 0 || index >= _builder.size()) { + throw new RangeError(); + } + + return new clazz.Builder(clazz.getBuilderAsElementOf(_builder, index)); + }; + + this.init = function(index, size) { + if (index < 0 || index >= _builder.size()) { + throw new RangeError(); + } + return new clazz.Builder(clazz.initBuilderAsElementOf(_builder, index, size)); + }; + + this.set = function(index, value) { + if (index < 0 || index >= _builder.size()) { + throw new RangeError(); + } + + if (goog.isArray(value)) { + var l = this.init(index, value.length); + + for (var i = 0, len = value.length; i < len; ++i) { + l.set(i, value[i]); + } + } + else { + this._setSingle(index, value); + } + }; + + this._setSingle = function(index, value) { + _builder.setListElement(index, value); + }; + + this.toString = function() { + var result = '[ '; + + for (var i = 0, len = this.length(); i < len; ++i) { + if (i > 0) result += ', '; + result += safeToString(this.get(i)); + } + + return result + ' ]'; + }; + + return this; + } + }; +}; +capnp.list.ListOfLists.prototype = new capnp.list.List(); + +capnp.list.ListOfBlobs = function(clazz) { + + /** + * @constructor + */ + var subType = function() { + + this.getElementSize = function() { + return capnp.layout.FieldSize.POINTER; + }; + + this.getReaderAsElementOf = function(reader, index, defaultValue, defaultBytes) { + return reader.getListElement(index, capnp.layout.FieldSize.POINTER); + }; + + this.getBuilderAsElementOf = function(builder, index, defaultValue, defaultBytes) { + return builder.getListElement(index, capnp.layout.FieldSize.POINTER); + }; + + this.initBuilderAsElementOf = function(builder, index, size) { + return builder.initListElement(index, capnp.layout.FieldSize.POINTER, size); + }; + + this.initBuilderAsFieldOf = function(builder, index, size) { + return builder.initListField(index, capnp.layout.FieldSize.POINTER, size); + }; + + this.getReaderAsFieldOf = function(reader, index, defaultValue) { + return reader.getListField(index, capnp.layout.FieldSize.POINTER, defaultValue); + }; + + this.getBuilderAsFieldOf = function(builder, index, defaultValue) { + var result = builder.getListField(index, capnp.layout.FieldSize.POINTER, defaultValue); + return result; + }; + + this.getReader = function(reader, index, defaultValue) { + return new this.Reader(this.getReaderAsFieldOf(reader, index, defaultValue)); + }; + + this.getBuilder = function(builder, index, defaultValue) { + return new this.Builder(this.getBuilderAsFieldOf(builder, index, defaultValue)); + }; + + /** + * @constructor + */ + this.Reader = function(_reader) { + + this.getReader = function() { + return _reader; + }; + + this.length = function() { + return _reader.size(); + }; + + this.size = function() { + return _reader.size(); + }; + + this.get = function(index) { + if (index < 0 || index >= _reader.size()) { + throw new RangeError(); + } + return clazz.getReaderAsElement(_reader, index); + }; + + this.toString = function() { + var result = '[ '; + + for (var i = 0, len = this.length(); i < len; ++i) { + if (i > 0) result += ', '; + result += safeToString(this.get(i)); + } + + return result + ' ]'; + }; + + return this; + }; + + /** + * @constructor + */ + this.Builder = function(_builder) { + + this.getReader = function() { + return _builder.asReader(); + }; + + this.length = function() { + return _builder.size(); + }; + + this.size = function() { + return _builder.size(); + }; + + this.get = function(index) { + if (index < 0 || index >= _builder.size()) { + throw new RangeError(); + } + + return clazz.getBuilderAsElement(_builder, index); + }; + + this.set = function(index, value) { + if (index < 0 || index >= _builder.size()) { + throw new RangeError(); + } + if (kj.util.isString(value)) { + value = new capnp.blob.StringTextReader(value); + } + clazz.setElement(_builder, index, value); + }; + + this.toString = function() { + var result = '[ '; + + for (var i = 0, len = this.length(); i < len; ++i) { + if (i > 0) result += ', '; + result += safeToString(this.get(i)); + } + + return result + ' ]'; + }; + + return this; + }; + }; + subType.prototype = new capnp.list.List(); + subType.prototype.constructor = subType; + return new subType; +}; +capnp.list.ListOfBlobs.prototype = new capnp.list.List(); + + +capnp.list.ListOfStructs = function(clazz) { + + /** + * @constructor + */ + var subType = function() { + + this.getElementSize = function() { + return capnp.layout.FieldSize.POINTER; + }; + + this.getReaderAsElementOf = function(reader, index) { + return reader.getListElement(index, clazz.ELEMENT_SIZE); + }; + + this.getBuilderAsElementOf = function(builder, index) { + return builder.getStructListElement(index, clazz.STRUCT_SIZE); + }; + + this.initBuilderAsElementOf = function(builder, index, size) { + return builder.initStructListElement(index, size, clazz.STRUCT_SIZE); + }; + + this.getReaderAsFieldOf = function(reader, index, defaultValue) { + return reader.getListField(index, clazz.ELEMENT_SIZE, defaultValue); + }; + + this.getReader = function(reader, index, defaultValue) { + return new this.Reader(this.getReaderAsFieldOf(reader, index, defaultValue)); + }; + + this.getBuilderAsFieldOf = function(builder, index, defaultValue) { + return builder.getStructListField(index, clazz.STRUCT_SIZE, defaultValue); + }; + + this.initBuilderAsFieldOf = function(builder, index, size) { + return builder.initStructListField(index, size, clazz.STRUCT_SIZE); + }; + + this.getBuilder = function(builder, index, defaultValue) { + var structBuilder = this.getBuilderAsFieldOf(builder, index, defaultValue); + return this.Builder(structBuilder); + }; + + this.initBuilder = function(builder, index, size) { + return this.Builder(this.initBuilderAsFieldOf(builder, index, size)); + }; + + this.getOrphan = function(builder) { + return this.Builder(builder.asStructList(clazz.STRUCT_SIZE)); + }; + + this.getNewOrphanList = function(arena, size) { + return capnp.layout.OrphanBuilder.initStructList(arena, size, clazz.STRUCT_SIZE); + }; + + /** + * @constructor + */ + this.Builder = function(_builder) { + return new (function() { + + this.getReader = function() { + return _builder.asReader(); + }; + + this.length = function() { + return _builder.size(); + }; + + this.size = function() { + return _builder.size(); + }; + + this.get = function(index) { + return new clazz.Builder(_builder.getStructElement(index)); + }; + + return this; + }); + }; + + /** + * @constructor + */ + this.Reader = function(_reader) { + + this.getReader = function() { + return _reader; + }; + + this.length = function() { + return _reader.size(); + }; + + this.size = function() { + return _reader.size(); + }; + + this.get = function(index) { + if (index < 0 || index >= _reader.size()) { + throw new RangeError(); + } + + return new clazz.Reader(_reader.getStructElement(index)); + }; + + this.toString = function() { + var result = '[ '; + + for (var i = 0, len = this.length(); i < len; ++i) { + if (i > 0) result += ', '; + result += safeToString(this.get(i)); + } + + return result + ' ]'; + }; + + return this; + }; + }; + subType.prototype = new capnp.list.List(); + return new subType; +}; +capnp.list.ListOfStructs.prototype = new capnp.list.List(); diff --git a/javascript/lib/capnp_message.js b/javascript/lib/capnp_message.js new file mode 100644 index 0000000..6706e47 --- /dev/null +++ b/javascript/lib/capnp_message.js @@ -0,0 +1,300 @@ +/* + * Copyright (c) 2013, Julian Scheid + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +goog.provide('capnp.message'); + +goog.require('kj.debug'); + +var defaultReaderOptions = { + traversalLimitInWords: 8 * 1024 * 1024, + nestingLimit: 64 +}; + +/** + * @enum {number} + */ +capnp.message.AllocationStrategy = { + + FIXED_SIZE: 0, + // The builder will prefer to allocate the same amount of space for each segment with no + // heuristic growth. It will still allocate larger segments when the preferred size is too small + // for some single object. This mode is generally not recommended, but can be particularly useful + // for testing in order to force a message to allocate a predictable number of segments. Note + // that you can force every single object in the message to be located in a separate segment by + // using this mode with firstSegmentWords = 0. + + GROW_HEURISTICALLY: 1 + // The builder will heuristically decide how much space to allocate for each segment. Each + // allocated segment will be progressively larger than the previous segments on the assumption + // that message sizes are exponentially distributed. The total number of segments that will be + // allocated for a message of size n is O(log n). +}; + +/** + * @constructor + */ +capnp.message.MessageReader = function(options) { + + this.options = {}; + this.options.traversalLimitInWords = (options && options.traversalLimitInWords) || defaultReaderOptions.traversalLimitInWords; + this.options.nestingLimit = (options && options.nestingLimit) || defaultReaderOptions.nestingLimit; + + return this; +}; + +capnp.message.MessageReader.prototype.getRootInternal = function() { + if (!this.arena) { + this.arena = new capnp.arena.ReaderArena(this); + } + + var segment = this.arena.tryGetSegment(0); + + /* + KJ_REQUIRE(segment != nullptr && + segment->containsInterval(segment->getStartPtr(), segment->getStartPtr() + 1), + "Message did not contain a root pointer.") { + return _::StructReader(); + } + */ + + return capnp.layout.StructReader.readRoot(0, segment, this.options.nestingLimit); +}; + +capnp.message.MessageReader.prototype.getRoot = function(RootType) { + return new RootType.Reader(this.getRootInternal()); +}; + +/** + * @constructor + * @extends capnp.message.MessageReader + */ +capnp.message.SegmentArrayMessageReader = function(segments, options) { + capnp.message.MessageReader.call(this, options); + this.segments = segments; +}; + +goog.inherits(capnp.message.SegmentArrayMessageReader, capnp.message.MessageReader); + +capnp.message.SegmentArrayMessageReader.prototype.getSegment = function(id) { + + if (id < this.segments.length) { + return this.segments[id]; + } + else { + return null; + } +}; + +capnp.message.SegmentArrayMessageReader.prototype.toString = function() { + return 'SegmentArrayMessageReader{...}'; +}; + + +/** + * @constructor + * @extends capnp.message.MessageReader + */ +capnp.message.FlatArrayMessageReader = function(arrayBuffer, options) { + + capnp.message.MessageReader.call(this, options); + + kj.debug.REQUIRE(kj.util.isArrayBuffer(arrayBuffer), 'FlatArrayMessageReader argument must be an ArrayBuffer'); + + var arraySize = arrayBuffer.byteLength >>> 3; + + if (arraySize < 1) { + // Assume empty message. + return; + } + + var table = new Uint32Array(arrayBuffer); + + var segmentCount = table[0] + 1; + var offset = (segmentCount >>> 1) + 1; + this.moreSegments = []; + + kj.debug.REQUIRE(arraySize >= offset, 'Message ends prematurely in segment table.'); + + if (segmentCount === 0) { + return; + } + + var segmentSize = table[1]; + + kj.debug.REQUIRE(arraySize >= offset + segmentSize, + 'Message ends prematurely in first segment.'); + + this.segment0 = new DataView(arrayBuffer, offset << 3, segmentSize << 3); + offset += segmentSize; + + if (segmentCount > 1) { + this.moreSegments = []; + + for (var i = 1; i < segmentCount; i++) { + var segmentSize = table[i + 1]; + + kj.debug.REQUIRE(arraySize >= offset + segmentSize, 'Message ends prematurely.'); + + this.moreSegments[i - 1] = new DataView(arrayBuffer, offset << 3, segmentSize << 3); + offset += segmentSize; + } + } +} + +goog.inherits(capnp.message.FlatArrayMessageReader, capnp.message.MessageReader); + +capnp.message.FlatArrayMessageReader.prototype.getSegment = function(id) { + if (id == 0) { + return this.segment0; + } else if (id <= this.moreSegments.length) { + return this.moreSegments[id - 1]; + } else { + return null; + } +}; + +/** + * @constructor + */ +capnp.message.MessageBuilder = function() { + this.arena = null; +} + +capnp.message.MessageBuilder.prototype.getRootSegment = function() { + if (this.arena) { + return this.arena.getSegment(0); + } + else { + this.arena = new capnp.arena.BuilderArena(this); + var allocation = this.arena.allocate(capnp.common.POINTER_SIZE_IN_WORDS); + return allocation.segment; + } +}; + +capnp.message.MessageBuilder.prototype.getArena = function() { + return this.arena; +}; + +capnp.message.MessageBuilder.prototype.getSegmentsForOutput = function() { + if (this.arena) { + return this.arena.getSegmentsForOutput(); + } else { + return null; + } +}; + +/** + * @constructor + * @extends capnp.message.MessageBuilder + */ +capnp.message.MallocMessageBuilder = function(firstSegmentWords, allocationStrategy) { + + var SUGGESTED_ALLOCATION_STRATEGY = capnp.message.AllocationStrategy.GROW_HEURISTICALLY; + + if (firstSegmentWords === undefined) { + firstSegmentWords = 1024; + } + if (allocationStrategy === undefined) { + allocationStrategy = SUGGESTED_ALLOCATION_STRATEGY; + } + this.allocationStrategy = allocationStrategy; + + var self = this; + this.nextSize = firstSegmentWords; + this.ownFirstSegment = true; + this.returnedFirstSegment = false; + this.firstSegment = null; + this.moreSegments = []; +}; + +goog.inherits(capnp.message.MallocMessageBuilder, capnp.message.MessageBuilder); + +capnp.message.MallocMessageBuilder.prototype.initRoot = function(RootType) { + + var self = this; + var initRootWithSize = function(structSize) { + goog.asserts.assert(structSize instanceof capnp.layout.StructSize, 'initRootWithSize got invalid structSize: ' + structSize); + var rootSegment = self.getRootSegment(); + return capnp.layout.StructBuilder.initRoot(rootSegment, 0, structSize); + }; + + return new RootType.Builder(initRootWithSize(RootType.STRUCT_SIZE)); //RootType.StructBuilder.initRoot(rootSegment, rootSegment.dataView, 0)); +}; + +capnp.message.MallocMessageBuilder.prototype.setRootInternal = function(reader) { + var rootSegment = this.getRootSegment(); + capnp.layout.StructBuilder.setRoot(rootSegment, 0, reader); +}; + +capnp.message.MallocMessageBuilder.prototype.setRoot = function(value) { + /* FIXME + typedef FromReader RootType; + static_assert(kind() == Kind::STRUCT, + "Parameter must be a Reader for a Cap'n Proto struct type."); + */ + this.setRootInternal(value._getReader()); +}; + +capnp.message.MallocMessageBuilder.prototype.getRoot = function(RootType) { + var rootSegment = this.getRootSegment(); + return new RootType.Builder(capnp.layout.StructBuilder.getRoot(rootSegment, 0, RootType.STRUCT_SIZE)); +}; + +capnp.message.MallocMessageBuilder.prototype.adoptRoot = function(orphan) { + throw new Error("NYI"); +}; + +capnp.message.MallocMessageBuilder.prototype.getOrphanage = function() { + if (!this.arena) this.getRootSegment(); + + return new capnp.orphan.Orphanage(this.arena); +}; + +capnp.message.MallocMessageBuilder.prototype.allocateSegment = function(minimumSize) { + + var size = Math.max(minimumSize, this.nextSize); + + var result = new ArrayBuffer(size * capnp.common.BYTES_PER_WORD); + + if (!this.returnedFirstSegment) { + this.firstSegment = result; + this.returnedFirstSegment = true; + + // After the first segment, we want nextSize to equal the total size allocated so far. + if (this.allocationStrategy === capnp.message.AllocationStrategy.GROW_HEURISTICALLY) this.nextSize = size; + } else { + this.moreSegments.push(result); + if (this.allocationStrategy === capnp.message.AllocationStrategy.GROW_HEURISTICALLY) this.nextSize += size; + } + + return new DataView(result); +}; + + +capnp.message.readMessageUnchecked = function(type, data) { + return new type.Reader(capnp.layout.StructReader.readRootUnchecked(data)); +}; + +capnp.message.SUGGESTED_FIRST_SEGMENT_WORDS = 1024; diff --git a/javascript/lib/capnp_orphan.js b/javascript/lib/capnp_orphan.js new file mode 100644 index 0000000..768e70e --- /dev/null +++ b/javascript/lib/capnp_orphan.js @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2013, Julian Scheid + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +goog.provide('capnp.orphan'); + +/** + * @constructor + */ +capnp.orphan.Orphan = function(type, builder) { + this.type = type; + this.builder = builder; +}; + +capnp.orphan.Orphan.prototype.destroy = function() { + if (this.builder) this.builder.destroy(); +} + +capnp.orphan.Orphan.prototype.isNull = function() { + return this.builder === null || this.builder.isNull(); +} + +capnp.orphan.Orphan.prototype.get = function() { + //return new this.type.Builder(this.builder.asStruct(this.type.STRUCT_SIZE)); + return this.type.getOrphan(this.builder); +} + +capnp.orphan.Orphan.prototype.getReader = function() { + //return new this.type.Reader(this.builder.asStructReader(this.type.STRUCT_SIZE)); + return this.type.getOrphanReader(this.builder); +} + +/** + * @constructor + */ +capnp.orphan.Orphanage = function(arena) { + this.arena = arena; +}; + +capnp.orphan.Orphanage.prototype.newOrphan = function(RootType, size) { + if ((RootType instanceof capnp.list.List) || (RootType === capnp.blob.Text) || (RootType === capnp.blob.Data)) { + return new capnp.orphan.Orphan(RootType, RootType.getNewOrphanList(this.arena, size)); + } + else { + return new capnp.orphan.Orphan(RootType, capnp.layout.OrphanBuilder.initStruct(this.arena, RootType.STRUCT_SIZE)); + } +}; + +capnp.orphan.Orphanage.prototype.newOrphanCopy = function(copyFrom) { + + return new capnp.orphan.Orphan(copyFrom._getParentType(), copyFrom._getParentType().copyOrphan( + this.arena, copyFrom._getInnerReader())); +} diff --git a/javascript/lib/capnp_prim.js b/javascript/lib/capnp_prim.js new file mode 100644 index 0000000..83d477d --- /dev/null +++ b/javascript/lib/capnp_prim.js @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2013, Julian Scheid + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +goog.provide('capnp.prim'); + +goog.require('kj.util'); +goog.require('capnp.layout'); + +capnp.prim.asUint64Val = function(args) { + if (args.length == 1 && kj.util.isNumber(args[0])) { + return { hi: (args[0] / 2e32) >>> 0, lo: args[0] >>> 0 }; + } + else if (args.length == 2 && kj.util.isNumber(args[0]) && kj.util.isNumber(args[1])) { + return { hi: args[0], lo: args[1] }; + } + else if (args.length == 1 && goog.isArray(args[0]) && kj.util.isNumber(args[0][0]) && kj.util.isNumber(args[0][1])) { + return { hi: args[0][0], lo: args[0][1] }; + } + else if (args[0].hasOwnProperty('remainder') && args[0].hasOwnProperty('divideAndRemainder')) { + throw new TypeError('appears to be bigdeciaml'); + } + else { + throw new TypeError('cannot convert to int64 from ' + args) + } +}; + +capnp.prim.asInt64Val = capnp.prim.asUint64Val + +capnp.prim.bool = { + elementSize: capnp.layout.FieldSize.BIT, + setValue: function(dataView, offset, value) { + var byteOffset = offset / capnp.common.BITS_PER_BYTE; + var bitOffset = offset % capnp.common.BITS_PER_BYTE; + dataView.setUint8(byteOffset, + dataView.getUint8(byteOffset) & + ~(1 << bitOffset) | (value << bitOffset)); + }, + getValue: function(dataView, offset) { + var byteOffset = offset / capnp.common.BITS_PER_BYTE; + var bitOffset = offset % capnp.common.BITS_PER_BYTE; + return (dataView.getUint8(byteOffset) & (1 << bitOffset)) !== 0; + }, + + Reader: function(message, segment, offset, relativeOffset) { + var value = segment.getInt8(offset + (relativeOffset / 8) >> 0); + return !!((value >>> (relativeOffset & 7)) & 1); + } +}; + +capnp.prim.int8_t = { + elementSize: capnp.layout.FieldSize.BYTE, + setValue: function(dataView, offset, value) { + dataView.setInt8(offset / capnp.common.BITS_PER_BYTE, value); + }, + getValue: function(dataView, offset) { + return dataView.getInt8(offset / capnp.common.BITS_PER_BYTE); + }, + + Reader: function(message, segment, offset, relativeOffset, mask) { + return segment.getInt8(offset + relativeOffset) ^ mask; + } +}; + + +capnp.prim.int16_t = { + elementSize: capnp.layout.FieldSize.TWO_BYTES, + setValue: function(dataView, offset, value) { + dataView.setInt16(offset / capnp.common.BITS_PER_BYTE, value, true); + }, + getValue: function(dataView, offset) { + return dataView.getInt16(offset / capnp.common.BITS_PER_BYTE, true); + }, + + Reader: function(message, segment, offset, relativeOffset, mask) { + return segment.getInt16(offset + relativeOffset * 2, true) ^ mask; + } +}; + +capnp.prim.int32_t = { + elementSize: capnp.layout.FieldSize.FOUR_BYTES, + setValue: function(dataView, offset, value) { + dataView.setInt32(offset / capnp.common.BITS_PER_BYTE, value, true); + }, + getValue: function(dataView, offset) { + return dataView.getInt32(offset / capnp.common.BITS_PER_BYTE, true); + }, + + Reader: function(message, segment, offset, relativeOffset, mask) { + return segment.getInt32(offset + relativeOffset * 4, true) ^ mask; + } +}; + +capnp.prim.int64_t = { + elementSize: capnp.layout.FieldSize.EIGHT_BYTES, + setValue: function(dataView, offset, value) { + var byteOffset = offset / capnp.common.BITS_PER_BYTE; + var int64val = capnp.prim.asInt64Val([value]); + dataView.setInt32(byteOffset, int64val.lo, true); + dataView.setInt32(byteOffset + 4, int64val.hi, true); + }, + getValue: function(dataView, offset) { + var byteOffset = offset / capnp.common.BITS_PER_BYTE; + var lo = dataView.getInt32(byteOffset, true); + var hi = dataView.getInt32(byteOffset + 4, true); + return [hi, lo]; + }, + + Reader: function(message, segment, offset, relativeOffset) { + var lo = segment.getInt32(offset + relativeOffset * 8 + 0, true); + var hi = segment.getInt32(offset + relativeOffset * 8 + 4, true); + return capnp.prim.makeInt64(hi, lo); + } +}; + +capnp.prim.uint8_t = { + elementSize: capnp.layout.FieldSize.BYTE, + setValue: function(dataView, offset, value) { + dataView.setUint8(offset / capnp.common.BITS_PER_BYTE, value); + }, + getValue: function(dataView, offset) { + return dataView.getUint8(offset / capnp.common.BITS_PER_BYTE); + }, + + Reader: function(message, segment, offset, relativeOffset, mask) { + return (segment.getUint8(offset + relativeOffset) ^ mask) >>> 0; + } +}; + +capnp.prim.uint16_t = { + elementSize: capnp.layout.FieldSize.TWO_BYTES, + setValue: function(dataView, offset, value) { + dataView.setUint16(offset / capnp.common.BITS_PER_BYTE, value, true); + }, + getValue: function(dataView, offset) { + return dataView.getUint16(offset / capnp.common.BITS_PER_BYTE, true); + }, + + Reader: function(message, segment, offset, relativeOffset, mask) { + return (segment.getUint16(offset + relativeOffset * 2, + true) ^ mask) >>> 0; + } +}; + +capnp.prim.uint32_t = { + elementSize: capnp.layout.FieldSize.FOUR_BYTES, + setValue: function(dataView, offset, value) { + dataView.setUint32(offset / capnp.common.BITS_PER_BYTE, value, true); + }, + getValue: function(dataView, offset) { + return dataView.getUint32(offset / capnp.common.BITS_PER_BYTE, true); + }, + + Reader: function(message, segment, offset, relativeOffset, mask) { + return (segment.getUint32(offset + relativeOffset * 4, + true) ^ mask) >>> 0; + } +}; + +capnp.prim.uint64_t = { + elementSize: capnp.layout.FieldSize.EIGHT_BYTES, + setValue: function(dataView, offset, value) { + var byteOffset = offset / capnp.common.BITS_PER_BYTE; + var uint64val = capnp.prim.asUint64Val([value]); + dataView.setUint32(byteOffset, uint64val.lo, true); + dataView.setUint32(byteOffset + 4, uint64val.hi, true); + }, + getValue: function(dataView, offset) { + var byteOffset = offset / capnp.common.BITS_PER_BYTE; + var lo = dataView.getUint32(byteOffset, true); + var hi = dataView.getUint32(byteOffset + 4, true); + return [hi, lo]; + }, + + Reader: function(message, segment, offset, relativeOffset) { + var lo = segment.getUint32(offset + relativeOffset * 8 + 0, true); + var hi = segment.getUint32(offset + relativeOffset * 8 + 4, true); + return capnp.prim.makeInt64(hi, lo); + } +}; + +capnp.prim.float32_t = { + elementSize: capnp.layout.FieldSize.FOUR_BYTES, + setValue: function(dataView, offset, value) { + dataView.setFloat32(offset / capnp.common.BITS_PER_BYTE, value, true); + }, + getValue: function(dataView, offset) { + return dataView.getFloat32(offset / capnp.common.BITS_PER_BYTE, true); + }, + + Reader: function(message, segment, offset, relativeOffset) { + return segment.getFloat32(offset + relativeOffset * 4, true); + } +}; + +capnp.prim.float64_t = { + elementSize: capnp.layout.FieldSize.EIGHT_BYTES, + setValue: function(dataView, offset, value) { + dataView.setFloat64(offset / capnp.common.BITS_PER_BYTE, value, true); + }, + getValue: function(dataView, offset) { + return dataView.getFloat64(offset / capnp.common.BITS_PER_BYTE, true); + }, + + Reader: function(message, segment, offset, relativeOffset) { + return segment.getFloat64(offset + relativeOffset * 8, true); + } +}; + +capnp.prim.Void = { + elementSize: capnp.layout.FieldSize.VOID, + setValue: function(dataView, offset, value) { + }, + getValue: function(dataView, offset) { + return undefined; + }, + + Builder: function(message, segment, offset) { + }, + + Reader: function(message, segment, offset, relativeOffset) { + return undefined; + } +}; diff --git a/javascript/lib/capnp_runtime.js b/javascript/lib/capnp_runtime.js new file mode 100644 index 0000000..65dd46b --- /dev/null +++ b/javascript/lib/capnp_runtime.js @@ -0,0 +1,147 @@ +/** + * @license Copyright (c) 2013, Julian Scheid + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +goog.provide('capnp.runtime'); + +goog.require('kj.io.InputStream'); +goog.require('kj.io.BufferedInputStream'); + +goog.require('capnp.arena'); +goog.require('capnp.blob'); +goog.require('capnp.genhelper'); +goog.require('capnp.layout'); +goog.require('capnp.list'); +goog.require('capnp.message'); +goog.require('capnp.packed'); +goog.require('capnp.serialize'); + +(function (exports) { + + "use strict"; + + exports['OutputStream'] = kj.io.OutputStream; + exports['InputStream'] = kj.io.InputStream; + exports['BufferedInputStream'] = kj.io.BufferedInputStream; + exports['BufferedOutputStream'] = kj.io.BufferedOutputStream; + exports['BufferedOutputStreamWrapper'] = kj.io.BufferedOutputStreamWrapper; + + exports['bool'] = capnp.prim.bool; + exports['int8_t'] = capnp.prim.int8_t; + exports['int16_t'] = capnp.prim.int16_t; + exports['int32_t'] = capnp.prim.int32_t; + exports['int64_t'] = capnp.prim.int64_t; + exports['uint8_t'] = capnp.prim.uint8_t; + exports['uint16_t'] = capnp.prim.uint16_t; + exports['uint32_t'] = capnp.prim.uint32_t; + exports['uint64_t'] = capnp.prim.uint64_t; + exports['float32_t'] = capnp.prim.float32_t; + exports['float64_t'] = capnp.prim.float64_t; + exports['Void'] = capnp.prim.Void; + exports['asUint64Val'] = capnp.prim.asUint64Val; + exports['asInt64Val'] = capnp.prim.asUint64Val; + + exports['List'] = capnp.list.List; + exports['ListOfPrimitives'] = capnp.list.ListOfPrimitives; + exports['ListOfEnums'] = capnp.list.ListOfEnums; + exports['ListOfLists'] = capnp.list.ListOfLists; + exports['ListOfBlobs'] = capnp.list.ListOfBlobs; + exports['ListOfStructs'] = capnp.list.ListOfStructs; + + exports['textBlobSet'] = capnp.genhelper.textBlobSet; + exports['dataBlobSet'] = capnp.genhelper.dataBlobSet; + + exports['writeMessageToFd'] = capnp.serialize.writeMessageToFd; + + exports['MessageReader'] = capnp.message.MessageReader; + exports['SegmentArrayMessageReader'] = capnp.message.SegmentArrayMessageReader; + exports['FlatArrayMessageReader'] = capnp.message.FlatArrayMessageReader; + exports['MessageBuilder'] = capnp.message.MessageBuilder; + exports['AllocationStrategy'] = capnp.message.AllocationStrategy; + exports['readMessageUnchecked'] = capnp.message.readMessageUnchecked; + exports['SUGGESTED_FIRST_SEGMENT_WORDS'] = capnp.message.SUGGESTED_FIRST_SEGMENT_WORDS; + + exports['ToStringHelper'] = capnp.genhelper.ToStringHelper; + exports['listInit'] = capnp.genhelper.listInit; + exports['listSet'] = capnp.genhelper.listSet; + exports['structSet'] = capnp.genhelper.structSet; + exports['objectInit'] = capnp.genhelper.objectInit; + exports['objectSet'] = capnp.genhelper.objectSet; + exports['objectGetFromBuilder'] = capnp.genhelper.objectGetFromBuilder; + exports['objectGetFromReader'] = capnp.genhelper.objectGetFromReader; + exports['ConstText'] = capnp.genhelper.ConstText; + exports['ConstData'] = capnp.genhelper.ConstData; + exports['ConstStruct'] = capnp.genhelper.ConstStruct; + exports['ConstList'] = capnp.genhelper.ConstList; + exports['NullStructReader'] = capnp.genhelper.NullStructReader; + + exports['StructSize'] = capnp.layout.StructSize; + exports['StructReader'] = capnp.layout.StructReader; + exports['StructBuilder'] = capnp.layout.StructBuilder; + + exports['BuilderArena'] = capnp.arena.BuilderArena; + + exports['NodeJsBufferMessageReader'] = capnp.serialize.NodeJsBufferMessageReader; + exports['StreamFdMessageReader'] = capnp.serialize.StreamFdMessageReader; + exports['writeMessageSegments'] = capnp.serialize.writeMessageSegments; + exports['messageToFlatArray'] = capnp.serialize.messageToFlatArray; + exports['InputStreamMessageReader'] = capnp.serialize.InputStreamMessageReader; + + exports['PackedInputStream'] = capnp.packed.PackedInputStream; + exports['PackedOutputStream'] = capnp.packed.PackedOutputStream; + exports['PackedMessageReader'] = capnp.packed.PackedMessageReader; + exports['writePackedMessage'] = capnp.packed.writePackedMessage; + + + goog.exportSymbol('MallocMessageBuilder', capnp.message.MallocMessageBuilder, exports); + goog.exportSymbol('MallocMessageBuilder.prototype.initRoot', capnp.message.MallocMessageBuilder.prototype.initRoot, exports); + + goog.exportSymbol('StructBuilder', capnp.layout.StructBuilder, exports); + goog.exportSymbol('StructBuilder.prototype.setDataField_bool', capnp.layout.StructBuilder.prototype.setDataField_bool, exports); + goog.exportSymbol('StructBuilder.prototype.setDataField_int8', capnp.layout.StructBuilder.prototype.setDataField_int8, exports); + goog.exportSymbol('StructBuilder.prototype.setDataField_int16', capnp.layout.StructBuilder.prototype.setDataField_int16, exports); + goog.exportSymbol('StructBuilder.prototype.setDataField_int32', capnp.layout.StructBuilder.prototype.setDataField_int32, exports); + goog.exportSymbol('StructBuilder.prototype.setDataField_int64', capnp.layout.StructBuilder.prototype.setDataField_int64, exports); + goog.exportSymbol('StructBuilder.prototype.setDataField_uint8', capnp.layout.StructBuilder.prototype.setDataField_uint8, exports); + goog.exportSymbol('StructBuilder.prototype.setDataField_uint16', capnp.layout.StructBuilder.prototype.setDataField_uint16, exports); + goog.exportSymbol('StructBuilder.prototype.setDataField_uint32', capnp.layout.StructBuilder.prototype.setDataField_uint32, exports); + goog.exportSymbol('StructBuilder.prototype.setDataField_uint64', capnp.layout.StructBuilder.prototype.setDataField_uint64, exports); + goog.exportSymbol('StructBuilder.prototype.setDataField_float32', capnp.layout.StructBuilder.prototype.setDataField_float32, exports); + goog.exportSymbol('StructBuilder.prototype.setDataField_float64', capnp.layout.StructBuilder.prototype.setDataField_float64, exports); + + goog.exportSymbol('Data', capnp.blob.Data, exports); + goog.exportSymbol('Data.Reader', capnp.blob.Data.Reader, exports); + goog.exportSymbol('Data.Builder', capnp.blob.Data.Builder, exports); + + goog.exportSymbol('Text', capnp.blob.Text, exports); + goog.exportSymbol('Text.Reader', capnp.blob.Text.Reader, exports); + goog.exportSymbol('Text.Builder', capnp.blob.Text.Builder, exports); + + + exports['StringTextReader'] = capnp.blob.StringTextReader; + + //goog.exportProperty(capnp.message.MallocMessageBuilder, 'initRoot', capnp.message.MallocMessageBuilder.initRoot); + + +})(typeof exports === 'undefined' ? this['capnp_runtime'] = {} : exports); diff --git a/javascript/lib/capnp_serialize.js b/javascript/lib/capnp_serialize.js new file mode 100644 index 0000000..46128eb --- /dev/null +++ b/javascript/lib/capnp_serialize.js @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2013, Julian Scheid + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +goog.provide('capnp.serialize'); + +goog.require('capnp.message'); +goog.require('kj.io'); + +capnp.serialize.writeMessageSegments = function(output, arg) { + var segments; + if (goog.isArray(arg)) { + segments = arg; + } + else { + segments = arg.getSegmentsForOutput(); + } + + var headerNumInt32Values = (segments.length + 2) & ~1; + var headerBuffer = new ArrayBuffer(headerNumInt32Values * 4); + var headerView = new DataView(headerBuffer); + + // We write the segment count - 1 because this makes the first + // word zero for single-segment messages, improving + // compression. We don't bother doing this with segment sizes + // because one-word segments are rare anyway. + + headerView.setUint32(0, (segments.length - 1), true); + + for (var i = 0, len = segments.length; i < len; ++i) { + headerView.setUint32((i + 1) << 2, segments[i].byteLength >> 3, true); + } + + output.write(headerBuffer, 0, headerBuffer.byteLength); + + for (var i = 0, len = segments.length; i < len; ++i) { + output.write(segments[i].buffer, segments[i].byteOffset, segments[i].byteLength); + } +}; + + +capnp.serialize.messageToFlatArray = function(segments) { + + kj.debug.REQUIRE(segments.length > 0, 'Tried to serialize uninitialized message.'); + + var totalSize = (segments.length >>> 1) + 1; + + for (var i = 0, len = segments.length; i < len; ++i) { + kj.debug.REQUIRE(segments[i].byteLength % 8 === 0, 'Segment byte length not a multiple of 8'); + totalSize += segments[i].byteLength >> 3; + } + + var buffer = new ArrayBuffer(totalSize * capnp.common.BYTES_PER_WORD); + var result = new DataView(buffer); + + var table = new Uint32Array(buffer); // FIXME: endian-ness + + // We write the segment count - 1 because this makes the first word zero for single-segment + // messages, improving compression. We don't bother doing this with segment sizes because + // one-word segments are rare anyway. + table[0] = segments.length - 1; + + for (var i = 0, len = segments.length; i < len; ++i) { + table[i + 1] = segments[i].byteLength >> 3; + } + + if (segments.length % 2 === 0) { + // Set padding byte. + table[segments.length + 1] = 0; + } + + var dst = ((segments.length >>> 1) + 1) << 1; + + for (var i = 0, len = segments.length; i < len; ++i) { + var segment = segments[i]; + new Uint8Array(buffer).set(new Uint8Array(segment.buffer, segment.byteOffset, segment.byteLength), dst << 2); + //table.set(new Uint32Array(segment.buffer, segment.byteOffset, segment.byteLength >> 2), dst); // FIXME: endian-ness + dst += (segment.byteLength >> 2); + } + + kj.debug.DASSERT((dst << 2) === buffer.byteLength, 'Buffer overrun/underrun bug in code above.'); + + return buffer; +}; + +/** + * @constructor + * @extends capnp.message.MessageReader + */ +capnp.serialize.InputStreamMessageReader = function(inputStream, options, scratchSpace) { + + capnp.message.MessageReader.call(this, options); + + var firstWordBuffer = new ArrayBuffer(8); + var firstWordView = new DataView(firstWordBuffer); + inputStream.read(firstWordBuffer, 0, 8); + var segmentCount = firstWordView.getUint32(0, true) + 1; + + var segment0Size = segmentCount == 0 ? 0 : firstWordView.getUint32(4, true); + + var totalWords = segment0Size; + var ownedSpace = null; + + // Reject messages with too many segments for security reasons. + if (segmentCount >= 512) { + console.error('Message has too many segments.'); + segmentCount = 1; + segment0Size = 1; + } + + // Read sizes for all segments except the first. Include padding if necessary. + var moreSizes = new ArrayBuffer((segmentCount & ~1) * 4); + var moreSizesArray; + if (segmentCount > 1) { + moreSizesArray = new DataView(moreSizes); // FIXME: endian-ness + inputStream.read(moreSizes, 0, moreSizes.byteLength); + for (var i = 0; i < segmentCount - 1; i++) { + totalWords += moreSizesArray.getUint32(i * 4, true); + } + } + + // Don't accept a message which the receiver couldn't possibly traverse without hitting the + // traversal limit. Without this check, a malicious client could transmit a very large segment + // size to make the receiver allocate excessive space and possibly crash. + kj.debug.REQUIRE(totalWords <= this.options.traversalLimitInWords, + 'Message is too large. To increase the limit on the receiving end, see capnp::ReaderOptions.'); + + if (!scratchSpace || scratchSpace.byteLength < totalWords * 8) { + // TODO(perf): Consider allocating each segment as a separate chunk to reduce memory + // fragmentation. + ownedSpace = new ArrayBuffer(totalWords * 8); + scratchSpace = ownedSpace; + } + + this.inputStream = inputStream; + + this.moreSegments = []; + + this.arena = null; + this.options = { nestingLimit: 64 }; + + this.readPos = 0; + + this.scratchSpace = scratchSpace; + + if (segmentCount > 1) { + var offset = segment0Size; + + for (var i = 0; i < segmentCount - 1; i++) { + var segmentSize = moreSizesArray.getUint32(i * 4, true); + this.moreSegments.push(new DataView(scratchSpace, offset * 8, segmentSize * 8)); + offset += segmentSize; + } + } + + if (segmentCount == 1) { + this.inputStream.read(scratchSpace, 0, totalWords * 8); + } else if (segmentCount > 1) { + this.readPos = 0; + this.readPos += this.inputStream.read(scratchSpace, 0, segment0Size * 8, totalWords * 8); + } + + this.segment0 = new DataView(this.scratchSpace, 0, segment0Size * 8); +}; + +goog.inherits(capnp.serialize.InputStreamMessageReader, capnp.message.MessageReader); + +capnp.serialize.InputStreamMessageReader.prototype.getSegment = function(id) { + + if (id > this.moreSegments.length) { + return null; + } + + var segment = id == 0 ? this.segment0 : this.moreSegments[id - 1]; + + if (this.readPos != 0) { + + // May need to lazily read more data. + var segmentEnd = segment.byteOffset + segment.byteLength; + if (this.readPos < segmentEnd) { + // Note that lazy reads only happen when we have multiple segments, so moreSegments.back() is + // valid. + var lastSegment = this.moreSegments[this.moreSegments.length - 1]; + var allEnd = lastSegment.byteOffset + lastSegment.byteLength; + this.readPos += this.inputStream.read(this.scratchSpace, this.readPos, segmentEnd - this.readPos, allEnd - this.readPos); + } + } + + return segment; + +}; + +capnp.serialize.InputStreamMessageReader.prototype.toString = function() { + return 'InputStreamMessageReader{...}'; +}; + +// var fs = require('fs'); + +// /** +// * @constructor +// */ +// var FdInputStream = function(fd) { +// exports.InputStream.call(this); +// this.fd = fd; +// }; +// FdInputStream.prototype = Object.create(kj.io.InputStream.prototype); +// FdInputStream.prototype.constructor = FdInputStream; +// FdInputStream.prototype.tryRead = function(buffer, offset, minBytes, maxBytes) { +// var nodeBuffer = new Buffer(minBytes); +// var count = fs.readSync(this.fd, nodeBuffer, 0, minBytes); +// for (var i = 0; i < minBytes; ++i) { +// buffer[offset + i] = nodeBuffer[i]; +// } +// return count; +// }; + +// /** +// * @constructor +// */ +// capnp.serialize.StreamFdMessageReader = function(fd, options, scratchSpace) { +// capnp.serialize.InputStreamMessageReader.call(this, new FdInputStream(fd), options, scratchSpace); +// }; +// capnp.serialize.StreamFdMessageReader.prototype = Object.create(capnp.serialize.InputStreamMessageReader.prototype); +// capnp.serialize.StreamFdMessageReader.prototype.constructor = capnp.serialize.StreamFdMessageReader; + +// /** +// * @constructor +// */ +// capnp.serialize.NodeJsBufferMessageReader = function(buffer, options) { + +// if (options) { +// this.options = options; +// } +// else { +// this.options = defaultReaderOptions; +// } + +// var segmentCount = buffer.readUInt32LE(0) + 1; + +// // Reject messages with too many segments for security reasons. +// kj.debug.REQUIRE(segmentCount < 512, 'Message has too many segments.'); + +// var segmentSizes = []; +// var totalWords = 0; +// for (var i = 0; i < segmentCount; ++i) { +// segmentSizes[i] = buffer.readUInt32LE((i + 1) * 4, true); +// totalWords += segmentSizes[i]; +// } + +// // Don't accept a message which the receiver couldn't possibly traverse without hitting the +// // traversal limit. Without this check, a malicious client could transmit a very large segment +// // size to make the receiver allocate excessive space and possibly crash. +// if (totalWords > this.options.traversalLimitInWords) { +// console.error('Message is too large. To increase the limit on the receiving end, see capnp::ReaderOptions.'); +// } + +// var offset = 8 + ((segmentCount & ~1) * 4); +// var segments = []; +// for (var i = 0; i < segmentCount; i++) { + +// segments.push(new DataView(new Uint8Array(buffer).buffer, offset, segmentSizes[i] * 8)); + +// offset += segmentSizes[i]; +// } + +// this.getSegment = function(id) { + +// if (id <= segments.length) { +// return segments[id]; +// } +// else { +// return null; +// } +// }; + +// this.toString = function() { +// return 'NodeJsBufferMessageReader{...}'; +// }; + +// return this; +// }; +// capnp.serialize.NodeJsBufferMessageReader.prototype = new capnp.message.MessageReader(); + +// capnp.serialize.writeMessageToFd = function(fd, object) { + +// var FdOutputStream = function() {}; +// FdOutputStream.prototype = Object.create(kj.io.OutputStream.prototype); +// FdOutputStream.prototype.constructor = FdOutputStream; +// FdOutputStream.prototype.write = function(buffer, offset, byteLength) { +// var nodeBuffer = new Buffer(byteLength); +// for (var i = 0; i < byteLength; ++i) { +// nodeBuffer[i] = buffer[offset + i]; +// } +// fs.writeSync(fd, nodeBuffer, 0, byteLength); +// }; + +// capnp.serialize.writeMessageSegments(new FdOutputStream(), object); +// }; diff --git a/javascript/lib/capnp_serialize_packed.js b/javascript/lib/capnp_serialize_packed.js new file mode 100644 index 0000000..aa5ae1b --- /dev/null +++ b/javascript/lib/capnp_serialize_packed.js @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2013, Julian Scheid + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +goog.provide('capnp.packed'); + +goog.require('kj.debug'); +goog.require('kj.io'); +goog.require('kj.util'); +goog.require('goog.asserts'); +goog.require('capnp.common'); +goog.require('capnp.serialize'); + +/** + * @constructor + */ +capnp.packed.PackedOutputStream = function(inner) { + kj.io.OutputStream.call(this); + this.inner = inner; +}; +capnp.packed.PackedOutputStream.prototype = Object.create(kj.io.OutputStream.prototype); +capnp.packed.PackedOutputStream.prototype.constructor = capnp.packed.PackedOutputStream; +capnp.packed.PackedOutputStream.prototype.write = function(src, offset, size) { + + goog.asserts.assert(kj.util.isArrayBuffer(src), 'PackedOutputStream.write requires ArrayBuffer as first argument'); + goog.asserts.assert(kj.util.isRegularNumber(offset), 'PackedOutputStream.write requires numeric offset as second argument'); + goog.asserts.assert(kj.util.isRegularNumber(size), 'PackedOutputStream.write requires numeric size as third argument'); + goog.asserts.assert(offset + size <= src.byteLength, 'PackedOutputStream.write asked to access buffer beyond end; byteLength=' + src.byteLength + ', offset=' + offset + ', size=' + size); + + var buffer = this.inner.getWriteBuffer(); + var slowBuffer = new ArrayBuffer(20); + + var inArray = new Uint8Array(src, offset, size); + + var inPos = 0; + var outPos = 0; + + while (inPos < size) { + + if (buffer.byteLength - outPos < 10) { + // Oops, we're out of space. We need at least 10 bytes for the fast path, since we don't + // bounds-check on every byte. + + // Write what we have so far. + this.inner.write(buffer, 0, outPos); + + // Use a slow buffer into which we'll encode 10 to 20 bytes. This should get us past the + // output stream's buffer boundary. + outPos = 0; + } + + var tagPos = outPos++; + + var tag = 0; + for (var j = 0; j < 8; ++j) { + var bit = inArray[inPos] != 0; + buffer[outPos] = inArray[inPos]; + outPos += bit; + ++inPos; + tag |= bit << j; + } + + buffer[tagPos] = tag; + + if (tag === 0) { + // An all-zero word is followed by a count of consecutive zero words (not including the + // first one). + + // We can check a whole word at a time. + var inLong = new Uint32Array(src); + var inLongPos = inPos >>> 3; + + // The count must fit it 1 byte, so limit to 255 words. + var limit = size >>> 3; + if (limit - inLongPos > 255) { + limit = inLongPos + 255; + } + + while (inLongPos < limit && inLong[inLongPos * 2 + 0] === 0 && inLong[inLongPos * 2 + 1] === 0) { + ++inLongPos; + } + + // Write the count. + buffer[outPos++] = inLongPos - (inPos >>> 3); + + // Advance input. + inPos = inLongPos << 3; + + } else if (tag === 0xff) { + + // An all-nonzero word is followed by a count of consecutive uncompressed words, followed + // by the uncompressed words themselves. + + // Count the number of consecutive words in the input which have no more than a single + // zero-byte. We look for at least two zeros because that's the point where our compression + // scheme becomes a net win. + // TODO(perf): Maybe look for three zeros? Compressing a two-zero word is a loss if the + // following word has no zeros. + var runStartPos = inPos; + + var limitPos = size; + if (limitPos - inPos > 255 * capnp.common.BYTES_PER_WORD) { + limitPos = inPos + 255 * capnp.common.BYTES_PER_WORD; + } + + while (inPos < limitPos) { + // Check eight input bytes for zeros. + var c = inArray[inPos++] === 0; + c += inArray[inPos++] === 0; + c += inArray[inPos++] === 0; + c += inArray[inPos++] === 0; + c += inArray[inPos++] === 0; + c += inArray[inPos++] === 0; + c += inArray[inPos++] === 0; + c += inArray[inPos++] === 0; + + if (c >= 2) { + // Un-read the word with multiple zeros, since we'll want to compress that one. + inPos -= 8; + break; + } + } + + // Write the count. + var count = inPos - runStartPos; + + buffer[outPos++] = count / 8; + + if (count <= buffer.byteLength - outPos) { + // There's enough space to memcpy. + buffer.set(inArray.subarray(runStartPos, runStartPos + count), outPos); + outPos += count; + } else { + // Input overruns the output buffer. We'll give it to the output stream in one chunk + // and let it decide what to do. + this.inner.write(buffer, 0, outPos); + this.inner.write(runStartPos, 0, inPos - runStartPos); + buffer = this.inner.getWriteBuffer(); + outPos = 0; + } + } + } + + // Write whatever is left. + this.inner.write(buffer, 0, outPos); +}; + +/** + * @constructor + */ +capnp.packed.PackedInputStream = function(inner) { + kj.io.InputStream.call(this); + this.inner = inner; +}; +capnp.packed.PackedInputStream.prototype = Object.create(kj.io.InputStream.prototype); +capnp.packed.PackedInputStream.prototype.constructor = capnp.packed.PackedInputStream; +capnp.packed.PackedInputStream.prototype.tryRead = function(dst, offset, minBytes, maxBytes) { + + if (maxBytes === 0) { + return 0; + } + + kj.debug.DREQUIRE(minBytes % capnp.common.BYTES_PER_WORD === 0, 'PackedInputStream reads must be word-aligned: ' + minBytes); + kj.debug.DREQUIRE(maxBytes % capnp.common.BYTES_PER_WORD === 0, 'PackedInputStream reads must be word-aligned: ' + maxBytes); + + var outPos = offset; + var outEnd = offset + maxBytes; + var outMin = offset + minBytes; + var outArray = new Uint8Array(dst); + + var buffer = this.inner.getReadBuffer(); + if (buffer.byteLength === 0) { + return 0; + } + var inPos = 0; + var inArray = new Uint8Array(buffer); + + var self = this; + var REFRESH_BUFFER = function() { + self.inner.skip(buffer.byteLength); + buffer = self.inner.getReadBuffer(); + inArray = new Uint8Array(buffer); + kj.debug.REQUIRE(buffer.byteLength > 0, 'Premature end of packed input.'); + inPos = 0; + }; + + var BUFFER_END = function() { return buffer.byteLength; }; + var BUFFER_REMAINING = function() { return BUFFER_END() - inPos; }; + + for (;;) { + var tag; + + kj.debug.DASSERT(outPos % capnp.common.BYTES_PER_WORD === 0, + 'Output pointer should always be aligned here.'); + + if (BUFFER_REMAINING() < 10) { + + if (outPos >= outMin) { + // We read at least the minimum amount, so go ahead and return. + this.inner.skip(inPos); + return outPos - offset; + } + + if (BUFFER_REMAINING() === 0) { + REFRESH_BUFFER(); + continue; + } + + // We have at least 1, but not 10, bytes available. We need to read slowly, doing a bounds + // check on each byte. + + tag = inArray[inPos++]; + + for (var i = 0; i < 8; i++) { + if (tag & (1 << i)) { + if (BUFFER_REMAINING() === 0) { + REFRESH_BUFFER(); + } + outArray[outPos++] = inArray[inPos++]; + } else { + outArray[outPos++] = 0; + } + } + + if (BUFFER_REMAINING() === 0 && (tag === 0 || tag === 0xff)) { + REFRESH_BUFFER(); + } + } else { + + tag = inArray[inPos++]; + + for (var n = 0; n < 8; ++n) { + var isNonzero = (tag & (1 << n)) != 0; + outArray[outPos++] = inArray[inPos] & (isNonzero ? 0xff : 0x00); + inPos += isNonzero; + } + } + + if (tag === 0) { + kj.debug.DASSERT(BUFFER_REMAINING() > 0, 'Should always have non-empty buffer here.'); + + var runLength = inArray[inPos++] * capnp.common.BYTES_PER_WORD; + + kj.debug.REQUIRE(runLength <= outEnd - outPos, + 'Packed input did not end cleanly on a segment boundary.'); + for (var i = 0; i < runLength; ++i) { + outArray[outPos + i] = 0; + } + outPos += runLength; + + } else if (tag === 0xff) { + kj.debug.DASSERT(BUFFER_REMAINING() > 0, 'Should always have non-empty buffer here.'); + + var runLength = inArray[inPos++] * capnp.common.BYTES_PER_WORD; + + kj.debug.REQUIRE(runLength <= outEnd - outPos, + 'Packed input did not end cleanly on a segment boundary.'); + + var inRemaining = BUFFER_REMAINING(); + if (inRemaining >= runLength) { + // Fast path. + outArray.set(inArray.subarray(inPos, inPos + runLength), outPos); + outPos += runLength; + inPos += runLength; + } else { + // Copy over the first buffer, then do one big read for the rest. + outArray.set(inArray.subarray(inPos, inPos + inRemaining), outPos); + outPos += inRemaining; + runLength -= inRemaining; + + this.inner.skip(buffer.byteLength); + this.inner.read(dst, outPos, runLength); + outPos += runLength; + + if (outPos === outEnd) { + return maxBytes; + } else { + buffer = this.inner.getReadBuffer(); + inArray = new Uint8Array(buffer); + inPos = 0; + + // Skip the bounds check below since we just did the same check above. + continue; + } + } + } + + if (outPos === outEnd) { + this.inner.skip(inPos); + return maxBytes; + } + } + + kj.debug.FAIL_ASSERT("Can't get here."); + return 0; // GCC knows kj.debug.FAIL_ASSERT doesn't return, but Eclipse CDT still warns... +}; + +capnp.packed.PackedInputStream.prototype.skip = function(bytes) { + + if (bytes === 0) { + return; + } + + kj.debug.DREQUIRE(bytes % capnp.common.BYTES_PER_WORD === 0, 'PackedInputStream reads must be word-aligned: ' + bytes); + + var buffer = this.inner.getReadBuffer(); + var inPos = 0; + var inArray = new Uint8Array(buffer); + + var self = this; + var REFRESH_BUFFER = function() { + self.inner.skip(self.buffer.byteLength); + self.buffer = self.inner.getReadBuffer(); + inArray = new Uint8Array(buffer); + kj.debug.REQUIRE(self.buffer.byteLength > 0, 'Premature end of packed input.'); + inPos = 0; + }; + + var BUFFER_END = function() { return buffer.byteLength; }; + var BUFFER_REMAINING = function() { return BUFFER_END() - inPos; }; + + for (;;) { + var tag; + + if (BUFFER_REMAINING() < 10) { + if (BUFFER_REMAINING() === 0) { + REFRESH_BUFFER(); + continue; + } + + // We have at least 1, but not 10, bytes available. We need to read slowly, doing a bounds + // check on each byte. + + tag = inArray[inPos++]; + + for (var i = 0; i < 8; i++) { + if (tag & (1 << i)) { + if (BUFFER_REMAINING() === 0) { + REFRESH_BUFFER(); + } + inPos++; + } + } + bytes -= 8; + + if (BUFFER_REMAINING() === 0 && (tag === 0 || tag === 0xff)) { + REFRESH_BUFFER(); + } + } else { + tag = inArray[inPos++]; + + for (var n = 0; n < 8; ++n) { + inPos += (tag & (1 << n)) != 0; + } + bytes -= 8; + } + + + if (tag === 0) { + kj.debug.DASSERT(BUFFER_REMAINING() > 0, 'Should always have non-empty buffer here.'); + + var runLength = inArray[inPos++] * capnp.common.BYTES_PER_WORD; + + kj.debug.REQUIRE(runLength <= bytes, 'Packed input did not end cleanly on a segment boundary.'); + + bytes -= runLength; + + + } else if (tag === 0xff) { + kj.debug.DASSERT(BUFFER_REMAINING() > 0, 'Should always have non-empty buffer here.'); + + var runLength = inArray[inPos++] * capnp.common.BYTES_PER_WORD; + + kj.debug.REQUIRE(runLength <= bytes, 'Packed input did not end cleanly on a segment boundary.'); + + bytes -= runLength; + + var inRemaining = BUFFER_REMAINING(); + if (inRemaining > runLength) { + // Fast path. + inPos += runLength; + } else { + // Forward skip to the underlying stream. + runLength -= inRemaining; + this.inner.skip(buffer.byteLength + runLength); + + if (bytes === 0) { + return; + } else { + buffer = this.inner.getReadBuffer(); + inArray = new Uint8Array(buffer); + inPos = 0; + + // Skip the bounds check below since we just did the same check above. + continue; + } + } + } + + if (bytes === 0) { + this.inner.skip(inPos); + return; + } + } + + kj.debug.FAIL_ASSERT("Can't get here."); +}; + +capnp.packed.writePackedMessage = function(output, arg) { + + if (!(output instanceof kj.io.BufferedOutputStream)) { + output = new kj.io.BufferedOutputStreamWrapper(output); + } + + var segments; + if (kj.util.isArray(arg)) { + segments = arg; + } + else { + segments = arg.getSegmentsForOutput(); + } + + var packedOutput = new capnp.packed.PackedOutputStream(output); + capnp.serialize.writeMessageSegments(packedOutput, segments); + output.flush(); +}; + + +/** + * @constructor + */ +capnp.packed.PackedMessageReader = function(inputStream, options, scratchSpace) { + capnp.serialize.InputStreamMessageReader.call(this, new capnp.packed.PackedInputStream(inputStream), options, scratchSpace); +}; +capnp.packed.PackedMessageReader.prototype = Object.create(capnp.serialize.InputStreamMessageReader.prototype); +capnp.packed.PackedMessageReader.prototype.constructor = capnp.packed.PackedMessageReader; diff --git a/javascript/lib/kj_common.js b/javascript/lib/kj_common.js new file mode 100644 index 0000000..60cbaf4 --- /dev/null +++ b/javascript/lib/kj_common.js @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2013, Julian Scheid + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +goog.provide('kj.debug'); + +kj.debug.REQUIRE = function(condition, message) { + if (!condition) { + throw new Error(message); + } +}; + +kj.debug.DREQUIRE = kj.debug.REQUIRE; +kj.debug.DASSERT = kj.debug.REQUIRE; + +goog.provide('kj.util'); + +var toString = Object.prototype.toString; + +kj.util.isString = function(obj) { + return toString.call(obj) === '[object String]'; +} + +kj.util.isNumber = function(obj) { + return toString.call(obj) === '[object Number]'; +} + +kj.util.isArray = function(obj) { + return toString.call(obj) === '[object Array]'; +} + +kj.util.isFunction = function(obj) { + return toString.call(obj) === '[object Function]'; +} + +kj.util.isArrayBuffer = function(obj) { + return toString.call(obj) === '[object ArrayBuffer]'; +} + +kj.util.isDataView = function(obj) { + return toString.call(obj) === '[object DataView]'; +} + +kj.util.isRegularNumber = function(obj) { + return kj.util.isNumber(obj) && !isNaN(obj); +} + +var alreadyWarned = {}; + +kj.util.warnOnce = function (message) { + if (!alreadyWarned[message]) { + console.warn(message); + alreadyWarned[message] = true; + } +}; + +kj.util.decimalToHex = function(d, padding) { + var hex = Number(d).toString(16); + padding = (padding === undefined || padding === null) ? 2 : padding; + + while (hex.length < padding) { + hex = '0' + hex; + } + + return hex; +}; diff --git a/javascript/lib/kj_io.js b/javascript/lib/kj_io.js new file mode 100644 index 0000000..60fe93c --- /dev/null +++ b/javascript/lib/kj_io.js @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2013, Julian Scheid + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +goog.provide('kj'); +goog.provide('kj.io'); +goog.provide('kj.io.InputStream'); +goog.provide('kj.io.BufferedInputStream'); +goog.provide('kj.io.OutputStream'); +goog.provide('kj.io.FdOutputStream'); +goog.provide('kj.io.BufferedOutputStream'); +goog.provide('kj.io.BufferedOutputStreamWrapper'); + +goog.require('kj.debug'); + +/** + * @constructor + */ +kj.io.InputStream = function() {}; +kj.io.InputStream.prototype.read = function(buffer, offset, minBytes, maxBytes) { + // Reads at least minBytes and at most maxBytes, copying them into the given buffer. Returns + // the size read. Throws an exception on errors. Implemented in terms of tryRead(). + // + // maxBytes is the number of bytes the caller really wants, but minBytes is the minimum amount + // needed by the caller before it can start doing useful processing. If the stream returns less + // than maxBytes, the caller will usually call read() again later to get the rest. Returning + // less than maxBytes is useful when it makes sense for the caller to parallelize processing + // with I/O. + // + // Never blocks if minBytes is zero. If minBytes is zero and maxBytes is non-zero, this may + // attempt a non-blocking read or may just return zero. To force a read, use a non-zero minBytes. + // To detect EOF without throwing an exception, use tryRead(). + // + // Cap'n Proto never asks for more bytes than it knows are part of the message. Therefore, if + // the InputStream happens to know that the stream will never reach maxBytes -- even if it has + // reached minBytes -- it should throw an exception to avoid wasting time processing an incomplete + // message. If it can't even reach minBytes, it MUST throw an exception, as the caller is not + // expected to understand how to deal with partial reads. + + if (maxBytes === undefined) { + maxBytes = minBytes; + } + + var n = this.tryRead(buffer, offset, minBytes, maxBytes); + kj.debug.REQUIRE(n >= minBytes, 'Premature EOF'); + return n; +}; + + +/** + * @constructor + * @extends kj.io.InputStream + */ +kj.io.BufferedInputStream = function() { + kj.io.InputStream.call(this); +}; +kj.io.BufferedInputStream.prototype = Object.create(kj.io.InputStream.prototype); +kj.io.BufferedInputStream.prototype.constructor = kj.io.BufferedInputStream; +kj.io.BufferedInputStream.prototype.getReadBuffer = function() { + var result = this.tryGetReadBuffer(); + kj.debug.REQUIRE(result.byteLength > 0, 'Premature EOF'); + return result; +}; + +/** + * @constructor + */ +kj.io.OutputStream = function() { +}; + + +/** + * @constructor + */ +kj.io.FdOutputStream = function(fd) { + +}; + +/** + * @constructor + * @extends kj.io.OutputStream + */ +kj.io.BufferedOutputStream = function() { + kj.io.OutputStream.call(this); +}; +kj.io.BufferedOutputStream.prototype = Object.create(kj.io.OutputStream.prototype); +kj.io.BufferedOutputStream.prototype.constructor = kj.io.BufferedOutputStream; + + +// Implements BufferedOutputStream in terms of an OutputStream. Note that writes to the +// underlying stream may be delayed until flush() is called or the wrapper is destroyed. + +// Creates a buffered stream wrapping the given non-buffered stream. +// +// If the second parameter is non-null, the stream uses the given buffer instead of allocating +// its own. This may improve performance if the buffer can be reused. + +/** + * @constructor + * @extends kj.io.BufferedOutputStream + */ +kj.io.BufferedOutputStreamWrapper = function(inner, buffer) { + this.inner = inner; + if (buffer) { + goog.assert(kj.util.isArrayBuffer(buffer)); + this.buffer = buffer; + } + else { + this.buffer = new ArrayBuffer(8192); + } + this.bufferArray = new Uint8Array(this.buffer); + this.bufferPos = 0; + kj.io.BufferedOutputStream.call(this); +}; +kj.io.BufferedOutputStreamWrapper.prototype = Object.create(kj.io.BufferedOutputStream.prototype); +kj.io.BufferedOutputStreamWrapper.prototype.constructor = kj.io.BufferedOutputStreamWrapper; + +// Force the wrapper to write any remaining bytes in its buffer to the inner stream. Note that +// this only flushes this object's buffer; this object has no idea how to flush any other buffers +// that may be present in the underlying stream. +kj.io.BufferedOutputStreamWrapper.prototype.flush = function() { + throw new Error('NYI'); +}; + +// implements BufferedOutputStream --------------------------------- +kj.io.BufferedOutputStreamWrapper.prototype.getWriteBuffer = function() { + return new Uint8Array(this.buffer, this.bufferPos); +}; + +kj.io.BufferedOutputStreamWrapper.prototype.write = function(src, offset, size) { + if (src === this.buffer && offset === this.bufferPos) { + // Oh goody, the caller wrote directly into our buffer. + this.bufferPos += size; + } else { + var available = this.buffer.byteLength - this.bufferPos; + + if (size <= available) { + if (toString.call(src) == '[object ArrayBuffer]') { + this.bufferArray.set(new Uint8Array(src, offset, size), this.bufferPos); + } + else { + this.bufferArray.set(src.subarray(offset, offset + size), this.bufferPos); + } + this.bufferPos += size; + } else if (size <= this.buffer.byteLength) { + // Too much for this buffer, but not a full buffer's worth, so we'll go ahead and copy. + this.bufferArray.set(new Uint8Array(src, offset, available), this.bufferPos); + this.inner.write(this.buffer, 0, this.buffer.byteLength); + size -= available; + this.bufferArray.set(new Uint8Array(src, available, size)); + this.bufferPos = size; + } else { + // Writing so much data that we might as well write directly to avoid a copy. + this.inner.write(this.buffer, 0, this.bufferPos); + this.bufferPos = 0; + this.inner.write(src, 0, size); + } + } +}; + +kj.io.BufferedOutputStreamWrapper.prototype.flush = function() { + if (this.bufferPos > 0) { + this.inner.write(this.buffer, 0, this.bufferPos); + this.bufferPos = 0; + } +}; diff --git a/javascript/run-tests.sh b/javascript/run-tests.sh new file mode 100755 index 0000000..be707cf --- /dev/null +++ b/javascript/run-tests.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +set -exuo pipefail + +export NODE_PATH=${NODE_PATH}:${PWD}/src/capnp:${PWD}/src:${srcdir}/../javascript/lib/ + +mocha --reporter tap ${srcdir}/../javascript/tests/ diff --git a/javascript/tests/all_tests.html b/javascript/tests/all_tests.html new file mode 100644 index 0000000..64a0464 --- /dev/null +++ b/javascript/tests/all_tests.html @@ -0,0 +1,107 @@ + + + + Closure - All JsUnit Tests + + + + + + + + + +

Closure - All JsUnit Tests

+ +
+ + + + + + diff --git a/javascript/tests/encoding-test.js b/javascript/tests/encoding-test.js new file mode 100644 index 0000000..c32cad7 --- /dev/null +++ b/javascript/tests/encoding-test.js @@ -0,0 +1,1608 @@ +/* + * Copyright (c) 2013, Julian Scheid + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +goog.provide('capnp.tests.encoding'); + +goog.require('capnp.serialize'); +goog.require('capnp.message'); + +goog.require('capnp.test.util'); + +goog.require('capnproto_test.capnp.test'); +goog.require('capnproto_test.capnp.test_import'); +goog.require('capnproto_test.capnp.test_import2'); + + +function checkListDataPtr(reader, expectedData, expectedPointers) { + assertEquals(expectedData.length, reader.size()); + for (var i = 0; i < expectedData.length; i++) { + assertArrayEquals(expectedData[i], reader.get(i).getOld1()); + assertEquals(expectedPointers[i], reader.get(i).getOld2().toString()); + } +} + +function checkUpgradedList(root, expectedData, expectedPointers) { + + { + var builder = root.getObjectField(capnp.list.List(test.TestNewVersion)); + + assertEquals(expectedData.length, builder.size()); + for (var i = 0; i < expectedData.length; i++) { + assertArrayEquals(expectedData[i], builder.get(i).getOld1()); + assertEquals(expectedPointers[i], builder.get(i).getOld2().toString()); + + // Other fields shouldn't be set. + assertArrayEquals([0, 0], builder.get(i).asReader().getOld3().getOld1()); + assertEquals("", builder.get(i).asReader().getOld3().getOld2().toString()); + assertArrayEquals([0, 987], builder.get(i).getNew1()); + assertEquals("baz", builder.get(i).getNew2().toString()); + + // Write some new data. + builder.get(i).setOld1([0, i * 123]); + builder.get(i).setOld2(("qux" + i + '\0')); + builder.get(i).setNew1([0, i * 456]); + builder.get(i).setNew2(("corge" + i + '\0')); + } + } + + // Read the newly-written data as TestOldVersion to ensure it was updated. + { + var builder = root.getObjectField(capnp.list.List(test.TestOldVersion)); + + assertEquals(expectedData.length, builder.size()); + for (var i = 0; i < expectedData.length; i++) { + assertArrayEquals([0, i * 123], builder.get(i).getOld1()); + assertEquals("qux" + i + "\0", builder.get(i).getOld2().toString()); // FIXME + } + } + + // Also read back as TestNewVersion again. + { + var builder = root.getObjectField(capnp.list.List(test.TestNewVersion)); + + assertEquals(expectedData.length, builder.size()); + for (var i = 0; i < expectedData.length; i++) { + assertArrayEquals([0, i * 123], builder.get(i).getOld1()); + assertEquals("qux" + i + '\0', builder.get(i).getOld2().toString()); // FIXME + assertArrayEquals([0, i * 456], builder.get(i).getNew1()); + assertEquals("corge" + i + '\0', builder.get(i).getNew2().toString()); // FIXME + } + } +} + +window['test_AllTypes'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + + var test = capnproto_test.capnp.test; + + capnp.test.util.initTestMessage(builder.initRoot(test.TestAllTypes)); + capnp.test.util.checkTestMessage(builder.getRoot(test.TestAllTypes)); + capnp.test.util.checkTestMessage(builder.getRoot(test.TestAllTypes).asReader()); + + var reader = new capnp.message.SegmentArrayMessageReader(builder.getSegmentsForOutput()); + + capnp.test.util.checkTestMessage(reader.getRoot(test.TestAllTypes)); + + assertEquals(1, builder.getSegmentsForOutput().length); + + capnp.test.util.checkTestMessage(capnp.message.readMessageUnchecked(test.TestAllTypes, builder.getSegmentsForOutput()[0])); + + assertEquals((builder.getSegmentsForOutput()[0].byteLength >> 3) - 1, // -1 for root pointer + reader.getRoot(test.TestAllTypes).totalSizeInWords()); +}; + +window['test_AllTypesMultiSegment'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(0, capnp.message.AllocationStrategy.FIXED_SIZE); + + capnp.test.util.initTestMessage(builder.initRoot(test.TestAllTypes)); + capnp.test.util.checkTestMessage(builder.getRoot(test.TestAllTypes)); + capnp.test.util.checkTestMessage(builder.getRoot(test.TestAllTypes).asReader()); + + var reader = new capnp.message.SegmentArrayMessageReader(builder.getSegmentsForOutput()); + + capnp.test.util.checkTestMessage(reader.getRoot(test.TestAllTypes)); +}; + +window['test_Defaults'] = function() { + var nullRoot = new ArrayBuffer(8); + var reader = new capnp.message.SegmentArrayMessageReader([ new DataView(nullRoot) ]); + + capnp.test.util.checkTestMessage(reader.getRoot(test.TestDefaults)); + capnp.test.util.checkTestMessage(capnp.message.readMessageUnchecked(test.TestDefaults, new DataView(nullRoot))); + + capnp.test.util.checkTestMessage(new test.TestDefaults.Reader()); +}; + +window['test_DefaultInitialization'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + + capnp.test.util.checkTestMessage(builder.getRoot(test.TestDefaults)); // first pass initializes to defaults + capnp.test.util.checkTestMessage(builder.getRoot(test.TestDefaults).asReader()); + + capnp.test.util.checkTestMessage(builder.getRoot(test.TestDefaults)); // second pass just reads the initialized structure + capnp.test.util.checkTestMessage(builder.getRoot(test.TestDefaults).asReader()); + + var reader = new capnp.message.SegmentArrayMessageReader(builder.getSegmentsForOutput()); + + capnp.test.util.checkTestMessage(reader.getRoot(test.TestDefaults)); +}; + +window['test_DefaultInitializationMultiSegment'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(0, capnp.message.AllocationStrategy.FIXED_SIZE); + + // first pass initializes to defaults + var root = builder.getRoot(test.TestDefaults); + + capnp.test.util.checkTestMessage(root); + capnp.test.util.checkTestMessage(builder.getRoot(test.TestDefaults).asReader()); + + // second pass just reads the initialized structure + capnp.test.util.checkTestMessage(builder.getRoot(test.TestDefaults)); + capnp.test.util.checkTestMessage(builder.getRoot(test.TestDefaults).asReader()); + + var reader = new capnp.message.SegmentArrayMessageReader(builder.getSegmentsForOutput()); + + capnp.test.util.checkTestMessage(reader.getRoot(test.TestDefaults)); +}; + +window['test_DefaultsFromEmptyMessage'] = function() { + + var emptyMessage = new ArrayBuffer(8); + + var reader = new capnp.message.SegmentArrayMessageReader([ new DataView(emptyMessage) ]); + + capnp.test.util.checkTestMessage(reader.getRoot(test.TestDefaults)); + capnp.test.util.checkTestMessage(capnp.message.readMessageUnchecked(test.TestDefaults, new DataView(emptyMessage))); +}; + +window['test_GenericObjects'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.getRoot(test.TestObject); + + capnp.test.util.initTestMessage(root.initObjectField(test.TestAllTypes)); + capnp.test.util.checkTestMessage(root.getObjectField(test.TestAllTypes)); + capnp.test.util.checkTestMessage(root.asReader().getObjectField(test.TestAllTypes)); + + root.setObjectField(capnp.blob.Text, "foo"); + assertEquals("foo", root.getObjectField(capnp.blob.Text).toString()); + assertEquals("foo", root.asReader().getObjectField(capnp.blob.Text).toString()); + + root.setObjectField(capnp.blob.Data, capnp.test.util.data("foo")); + assertTrue(capnp.test.util.data("foo").equals(root.getObjectField(capnp.blob.Data))); + assertTrue(capnp.test.util.data("foo").equals(root.asReader().getObjectField(capnp.blob.Data))); + + { + root.setObjectField(capnp.list.List(capnp.prim.uint32_t), [123, 456, 789]); + + { + var list = root.getObjectField(capnp.list.List(capnp.prim.uint32_t)); + assertEquals(3, list.size()); + assertEquals(123, list.get(0)); + assertEquals(456, list.get(1)); + assertEquals(789, list.get(2)); + } + + { + var list = root.asReader().getObjectField(capnp.list.List(capnp.prim.uint32_t)); + assertEquals(3, list.size()); + assertEquals(123, list.get(0)); + assertEquals(456, list.get(1)); + assertEquals(789, list.get(2)); + } + } + + { + root.setObjectField(capnp.list.List(capnp.blob.Text), ["foo", "bar"]); + + { + var list = root.getObjectField(capnp.list.List(capnp.blob.Text)); + assertEquals(2, list.size()); + assertEquals("foo", list.get(0).toString()); + assertEquals("bar", list.get(1).toString()); + } + + { + var list = root.asReader().getObjectField(capnp.list.List(capnp.blob.Text)); + assertEquals(2, list.size()); + assertEquals("foo", list.get(0).toString()); + assertEquals("bar", list.get(1).toString()); + } + } + + { + { + var list = root.initObjectField(capnp.list.List(test.TestAllTypes), 2); + assertEquals(2, list.size()); + capnp.test.util.initTestMessage(list.get(0)); + } + + { + var list = root.getObjectField(capnp.list.List(test.TestAllTypes)); + assertEquals(2, list.size()); + capnp.test.util.checkTestMessage(list.get(0)); + capnp.test.util.checkTestMessageAllZero(list.get(1)); + } + + { + var list = root.asReader().getObjectField(capnp.list.List(test.TestAllTypes)); + assertEquals(2, list.size()); + capnp.test.util.checkTestMessage(list.get(0)); + capnp.test.util.checkTestMessageAllZero(list.get(1)); + } + } +}; + +window['test_UnionLayout'] = function() { + + var INIT_UNION = function(initializer) { + // Use the given setter to initialize the given union field and then return a struct indicating + // the location of the data that was written as well as the values of the four union + // discriminants. + + var builder = new capnp.message.MallocMessageBuilder(); + initializer(builder.getRoot(test.TestUnion)); + var segment = builder.getSegmentsForOutput()[0]; + + assertTrue((segment.byteLength >> 3) > 2); + + // Find the offset of the first set bit after the union discriminants. + var offset = 0; + var found = false; + var uint8Array = new Uint8Array(segment.buffer, segment.byteOffset, segment.byteOffset + segment.byteLength); + for (var p = 16; p < uint8Array.byteLength; p++) { + var dp = uint8Array[p]; + if (dp != 0) { + var bits = dp; + while ((bits & 1) === 0) { + ++offset; + bits >>>= 1; + } + found = true; + break; + } + offset += 8; + } + if (!found) { + offset = -1; + } + + return [ [ uint8Array[8+0], uint8Array[8+2], uint8Array[8+4], uint8Array[8+6] ], offset ]; + }; + + assertArrayEquals([ [ 0,0,0,0 ], -1], INIT_UNION(function(b) { b.getUnion0().setU0f0s0(undefined); })); + assertArrayEquals([ [ 1,0,0,0 ], 0], INIT_UNION(function(b) { b.getUnion0().setU0f0s1(1); })); + assertArrayEquals([ [ 2,0,0,0 ], 0], INIT_UNION(function(b) { b.getUnion0().setU0f0s8(1); })); + assertArrayEquals([ [ 3,0,0,0 ], 0], INIT_UNION(function(b) { b.getUnion0().setU0f0s16(1); })); + assertArrayEquals([ [ 4,0,0,0 ], 0], INIT_UNION(function(b) { b.getUnion0().setU0f0s32(1); })); + assertArrayEquals([ [ 5,0,0,0 ], 0], INIT_UNION(function(b) { b.getUnion0().setU0f0s64(1); })); + assertArrayEquals([ [ 6,0,0,0 ], 448], INIT_UNION(function(b) { b.getUnion0().setU0f0sp("1"); })); + + assertArrayEquals([ [ 7,0,0,0 ], -1], INIT_UNION(function(b) { b.getUnion0().setU0f1s0(undefined); })); + assertArrayEquals([ [ 8,0,0,0 ], 0], INIT_UNION(function(b) { b.getUnion0().setU0f1s1(1); })); + assertArrayEquals([ [ 9,0,0,0 ], 0], INIT_UNION(function(b) { b.getUnion0().setU0f1s8(1); })); + assertArrayEquals([ [10,0,0,0 ], 0], INIT_UNION(function(b) { b.getUnion0().setU0f1s16(1); })); + assertArrayEquals([ [11,0,0,0 ], 0], INIT_UNION(function(b) { b.getUnion0().setU0f1s32(1); })); + assertArrayEquals([ [12,0,0,0 ], 0], INIT_UNION(function(b) { b.getUnion0().setU0f1s64(1); })); + assertArrayEquals([ [13,0,0,0 ], 448], INIT_UNION(function(b) { b.getUnion0().setU0f1sp("1"); })); + + assertArrayEquals([ [0, 0,0,0 ], -1], INIT_UNION(function(b) { b.getUnion1().setU1f0s0(undefined); })); + assertArrayEquals([ [0, 1,0,0 ], 65], INIT_UNION(function(b) { b.getUnion1().setU1f0s1(1); })); + assertArrayEquals([ [0, 2,0,0 ], 65], INIT_UNION(function(b) { b.getUnion1().setU1f1s1(1); })); + assertArrayEquals([ [0, 3,0,0 ], 72], INIT_UNION(function(b) { b.getUnion1().setU1f0s8(1); })); + assertArrayEquals([ [0, 4,0,0 ], 72], INIT_UNION(function(b) { b.getUnion1().setU1f1s8(1); })); + assertArrayEquals([ [0, 5,0,0 ], 80], INIT_UNION(function(b) { b.getUnion1().setU1f0s16(1); })); + assertArrayEquals([ [0, 6,0,0 ], 80], INIT_UNION(function(b) { b.getUnion1().setU1f1s16(1); })); + assertArrayEquals([ [0, 7,0,0 ], 96], INIT_UNION(function(b) { b.getUnion1().setU1f0s32(1); })); + assertArrayEquals([ [0, 8,0,0 ], 96], INIT_UNION(function(b) { b.getUnion1().setU1f1s32(1); })); + assertArrayEquals([ [0, 9,0,0 ], 128], INIT_UNION(function(b) { b.getUnion1().setU1f0s64(1); })); + assertArrayEquals([ [0,10,0,0 ], 128], INIT_UNION(function(b) { b.getUnion1().setU1f1s64(1); })); + assertArrayEquals([ [0,11,0,0 ], 512], INIT_UNION(function(b) { b.getUnion1().setU1f0sp("1"); })); + assertArrayEquals([ [0,12,0,0 ], 512], INIT_UNION(function(b) { b.getUnion1().setU1f1sp("1"); })); + + assertArrayEquals([ [0,13,0,0 ], -1], INIT_UNION(function(b) { b.getUnion1().setU1f2s0(undefined); })); + assertArrayEquals([ [0,14,0,0 ], 65], INIT_UNION(function(b) { b.getUnion1().setU1f2s1(1); })); + assertArrayEquals([ [0,15,0,0 ], 72], INIT_UNION(function(b) { b.getUnion1().setU1f2s8(1); })); + assertArrayEquals([ [0,16,0,0 ], 80], INIT_UNION(function(b) { b.getUnion1().setU1f2s16(1); })); + assertArrayEquals([ [0,17,0,0 ], 96], INIT_UNION(function(b) { b.getUnion1().setU1f2s32(1); })); + assertArrayEquals([ [0,18,0,0 ], 128], INIT_UNION(function(b) { b.getUnion1().setU1f2s64(1); })); + assertArrayEquals([ [0,19,0,0 ], 512], INIT_UNION(function(b) { b.getUnion1().setU1f2sp("1"); })); + + assertArrayEquals([ [0,0,0,0 ], 192], INIT_UNION(function(b) { b.getUnion2().setU2f0s1(1); })); + assertArrayEquals([ [0,0,0,0 ], 193], INIT_UNION(function(b) { b.getUnion3().setU3f0s1(1); })); + assertArrayEquals([ [0,0,1,0 ], 200], INIT_UNION(function(b) { b.getUnion2().setU2f0s8(1); })); + assertArrayEquals([ [0,0,0,1 ], 208], INIT_UNION(function(b) { b.getUnion3().setU3f0s8(1); })); + assertArrayEquals([ [0,0,2,0 ], 224], INIT_UNION(function(b) { b.getUnion2().setU2f0s16(1); })); + assertArrayEquals([ [0,0,0,2 ], 240], INIT_UNION(function(b) { b.getUnion3().setU3f0s16(1); })); + assertArrayEquals([ [0,0,3,0 ], 256], INIT_UNION(function(b) { b.getUnion2().setU2f0s32(1); })); + assertArrayEquals([ [0,0,0,3 ], 288], INIT_UNION(function(b) { b.getUnion3().setU3f0s32(1); })); + assertArrayEquals([ [0,0,4,0 ], 320], INIT_UNION(function(b) { b.getUnion2().setU2f0s64(1); })); + assertArrayEquals([ [0,0,0,4 ], 384], INIT_UNION(function(b) { b.getUnion3().setU3f0s64(1); })); + +}; + +window['test_UnnamedUnion'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.initRoot(test.TestUnnamedUnion); + assertEquals(test.TestUnnamedUnion.FOO, root.which()); + + root.setBar(321); + assertEquals(test.TestUnnamedUnion.BAR, root.which()); + assertEquals(test.TestUnnamedUnion.BAR, root.asReader().which()); + assertFalse(root.hasFoo()); + assertTrue(root.hasBar()); + assertFalse(root.asReader().hasFoo()); + assertTrue(root.asReader().hasBar()); + assertEquals(321, root.getBar()); + assertEquals(321, root.asReader().getBar()); + assertThrows(root.getFoo); + assertThrows(root.asReader().getFoo); + + root.setFoo(123); + assertEquals(test.TestUnnamedUnion.FOO, root.which()); + assertEquals(test.TestUnnamedUnion.FOO, root.asReader().which()); + assertTrue(root.hasFoo()); + assertFalse(root.hasBar()); + assertTrue(root.asReader().hasFoo()); + assertFalse(root.asReader().hasBar()); + assertEquals(123, root.getFoo()); + assertEquals(123, root.asReader().getFoo()); + assertThrows(root.getBar); + assertThrows(root.asReader().getBar); + + /*** FIXME + + StructSchema schema = Schema::from(); + + // The discriminant is allocated just before allocating "bar". + assertEquals(2u, schema.getProto().getStruct().getDiscriminantOffset()); + assertEquals(0u, schema.getFieldByName("foo").getProto().getSlot().getOffset()); + assertEquals(2u, schema.getFieldByName("bar").getProto().getSlot().getOffset()); + */ +}; + +window['test_Groups'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.initRoot(test.TestGroups); + + { + var foo = root.getGroups().initFoo(); + foo.setCorge(12345678); + foo.setGrault([28744, 2249056121]); + foo.setGarply("foobar"); + + assertEquals(12345678, foo.getCorge()); + assertArrayEquals([ 28744, -2045911175 ], foo.getGrault()); + assertEquals("foobar", foo.getGarply().toString()); + } + + { + var bar = root.getGroups().initBar(); + bar.setCorge(23456789); + bar.setGrault("barbaz"); + bar.setGarply([54614, 2546219712]); //234567890123456); + + assertEquals(23456789, bar.getCorge()); + assertEquals("barbaz", bar.getGrault().toString()); + assertArrayEquals([ 54614, -1748747584 ], bar.getGarply()); + } + + { + var baz = root.getGroups().initBaz(); + baz.setCorge(34567890); + baz.setGrault("bazqux"); + baz.setGarply("quxquux"); + + assertEquals(34567890, baz.getCorge()); + assertEquals("bazqux", baz.getGrault().toString()); + assertEquals("quxquux", baz.getGarply().toString()); + } +}; + +window['test_Unions'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.getRoot(test.TestUnion); + + assertEquals(test.TestUnion.Union0.U0F0S0, root.getUnion0().which()); + assertEquals(undefined, root.getUnion0().getU0f0s0()); + assertThrows(root.getUnion0().getU0f0s1); + + root.getUnion0().setU0f0s1(true); + assertEquals(test.TestUnion.Union0.U0F0S1, root.getUnion0().which()); + assertTrue(root.getUnion0().getU0f0s1()); + assertThrows(root.getUnion0().getU0f0s0); + + root.getUnion0().setU0f0s8(123); + assertEquals(test.TestUnion.Union0.U0F0S8, root.getUnion0().which()); + assertEquals(123, root.getUnion0().getU0f0s8()); + assertThrows(root.getUnion0().getU0f0s1); +}; + +window['test_InterleavedGroups'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.initRoot(test.TestInterleavedGroups); + + assertFalse(root.hasGroup1()); + assertFalse(root.hasGroup2()); + assertFalse(root.asReader().hasGroup1()); + assertFalse(root.asReader().hasGroup2()); + + root.getGroup1().setBar(1); + + assertTrue(root.hasGroup1()); + assertFalse(root.hasGroup2()); + assertTrue(root.asReader().hasGroup1()); + assertFalse(root.asReader().hasGroup2()); + + // Merely setting the union to a non-default field should also make "has" light up. + root.getGroup2().initCorge(); + + assertTrue(root.hasGroup1()); + assertTrue(root.hasGroup2()); + assertTrue(root.asReader().hasGroup1()); + assertTrue(root.asReader().hasGroup2()); + + // Init both groups to different values. + { + var group = root.getGroup1(); + group.setFoo(12345678); + group.setBar([28744, 2249056121]); // 123456789012345 + + var corge = group.initCorge(); + corge.setGrault([229956, 821579789]); // 987654321098765 + corge.setGarply(12345); + corge.setPlugh("plugh"); + + corge.setXyzzy("xyzzy"); + group.setWaldo("waldo"); + } + + { + var group = root.getGroup2(); + group.setFoo(23456789); + group.setBar([54614, 2546219712]); // 234567890123456 + + var corge = group.initCorge(); + + corge.setGrault([204086, 515416198]); // 876543210987654 + + corge.setGarply(23456); + corge.setPlugh("hgulp"); + corge.setXyzzy("yzzyx"); + group.setWaldo("odlaw"); + } + + assertTrue(root.hasGroup1()); + assertTrue(root.hasGroup2()); + assertTrue(root.asReader().hasGroup1()); + assertTrue(root.asReader().hasGroup2()); + + // Check group1 is still set correctly. + { + var group = root.asReader().getGroup1(); + assertEquals(12345678, group.getFoo()); + assertArrayEquals([ 28744, 2249056121 ], group.getBar()); + var corge = group.getCorge(); + assertArrayEquals([ 229956, 821579789 ], corge.getGrault()); + assertEquals(12345, corge.getGarply()); + assertEquals("plugh", corge.getPlugh().toString()); + assertEquals("xyzzy", corge.getXyzzy().toString()); + assertEquals("waldo", group.getWaldo().toString()); + } + + // Zero out group 1 and see if it is zero'd. + { + var group = root.initGroup1().asReader(); + assertEquals(0, group.getFoo()); + assertArrayEquals([0, 0], group.getBar()); + assertEquals(test.TestInterleavedGroups.Group1.QUX, group.which()); + assertEquals(0, group.getQux()); + assertFalse(group.hasWaldo()); + } + + assertFalse(root.hasGroup1()); + assertTrue(root.hasGroup2()); + assertFalse(root.asReader().hasGroup1()); + assertTrue(root.asReader().hasGroup2()); + + // Group 2 should not have been touched. + { + var group = root.asReader().getGroup2(); + assertEquals(23456789, group.getFoo()); + assertArrayEquals([ 54614, 2546219712 ], group.getBar()); + var corge = group.getCorge(); + assertArrayEquals([ 204086, 515416198 ], corge.getGrault()); + assertEquals(23456, corge.getGarply()); + assertEquals("hgulp", corge.getPlugh().toString()); + assertEquals("yzzyx", corge.getXyzzy().toString()); + assertEquals("odlaw", group.getWaldo().toString()); + } +}; + +window['test_UnionDefault'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + var reader = builder.getRoot(test.TestUnionDefaults).asReader(); + + { + var field = reader.getS16s8s64s8Set(); + assertEquals(test.TestUnion.Union0.U0F0S16, field.getUnion0().which()); + assertEquals(test.TestUnion.Union1.U1F0S8 , field.getUnion1().which()); + assertEquals(test.TestUnion.Union2.U2F0S64, field.getUnion2().which()); + assertEquals(test.TestUnion.Union3.U3F0S8 , field.getUnion3().which()); + assertEquals(321, field.getUnion0().getU0f0s16()); + assertEquals(123, field.getUnion1().getU1f0s8()); + assertArrayEquals([ 2874452, 1567312775 ], field.getUnion2().getU2f0s64()); // 12345678901234567 + assertEquals(55, field.getUnion3().getU3f0s8()); + } + + { + var field = reader.getS0sps1s32Set(); + assertEquals(test.TestUnion.Union0.U0F1S0 , field.getUnion0().which()); + assertEquals(test.TestUnion.Union1.U1F0SP , field.getUnion1().which()); + assertEquals(test.TestUnion.Union2.U2F0S1 , field.getUnion2().which()); + assertEquals(test.TestUnion.Union3.U3F0S32, field.getUnion3().which()); + assertEquals(undefined, field.getUnion0().getU0f1s0()); + assertEquals("foo", field.getUnion1().getU1f0sp().toString()); + assertEquals(true, field.getUnion2().getU2f0s1()); + assertEquals(12345678, field.getUnion3().getU3f0s32()); + } + + { + var field = reader.getUnnamed1(); + assertEquals(test.TestUnnamedUnion.FOO, field.which()); + assertEquals(123, field.getFoo()); + assertFalse(field.hasBefore()); + assertFalse(field.hasAfter()); + } + + { + var field = reader.getUnnamed2(); + assertEquals(test.TestUnnamedUnion.BAR, field.which()); + assertEquals(321, field.getBar()); + assertEquals("foo", field.getBefore().toString()); + assertEquals("bar", field.getAfter().toString()); + } +}; + + +// ======================================================================================= + +window['test_ListDefaults'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.getRoot(test.TestListDefaults); + + capnp.test.util.genericCheckListDefaults(root.asReader()); + capnp.test.util.genericCheckListDefaults(root); + capnp.test.util.genericCheckListDefaults(root.asReader()); +}; + +window['test_BuildListDefaults'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.getRoot(test.TestListDefaults); + + capnp.test.util.genericInitListDefaults(root); + capnp.test.util.genericCheckListDefaults(root.asReader()); + capnp.test.util.genericCheckListDefaults(root); + capnp.test.util.genericCheckListDefaults(root.asReader()); +}; + +window['test_ListSetters'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.getRoot(test.TestListDefaults); + capnp.test.util.genericInitListDefaults(root); + + { + var builder2 = new capnp.message.MallocMessageBuilder(); + var root2 = builder2.getRoot(test.TestListDefaults); + + root2.getLists().setList0(root.getLists().getList0()); + var _list1 = root.getLists().getList1(); + builder2.getArena().getSegment0().validateIntegrity(); + root2.getLists().setList1(_list1); + root2.getLists().setList8(root.getLists().getList8()); + root2.getLists().setList16(root.getLists().getList16()); + root2.getLists().setList32(root.getLists().getList32()); + root2.getLists().setList64(root.getLists().getList64()); + root2.getLists().setListP(root.getLists().getListP()); + + { + var dst = root2.getLists().initInt32ListList(3); + var src = root.getLists().getInt32ListList(); + dst.set(0, src.get(0)); + dst.set(1, src.get(1)); + dst.set(2, src.get(2)); + } + + { + var dst = root2.getLists().initTextListList(3); + var src = root.getLists().getTextListList(); + dst.set(0, src.get(0)); + dst.set(1, src.get(1)); + dst.set(2, src.get(2)); + } + + { + var dst = root2.getLists().initStructListList(2); + var src = root.getLists().getStructListList(); + dst.set(0, src.get(0)); + dst.set(1, src.get(1)); + } + } +}; + +window['test_ZeroOldObject'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + + var root = builder.initRoot(test.TestAllTypes); + capnp.test.util.initTestMessage(root); + + var oldRoot = root.asReader(); + capnp.test.util.checkTestMessage(oldRoot); + + var oldSub = oldRoot.getStructField(); + var oldSub2 = oldRoot.getStructList().get(0); + + root = builder.initRoot(test.TestAllTypes); + capnp.test.util.checkTestMessageAllZero(oldRoot); + capnp.test.util.checkTestMessageAllZero(oldSub); + capnp.test.util.checkTestMessageAllZero(oldSub2); +}; + +window['test_Has'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + + var root = builder.initRoot(test.TestAllTypes); + + assertFalse(root.hasTextField()); + assertFalse(root.hasDataField()); + assertFalse(root.hasStructField()); + assertFalse(root.hasInt32List()); + + assertFalse(root.asReader().hasTextField()); + assertFalse(root.asReader().hasDataField()); + assertFalse(root.asReader().hasStructField()); + assertFalse(root.asReader().hasInt32List()); + + capnp.test.util.initTestMessage(root); + + assertTrue(root.hasTextField()); + assertTrue(root.hasDataField()); + assertTrue(root.hasStructField()); + assertTrue(root.hasInt32List()); + + assertTrue(root.asReader().hasTextField()); + assertTrue(root.asReader().hasDataField()); + assertTrue(root.asReader().hasStructField()); + assertTrue(root.asReader().hasInt32List()); + +}; + +window['test_Constants'] = function() { + + assertEquals(undefined, test.TestConstants.VOID_CONST); + assertEquals(true, test.TestConstants.BOOL_CONST); + assertEquals(-123, test.TestConstants.INT8_CONST); + assertEquals(-12345, test.TestConstants.INT16_CONST); + assertEquals(-12345678, test.TestConstants.INT32_CONST); + assertArrayEquals([ -28745, 2045911175 ], test.TestConstants.INT64_CONST); + assertEquals(234, test.TestConstants.UINT8_CONST); + assertEquals(45678, test.TestConstants.UINT16_CONST); + assertEquals(3456789012, test.TestConstants.UINT32_CONST); + assertArrayEquals([ 2874452364, 3944680146 ], test.TestConstants.UINT64_CONST); + assertRoughlyEquals(1234.5, test.TestConstants.FLOAT32_CONST, 1e-10); + assertRoughlyEquals(-123e45, test.TestConstants.FLOAT64_CONST, 1e35); + assertEquals("foo", test.TestConstants.TEXT_CONST.get().toString()); + assertTrue(capnp.test.util.data("bar").equals(test.TestConstants.DATA_CONST.get())); + { + var subReader = test.TestConstants.STRUCT_CONST.get(); + assertEquals(undefined, subReader.getVoidField()); + assertEquals(true, subReader.getBoolField()); + assertEquals(-12, subReader.getInt8Field()); + assertEquals(3456, subReader.getInt16Field()); + assertEquals(-78901234, subReader.getInt32Field()); + assertArrayEquals([ 13222, 954757966 ], subReader.getInt64Field()); // 56789012345678 + assertEquals(90, subReader.getUInt8Field()); + assertEquals(1234, subReader.getUInt16Field()); + assertEquals(56789012, subReader.getUInt32Field()); + assertArrayEquals([ 80484641, 309267154 ], subReader.getUInt64Field()); + assertRoughlyEquals(-1.25e-10, subReader.getFloat32Field(), 1e-17); + assertEquals(345, subReader.getFloat64Field()); + assertEquals("baz", subReader.getTextField().toString()); + assertTrue(capnp.test.util.data("qux").equals(subReader.getDataField())); + { + var subSubReader = subReader.getStructField(); + assertEquals("nested", subSubReader.getTextField().toString()); + assertEquals("really nested", subSubReader.getStructField().getTextField().toString()); + } + assertEquals(test.TestEnum.BAZ, subReader.getEnumField()); + + capnp.test.util.checkList(subReader.getVoidList(), [undefined, undefined, undefined]); + capnp.test.util.checkList(subReader.getBoolList(), [false, true, false, true, true]); + capnp.test.util.checkList(subReader.getInt8List(), [12, -34, -0x80, 0x7f]); + capnp.test.util.checkList(subReader.getInt16List(), [1234, -5678, -0x8000, 0x7fff]); + // gcc warns on -0x800... and the only work-around I could find was to do -0x7ff...-1. + capnp.test.util.checkList(subReader.getInt32List(), [12345678, -90123456, -0x7fffffff - 1, 0x7fffffff]); + capnp.test.util.checkList(subReader.getInt64List(), [ [ 28744, 2249056121 - 0x100000000 ], [ -158070, -49056466 ], [ -2147483648, 0 ], [ 2147483647, 4294967295 - 0x100000000 ] ]); + capnp.test.util.checkList(subReader.getUInt8List(), [12, 34, 0, 0xff]); + capnp.test.util.checkList(subReader.getUInt16List(), [1234, 5678, 0, 0xffff]); + capnp.test.util.checkList(subReader.getUInt32List(), [12345678, 90123456, 0, 0xffffffff]); + capnp.test.util.checkList(subReader.getUInt64List(), [[ 28744, 2249056121 ], [ 158069, 49056466 ], [0, 0], [0xffffffff, 0xffffffff ] ]); + capnp.test.util.checkFloatList(subReader.getFloat32List(), [0.0, 1234567.0, 1e37, -1e37, 1e-37, -1e-37], 1e30); + capnp.test.util.checkList(subReader.getFloat64List(), [0.0, 123456789012345.0, 1e306, -1e306, 1e-306, -1e-306]); + capnp.test.util.checkStrList(subReader.getTextList(), ["quux", "corge", "grault"]); + capnp.test.util.checkDataList(subReader.getDataList(), [capnp.test.util.data("garply"), capnp.test.util.data("waldo"), capnp.test.util.data("fred")]); + { + var listReader = subReader.getStructList(); + assertEquals(3, listReader.size()); + assertEquals("x structlist 1", listReader.get(0).getTextField().toString()); + assertEquals("x structlist 2", listReader.get(1).getTextField().toString()); + assertEquals("x structlist 3", listReader.get(2).getTextField().toString()); + } + capnp.test.util.checkList(subReader.getEnumList(), [test.TestEnum.QUX, test.TestEnum.BAR, test.TestEnum.GRAULT]); + } + assertEquals(test.TestEnum.CORGE, test.TestConstants.ENUM_CONST); + + assertEquals(6, test.TestConstants.VOID_LIST_CONST.get().size()); + capnp.test.util.checkList(test.TestConstants.BOOL_LIST_CONST.get(), [true, false, false, true]); + capnp.test.util.checkList(test.TestConstants.INT8_LIST_CONST.get(), [111, -111]); + capnp.test.util.checkList(test.TestConstants.INT16_LIST_CONST.get(), [11111, -11111]); + capnp.test.util.checkList(test.TestConstants.INT32_LIST_CONST.get(), [111111111, -111111111]); + capnp.test.util.checkList(test.TestConstants.INT64_LIST_CONST.get(), [ [ 258700715, 734294471 ], [ -258700716, -734294471 ] ]); + capnp.test.util.checkList(test.TestConstants.UINT8_LIST_CONST.get(), [111, 222]); + capnp.test.util.checkList(test.TestConstants.UINT16_LIST_CONST.get(), [33333, 44444]); + capnp.test.util.checkList(test.TestConstants.UINT32_LIST_CONST.get(), [3333333333]); + capnp.test.util.checkList(test.TestConstants.UINT64_LIST_CONST.get(), [ [ 2587007151, 3047977415 ] ]); + { + var listReader = test.TestConstants.FLOAT32_LIST_CONST.get(); + assertEquals(4, listReader.size()); + assertEquals(5555.5, listReader.get(0)); + assertEquals(Infinity, listReader.get(1)); + assertEquals(-Infinity, listReader.get(2)); + assertTrue(listReader.get(3) != listReader.get(3)); + } + { + var listReader = test.TestConstants.FLOAT64_LIST_CONST.get(); + assertEquals(4, listReader.size()); + assertEquals(7777.75, listReader.get(0)); + assertEquals(Infinity, listReader.get(1)); + assertEquals(-Infinity, listReader.get(2)); + assertTrue(listReader.get(3) != listReader.get(3)); + } + capnp.test.util.checkStrList(test.TestConstants.TEXT_LIST_CONST.get(), ["plugh", "xyzzy", "thud"]); + capnp.test.util.checkDataList(test.TestConstants.DATA_LIST_CONST.get(), [capnp.test.util.data("oops"), capnp.test.util.data("exhausted"), capnp.test.util.data("rfc3092")]); + { + var listReader = test.TestConstants.STRUCT_LIST_CONST.get(); + assertEquals(3, listReader.size()); + assertEquals("structlist 1", listReader.get(0).getTextField().toString()); + assertEquals("structlist 2", listReader.get(1).getTextField().toString()); + assertEquals("structlist 3", listReader.get(2).getTextField().toString()); + } + capnp.test.util.checkList(test.TestConstants.ENUM_LIST_CONST.get(), [test.TestEnum.FOO, test.TestEnum.GARPLY]); +}; + +window['test_GlobalConstants'] = function() { + + assertEquals(12345, test.GLOBAL_INT); + assertEquals("foobar", test.GLOBAL_TEXT.get().toString()); + assertEquals(54321, test.GLOBAL_STRUCT.get().getInt32Field()); + + var reader = test.DERIVED_CONSTANT.get(); + + assertEquals(12345, reader.getUInt32Field()); + assertEquals("foo", reader.getTextField().toString()); + capnp.test.util.checkStrList(reader.getStructField().getTextList(), ["quux", "corge", "grault"]); + capnp.test.util.checkList(reader.getInt16List(), [11111, -11111]); + + { + var listReader = reader.getStructList(); + assertEquals(3, listReader.size()); + assertEquals("structlist 1", listReader.get(0).getTextField().toString()); + assertEquals("structlist 2", listReader.get(1).getTextField().toString()); + assertEquals("structlist 3", listReader.get(2).getTextField().toString()); + } +}; + +window['test_HasEmptyStruct'] = function() { + + var message = new capnp.message.MallocMessageBuilder(); + var root = message.initRoot(test.TestObject); + + assertEquals(1, root.totalSizeInWords()); + + assertFalse(root.asReader().hasObjectField()); + assertFalse(root.hasObjectField()); + root.initObjectField(test.TestEmptyStruct); + assertTrue(root.asReader().hasObjectField()); + assertTrue(root.hasObjectField()); + + assertEquals(1, root.totalSizeInWords()); +}; + +window['test_HasEmptyList'] = function() { + + var message = new capnp.message.MallocMessageBuilder(); + var root = message.initRoot(test.TestObject); + + assertEquals(1, root.totalSizeInWords()); + + assertFalse(root.asReader().hasObjectField()); + assertFalse(root.hasObjectField()); + root.initObjectField(capnp.list.ListOfPrimitives(capnp.prim.int32_t), 0); + assertTrue(root.asReader().hasObjectField()); + assertTrue(root.hasObjectField()); + + assertEquals(1, root.totalSizeInWords()); +}; + +window['test_SmallStructLists'] = function() { + + // In this test, we will manually initialize TestListDefaults.lists to match the default + // value and verify that we end up with the same encoding that the compiler produces. + + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.getRoot(test.TestListDefaults); + var sl = root.initLists(); + + assertEquals(0, sl.getList0 ().size()); + assertEquals(0, sl.getList1 ().size()); + assertEquals(0, sl.getList8 ().size()); + assertEquals(0, sl.getList16().size()); + assertEquals(0, sl.getList32().size()); + assertEquals(0, sl.getList64().size()); + assertEquals(0, sl.getListP ().size()); + assertEquals(0, sl.getInt32ListList().size()); + assertEquals(0, sl.getTextListList().size()); + assertEquals(0, sl.getStructListList().size()); + + { var l = sl.initList0 (2); l.get(0).setF(undefined); l.get(1).setF(undefined); } + { var l = sl.initList1 (4); l.get(0).setF(true); l.get(1).setF(false); + l.get(2).setF(true); l.get(3).setF(true); } + { var l = sl.initList8 (2); l.get(0).setF(123); l.get(1).setF(45); } + { var l = sl.initList16(2); l.get(0).setF(12345); l.get(1).setF(6789); } + { var l = sl.initList32(2); l.get(0).setF(123456789); l.get(1).setF(234567890); } + { var l = sl.initList64(2); l.get(0).setF([ 287445, 1015724736 ]); l.get(1).setF([ 546145, 3987360647 ]); } + { var l = sl.initListP (2); l.get(0).setF("foo"); l.get(1).setF("bar"); } + + { + var l = sl.initInt32ListList(3); + l.set(0, [1, 2, 3]); + l.set(1, [4, 5]); + l.set(2, [12341234]); + } + + { + var l = sl.initTextListList(3); + l.set(0, ["foo", "bar"]); + l.set(1, ["baz"]); + l.set(2, ["qux", "corge"]); + } + + { + var l = sl.initStructListList(2); + l.init(0, 2); + l.init(1, 1); + + l.get(0).get(0).setInt32Field(123); + l.get(0).get(1).setInt32Field(456); + l.get(1).get(0).setInt32Field(789); + } + + var segment = builder.getSegmentsForOutput()[0]; + + // Initialize another message such that it copies the default value for that field. + var defaultBuilder = new capnp.message.MallocMessageBuilder(); + defaultBuilder.getRoot(test.TestListDefaults).getLists(); + var defaultSegment = defaultBuilder.getSegmentsForOutput()[0]; + + // Should match... + assertEquals(defaultSegment.byteLength, segment.byteLength); + + for (var i = 0; i < Math.max(segment.byteLength, defaultSegment.byteLength); i++) { + assertEquals(defaultSegment.getUint8(i), + segment.getUint8(i)); + } +}; + +// ======================================================================================= + +window['test_ListUpgrade'] = function() { + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.initRoot(test.TestObject); + + root.setObjectField(capnp.list.List(capnp.prim.uint16_t), [12, 34, 56]); + + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.uint8_t)), [12, 34, 56]); + + { + var l = root.getObjectField(capnp.list.List(test.TestLists.Struct8)); + assertEquals(3, l.size()); + assertEquals(12, l.get(0).getF()); + assertEquals(34, l.get(1).getF()); + assertEquals(56, l.get(2).getF()); + } + + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.uint16_t)), [12, 34, 56]); + + var reader = root.asReader(); + + capnp.test.util.checkList(reader.getObjectField(capnp.list.List(capnp.prim.uint8_t)), [12, 34, 56]); + + { + var l = reader.getObjectField(capnp.list.List(test.TestLists.Struct8)); + assertEquals(3, l.size()); + assertEquals(12, l.get(0).getF()); + assertEquals(34, l.get(1).getF()); + assertEquals(56, l.get(2).getF()); + } + + assertThrows(function() { reader.getObjectField(capnp.list.List(capnp.prim.uint32_t)); }); + + { + var l = reader.getObjectField(capnp.list.List(test.TestLists.Struct32)); + assertEquals(3, l.size()); + assertEquals(0, l.get(0).getF()); + assertEquals(0, l.get(1).getF()); + assertEquals(0, l.get(2).getF()); + } + + capnp.test.util.checkList(reader.getObjectField(capnp.list.List(capnp.prim.uint16_t)), [12, 34, 56]); +}; + +window['test_BitListDowngrade'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.initRoot(test.TestObject); + + root.setObjectField(capnp.list.List(capnp.prim.uint16_t), [0x1201, 0x3400, 0x5601, 0x7801]); + + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.bool)), [true, false, true, true]); + + { + var l = root.getObjectField(capnp.list.List(test.TestLists.Struct1)); + assertEquals(4, l.size()); + assertTrue(l.get(0).getF()); + assertFalse(l.get(1).getF()); + assertTrue(l.get(2).getF()); + assertTrue(l.get(3).getF()); + } + + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.uint16_t)), [0x1201, 0x3400, 0x5601, 0x7801]); + + var reader = root.asReader(); + + capnp.test.util.checkList(reader.getObjectField(capnp.list.List(capnp.prim.bool)), [true, false, true, true]); + + { + var l = reader.getObjectField(capnp.list.List(test.TestLists.Struct1)); + assertEquals(4, l.size()); + assertTrue(l.get(0).getF()); + assertFalse(l.get(1).getF()); + assertTrue(l.get(2).getF()); + assertTrue(l.get(3).getF()); + } + + capnp.test.util.checkList(reader.getObjectField(capnp.list.List(capnp.prim.uint16_t)), [0x1201, 0x3400, 0x5601, 0x7801]); +}; + +window['test_BitListDowngradeFromStruct'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.initRoot(test.TestObject); + + { + var list = root.initObjectField(capnp.list.List(test.TestLists.Struct1c), 4); + list.get(0).setF(true); + list.get(1).setF(false); + list.get(2).setF(true); + list.get(3).setF(true); + } + + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.bool)), [true, false, true, true]); + + { + var l = root.getObjectField(capnp.list.List(test.TestLists.Struct1)); + assertEquals(4, l.size()); + assertTrue(l.get(0).getF()); + assertFalse(l.get(1).getF()); + assertTrue(l.get(2).getF()); + assertTrue(l.get(3).getF()); + } + + var reader = root.asReader(); + + capnp.test.util.checkList(reader.getObjectField(capnp.list.List(capnp.prim.bool)), [true, false, true, true]); + + { + var l = reader.getObjectField(capnp.list.List(test.TestLists.Struct1)); + assertEquals(4, l.size()); + assertTrue(l.get(0).getF()); + assertFalse(l.get(1).getF()); + assertTrue(l.get(2).getF()); + assertTrue(l.get(3).getF()); + } +}; + +window['test_HasEmptyStructList'] = function() { + + var message = new capnp.message.MallocMessageBuilder(); + var root = message.initRoot(test.TestObject); + + assertEquals(1, root.totalSizeInWords()); + + assertFalse(root.asReader().hasObjectField()); + assertFalse(root.hasObjectField()); + root.initObjectField(capnp.list.ListOfStructs(test.TestAllTypes), 0); // FIXME - use capnp.list.List + assertTrue(root.asReader().hasObjectField()); + assertTrue(root.hasObjectField()); + + assertEquals(2, root.totalSizeInWords()); +}; + + +window['test_BitListUpgrade'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.initRoot(test.TestObject); + + root.setObjectField(capnp.list.List(capnp.prim.bool), [true, false, true, true]); + + { + var l = root.getObjectField(capnp.list.List(test.TestLists.Struct1)); + assertEquals(4, l.size()); + assertTrue(l.get(0).getF()); + assertFalse(l.get(1).getF()); + assertTrue(l.get(2).getF()); + assertTrue(l.get(3).getF()); + } + + var reader = root.asReader(); + + assertThrows(function() { reader.getObjectField(capnp.list.List(capnp.prim.uint8_t)); }); + + { + var l = reader.getObjectField(capnp.list.List(test.TestFieldZeroIsBit)); + assertEquals(4, l.size()); + assertTrue(l.get(0).getBit()); + assertFalse(l.get(1).getBit()); + assertTrue(l.get(2).getBit()); + assertTrue(l.get(3).getBit()); + + // Other fields are defaulted. + assertTrue(l.get(0).getSecondBit()); + assertTrue(l.get(1).getSecondBit()); + assertTrue(l.get(2).getSecondBit()); + assertTrue(l.get(3).getSecondBit()); + assertEquals(123, l.get(0).getThirdField()); + assertEquals(123, l.get(1).getThirdField()); + assertEquals(123, l.get(2).getThirdField()); + assertEquals(123, l.get(3).getThirdField()); + } + + capnp.test.util.checkList(reader.getObjectField(capnp.list.List(capnp.prim.bool)), [true, false, true, true]); +}; + +window['test_UpgradeStructInBuilder'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.initRoot(test.TestObject); + + var oldReader; + + { + var oldVersion = root.initObjectField(test.TestOldVersion); + oldVersion.setOld1([0, 123]); + oldVersion.setOld2("foo"); + var sub = oldVersion.initOld3(); + sub.setOld1(456); + sub.setOld2("bar"); + + oldReader = oldVersion; + } + + var size = builder.getSegmentsForOutput()[0].byteLength >> 3; + var size2; + + { + var newVersion = root.getObjectField(test.TestNewVersion); + + // The old instance should have been zero'd. + assertArrayEquals([0, 0], oldReader.getOld1()); + assertEquals("", oldReader.getOld2().toString()); + assertArrayEquals([0, 0], oldReader.getOld3().getOld1()); + assertEquals("", oldReader.getOld3().getOld2().toString()); + + // Size should have increased due to re-allocating the struct. + var size1 = builder.getSegmentsForOutput()[0].byteLength >> 3; + assertTrue(size1 > size); + + var sub = newVersion.getOld3(); + + // Size should have increased due to re-allocating the sub-struct. + size2 = builder.getSegmentsForOutput()[0].byteLength >> 3; + assertTrue(size2 > size1); + + // Check contents. + assertArrayEquals([0, 123], newVersion.getOld1()); + assertEquals("foo", newVersion.getOld2().toString()); + assertArrayEquals([0, 987], newVersion.getNew1()); + assertEquals("baz", newVersion.getNew2().toString()); + + assertArrayEquals([0, 456], sub.getOld1()); + assertEquals("bar", sub.getOld2().toString()); + assertArrayEquals([0, 987], sub.getNew1()); + assertEquals("baz", sub.getNew2().toString()); + + newVersion.setOld1([0, 234]); + newVersion.setOld2("qux"); + newVersion.setNew1([0, 321]); + newVersion.setNew2("quux"); + + sub.setOld1([0, 567]); + sub.setOld2("corge"); + sub.setNew1([0, 654]); + sub.setNew2("grault"); + } + + // We set four small text fields and implicitly initialized two to defaults, so the size should + // have raised by six words. + var size3 = builder.getSegmentsForOutput()[0].byteLength >> 3; + assertEquals(size2 + 6, size3); + + { + // Go back to old version. It should have the values set on the new version. + var oldVersion = root.getObjectField(test.TestOldVersion); + assertArrayEquals([0, 234], oldVersion.getOld1()); + assertEquals("qux", oldVersion.getOld2().toString()); + + var sub = oldVersion.getOld3(); + assertArrayEquals([0, 567], sub.getOld1()); + assertEquals("corge", sub.getOld2().toString()); + + // Overwrite the old fields. The new fields should remain intact. + oldVersion.setOld1([0, 345]); + oldVersion.setOld2("garply"); + sub.setOld1([0, 678]); + sub.setOld2("waldo"); + } + + // We set two small text fields, so the size should have raised by two words. + var size4 = builder.getSegmentsForOutput()[0].byteLength >> 3; + assertEquals(size3 + 2, size4); + + { + // Back to the new version again. + var newVersion = root.getObjectField(test.TestNewVersion); + assertArrayEquals([0, 345], newVersion.getOld1()); + assertEquals("garply", newVersion.getOld2().toString()); + assertArrayEquals([0, 321], newVersion.getNew1()); + assertEquals("quux", newVersion.getNew2().toString()); + + var sub = newVersion.getOld3(); + assertArrayEquals([0, 678], sub.getOld1()); + assertEquals("waldo", sub.getOld2().toString()); + assertArrayEquals([0, 654], sub.getNew1()); + assertEquals("grault", sub.getNew2().toString()); + } + + // Size should not have changed because we didn't write anything and the structs were already + // the right size. + assertEquals(size4, builder.getSegmentsForOutput()[0].byteLength >> 3); +}; + +window['test_UpgradeStructInBuilderFarPointers'] = function() { + + // Force allocation of a Far pointer. + + var builder = new capnp.message.MallocMessageBuilder(7, capnp.message.AllocationStrategy.FIXED_SIZE); + var root = builder.initRoot(test.TestObject); + + root.initObjectField(test.TestOldVersion).setOld2("foo"); + + // We should have allocated all but one word of the first segment. + assertEquals(1, builder.getSegmentsForOutput().length); + assertEquals(6, builder.getSegmentsForOutput()[0].byteLength >> 3); + + // Now if we upgrade... + assertEquals("foo", root.getObjectField(test.TestNewVersion).getOld2().toString()); + + // We should have allocated the new struct in a new segment, but allocated the far pointer + // landing pad back in the first segment. + assertEquals(2, builder.getSegmentsForOutput().length); + assertEquals(7, builder.getSegmentsForOutput()[0].byteLength >> 3); + assertEquals(6, builder.getSegmentsForOutput()[1].byteLength >> 3); +}; + +window['test_UpgradeStructInBuilderDoubleFarPointers'] = function() { + + // Force allocation of a double-Far pointer. + + var builder = new capnp.message.MallocMessageBuilder(6, capnp.message.AllocationStrategy.FIXED_SIZE); + var root = builder.initRoot(test.TestObject); + + root.initObjectField(test.TestOldVersion).setOld2("foo"); + + // We should have allocated all of the first segment. + assertEquals(1, builder.getSegmentsForOutput().length); + assertEquals(6, builder.getSegmentsForOutput()[0].byteLength >> 3); + + // Now if we upgrade... + assertEquals("foo", root.getObjectField(test.TestNewVersion).getOld2().toString()); + + // We should have allocated the new struct in a new segment, and also allocated the far pointer + // landing pad in yet another segment. + assertEquals(3, builder.getSegmentsForOutput().length); + assertEquals(6, builder.getSegmentsForOutput()[0].byteLength >> 3); + assertEquals(6, builder.getSegmentsForOutput()[1].byteLength >> 3); + assertEquals(2, builder.getSegmentsForOutput()[2].byteLength >> 3); +}; + +window['test_UpgradeListInBuilder'] = function() { + + // Test every damned list upgrade. + + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.initRoot(test.TestObject); + + // ----------------------------------------------------------------- + + root.setObjectField(capnp.list.List(capnp.prim.Void), [ undefined, undefined, undefined, undefined ]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.Void)), [ undefined, undefined, undefined, undefined ]); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.bool)); }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint8_t)); }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint16_t)) }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint32_t)); }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint64_t)); }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.blob.Text)); }); + checkUpgradedList(root, [[0,0], [0,0], [0,0], [0,0]], ["", "", "", ""]); + + // ----------------------------------------------------------------- + + { + root.setObjectField(capnp.list.List(capnp.prim.bool), [true, false, true, true]); + var orig = root.asReader().getObjectField(capnp.list.List(capnp.prim.bool)); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.Void)), [undefined, undefined, undefined, undefined]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.bool)), [true, false, true, true]); + + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint8_t)); }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint16_t)) }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint32_t)); }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint64_t)); }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.blob.Text)); }); + + capnp.test.util.checkList(orig, [true, false, true, true]); + checkUpgradedList(root, [[0,1], [0,0], [0,1], [0,1]], ["", "", "", ""]); + capnp.test.util.checkList(orig, [false, false, false, false]); // old location zero'd during upgrade + } + + // ----------------------------------------------------------------- + + { + root.setObjectField(capnp.list.List(capnp.prim.uint8_t), [0x12, 0x23, 0x33, 0x44]); + var orig = root.asReader().getObjectField(capnp.list.List(capnp.prim.uint8_t)); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.Void)), [undefined, undefined, undefined, undefined]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.bool)), [false, true, true, false]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.uint8_t)), [0x12, 0x23, 0x33, 0x44]); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint16_t)) }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint32_t)); }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint64_t)); }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.blob.Text)); }); + + capnp.test.util.checkList(orig, [0x12, 0x23, 0x33, 0x44]); + checkUpgradedList(root, [[0,0x12], [0,0x23], [0,0x33], [0,0x44]], ["", "", "", ""]); + capnp.test.util.checkList(orig, [0, 0, 0, 0]); // old location zero'd during upgrade + } + + // ----------------------------------------------------------------- + + { + root.setObjectField(capnp.list.List(capnp.prim.uint16_t), [0x5612, 0x7823, 0xab33, 0xcd44]); + var orig = root.asReader().getObjectField(capnp.list.List(capnp.prim.uint16_t)); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.Void)), [undefined, undefined, undefined, undefined]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.bool)), [false, true, true, false]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.uint8_t)), [0x12, 0x23, 0x33, 0x44]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.uint16_t)), [0x5612, 0x7823, 0xab33, 0xcd44]); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint32_t)); }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint64_t)); }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.blob.Text)); }); + + capnp.test.util.checkList(orig, [0x5612, 0x7823, 0xab33, 0xcd44]); + checkUpgradedList(root, [[0,0x5612], [0,0x7823], [0,0xab33], [0,0xcd44]], ["", "", "", ""]); + capnp.test.util.checkList(orig, [0, 0, 0, 0]); // old location zero'd during upgrade + } + + // ----------------------------------------------------------------- + + { + root.setObjectField(capnp.list.List(capnp.prim.uint32_t), [0x17595612, 0x29347823, 0x5923ab32, 0x1a39cd45]); + var orig = root.asReader().getObjectField(capnp.list.List(capnp.prim.uint32_t)); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.Void)), [undefined, undefined, undefined, undefined]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.bool)), [false, true, false, true]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.uint8_t)), [0x12, 0x23, 0x32, 0x45]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.uint16_t)), [0x5612, 0x7823, 0xab32, 0xcd45]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.uint32_t)), [0x17595612, 0x29347823, 0x5923ab32, 0x1a39cd45]); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint64_t)); }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.blob.Text)); }); + + capnp.test.util.checkList(orig, [0x17595612, 0x29347823, 0x5923ab32, 0x1a39cd45]); + checkUpgradedList(root, [[0,0x17595612], [0,0x29347823], [0,0x5923ab32], [0,0x1a39cd45]], ["", "", "", ""]); + capnp.test.util.checkList(orig, [0, 0, 0, 0]); // old location zero'd during upgrade + } + + // ----------------------------------------------------------------- + + { + root.setObjectField(capnp.list.List(capnp.prim.uint64_t), [[0x1234abcd, 0x8735fe21], [0x7173bc0e, 0x1923af36]]); + var orig = root.asReader().getObjectField(capnp.list.List(capnp.prim.uint64_t)); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.Void)), [undefined, undefined]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.bool)), [true, false]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.uint8_t)), [0x21, 0x36]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.uint16_t)), [0xfe21, 0xaf36]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.uint32_t)), [0x8735fe21, 0x1923af36]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.uint64_t)), [[0x1234abcd, 0x8735fe21], [0x7173bc0e, 0x1923af36]]); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.blob.Text)); }); + + capnp.test.util.checkList(orig, [[0x1234abcd, 0x8735fe21], [0x7173bc0e, 0x1923af36]]); + checkUpgradedList(root, [[0x1234abcd, 0x8735fe21-0x100000000], [0x7173bc0e, 0x1923af36]], ["", ""]); + capnp.test.util.checkList(orig, [[0,0], [0,0]]); // old location zero'd during upgrade + } + + // ----------------------------------------------------------------- + + { + root.setObjectField(capnp.list.List(capnp.blob.Text), ["foo", "bar", "baz"]); + var orig = root.asReader().getObjectField(capnp.list.List(capnp.blob.Text)); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.Void)), [undefined, undefined, undefined]); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.bool)); }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint8_t)); }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint16_t)) }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint32_t)); }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint64_t)); }); + capnp.test.util.checkStrList(root.getObjectField(capnp.list.List(capnp.blob.Text)), ["foo", "bar", "baz"]); + + capnp.test.util.checkStrList(orig, ["foo", "bar", "baz"]); + checkUpgradedList(root, [[0,0], [0,0], [0,0]], ["foo", "bar", "baz"]); + capnp.test.util.checkStrList(orig, ["", "", ""]); // old location zero'd during upgrade + } + + // ----------------------------------------------------------------- + + { + { + var l = root.initObjectField(capnp.list.List(test.TestOldVersion), 3); + l.get(0).setOld1([0x12345678, 0x90abcdef]); + l.get(1).setOld1([0x23456789, 0x0abcdef1]); + l.get(2).setOld1([0x34567890, 0xabcdef12]); + l.get(0).setOld2("foo"); + l.get(1).setOld2("bar"); + l.get(2).setOld2("baz"); + } + var orig = root.asReader().getObjectField(capnp.list.List(test.TestOldVersion)); + + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.Void)), [undefined, undefined, undefined]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.bool)), [true, true, false]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.uint8_t)), [0xef, 0xf1, 0x12]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.uint16_t)), [0xcdef, 0xdef1, 0xef12]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.uint32_t)), [0x90abcdef, 0x0abcdef1, 0xabcdef12]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.uint64_t)), + [[0x12345678, 0x90abcdef], [0x23456789, 0x0abcdef1], [0x34567890, 0xabcdef12]]); + capnp.test.util.checkStrList(root.getObjectField(capnp.list.List(capnp.blob.Text)), ["foo", "bar", "baz"]); + + checkListDataPtr(orig, [[0x12345678, 0x90abcdef-0x100000000], [0x23456789, 0x0abcdef1], [0x34567890, 0xabcdef12-0x100000000]], + ["foo", "bar", "baz"]); + checkUpgradedList(root, [[0x12345678, 0x90abcdef-0x100000000], [0x23456789, 0x0abcdef1], [0x34567890, 0xabcdef12-0x100000000]], + ["foo", "bar", "baz"]); + checkListDataPtr(orig, [[0,0], [0,0], [0,0]], ["", "", ""]); // old location zero'd during upgrade + } + + // ----------------------------------------------------------------- + // OK, now we've tested upgrading every primitive list to every primitive list, every primitive + // list to a multi-word struct, and a multi-word struct to every primitive list. But we haven't + // tried upgrading primitive lists to sub-word structs. + + // Upgrade from bool. + root.setObjectField(capnp.list.List(capnp.prim.bool), [true, false, true, true]); + { + var orig = root.asReader().getObjectField(capnp.list.List(capnp.prim.bool)); + capnp.test.util.checkList(orig, [true, false, true, true]); + var l = root.getObjectField(capnp.list.List(test.TestLists.Struct16)); + capnp.test.util.checkList(orig, [false, false, false, false]); // old location zero'd during upgrade + assertEquals(4, l.size()); + assertEquals(1, l.get(0).getF()); + assertEquals(0, l.get(1).getF()); + assertEquals(1, l.get(2).getF()); + assertEquals(1, l.get(3).getF()); + l.get(0).setF(12573); + l.get(1).setF(3251); + l.get(2).setF(9238); + l.get(3).setF(5832); + } + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.bool)), [true, true, false, false]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.uint16_t)), [12573, 3251, 9238, 5832]); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint32_t)); }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint64_t)); }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.blob.Text)); }); + + // Upgrade from multi-byte, sub-word data. + root.setObjectField(capnp.list.List(capnp.prim.uint16_t), [12, 34, 56, 78]); + { + var orig = root.asReader().getObjectField(capnp.list.List(capnp.prim.uint16_t)); + capnp.test.util.checkList(orig, [12, 34, 56, 78]); + var l = root.getObjectField(capnp.list.List(test.TestLists.Struct32)); + capnp.test.util.checkList(orig, [0, 0, 0, 0]); // old location zero'd during upgrade + assertEquals(4, l.size()); + assertEquals(12, l.get(0).getF()); + assertEquals(34, l.get(1).getF()); + assertEquals(56, l.get(2).getF()); + assertEquals(78, l.get(3).getF()); + l.get(0).setF(0x65ac1235); + l.get(1).setF(0x13f12879); + l.get(2).setF(0x33423082); + l.get(3).setF(0x12988948); + } + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.bool)), [true, true, false, false]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.uint8_t)), [0x35, 0x79, 0x82, 0x48]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.uint16_t)), [0x1235, 0x2879, 0x3082, 0x8948]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.uint32_t)), + [0x65ac1235, 0x13f12879, 0x33423082, 0x12988948]); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint64_t)); }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.blob.Text)); }); + + // Upgrade from void -> data struct + root.setObjectField(capnp.list.List(capnp.prim.Void), [undefined, undefined, undefined, undefined]); + { + var l = root.getObjectField(capnp.list.List(test.TestLists.Struct16)); + assertEquals(4, l.size()); + assertEquals(0, l.get(0).getF()); + assertEquals(0, l.get(1).getF()); + assertEquals(0, l.get(2).getF()); + assertEquals(0, l.get(3).getF()); + l.get(0).setF(12573); + l.get(1).setF(3251); + l.get(2).setF(9238); + l.get(3).setF(5832); + } + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.bool)), [true, true, false, false]); + capnp.test.util.checkList(root.getObjectField(capnp.list.List(capnp.prim.uint16_t)), [12573, 3251, 9238, 5832]); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint32_t)); }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint64_t)); }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.blob.Text)); }); + + // Upgrade from void -> pointer struct + root.setObjectField(capnp.list.List(capnp.prim.Void), [undefined, undefined, undefined, undefined]); + { + var l = root.getObjectField(capnp.list.List(test.TestLists.StructP)); + assertEquals(4, l.size()); + assertEquals("", l.get(0).getF().toString()); + assertEquals("", l.get(1).getF().toString()); + assertEquals("", l.get(2).getF().toString()); + assertEquals("", l.get(3).getF().toString()); + l.get(0).setF("foo"); + l.get(1).setF("bar"); + l.get(2).setF("baz"); + l.get(3).setF("qux"); + } + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.bool)); }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint16_t)); }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint32_t)); }); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.prim.uint64_t)); }); + capnp.test.util.checkStrList(root.getObjectField(capnp.list.List(capnp.blob.Text)), ["foo", "bar", "baz", "qux"]); + + // Verify that we cannot "side-grade" a pointer list to a data struct list, or a data list to + // a pointer struct list. + root.setObjectField(capnp.list.List(capnp.blob.Text), ["foo", "bar", "baz", "qux"]); + assertThrows(function() { root.getObjectField(capnp.list.List(test.TestLists.Struct32)); }); + root.setObjectField(capnp.list.List(capnp.prim.uint32_t), [12, 34, 56, 78]); + assertThrows(function() { root.getObjectField(capnp.list.List(capnp.blob.Text)); }); +}; + +// ======================================================================================= +// Tests of generated code, not really of the encoding. +// TODO(cleanup): Move to a different test? + +window['test_NestedTypes'] = function() { + + // This is more of a test of the generated code than the encoding. + + var builder = new capnp.message.MallocMessageBuilder(); + var reader = builder.getRoot(test.TestNestedTypes).asReader(); + + assertEquals(test.TestNestedTypes.NestedEnum.BAR, reader.getOuterNestedEnum()); + assertEquals(test.TestNestedTypes.NestedStruct.NestedEnum.QUUX, reader.getInnerNestedEnum()); + + var nested = reader.getNestedStruct(); + assertEquals(test.TestNestedTypes.NestedEnum.BAR, nested.getOuterNestedEnum()); + assertEquals(test.TestNestedTypes.NestedStruct.NestedEnum.QUUX, nested.getInnerNestedEnum()); +}; + +window['test_Imports'] = function() { + // Also just testing the generated code. + { + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.getRoot(capnproto_test.capnp.test_import.TestImport); + capnp.test.util.initTestMessage(root.initField()); + capnp.test.util.checkTestMessage(root.asReader().getField()); + } + + { + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.getRoot(capnproto_test.capnp.test_import2.TestImport2); + capnp.test.util.initTestMessage(root.initFoo()); + capnp.test.util.checkTestMessage(root.asReader().getFoo()); + /* FIXME + root.setBar(Schema.from(TestAllTypes).getProto()); + capnp.test.util.initTestMessage(root.initBaz().initField()); + capnp.test.util.checkTestMessage(root.asReader().getBaz().getField()); + */ + } +}; + +window['test_Using'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + var reader = builder.getRoot(test.TestUsing).asReader(); + assertEquals(test.TestNestedTypes.NestedEnum.BAR, reader.getOuterNestedEnum()); + assertEquals(test.TestNestedTypes.NestedStruct.NestedEnum.QUUX, reader.getInnerNestedEnum()); +}; + +window['test_StructSetters'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.getRoot(test.TestAllTypes); + capnp.test.util.initTestMessage(root); + + { + var builder2 = new capnp.message.MallocMessageBuilder(); + builder2.setRoot(root.asReader()); + capnp.test.util.checkTestMessage(builder2.getRoot(test.TestAllTypes)); + } + + { + var builder2 = new capnp.message.MallocMessageBuilder(); + var root2 = builder2.getRoot(test.TestAllTypes); + root2.setStructField(root.asReader()); + capnp.test.util.checkTestMessage(root2.getStructField()); + } + + { + var builder2 = new capnp.message.MallocMessageBuilder(); + var root2 = builder2.getRoot(test.TestObject); + root2.setObjectField(test.TestAllTypes, root.asReader()); + capnp.test.util.checkTestMessage(root2.getObjectField(test.TestAllTypes)); + } +}; diff --git a/javascript/tests/interop-tests.js b/javascript/tests/interop-tests.js new file mode 100644 index 0000000..575c7ec --- /dev/null +++ b/javascript/tests/interop-tests.js @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2013, Julian Scheid + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +function assertMessageDeserializesTo(message, capnpType, deserializedString, done) { + var command = process.env.CAPNP + ' decode --short ' + process.env.CAPNP_SOURCE + '/src/capnp/test.capnp ' + capnpType; + var child = exec( + command, + function(error, stdout, stderr) { + assert.ok(error === null); + assert.equal(stdout, deserializedString); + done(); + }); + capnp.writeMessageToFd(child.stdin._handle.fd, message); + child.stdin.end(); +} + +function withMessageFromString(capnpType, deserializedString, callback) { + var command = process.env.CAPNP + ' encode ' + process.env.CAPNP_SOURCE + '/src/capnp/test.capnp ' + capnpType; + var child = exec( + command, + function(error, stdout, stderr) { + assert.ok(error === null); + var message = new capnp.NodeJsBufferMessageReader(new Buffer(stdout)); + callback(message) + }); + child.stdin.write(deserializedString); + child.stdin.end(); +} + + +describe('Interop', function() { + + it('should build a message correctly', function(done) { + var message = new capnp.MallocMessageBuilder(); + var testOutOfOrder = message.initRoot(test.TestOutOfOrder); + + testOutOfOrder.setFoo('foo'); + testOutOfOrder.setWaldo('waldo'); + testOutOfOrder.setGrault('grault'); + testOutOfOrder.setQuux('quux'); + testOutOfOrder.setCorge('corge'); + testOutOfOrder.setBaz('baz'); + testOutOfOrder.setBar('bar'); + testOutOfOrder.setQux('qux'); + testOutOfOrder.setGarply('garply'); + + assertMessageDeserializesTo( + message, 'TestOutOfOrder', + '(qux = "qux", grault = "grault", bar = "bar", foo = "foo", corge = "corge", waldo = "waldo", quux = "quux", garply = "garply", baz = "baz")\n', + done); + }), + + it('should parse a message correctly', function(done) { + withMessageFromString( + 'TestOutOfOrder', + '(qux = "qux", grault = "grault", bar = "bar", foo = "foo", corge = "corge", waldo = "waldo", quux = "quux", garply = "garply", baz = "baz")\n', + function (message) { + var testOutOfOrder = message.getRoot(test.TestOutOfOrder); + assert.equal(testOutOfOrder.getWaldo(), 'waldo'); + assert.equal(testOutOfOrder.getFoo(), 'foo'); + assert.equal(testOutOfOrder.getGrault(), 'grault'); + assert.equal(testOutOfOrder.getBar(), 'bar'); + assert.equal(testOutOfOrder.getGarply(), 'garply'); + assert.equal(testOutOfOrder.getBaz(), 'baz'); + assert.equal(testOutOfOrder.getQuux(), 'quux'); + assert.equal(testOutOfOrder.getQux(), 'qux'); + done(); + }); + }), + + it('should build a message correctly', function(done) { + var message = new capnp.MallocMessageBuilder(); + var testDefaults = message.initRoot(test.TestDefaults); + + testDefaults.setVoidField(); + testDefaults.setBoolField(false); + testDefaults.setInt8Field(100); + testDefaults.setInt16Field(10000); + testDefaults.setInt32Field(1000000); + //testDefaults.setInt64Field([ 1000000, 1000000 ]); + + assertMessageDeserializesTo( + message, 'TestDefaults', + '(boolField = false, int8Field = 100, int16Field = 10000, int32Field = 1000000)\n', + done); + }); +}); diff --git a/javascript/tests/layout-test.js b/javascript/tests/layout-test.js new file mode 100644 index 0000000..bb6513c --- /dev/null +++ b/javascript/tests/layout-test.js @@ -0,0 +1,372 @@ +/* + * Copyright (c) 2013, Julian Scheid + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +goog.provide('capnp.tests.layout'); + +goog.require('capnp.serialize'); +goog.require('capnp.message'); +goog.require('capnp.packed'); +goog.require('capnp.test.util'); +goog.require('kj.io'); + +goog.require('capnproto_test.capnp.test'); + +var STRUCTLIST_ELEMENT_SIZE = new capnp.layout.StructSize(1, 1, capnp.layout.FieldSize.INLINE_COMPOSITE); +var SUBSTRUCT_DEFAULT = new Uint8Array([ 0,0,0,0,1,0,0,0, 0,0,0,0,0,0,0,0 ]).buffer; +var STRUCTLIST_ELEMENT_SUBSTRUCT_DEFAULT = new Uint8Array([ 0,0,0,0,1,0,0,0, 0,0,0,0,0,0,0,0 ]).buffer; + +var setupStruct = function(builder) { + builder.setDataField_uint64(0, [ 269554195, 336926231 ]); + builder.setDataField_uint32(2, 0x20212223); + builder.setDataField_uint16(6, 0x3031); + builder.setDataField_uint8(14, 0x40); + builder.setDataField_bool(120, false); + builder.setDataField_bool(121, false); + builder.setDataField_bool(122, true); + builder.setDataField_bool(123, false); + builder.setDataField_bool(124, true); + builder.setDataField_bool(125, true); + builder.setDataField_bool(126, true); + builder.setDataField_bool(127, false); + + { + var subStruct = builder.initStructField( + 0, new capnp.layout.StructSize(1, 0, capnp.layout.FieldSize.EIGHT_BYTES)); + subStruct.setDataField_uint32(0, 123); + } + + { + var list = builder.initListField(1, capnp.layout.FieldSize.FOUR_BYTES, 3); + assertEquals(3, list.size()); + list.setDataElement(capnp.prim.int32_t, 0, 200); + list.setDataElement(capnp.prim.int32_t, 1, 201); + list.setDataElement(capnp.prim.int32_t, 2, 202); + } + + { + var list = builder.initStructListField( + 2, 4, STRUCTLIST_ELEMENT_SIZE); + assertEquals(4, list.size()); + for (var i = 0; i < 4; i++) { + var element = list.getStructElement(i); + element.setDataField_int32(0, 300 + i); + element.initStructField(0, + new capnp.layout.StructSize(1, 0, capnp.layout.FieldSize.EIGHT_BYTES)) + .setDataField_int32(0, 400 + i); + } + } + + { + var list = builder.initListField(3, capnp.layout.FieldSize.POINTER, 5); + assertEquals(5, list.size()); + for (var i = 0; i < 5; i++) { + var element = list.initListElement( + i, 3 /*FieldSize::TWO_BYTES*/, (i + 1)); + assertEquals((i + 1), element.size()); + for (var j = 0; j <= i; j++) { + element.setDataElement(capnp.prim.uint16_t, j, 500 + j); + } + } + } +}; + +var checkStructWithBuilder = function(builder) { + assertArrayEquals([ 269554195, 336926231 ], builder.getDataField_uint64(0)); + assertEquals(0x20212223, builder.getDataField_uint32(2)); + assertEquals(0x3031, builder.getDataField_uint16(6)); + assertEquals(0x40, builder.getDataField_uint8(14)); + assertFalse(builder.getDataField_bool(120)); + assertFalse(builder.getDataField_bool(121)); + assertTrue (builder.getDataField_bool(122)); + assertFalse(builder.getDataField_bool(123)); + assertTrue (builder.getDataField_bool(124)); + assertTrue (builder.getDataField_bool(125)); + assertTrue (builder.getDataField_bool(126)); + assertFalse(builder.getDataField_bool(127)); + + { + var subStruct = builder.getStructField( + 0, new capnp.layout.StructSize(1, 0, capnp.layout.FieldSize.EIGHT_BYTES), + SUBSTRUCT_DEFAULT); + assertEquals(123, subStruct.getDataField_uint32(0)); + } + + { + var list = builder.getListField(1, capnp.layout.FieldSize.FOUR_BYTES, null); + assertEquals(3, list.size()); + assertEquals(200, list.getDataElement(capnp.prim.int32_t, 0)); + assertEquals(201, list.getDataElement(capnp.prim.int32_t, 1)); + assertEquals(202, list.getDataElement(capnp.prim.int32_t, 2)); + } + + { + var list = builder.getStructListField(2, STRUCTLIST_ELEMENT_SIZE, null); + assertEquals(4, list.size()); + for (var i = 0; i < 4; i++) { + var element = list.getStructElement(i); + assertEquals(300 + i, element.getDataField_int32(0)); + assertEquals(400 + i, + element.getStructField(0, + new capnp.layout.StructSize(1, 0, capnp.layout.FieldSize.EIGHT_BYTES), + STRUCTLIST_ELEMENT_SUBSTRUCT_DEFAULT.words) + .getDataField_int32(0)); + } + } + + { + var list = builder.getListField(3, 6 /*FieldSize::POINTER*/, null); + assertEquals(5, list.size()); + for (var i = 0; i < 5; i++) { + var element = list.getListElement(i, 3 /*FieldSize::TWO_BYTES*/); + assertEquals((i + 1), element.size()); + for (var j = 0; j <= i; j++) { + assertEquals(500 + j, element.getDataElement(capnp.prim.uint16_t, j)); + } + } + } +}; + +var checkStructWithReader = function(reader) { + + assertArrayEquals([ 269554195, 336926231 ], reader.getDataField_uint64(0)); + assertEquals(0x20212223, reader.getDataField_uint32(2)); + assertEquals(0x3031, reader.getDataField_uint16(6)); + assertEquals(0x40, reader.getDataField_uint8(14)); + assertFalse(reader.getDataField_bool(120)); + assertFalse(reader.getDataField_bool(121)); + assertTrue (reader.getDataField_bool(122)); + assertFalse(reader.getDataField_bool(123)); + assertTrue (reader.getDataField_bool(124)); + assertTrue (reader.getDataField_bool(125)); + assertTrue (reader.getDataField_bool(126)); + assertFalse(reader.getDataField_bool(127)); + + { + var subStruct = reader.getStructField(0, SUBSTRUCT_DEFAULT); + assertEquals(123, subStruct.getDataField_uint32(0)); + } + + { + var list = reader.getListField(1, capnp.layout.FieldSize.FOUR_BYTES, null); + assertEquals(3, list.size()); + assertEquals(200, list.getDataElement(capnp.prim.uint32_t, 0)); + assertEquals(201, list.getDataElement(capnp.prim.uint32_t, 1)); + assertEquals(202, list.getDataElement(capnp.prim.uint32_t, 2)); + } + + { + var list = reader.getListField(2, capnp.layout.FieldSize.INLINE_COMPOSITE, null); + assertEquals(4, list.size()); + for (var i = 0; i < 4; i++) { + var element = list.getStructElement(i); + assertEquals(300 + i, element.getDataField_int32(0)); + assertEquals(400 + i, + element.getStructField(0, STRUCTLIST_ELEMENT_SUBSTRUCT_DEFAULT) + .getDataField_int32(0)); + } + } + + { + var list = reader.getListField(3, capnp.layout.FieldSize.POINTER, null); + assertEquals(5, list.size()); + for (var i = 0; i < 5; i++) { + var element = list.getListElement(i, capnp.layout.FieldSize.TWO_BYTES); + assertEquals((i + 1), element.size()); + for (var j = 0; j <= i; j++) { + assertEquals(500 + j, element.getDataElement(capnp.prim.uint16_t, j)); + } + } + } +}; + +window['test_SimpleRawDataStruct'] = function() { + + var data = new Uint8Array([ + // Struct ref, offset = 1, dataSize = 1, pointerCount = 0 + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + // Content for the data section. + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef + ]).buffer + + var reader = capnp.layout.StructReader.readRootUnchecked(new DataView(data)); + + assertArrayEquals([ 4023233417, 1732584193 ], reader.getDataField_uint64(0)); // 0xefcdab8967452301 + assertArrayEquals([0, 0], reader.getDataField_uint64(1)); + assertEquals(0x67452301, reader.getDataField_uint32(0)); + assertEquals(0xefcdab89, reader.getDataField_uint32(1)); + assertEquals(0, reader.getDataField_uint32(2)); + assertEquals(0x2301, reader.getDataField_uint16(0)); + assertEquals(0x6745, reader.getDataField_uint16(1)); + assertEquals(0xab89, reader.getDataField_uint16(2)); + assertEquals(0xefcd, reader.getDataField_uint16(3)); + assertEquals(0, reader.getDataField_uint16(4)); + + assertArrayEquals([ 4023233417, 321 ^ 1732584193 ], reader.getDataField_uint64_masked(0, [0, 321])); + assertEquals(321 ^ 0x67452301, reader.getDataField_uint32_masked(0, 321)); + assertEquals(321 ^ 0x2301, reader.getDataField_uint16_masked(0, 321)); + assertArrayEquals([0, 321], reader.getDataField_uint64_masked(1, [0, 321])); + assertEquals(321, reader.getDataField_uint32_masked(2, 321)); + assertEquals(321, reader.getDataField_uint16_masked(4, 321)); + + // Bits + assertTrue (reader.getDataField_bool(0)); + assertFalse(reader.getDataField_bool(1)); + assertFalse(reader.getDataField_bool(2)); + assertFalse(reader.getDataField_bool(3)); + assertFalse(reader.getDataField_bool(4)); + assertFalse(reader.getDataField_bool(5)); + assertFalse(reader.getDataField_bool(6)); + assertFalse(reader.getDataField_bool(7)); + + assertTrue (reader.getDataField_bool(8)); + assertTrue (reader.getDataField_bool(9)); + assertFalse(reader.getDataField_bool(10)); + assertFalse(reader.getDataField_bool(11)); + assertFalse(reader.getDataField_bool(12)); + assertTrue (reader.getDataField_bool(13)); + assertFalse(reader.getDataField_bool(14)); + assertFalse(reader.getDataField_bool(15)); + + assertTrue (reader.getDataField_bool(63)); + assertFalse(reader.getDataField_bool(64)); + + assertTrue (reader.getDataField_bool_masked(0, false)); + assertFalse(reader.getDataField_bool_masked(1, false)); + assertTrue (reader.getDataField_bool_masked(63, false)); + assertFalse(reader.getDataField_bool_masked(64, false)); + assertFalse(reader.getDataField_bool_masked(0, true)); + assertTrue (reader.getDataField_bool_masked(1, true)); + assertFalse(reader.getDataField_bool_masked(63, true)); + assertTrue (reader.getDataField_bool_masked(64, true)); + +} + +window['test_StructRoundTrip_OneSegment'] = function() { + + var message = new capnp.message.MallocMessageBuilder(); + var arena = new capnp.arena.BuilderArena(message); + var allocation = arena.allocate(1); + var segment = allocation.segment; + var rootLocation = allocation.words; + + var builder = capnp.layout.StructBuilder.initRoot( + segment, rootLocation, new capnp.layout.StructSize(2, 4, capnp.layout.FieldSize.INLINE_COMPOSITE)); + + setupStruct(builder); + + // word count: + // 1 root pointer + // 6 root struct + // 1 sub message + // 2 3-element int32 list + // 13 struct list + // 1 tag + // 12 4x struct + // 1 data section + // 1 pointer section + // 1 sub-struct + // 11 list list + // 5 pointers to sub-lists + // 6 sub-lists (4x 1 word, 1x 2 words) + // ----- + // 34 + var segments = arena.getSegmentsForOutput(); + assertEquals(1, segments.length); + assertEquals(34 << 3, segments[0].byteLength); + + checkStructWithBuilder(builder); + checkStructWithReader(builder.asReader()); + checkStructWithReader(capnp.layout.StructReader.readRootUnchecked(segment.getDataView())); + checkStructWithReader(capnp.layout.StructReader.readRoot(0, segment, 4)); +} + +window['test_StructRoundTrip_OneSegmentPerAllocation'] = function() { + + var message = new capnp.message.MallocMessageBuilder(0, capnp.message.AllocationStrategy.FIXED_SIZE); + var arena = new capnp.arena.BuilderArena(message); + var allocation = arena.allocate(1); + var segment = allocation.segment; + var rootLocation = allocation.words; + + var builder = capnp.layout.StructBuilder.initRoot( + segment, rootLocation, new capnp.layout.StructSize(2, 4, capnp.layout.FieldSize.INLINE_COMPOSITE)); + setupStruct(builder); + + // Verify that we made 15 segments. + var segments = arena.getSegmentsForOutput(); + assertEquals(15, segments.length); + + // Check that each segment has the expected size. Recall that the first word of each segment will + // actually be a pointer to the first thing allocated within that segment. + assertEquals( 1, segments[ 0].byteLength >>> 3); // root ref + assertEquals( 7, segments[ 1].byteLength >>> 3); // root struct + assertEquals( 2, segments[ 2].byteLength >>> 3); // sub-struct + assertEquals( 3, segments[ 3].byteLength >>> 3); // 3-element int32 list + assertEquals(10, segments[ 4].byteLength >>> 3); // struct list + assertEquals( 2, segments[ 5].byteLength >>> 3); // struct list substruct 1 + assertEquals( 2, segments[ 6].byteLength >>> 3); // struct list substruct 2 + assertEquals( 2, segments[ 7].byteLength >>> 3); // struct list substruct 3 + assertEquals( 2, segments[ 8].byteLength >>> 3); // struct list substruct 4 + assertEquals( 6, segments[ 9].byteLength >>> 3); // list list + assertEquals( 2, segments[10].byteLength >>> 3); // list list sublist 1 + assertEquals( 2, segments[11].byteLength >>> 3); // list list sublist 2 + assertEquals( 2, segments[12].byteLength >>> 3); // list list sublist 3 + assertEquals( 2, segments[13].byteLength >>> 3); // list list sublist 4 + assertEquals( 3, segments[14].byteLength >>> 3); // list list sublist 5 + + checkStructWithBuilder(builder); + checkStructWithReader(builder.asReader()); + checkStructWithReader(capnp.layout.StructReader.readRoot(0, segment, 4)); +} + +window['test_StructRoundTrip_MultipleSegmentsWithMultipleAllocations'] = function() { + + var message = new capnp.message.MallocMessageBuilder(8, capnp.message.AllocationStrategy.FIXED_SIZE); + var arena = new capnp.arena.BuilderArena(message); + var allocation = arena.allocate(1); + var segment = allocation.segment; + var rootLocation = allocation.words; + + var builder = capnp.layout.StructBuilder.initRoot( + segment, rootLocation, new capnp.layout.StructSize(2, 4, capnp.layout.FieldSize.INLINE_COMPOSITE)); + setupStruct(builder); + + // Verify that we made 6 segments. + var segments = arena.getSegmentsForOutput(); + assertEquals(6, segments.length); + + // Check that each segment has the expected size. Recall that each object will be prefixed by an + // extra word if its parent is in a different segment. + assertEquals( 8, segments[0].byteLength >>> 3); // root ref + struct + sub + assertEquals( 3, segments[1].byteLength >>> 3); // 3-element int32 list + assertEquals(10, segments[2].byteLength >>> 3); // struct list + assertEquals( 8, segments[3].byteLength >>> 3); // struct list substructs + assertEquals( 8, segments[4].byteLength >>> 3); // list list + sublist 1,2 + assertEquals( 7, segments[5].byteLength >>> 3); // list list sublist 3,4,5 + + checkStructWithBuilder(builder); + checkStructWithReader(builder.asReader()); + checkStructWithReader(capnp.layout.StructReader.readRoot(0, segment, 4)); +} diff --git a/javascript/tests/orphan-test.js b/javascript/tests/orphan-test.js new file mode 100644 index 0000000..9cb1f4f --- /dev/null +++ b/javascript/tests/orphan-test.js @@ -0,0 +1,521 @@ +/* + * Copyright (c) 2013, Julian Scheid + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +goog.provide('capnp.tests.orphans'); + +goog.require('capnp.message'); +goog.require('capnp.test.util'); + +goog.require('capnproto_test.capnp.test'); + + +window['test_orphans_Structs'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.initRoot(test.TestAllTypes); + + capnp.test.util.initTestMessage(root.initStructField()); + assertTrue(root.hasStructField()); + + var orphan = root.disownStructField(); + assertNotNull(orphan); + + capnp.test.util.checkTestMessage(orphan.getReader()); + capnp.test.util.checkTestMessage(orphan.get()); + assertFalse(root.hasStructField()); + + root.adoptStructField(orphan); + assertTrue(orphan.isNull()); + assertTrue(root.hasStructField()); + capnp.test.util.checkTestMessage(root.asReader().getStructField()); +}; + +window['test_orphans_Lists'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.initRoot(test.TestAllTypes); + + root.setUInt32List([12, 34, 56]); + assertTrue(root.hasUInt32List()); + + var orphan = root.disownUInt32List(); + assertFalse(orphan === null); + + checkList(orphan.getReader(), [12, 34, 56]); + checkList(orphan.get(), [12, 34, 56]); + assertFalse(root.hasUInt32List()); + + root.adoptUInt32List(orphan); + assertTrue(orphan.isNull()); + assertTrue(root.hasUInt32List()); + checkList(root.asReader().getUInt32List(), [12, 34, 56]); +}; + +window['test_orphans_StructLists'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.initRoot(test.TestAllTypes); + + var list = root.initStructList(2); + list.get(0).setTextField("foo"); + list.get(1).setTextField("bar"); + assertTrue(root.hasStructList()); + + var orphan = root.disownStructList(); + assertFalse(orphan.isNull()); + + assertEquals(2, orphan.getReader().size()); + assertEquals("foo", orphan.getReader().get(0).getTextField().toString()); + assertEquals("bar", orphan.getReader().get(1).getTextField().toString()); + assertEquals(2, orphan.get().size()); + assertEquals("foo", orphan.get().get(0).getTextField().toString()); + assertEquals("bar", orphan.get().get(1).getTextField().toString()); + assertFalse(root.hasStructList()); + + root.adoptStructList(orphan); + assertTrue(orphan.isNull()); + assertTrue(root.hasStructList()); + assertEquals(2, root.asReader().getStructList().size()); + assertEquals("foo", root.asReader().getStructList().get(0).getTextField().toString()); + assertEquals("bar", root.asReader().getStructList().get(1).getTextField().toString()); +} + +window['test_orphans_Text'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.initRoot(test.TestAllTypes); + + root.setTextField("foo"); + assertTrue(root.hasTextField()); + + var orphan = root.disownTextField(); + assertFalse(orphan.isNull()); + + assertEquals("foo", orphan.getReader().toString()); + assertEquals("foo", orphan.get().toString()); + assertFalse(root.hasTextField()); + + root.adoptTextField(orphan); + assertTrue(orphan.isNull()); + assertTrue(root.hasTextField()); + assertEquals("foo", root.getTextField().toString()); +} + +window['test_orphans_Data'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.initRoot(test.TestAllTypes); + + root.setDataField(capnp.test.util.data("foo")); + assertTrue(root.hasDataField()); + + var orphan = root.disownDataField(); + assertFalse(orphan.isNull()); + + assertTrue(capnp.test.util.data("foo").equals(orphan.getReader())); + assertTrue(capnp.test.util.data("foo").equals(orphan.get())); + assertFalse(root.hasDataField()); + + root.adoptDataField(orphan); + assertTrue(orphan.isNull()); + assertTrue(root.hasDataField()); + assertTrue(capnp.test.util.data("foo").equals(root.getDataField())); +} + +window['test_orphans_NoCrossMessageTransfers'] = function() { + + var builder1 = new capnp.message.MallocMessageBuilder(); + var builder2 = new capnp.message.MallocMessageBuilder(); + var root1 = builder1.initRoot(test.TestAllTypes); + var root2 = builder2.initRoot(test.TestAllTypes); + + capnp.test.util.initTestMessage(root1.initStructField()); + + assertThrows(function() { root2.adoptStructField(root1.disownStructField()); }); +} + +window['test_orphans_OrphanageStruct'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + + var orphan = builder.getOrphanage().newOrphan(test.TestAllTypes); + capnp.test.util.initTestMessage(orphan.get()); + capnp.test.util.checkTestMessage(orphan.getReader()); + + var root = builder.initRoot(test.TestAllTypes); + root.adoptStructField(orphan); +} + +window['test_orphans_OrphanageList'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + + var orphan = builder.getOrphanage().newOrphan(capnp.list.ListOfPrimitives(capnp.prim.int32_t), 2); + orphan.get().set(0, 123); + orphan.get().set(1, 456); + + var reader = orphan.getReader(); + assertEquals(2, reader.size()); + assertEquals(123, reader.get(0)); + assertEquals(456, reader.get(1)); + + var root = builder.initRoot(test.TestAllTypes); + root.adoptUInt32List(orphan); +} + +window['test_orphans_OrphanageText'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + + var orphan = builder.getOrphanage().newOrphan(capnp.blob.Text, 8); + assertEquals(8, orphan.get().size()); + orphan.get().asUint8Array().set(new capnp.blob.StringTextReader("12345678").asUint8Array()); + + var root = builder.initRoot(test.TestAllTypes); + root.adoptTextField(orphan); + assertEquals("12345678", root.getTextField().toString()); +} + + +window['test_orphans_OrphanageData'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + + var orphan = builder.getOrphanage().newOrphan(capnp.blob.Data, 2); + assertEquals(2, orphan.get().size()); + orphan.get().asUint8Array()[0] = 123; + orphan.get().asUint8Array()[1] = 45; + + var root = builder.initRoot(test.TestAllTypes); + root.adoptDataField(orphan); + assertEquals(2, root.getDataField().size()); + assertEquals(123, root.getDataField().asUint8Array()[0]); + assertEquals(45, root.getDataField().asUint8Array()[1]); +} + +window['test_orphans_OrphanageStructCopy'] = function() { + var builder1 = new capnp.message.MallocMessageBuilder(); + var builder2 = new capnp.message.MallocMessageBuilder(); + + var root1 = builder1.initRoot(test.TestAllTypes); + capnp.test.util.initTestMessage(root1); + + var orphan = builder2.getOrphanage().newOrphanCopy(root1.asReader()); + capnp.test.util.checkTestMessage(orphan.getReader()); + + var root2 = builder2.initRoot(test.TestAllTypes); + root2.adoptStructField(orphan); +} + + +window['test_orphans_OrphanageListCopy'] = function() { + var builder1 = new capnp.message.MallocMessageBuilder(); + var builder2 = new capnp.message.MallocMessageBuilder(); + + var root1 = builder1.initRoot(test.TestAllTypes); + root1.setUInt32List([12, 34, 56]); + + var orphan = builder2.getOrphanage().newOrphanCopy( + root1.asReader().getUInt32List()); + capnp.test.util.checkList(orphan.getReader(), [12, 34, 56]); + + var root2 = builder2.initRoot(test.TestAllTypes); + root2.adoptUInt32List(orphan); +} + +window['test_orphans_OrphanageTextCopy'] = function() { + var builder = new capnp.message.MallocMessageBuilder(); + + var orphan = builder.getOrphanage().newOrphanCopy(new capnp.blob.StringTextReader("foobarba")); + assertEquals("foobarba", orphan.getReader().toString()); + + var root = builder.initRoot(test.TestAllTypes); + root.adoptTextField(orphan); +} + +window['test_orphans_OrphanageDataCopy'] = function() { + var builder = new capnp.message.MallocMessageBuilder(); + + var orphan = builder.getOrphanage().newOrphanCopy(capnp.test.util.data("foo")); + assertTrue(capnp.test.util.data("foo").equals(orphan.getReader())); + + var root = builder.initRoot(test.TestAllTypes); + root.adoptDataField(orphan); +} + +window['test_orphans_ZeroOut'] = function() { + var builder = new capnp.message.MallocMessageBuilder(); + + var orphan = builder.getOrphanage().newOrphan(test.TestAllTypes); + var orphanReader = orphan.getReader(); + capnp.test.util.initTestMessage(orphan.get()); + capnp.test.util.checkTestMessage(orphan.getReader()); + orphan.destroy(); + + // Once the Orphan destructor is called, the message should be zero'd out. + capnp.test.util.checkTestMessageAllZero(orphanReader); +} + +/* +window['test_orphans_StructObject'] = function() { + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.initRoot(test.TestObject); + + capnp.test.util.initTestMessage(root.ObjectField(test.TestAllTypes).initAs(test.TestAllTypes)); + assertTrue(root.hasObjectField()); + + var orphan = root.getObjectField().disownAs(test.TestAllTypes); + assertFalse(orphan.isNull()); + + capnp.test.util.checkTestMessage(orphan.getReader()); + assertFalse(root.hasObjectField()); + + root.getObjectField().adopt(orphan); + assertTrue(orphan.isNull()); + assertTrue(root.hasObjectField()); + capnp.test.util.checkTestMessage(root.asReader().getObjectField().getAs(test.TestAllTypes)); +} + +window['test_orphans_ListObject'] = function() { + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.initRoot(test.TestObject); + + root.getObjectField().setAs(capnp.list.ListOfPrimitives(capnp.prim.uint32_t), [12, 34, 56]); + assertTrue(root.hasObjectField()); + + var orphan = root.getObjectField().disownAs(capnp.list.ListOfPrimitives(capnp.prim.uint32_t)); + assertFalse(orphan.isNull()); + + capnp.test.util.genericCheckList(orphan.getReader(), [12, 34, 56]); + assertFalse(root.hasObjectField()); + + root.getObjectField().adopt(orphan); + assertTrue(orphan.isNull()); + assertNull(root.hasObjectField()); + capnp.test.util.genericCheckList(root.asReader().getObjectField().getAs(capnp.list.ListOfPrimitives(capnp.prim.uint32_t)), [12, 34, 56]); +} +*/ + +function allZero(dataView, begin, end) { + for (var pos=begin; pos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +goog.provide('capnp.tests.packed'); + +goog.require('capnp.serialize'); +goog.require('capnp.message'); +goog.require('capnp.packed'); +goog.require('capnp.test.util'); +goog.require('kj.io'); + +goog.require('capnproto_test.capnp.test'); + +var repeat = function(pattern, count) { + if (count < 1) return ''; + var result = ''; + while (count > 0) { + if (count & 1) result += pattern; + count >>= 1, pattern += pattern; + } + return result; +}; + +var bufferLength = function(buffer) { + if (toString.call(buffer) == '[object Array]') { + return buffer.length; + } + else { + return buffer.byteLength; + } +}; + +var equalBuffer = function(buffer1, buffer2) { + var len1 = bufferLength(buffer1); + var len2 = bufferLength(buffer2); + + if (len1 === len2) { + for (var i=0; i + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +goog.provide('capnp.tests.serialize'); + +goog.require('capnp.serialize'); +goog.require('capnp.message'); +goog.require('capnp.test.util'); +goog.require('kj.io'); + +goog.require('capnproto_test.capnp.test'); + +/** + * @constructor + */ +var TestInputStream = function(data, lazy) { + kj.io.InputStream.call(this); + + this.pos = 0; + this.end = data.byteLength; + this.data = data; + this.lazy = lazy; +}; + +TestInputStream.prototype = Object.create(kj.io.InputStream.prototype); +TestInputStream.prototype.constructor = TestInputStream; +TestInputStream.prototype.tryRead = function(buffer, offset, minBytes, maxBytes) { + assertTrue("Overran end of stream.", maxBytes <= (this.end - this.pos)); + var amount = this.lazy ? minBytes : maxBytes; + new Uint8Array(buffer, offset).set(new Uint8Array(this.data, this.pos, amount)); + this.pos += amount; + return amount; +}; + +/** + * @constructor + */ +var TestOutputStream = function(buffer, size) { + + var arrays = []; + this.byteLength = 0; + + this.write = function(buffer, fromOfs, size) { + assertTrue("Buffer underrun", fromOfs + size <= buffer.byteLength); + if (size > 0) { + arrays.push(buffer.slice(fromOfs, fromOfs + size)); + } + this.byteLength += size; + } + + this.getData = function() { + var totalLength = 0; + for (var i=0, len=arrays.length; i= other.byteLength) { + return false; + } + var arr1 = arrays[i]; + var arr2 = other.slice(ofs, ofs + arr1.byteLength); + if (arr1.byteLength != arr2.byteLength) { + return false; + } + for (var j=0, jlen=arr1.byteLength; j + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +goog.provide('capnp.tests.stringify'); + +goog.require('capnp.message'); +goog.require('capnp.test.util'); + +goog.require('capnproto_test.capnp.test'); + + +window['test_stringify_KjStringification'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.initRoot(test.TestAllTypes); + + assertEquals("()", root.toString()); + + capnp.test.util.initTestMessage(root); +}; + +window['test_stringify_Unions'] = function() { + + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.initRoot(test.TestUnion); + + root.getUnion0().setU0f0s16(123); + root.getUnion1().setU1f0sp("foo"); + root.getUnion2().setU2f0s1(true); + root.getUnion3().setU3f0s64(123456789012345678); + + /** FIXME + assertEquals("(" + + "union0 = (u0f0s16 = 123), " + + "union1 = (u1f0sp = \"foo\"), " + + "union2 = (u2f0s1 = true), " + + "union3 = (u3f0s64 = 123456789012345678))", + root.toString()); + + assertEquals("(u0f0s16 = 123)", root.getUnion0().toString()); + assertEquals("(u1f0sp = \"foo\")", root.getUnion1().toString()); + assertEquals("(u2f0s1 = true)", root.getUnion2().toString()); + assertEquals("(u3f0s64 = 123456789012345678)", root.getUnion3().toString()); + **/ +}; + +window['test_stringify_UnionDefaults'] =function() { + + var builder = new capnp.message.MallocMessageBuilder(); + var root = builder.initRoot(test.TestUnion); + + root.getUnion0().setU0f0s16(0); // Non-default field has default value. + root.getUnion1().setU1f0sp("foo"); // Non-default field has non-default value. + root.getUnion2().setU2f0s1(false); // Default field has default value. + root.getUnion3().setU3f0s1(true); // Default field has non-default value. + + assertEquals("(" + + "union0 = (u0f0s16 = 0), " + + "union1 = (u1f0sp = \"foo\"), " + + "union3 = (u3f0s1 = true))", + root.toString()); + + assertEquals("(u0f0s16 = 0)", root.getUnion0().toString()); + assertEquals("(u1f0sp = \"foo\")", root.getUnion1().toString()); + assertEquals("()", root.getUnion2().toString()); + assertEquals("(u3f0s1 = true)", root.getUnion3().toString()); +}; diff --git a/javascript/tests/test-util.js b/javascript/tests/test-util.js new file mode 100644 index 0000000..1a7b3b5 --- /dev/null +++ b/javascript/tests/test-util.js @@ -0,0 +1,494 @@ +/* + * Copyright (c) 2013, Julian Scheid + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +goog.provide('capnp.test.util'); + +goog.require('capnproto_test.capnp.test'); +goog.require('capnp.message'); + +var test = capnproto_test.capnp.test; + +var checkList = function(reader, expected) { + assertEquals(expected.length, reader.size()); + for (var i = 0; i < expected.length; i++) { + if (goog.isArray(expected[i])) { + assertArrayEquals(expected[i], reader.get(i)); + } + else { + assertEquals(expected[i], reader.get(i)); + } + } +} +capnp.test.util.checkList = checkList; + +var checkStrList = function(reader, expected) { + assertEquals(expected.length, reader.size()); + for (var i = 0; i < expected.length; i++) { + assertEquals(expected[i], reader.get(i).toString()); + } +} +capnp.test.util.checkStrList = checkStrList; + +var checkDataList = function(reader, expected) { + assertEquals(expected.length, reader.size()); + for (var i = 0; i < expected.length; i++) { + assertTrue(expected[i].equals(reader.get(i))); + } +} +capnp.test.util.checkDataList = checkDataList; + +var checkFloatList = function(reader, expected, maxDelta) { + assertEquals(expected.length, reader.size()); + for (var i = 0; i < expected.length; i++) { + assertRoughlyEquals(expected[i], reader.get(i), maxDelta); + } +} +capnp.test.util.checkFloatList = checkFloatList; + +var data = function(str) { + var strUtf8 = unescape(encodeURIComponent(str)); + var ab = new Uint8Array(strUtf8.length); + for (var i = 0; i < strUtf8.length; i++) { + ab[i] = strUtf8.charCodeAt(i); + } + + return capnp.blob.Data.Reader(ab, strUtf8.length); +} +capnp.test.util.data = data; + +capnp.test.util.initTestMessage = function(builder) { + builder.setVoidField(undefined); + builder.setVoidField(); // Means the same as above. + builder.setBoolField(true); + builder.setInt8Field(-123); + builder.setInt16Field(-12345); + builder.setInt32Field(-12345678); + builder.setInt64Field([-28745, -2249056121]); // -123456789012345 + builder.setUInt8Field(234); + builder.setUInt16Field(45678); + builder.setUInt32Field(3456789012); + builder.setUInt64Field([2874452364, 3944680146]); // 12345678901234567890 + builder.setFloat32Field(1234.5); + builder.setFloat64Field(-123e45); + builder.setTextField("foo"); + builder.setDataField(data("bar")); + { + var subBuilder = builder.initStructField(); + subBuilder.setVoidField(undefined); + subBuilder.setBoolField(true); + subBuilder.setInt8Field(-12); + subBuilder.setInt16Field(3456); + subBuilder.setInt32Field(-78901234); + subBuilder.setInt64Field([13222, 954757966]); // 56789012345678 + subBuilder.setUInt8Field(90); + subBuilder.setUInt16Field(1234); + subBuilder.setUInt32Field(56789012); + subBuilder.setUInt64Field([80484641, 309267154]); // 345678901234567890 + subBuilder.setFloat32Field(-1.25e-10); + subBuilder.setFloat64Field(345); + subBuilder.setTextField("baz"); + subBuilder.setDataField(data("qux")); + { + var subSubBuilder = subBuilder.initStructField(); + subSubBuilder.setTextField("nested"); + subSubBuilder.initStructField().setTextField("really nested"); + } + subBuilder.setEnumField(test.TestEnum.BAZ); + + subBuilder.setVoidList([undefined, undefined, undefined]); + subBuilder.setBoolList([false, true, false, true, true]); + subBuilder.setInt8List([12, -34, -0x80, 0x7f]); + subBuilder.setInt16List([1234, -5678, -0x8000, 0x7fff]); + // gcc warns on -0x800... and the only work-around I could find was to do -0x7ff...-1. + subBuilder.setInt32List([12345678, -90123456, -0x7fffffff - 1, 0x7fffffff]); + subBuilder.setInt64List([ [ 28744, 2249056121 ], [ -158070, -49056466 ], [ -2147483648, 0 ], [ 0x7fffffff, 0xffffffff ] ]); + subBuilder.setUInt8List([12, 34, 0, 0xff]); + subBuilder.setUInt16List([1234, 5678, 0, 0xffff]); + subBuilder.setUInt32List([12345678, 90123456, 0, 0xffffffff]); + subBuilder.setUInt64List([ [ 28744, 2249056121 ], [ 158069, 49056466 ], [0, 0], [ 4294967295, 4294967295 ] ]); // [123456789012345, 678901234567890, 0, 0xffffffffffffffff] + subBuilder.setFloat32List([0, 1234567, 1e37, -1e37, 1e-37, -1e-37]); + subBuilder.setFloat64List([0, 123456789012345, 1e306, -1e306, 1e-306, -1e-306]); + subBuilder.setTextList(["quux", "corge", "grault"]); + subBuilder.setDataList([data("garply"), data("waldo"), data("fred")]); + { + var listBuilder = subBuilder.initStructList(3); + listBuilder.get(0).setTextField("x structlist 1"); + listBuilder.get(1).setTextField("x structlist 2"); + listBuilder.get(2).setTextField("x structlist 3"); + } + subBuilder.setEnumList([test.TestEnum.QUX, test.TestEnum.BAR, test.TestEnum.GRAULT]); + } + builder.setEnumField(test.TestEnum.CORGE); + + builder.initVoidList(6); + builder.setBoolList([true, false, false, true]); + builder.setInt8List([111, -111]); + builder.setInt16List([11111, -11111]); + builder.setInt32List([111111111, -111111111]); + builder.setInt64List([ [ 258700715, 734294471 ], [ -258700716, -734294471 ] ]); // [1111111111111111111, -1111111111111111111] + builder.setUInt8List([111, 222]); + builder.setUInt16List([33333, 44444]); + builder.setUInt32List([3333333333]); + builder.setUInt64List([ [ 2587007151, 3047977415 ] ]); // [11111111111111111111] + builder.setFloat32List([5555.5, Infinity, -Infinity, NaN]); + builder.setFloat64List([7777.75, Infinity, -Infinity, NaN]); + builder.setTextList(["plugh", "xyzzy", "thud"]); + builder.setDataList([data("oops"), data("exhausted"), data("rfc3092")]); + { + var listBuilder = builder.initStructList(3); + listBuilder.get(0).setTextField("structlist 1"); + listBuilder.get(1).setTextField("structlist 2"); + listBuilder.get(2).setTextField("structlist 3"); + } + builder.setEnumList([test.TestEnum.FOO, test.TestEnum.GARPLY]); +}; + +capnp.test.util.checkTestMessage = function(reader) { + + assertEquals(undefined, reader.getVoidField()); + assertEquals(true, reader.getBoolField()); + assertEquals(-123, reader.getInt8Field()); + assertEquals(-12345, reader.getInt16Field()); + assertEquals(-12345678, reader.getInt32Field()); + assertArrayEquals([ -28745, 2045911175 ], reader.getInt64Field()); // -123456789012345 + assertEquals(234, reader.getUInt8Field()); + assertEquals(45678, reader.getUInt16Field()); + assertEquals(3456789012, reader.getUInt32Field()); + assertArrayEquals([ 2874452364, 3944680146 ], reader.getUInt64Field()); // 12345678901234567890 + assertRoughlyEquals(1234.5, reader.getFloat32Field(), 1e-10); + assertRoughlyEquals(-123e45, reader.getFloat64Field(), 1e-10); // FIXME + assertEquals("foo", reader.getTextField().toString()); + assertTrue(data("bar").equals(reader.getDataField())); + { + var subReader = reader.getStructField(); + assertEquals(undefined, subReader.getVoidField()); + assertEquals(true, subReader.getBoolField()); + assertEquals(-12, subReader.getInt8Field()); + assertEquals(3456, subReader.getInt16Field()); + assertEquals(-78901234, subReader.getInt32Field()); + assertArrayEquals([13222, 954757966], subReader.getInt64Field()); // 56789012345678 + assertEquals(90, subReader.getUInt8Field()); + assertEquals(1234, subReader.getUInt16Field()); + assertEquals(56789012, subReader.getUInt32Field()); + assertArrayEquals([80484641, 309267154], subReader.getUInt64Field()); // 345678901234567890 + assertRoughlyEquals(-1.25e-10, subReader.getFloat32Field(), 1e-10); + assertRoughlyEquals(345, subReader.getFloat64Field(), 1e-10); // FIXME + assertEquals("baz", subReader.getTextField().toString()); + assertTrue(data("qux").equals(subReader.getDataField())); + { + var subSubReader = subReader.getStructField(); + assertEquals("nested", subSubReader.getTextField().toString()); + assertEquals("really nested", subSubReader.getStructField().getTextField().toString()); + } + assertEquals(test.TestEnum.BAZ, subReader.getEnumField()); + + checkList(subReader.getVoidList(), [undefined, undefined, undefined]); + checkList(subReader.getBoolList(), [false, true, false, true, true]); + checkList(subReader.getInt8List(), [12, -34, -0x80, 0x7f]); + checkList(subReader.getInt16List(), [1234, -5678, -0x8000, 0x7fff]); + // gcc warns on -0x800... and the only work-around I could find was to do -0x7ff...-1. + checkList(subReader.getInt32List(), [12345678, -90123456, -0x7fffffff - 1, 0x7fffffff]); + checkList(subReader.getInt64List(), [ [ 28744, 2249056121 - 0x100000000 ], [ -158070, -49056466 ], [ -2147483648, 0 ], [ 0x7fffffff, -1 ] ]); + checkList(subReader.getUInt8List(), [12, 34, 0, 0xff]); + checkList(subReader.getUInt16List(), [1234, 5678, 0, 0xffff]); + checkList(subReader.getUInt32List(), [12345678, 90123456, 0, 0xffffffff]); + checkList(subReader.getUInt64List(), [ [ 28744, 2249056121 ], [ 158069, 49056466 ], [0, 0], [ 4294967295, 4294967295 ] ]); + checkFloatList(subReader.getFloat32List(), [0.0, 1234567.0, 1e37, -1e37, 1e-37, -1e-37], 1e30); + checkList(subReader.getFloat64List(), [0.0, 123456789012345.0, 1e306, -1e306, 1e-306, -1e-306]); + checkStrList(subReader.getTextList(), ["quux", "corge", "grault"]); + checkDataList(subReader.getDataList(), [data("garply"), data("waldo"), data("fred")]); + { + var listReader = subReader.getStructList(); + assertEquals(3, listReader.size()); + assertEquals("x structlist 1", listReader.get(0).getTextField().toString()); + assertEquals("x structlist 2", listReader.get(1).getTextField().toString()); + assertEquals("x structlist 3", listReader.get(2).getTextField().toString()); + } + checkList(subReader.getEnumList(), [test.TestEnum.QUX, test.TestEnum.BAR, test.TestEnum.GRAULT]); + } + assertEquals(test.TestEnum.CORGE, reader.getEnumField()); + + assertEquals(6, reader.getVoidList().size()); + checkList(reader.getBoolList(), [true, false, false, true]); + checkList(reader.getInt8List(), [111, -111]); + checkList(reader.getInt16List(), [11111, -11111]); + checkList(reader.getInt32List(), [111111111, -111111111]); + checkList(reader.getInt64List(), [ [ 258700715, 734294471 ], [ -258700716, -734294471 ] ]); // [ 1111111111111111111, -1111111111111111111 ] + checkList(reader.getUInt8List(), [111, 222]); + checkList(reader.getUInt16List(), [33333, 44444]); + checkList(reader.getUInt32List(), [3333333333]); + checkList(reader.getUInt64List(), [ [ 2587007151, 3047977415 ] ]); // 11111111111111111111 + { + var listReader = reader.getFloat32List(); + assertEquals(4, listReader.size()); + assertEquals(5555.5, listReader.get(0)); + assertEquals(Infinity, listReader.get(1)); + assertEquals(-Infinity, listReader.get(2)); + assertTrue(isNaN(listReader.get(3))); + } + { + var listReader = reader.getFloat64List(); + assertEquals(4, listReader.size()); + assertEquals(7777.75, listReader.get(0)); + assertEquals(Infinity, listReader.get(1)); + assertEquals(-Infinity, listReader.get(2)); + assertTrue(isNaN(listReader.get(3))); + } + checkStrList(reader.getTextList(), ["plugh", "xyzzy", "thud"]); + checkDataList(reader.getDataList(), [data("oops"), data("exhausted"), data("rfc3092")]); + { + var listReader = reader.getStructList(); + assertEquals(3, listReader.size()); + assertEquals("structlist 1", listReader.get(0).getTextField().toString()); + assertEquals("structlist 2", listReader.get(1).getTextField().toString()); + assertEquals("structlist 3", listReader.get(2).getTextField().toString()); + } + checkList(reader.getEnumList(), [test.TestEnum.FOO, test.TestEnum.GARPLY]); +} + + +capnp.test.util.genericInitListDefaults = function(builder) { + var lists = builder.initLists(); + + lists.initList0(2); + lists.initList1(4); + lists.initList8(2); + lists.initList16(2); + lists.initList32(2); + lists.initList64(2); + lists.initListP(2); + + lists.getList0().get(0).setF(undefined); + lists.getList0().get(1).setF(undefined); + lists.getList1().get(0).setF(true); + lists.getList1().get(1).setF(false); + lists.getList1().get(2).setF(true); + var val = lists.getList1().get(2).getF(); + + lists.getList1().get(3).setF(true); + lists.getList8().get(0).setF(123); + lists.getList8().get(1).setF(45); + lists.getList16().get(0).setF(12345); + lists.getList16().get(1).setF(6789); + lists.getList32().get(0).setF(123456789); + lists.getList32().get(1).setF(234567890); + lists.getList64().get(0).setF([287445, 1015724736]); // 1234567890123456; + lists.getList64().get(1).setF([546145, 3987360647]); // 2345678901234567; + lists.getListP().get(0).setF("foo"); + lists.getListP().get(1).setF("bar"); + + { + var l = lists.initInt32ListList(3); + l.set(0, [1, 2, 3]); + checkList(lists.asReader().getInt32ListList().get(0), [1, 2, 3]); + + + l.set(1, [4, 5]); + l.set(2, [12341234]); + } + + { + var l = lists.initTextListList(3); + l.set(0, ["foo", "bar"]); + l.set(1, ["baz"]); + l.set(2, ["qux", "corge"]); + } + + { + var l = lists.initStructListList(2); + var e = l.init(0, 2); + e.get(0).setInt32Field(123); + e.get(1).setInt32Field(456); + e = l.init(1, 1); + e.get(0).setInt32Field(789); + } +} + + +capnp.test.util.genericCheckListDefaults = function(reader) { + var lists = reader.getLists(); + assertTrue(lists.hasList0()); + assertTrue(lists.hasList1()); + assertTrue(lists.hasList8()); + assertTrue(lists.hasList16()); + assertTrue(lists.hasList32()); + assertTrue(lists.hasList64()); + assertTrue(lists.hasListP()); + + assertEquals(2, lists.getList0().size()); + assertEquals(4, lists.getList1().size()); + assertEquals(2, lists.getList8().size()); + assertEquals(2, lists.getList16().size()); + assertEquals(2, lists.getList32().size()); + assertEquals(2, lists.getList64().size()); + assertEquals(2, lists.getListP().size()); + + assertEquals(undefined, lists.getList0().get(0).getF()); + assertEquals(undefined, lists.getList0().get(1).getF()); + assertTrue(lists.getList1().get(0).getF()); + assertFalse(lists.getList1().get(1).getF()); + assertTrue(lists.getList1().get(2).getF()); + assertTrue(lists.getList1().get(3).getF()); + assertEquals(123, lists.getList8().get(0).getF()); + assertEquals(45, lists.getList8().get(1).getF()); + assertEquals(12345, lists.getList16().get(0).getF()); + assertEquals(6789, lists.getList16().get(1).getF()); + assertEquals(123456789, lists.getList32().get(0).getF()); + assertEquals(234567890, lists.getList32().get(1).getF()); + assertArrayEquals([ 287445, 1015724736 ], lists.getList64().get(0).getF()); // 1234567890123456 + assertArrayEquals([ 546145, 3987360647 ], lists.getList64().get(1).getF()); // 2345678901234567 + assertEquals("foo", lists.getListP().get(0).getF().toString()); + assertEquals("bar", lists.getListP().get(1).getF().toString()); + + { + var l = lists.getInt32ListList(); + assertEquals(3, l.size()); + + checkList(l.get(0), [1, 2, 3]); + checkList(l.get(1), [4, 5]); + checkList(l.get(2), [12341234]); + } + + { + var l = lists.getTextListList(); + assertEquals(3, l.size()); + checkStrList(l.get(0), ["foo", "bar"]); + checkStrList(l.get(1), ["baz"]); + checkStrList(l.get(2), ["qux", "corge"]); + } + + { + var l = lists.getStructListList(); + assertEquals(2, l.size()); + var e = l.get(0); + assertEquals(2, e.size()); + assertEquals(123, e.get(0).getInt32Field()); + assertEquals(456, e.get(1).getInt32Field()); + e = l.get(1); + assertEquals(1, e.size()); + assertEquals(789, e.get(0).getInt32Field()); + } +} + +capnp.test.util.checkTestMessageAllZero = function(reader) { + assertEquals(undefined, reader.getVoidField()); + assertEquals(false, reader.getBoolField()); + assertEquals(0, reader.getInt8Field()); + assertEquals(0, reader.getInt16Field()); + assertEquals(0, reader.getInt32Field()); + assertArrayEquals([0, 0], reader.getInt64Field()); + assertEquals(0, reader.getUInt8Field()); + assertEquals(0, reader.getUInt16Field()); + assertEquals(0, reader.getUInt32Field()); + assertArrayEquals([0, 0], reader.getUInt64Field()); + assertEquals(0, reader.getFloat32Field()); + assertEquals(0, reader.getFloat64Field()); + assertEquals("", reader.getTextField().toString().toString()); + assertTrue(data("").equals(reader.getDataField())); + { + var subReader = reader.getStructField(); + assertEquals(undefined, subReader.getVoidField()); + assertEquals(false, subReader.getBoolField()); + assertEquals(0, subReader.getInt8Field()); + assertEquals(0, subReader.getInt16Field()); + assertEquals(0, subReader.getInt32Field()); + assertArrayEquals([0, 0], subReader.getInt64Field()); + assertEquals(0, subReader.getUInt8Field()); + assertEquals(0, subReader.getUInt16Field()); + assertEquals(0, subReader.getUInt32Field()); + assertArrayEquals([0, 0], subReader.getUInt64Field()); + assertEquals(0, subReader.getFloat32Field()); + assertEquals(0, subReader.getFloat64Field()); + assertEquals("", subReader.getTextField().toString()); + assertTrue(data("").equals(subReader.getDataField())); + { + var subSubReader = subReader.getStructField(); + assertEquals("", subSubReader.getTextField().toString()); + assertEquals("", subSubReader.getStructField().getTextField().toString()); + } + + assertEquals(0, subReader.getVoidList().size()); + assertEquals(0, subReader.getBoolList().size()); + assertEquals(0, subReader.getInt8List().size()); + assertEquals(0, subReader.getInt16List().size()); + assertEquals(0, subReader.getInt32List().size()); + assertEquals(0, subReader.getInt64List().size()); + assertEquals(0, subReader.getUInt8List().size()); + assertEquals(0, subReader.getUInt16List().size()); + assertEquals(0, subReader.getUInt32List().size()); + assertEquals(0, subReader.getUInt64List().size()); + assertEquals(0, subReader.getFloat32List().size()); + assertEquals(0, subReader.getFloat64List().size()); + assertEquals(0, subReader.getTextList().size()); + assertEquals(0, subReader.getDataList().size()); + assertEquals(0, subReader.getStructList().size()); + } + + assertEquals(0, reader.getVoidList().size()); + assertEquals(0, reader.getBoolList().size()); + assertEquals(0, reader.getInt8List().size()); + assertEquals(0, reader.getInt16List().size()); + assertEquals(0, reader.getInt32List().size()); + assertEquals(0, reader.getInt64List().size()); + assertEquals(0, reader.getUInt8List().size()); + assertEquals(0, reader.getUInt16List().size()); + assertEquals(0, reader.getUInt32List().size()); + assertEquals(0, reader.getUInt64List().size()); + assertEquals(0, reader.getFloat32List().size()); + assertEquals(0, reader.getFloat64List().size()); + assertEquals(0, reader.getTextList().size()); + assertEquals(0, reader.getDataList().size()); + assertEquals(0, reader.getStructList().size()); +} + + +/** + * @constructor + */ +var TestMessageBuilder = function(desiredSegmentCount) { + // A MessageBuilder that tries to allocate an exact number of total segments, by allocating + // minimum-size segments until it reaches the number, then allocating one large segment to + // finish. + + capnp.message.MallocMessageBuilder.call(this, 0, capnp.message.AllocationStrategy.FIXED_SIZE); + + this.allocateSegment = function(minimumSize) { + if (desiredSegmentCount <= 1) { + if (desiredSegmentCount < 1) { + fail("Allocated more segments than desired."); + } else { + --desiredSegmentCount; + } + return capnp.message.MallocMessageBuilder.prototype.allocateSegment.call(this, capnp.message.SUGGESTED_FIRST_SEGMENT_WORDS); + } else { + --desiredSegmentCount; + return capnp.message.MallocMessageBuilder.prototype.allocateSegment.call(this, minimumSize); + } + }; +}; +TestMessageBuilder.prototype = Object.create(capnp.message.MallocMessageBuilder.prototype); +TestMessageBuilder.prototype.constructor = TestMessageBuilder; + +capnp.test.util.TestMessageBuilder = TestMessageBuilder;