After a quick chat with TJ and Trevor on IRC, I've decided to write up a
quick sample of the kind of API I would like as a JavaScript person.  The
evolving code is at https://gist.github.com/creationix/5954513.

I'll inline some snippets here for conversation.

First is an ultra simple library that has an "addNumbers" function that
adds two values as integers.

#include "js_api.h"


// Equal to the following JS snippet:
// function addNumbers(a, b) { a |= 0; b |= 0; return a + b; }
bool simple_add_numbers(js_context* C) {
  int32_t a = js_to_int32(C, 1);
  int32_t b = js_to_int32(C, 2);
  return js_return(C, js_create_int32(C, a + b));
}

// The module exports an object with functions on it.
bool export_simple(js_context* C) {
  int exports = js_create_object(C); // Create a new object and place
it on the stack
  js_set_function(C, exports, "addNumbers", simple_add_numbers);
  return js_return(C, exports);
}

// If we wanted to export the add function directly, we could do
int export_simple(js_context* C) {
  return js_return_function(C, simple_add_numbers);
}



A slightly more complicated example is one that creates a constructor with
a prototype method.

#include "js_api.h"

// Sometimes you want to access the js "this" value in a function

// function Point(x, y) { this.x = x; this.y = y; }
bool point_constructor(js_context* C) {
  js_set(C, 0, "x", 1); // this.x = arguments[0]
  js_set(C, 0, "y", 2); // this.y = arguments[1]
  return true;          // js returns undefined, but C returns true
meaning no error.
}

// Point.prototype.add = function () { var x = this.x | 0; var y =
this.y | 0; return x + y; };
bool point_add(js_context* C) {
  int32_t x = js_get_int32(C, 0, "x"); // var x = this.x
  int32_t y = js_get_int32(C, 0, "y"); // var y = this.y
  return js_return_int32(C, x + y);    // return x + y
}

bool export_point(js_context* C) {
  int Point = js_create_function(C, point_constructor);
  // All functions in JavaScript already have a prototype object.  Get it.
  int proto = js_load(C, Point, "prototype");
  // Add a function to the prototype object
  js_set_function(C, proto, "add", point_add);
  // Then return the constructor as the module exports
  return js_return(C, Point);
}

I'm still working out sample APIs for things like passing a C struct to JS
and back, wrapping external binary buffers, and binary data in general, but
this should be enough to get a general idea of the kind of API I'm looking
for.

The important thing is that it's as 1:1 with the JS it represents as
possible, but in simple C.

On Mon, Jul 8, 2013 at 9:58 PM, Tim Caswell <t...@creationix.com> wrote:

> I can vouch for a stable and documented C API.  The main thing that
> stopped me from re-implementing node on top of SpiderMonkey was the SM APIs
> changing constantly and the docs on the wiki being constantly out of date.
>  They were too new for the last stable release and too old for the latest
> mainline code.  (Also the build system for SM itself was a mess and very
> particular).
>
> Contrast with my luvit project which I did largely on my own for the first
> few months and had a good chunk of node re-implemented.  Lua's C API is
> simple and consistent.  I didn't have to use C++ at all which made things
> much easier for me.  C is a fairly simple language, C++ is crazy
> complicated.
>
> I'm a scripter.  I've been writing programs in scripting languages for
> over 20 years.  I've been know to write a node addon or two because I
> really like the extra power it gives (node-webgl was a fun 24 hour hack).
>
> My only advice for the new common API is to keep it simple and avoid C++
> classes.  I'm not encouraging the stack oriented nature of the Lua API, but
> being able to work in terms of objects, properties, functions, etc (the
> same terms you use in JS) would be great.  Treat everything as the simple
> data it is.
>
> I can draft some APIs based on my experience with writing libuv bindings
> for various runtimes if you're interested.
>
> -Tim Caswell
>
>
> On Mon, Jul 8, 2013 at 1:35 PM, Timothy J Fontaine 
> <tjfonta...@gmail.com>wrote:
>
>> [cross post from http://atxconsulting.com/2013/07/06/rewrite-it-anyway/]
>>
>> Node v1.0 is approaching, and v0.12 is imminent (as far as that goes for
>> FOSS
>> projects). As we work towards getting v0.12 out the door, there have been
>> a lot
>> of changes happening for node's primary dependency v8. Ben is working on
>> moving
>> us to the 3.20 branch, follow his progress
>> [here](https://github.com/joyent/node/pull/5804).
>>
>> As you can tell this is a signficant change to the API, which requires a
>> touch
>>  of virtually every file in our `src/`, this has been a huge headache for
>> him,
>> and will ultimately cause a huge headache for developers of binary addons.
>>
>> You're going to have to `#ifdef` around significant portions of the API
>> to keep
>> your module working across different version of node, this is going to
>> cause
>> endless amounts of pain and issues for node and developers who have for
>> the
>> most part been accepting of the churn in our underspecified addon API.
>>
>> This one is going to hurt.
>>
>> A lot.
>>
>> ## TL;DR -- A modest proposal
>>
>> Since you're going to have to rewrite your module anyway, it's time for
>> node to
>> specify and export the API we are going to "bless" for addons. That is,
>> just
>> what API we are going to support and make sure continues to work from
>> minor and
>> major releases, as well as a deprecation policy.
>>
>> More specifically I think we should be exporting a separate (and not
>> equal)
>> wrapper around (at the very least) javascript object creation, get/set,
>> function
>> calling.
>>
>>  Additionally we should package and distribute (if possible in npm) a
>> transitional library/headers which module authors can target today which
>> will
>> allow their module to compile and work from v0.8 through v1.0
>>
>> ## The Platform Problem
>>
>> We currently allow platforms/distributors to build against shared (their
>> own)
>> versions of many of our dependencies, including but not limited to:
>>
>>  * v8
>>    - Holy crap, we're about as tightly coupled to the version of v8 we
>> ship as
>> chromium itself is.
>>  * libuv
>>    - If we weren't strictly coupled to v8, we certainly are for libuv,
>> there
>> would be no (useful) node, without libuv.
>>  * openssl
>>    - This is a must for linux distributions, who like to break DSA keys
>> and then
>> make every dependency vulnerable as a result (sorry Debian, I keed I
>> keed).
>>    - This actually allows distributors who know specific things about
>> their
>> platform to enable/disable the features that allow it to run best.
>>  * zlib
>>    - Meh, this isn't such a big deal, it doesn't really change all that
>> often.
>>  * http_parser
>>    - Really? People ship this as a separate library?
>>
>> This functionality was added to appease platform builders, the likes of
>> Debian,
>> Fedora, and even SmartOS. However, doing so has complicated and muddled
>> the
>> scenario of building and linking binary addons.
>>
>> Currently node-gyp downloads the sourceball, extracts the headers from it,
>> and makes some assumptions from `process.config` about how to build your
>> addon.
>> In practice this has been working reasonably well.
>>
>> However, I'm very concerned about this as a long term strategy. It's
>> possible
>> for someone to have tweaked or twisted the node (or one of its
>> dependencies)
>> builds, which could lead to some unintended consequences. In the "best"
>> case,
>> you'll get a compiler error from a changed API or clashing symbol. In the
>> worst
>> case they have modified the ABI which will manifest itself in unexpected
>> and
>> often subtle ways.
>>
>> Not to mention that we have no good answer on how to build and link addon
>> modules against the proper version of a shared dependency (what if the
>> system
>> has multiple openssl's, what if they compiled against it in one place,
>> but now
>> run against it in another).
>>
>> And last but not least, how do modules consume symbols from our
>> dependencies
>> that node itself doesn't consume. Consider a specific crypto routine from
>> openssl that you want to provide as an addon module because node doesn't
>> currently have an interface for it.
>>
>> ## Enemies without, and enemies within
>>
>> As if it weren't bad enough that platforms may ship against a version of
>> v8
>> that we haven't blessed, we (and addon developers) have to fight against
>> the
>> beast that is the v8 API churn.
>>
>> I don't really fault Google and the chromium or v8 team for how they are
>> handling this, more often then not we just end up with ugly compile time
>> deprecation warnings, letting us know the world is about to break.
>>
>> However, there have been times -- like right now -- where node can't
>> paper over
>> the drastic change in the v8 API for module developers. And as a result we
>> begrudgingly pass the API change to module authors.
>>
>> To paraphrase, don't forget that execrement will inevitably lose its
>> battle
>> with gravity.
>>
>> So what are we going to do?
>>
>> ## Meat and Potatoes
>>
>> This is where I don't particularly have everything fleshed out, and I'm
>> sure I
>> will take a considerable amount of heat from people on API decisions that
>> haven't been made.
>>
>> I want to export the following interfaces:
>>
>>  * `node/js.h`
>>    - Object creation and manipulation.
>>    - Function calling and Error throwing.
>>  * `node/platform.h`
>>    - IO and event loop abstraction.
>>  * `node/ssl.h`
>>  * `node/zlib.h`
>>  * `node/http.h`
>>
>> While I am not particularly attached to the names of these headers, each
>> represent an interface that I think module authors would opt to target. I
>> only
>> feel strongly that we export `js` and `platform` as soon as possible as
>> they are the primary interactions for every module.
>>
>> ### Basic Principles
>>
>> There are only a few principles:
>>
>>  * Avoid (like the plague) any scenario where we expose an ABI to module
>> authors.
>>    - Where possible use opaque handles and getter/setter functions.
>>  * The exported API should be a reliable interface which authors can
>> depend on
>> working across releases.
>>  * While a dependency may change its API, we have committed to our
>> external API
>> and need to provide a transitional interface in accordance with our
>> deprecation
>> policy.
>>  * The API should never expose an implementation detail to module authors
>> (A
>> spidermonkey backed node one day?).
>>
>> ### Platform
>>
>> The `platform` interface is the easiest to discuss, but the pattern would
>> follow for `ssl`, `zlib`, and `http`.
>>
>> This would just rexport the existing `uv` API, however with a C-style
>> namespace
>> of `node_`. Any struct passing should be avoided, and libuv would need to
>> be
>> updated to reflect that.
>>
>> ### JS
>>
>> I expect the `js` interface to be the most contentious, and also fraught
>> with
>> peril.
>>
>> The interface for addon authors should be C, I don't want to forsake the
>> C++
>> folk, but I think the binding for that should be based on our C interface.
>>
>> I was going to describe my ideal interface, and frame it in context of my
>> ruby
>> and python experience. However, after a brief investigation, the JSAPI for
>> spidermonkey exports almost exactly the API I had in mind. So read about
>> that
>> [here](
>> https://developer.mozilla.org/en-US/docs/SpiderMonkey/JSAPI_User_Guide).
>>
>> Would it make sense, and would it be worth the effort, for node to export
>> a
>> JSAPI compatible interface?
>>
>> Would it make more sense to export a JSAPI influenced API currently
>> targetted
>> at v8 which could be trivially extended to also support spidermonkey?
>>
>> UPDATE 2013-07-08:
>>
>> > It's interesting and worthy to have a conversation about being able to
>> > provide a backend neutral object model, though our current coupling to
>> v8 and
>> > its usage in existing addons may not make it possible to entirely hide
>> away
>> > the eccentricities of the v8 API. But what we can provide is an
>> interface
>> > that is viable to target against from release to release regardless of
>> how
>> > the public v8 API changes.
>>
>> ## Prior Art
>>
>> A lot of these ideas came from a discussion I had with
>> [Joshua Clulow](http://blog.sysmgr.org/) while en route to
>> [NodeConf](http://nodeconf.com).
>>
>> Part of that conversation was about [v8+](
>> https://github.com/wesolows/v8plus)
>> which was written by a particularly talented coworker, who had a rather
>> nasty
>> experience writing for the existing C++ API (such as it is).
>>
>> There's some overlap in how it works and how I envisioned the new API.
>> However,
>> I'm not sure I'm particularly fond of automatically converting objects
>> into
>> nvlists, though that does solve some of the release and retain issues.
>>
>> In general I would advocate opaque handles and getter and setter
>> functions,
>> with a helper API which could do that wholesale conversion for you.
>>
>> Really though this matters less in a world where addon authors are
>> following
>> some defined "Best Practices".
>>
>>  * Only pass and return "primitives" to/from the javascript/C boundary
>>    - Primitives would be things like: `String`, `Number`, `Buffer`.
>>  * Only perform objection manipulation in javascript where the JIT can
>> work
>> its magic
>>
>> ## Dessert
>>
>> Work on this needs to begin as soon as possible. We should be able to
>> distribute it in npm, and authors should be able to target it by
>> including a
>> few headers in their source and adding a dependency stanza in their
>> `binding.gyp`, and by doing so their module will work from v0.8 through
>> v1.0
>>
>> I mean, you're going to have to rewrite it anyway.
>>
>> --
>> --
>> Job Board: http://jobs.nodejs.org/
>> Posting guidelines:
>> https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
>> You received this message because you are subscribed to the Google
>> Groups "nodejs" group.
>> To post to this group, send email to nodejs@googlegroups.com
>> To unsubscribe from this group, send email to
>> nodejs+unsubscr...@googlegroups.com
>> For more options, visit this group at
>> http://groups.google.com/group/nodejs?hl=en?hl=en
>>
>> ---
>> You received this message because you are subscribed to the Google Groups
>> "nodejs" group.
>> To unsubscribe from this group and stop receiving emails from it, send an
>> email to nodejs+unsubscr...@googlegroups.com.
>> For more options, visit https://groups.google.com/groups/opt_out.
>>
>>
>>
>
>

-- 
-- 
Job Board: http://jobs.nodejs.org/
Posting guidelines: 
https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
You received this message because you are subscribed to the Google
Groups "nodejs" group.
To post to this group, send email to nodejs@googlegroups.com
To unsubscribe from this group, send email to
nodejs+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/nodejs?hl=en?hl=en

--- 
You received this message because you are subscribed to the Google Groups 
"nodejs" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to nodejs+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.


Reply via email to