jsgen by Marcello Bastéa-Forte

version: beta 8


Contents:


Introduction

jsgen is a program that allows you to easily interface C++ classes from JavaScript in SpiderMonkey.  SpiderMonkey is a JavaScript engine for C/C++.

Why jsgen?

If you look at the code typically required to create a JavaScript class for SpiderMonkey, it's quite intimidating. To build a class that can be used in JavaScript requires lots of wrapper code that does conversions between SpiderMonkey datatypes and regular C++ datatypes.

Although it's not impossible, it quickly becomes tedious if you have many classes that need to be integrated.

So what exactly does jsgen do?

jsgen takes a regular C++ class header file, looks for special comments around methods and constructors you want to make accessible from JavaScript, and generates a header and source file for direct inclusion into that class's header and source file.

The code contains code wrappers and the necessary functions to create and use the C++ classes as objects in JavaScript.  The advantage here is that you can design your in C++, instead of separately for C++ and JavaScript.

The generated code links each C++ class instance to a JSObject *, and in each JavaScript object there is a link to an instance of the C++ class.  This allows you to use the same instances from both entry points. (An object made in C++ won't automatically create a JavaScript object unless you call getJSObject() on it.)

How is it used?

You can take an existing class header, add a couple comments here and there, add an include in your .h and .cpp files, and run it through this program and it will be ready for use in a JavaScript context.

jsgen works best if you're using a single .cpp/.h file for each separate class.

After you define your context, you must initialize each class using the JSInit function generated by jsgen.  This makes SpiderMonkey aware of your class, and if you made any constructors visible, allows the user to create instances of that class from JavaScript.

Example:

	// Define JSTest class
JSTest::JSInit(cx, globalObj);
// Define Vector2d class
Vector2d::JSInit(cx, globalObj);

The user can now create JSTest and Vector2d objects from JavaScript by using new JSTest, new Vector2d, or whatever constructors they have.

In addition, you can create class using a C++ constructor, then "import" it into the javascript context with the generated getJSObject(JSContext *) function.

Example:

	JSTest test;
JS_DefineProperty(cx, globalObj, "test", OBJECT_TO_JSVAL(test.getJSObject(cx)), NULL, NULL, JSPROP_ENUMERATE);
This is also useful if you want to hide constructors from JavaScript, but allow the user to access some instance created in C++.

Back to Top


License

jsgen isn't under any specific license. Feel free to use it, modify it, do whatever with it, for any use, whether it's personal, academic, commercial, or whatever.  The main the credit goes to the writers of SpiderMonkey for such as a cool scripting language.

If you have some good changes for jsgen, I'd be happy to include them into the main source.

Back to Top


Download

The latest version can currently be downloaded at: http://www.cs.unm.edu/~cello/jsgen/download/

The website for jsgen is: http://www.cs.unm.edu/~cello/jsgen/

Back to Top

Changelog

First version of website
Back to Top


Compiling

jsgen was designed on mingw32 (gcc for win32), but it should theoretically work with any modern C++ compiler or platform that supports the standard classes (iostream, string, fstream, sstream, vector, map), and some other headers (ctype.h, string.h).

jsgen itself doesn't use SpiderMonkey, but anything generated by it (such as the example included) will need it.

To compile everything:

  1. Modify Makefile to point to the path of SpiderMonkey's source (unless you don't want to compile the example).

    For instructions on setting up SpiderMonkey with mingw see SpiderMonkey-mingw.txt.

  2. Type make

To compile just jsgen:

  1. Type make jsgen.exe
To compile just mergeJS:
  1. Type make mergeJS.exe

Back to Top

Usage

jsgen <source.h> <output.h> <output.cpp>

The most important point to keep in mind is that jsgen is a very dumb parser (anyone who wants to help make it better, let me know). It's a basic linear parser that looks for specific phrases and hardcoded formatting that should function with most C++. 

Anything it's not specifically looking for will likely screw things up, so make sure you read this section carefully.

Because jsgen cares only about syntax in its comment regions, they're the only important parts to clean up for jsgen.  (For example, comments inside a region will confuse jsgen.)

In general it will accept odd code without crashing, but it will generate code may be odd (and not work) as well.

jsgen doesn't use a preprocessor, so macros and includes are ignored.

jsgen's parsing strategy:

  1. jsgen first tries to find the classname by looking through the file for the word class, and grabbing the following word (word is everything but whitespace). If the next word ends with a ; jsgen will ignore it and start looking for class again (a semicolon should signify that the class is part of a forward declaration).

    If you put multiple classes in a file (aside from #included classes), jsgen will not work as you might expect.  It assumes everything is in one big happy class (it doesn't even look at curly braces).  (You could have other classes after the main one, but if they have javascript regions, it'll screw up.)

    Additionally, inner classes will most likely pose similar problems.

  2. If you have the format: "class yourclass : public/protected anotherclass," with whitespace around the :, then jsgen will assume inheritance and automatically generate code to deal with prototypes. 

    The parent class will also need jsgen information for this to work.  If you use private, jsgen will treat the class if it had no parent (and generate no code for dealing with prototype).

  3. jsgen next looks for "/* javascript", the start of a block.  You can have as many of these blocks as you want (theoretically).  The word after javascript signifies the type of block.

    The types you can use are:

    1. variables (or variable, vars, var, v): jsgen reads to the end of the comment and parses them as variable definitions.

      1. The following format is used (text in [brackets] is optional, without the []):

        [static] [virtual] [const] type name [readonly] [normal] [const];

      2. It reads to each ; and assumes that's a variable definition.

        1. The variable definition is then separated by whitespace. If name starts with *, it'll be stripped off and appended to the type, this will make int *i into type=int*, and name=i.

        2. The options can be any of the following, none are required, and the order doesn't matter.
          • normal will create a public member variable in the C++ class with the specified type and name.  jsgen will generate code to assign and retrieve with this variable with no further interfacing.

          • Without normal, jsgen creates two function prototypes: type getName() and void setName(type). jsgen capitalizes the first letter of the variable, and appends it to get or set.  So int x; will create int getX(); and void setX(int x);

          • readonly will make a variable readonly in JavaScript. In conjunction with normal, the code for assigning to the variable is left out, otherwise no set function is defined.

          • virtual will simply mark the generated get/set functions as virtual.

          • static makes a static variable.

          • const has a double meaning.  It will either generate get() functions that are const if normal is not set; or, if normal is set, jsgen will assume this value is already defined as a constant somewhere (such as a const var or enum), and return that every time.

    2. functions (or function, methods, method, f, m): jsgen first looks for the */, then immediately grabs everything from */ to /* end */ and tries to parse functions from that.  (In the future extra keywords may be placed after the word functions.)

      1. jsgen tries to read real C++ functions, unlike the custom format for variables.

        Inline functions may cause problems.

      2. Keep in mind that comments inside the /* javascript functions */ /* end */ block will confuse the parser, so keep them outside the block.

      3. jsgen reads the first non-whitespace word as the type.

        If the first word is "static," jsgen will check the next word as type and make the function static.  For convenience jsgen will also generate code so that static functions can be called on objects instances, and not just the classname. (Typically static functions cannot necessarily be called on instances of classes in JavaScript.)

      4. jsgen then reads non whitespace to the next ( as the method name.

        If you have a pointer type (in the format: type *name), jsgen will move * and & from the name to the type so as not to cause problems.

      5. jsgen then reads to the next ) as the arguments

        1. The arguments then are read as variables by reading to either , (comma) or ), and getting the first word as a type, and the second as a name.

    3. constructors (or constructor, c): grabs everything the same way as with functions.
      1. jsgen tries to read real constructors, following the same algorithm as used for functions, except with the lack of return types to read.

    4. enums (or enum, e): grabs everything the same way as with functions
      1. jsgen tries to read real enums, and although it doesn't care about assignment of specific enums, it can read enums of that format.

      2. jsgen reads everything until the next , (comma), then strips off = and anything after it if found, this is used as the variable name.

      3. This is used as a shortcut for static const int NAME normal readonly; variables, and will function as if you made a variables block with definitions for each enum.

Back to Top

Usage Notes

Variable Types

The standard (argument/return/variable) types that jsgen can recognize are int, bool, float, double, string, jsval, JSObject*, and JSString*.  These will all be autoconverted using jsgen's macros.

Any other type is considered an object. You can use other C++ classes as parameters and variables with jsgen, provided they've been jsgen-generated as well.  If you want to use a class that doesn't have jsgen-generated code in it, it will need the following function: JSObject *getJSObject(JSContext *).

You should be able to pass classes by reference by using &, but this hasn't been tested on non-class types (for example, int &).

You can use class variables by reference using the pointer type of the class if you don't want the class copied around using the assignment operator.

The const keyword on function arguments isn't yet understood by jsgen, and will cause jsgen to generate invalid code.  const functions should theoretically work, because that keyword is after the ), and jsgen ignores that part.

Special Type: jsval

jsval type arguments and variables allow you to get use values without any automatic conversions by jsgen.  It's also useful as an unspecific variable type.

Although this breaks the seamlessness (you probably wouldn't want to call a function from C++ that takes a jsval), it gives you full control over your variables.

Special Types: JSObject* and JSString *

The JSObject* and JSString* type arguments allow you to pass JavaScript objects and strings around without having to use OBJECT_TO_JSVAL/STRING_TO_JSVAL and JSVAL_TO_OBJECT/JSVAL_TO_STRING in your own code.

For JSString* type arguments, the argument will be checked with JSVAL_IS_STRING unlike with using a regular jsval type.

Function/Constructor Overloading

Because JavaScript functions can have a variable number of arguments, jsgen handles overloaded constructors and methods by checking the argument types.

  1. First jsgen checks the number of arguments passed.
  2. Then jsgen checks the basic types of the individual arguments as a JavaScript number (int or double), boolean, string, or the generic object type. 

    jsgen doesn't look at specific class types, so either don't overload, or use the generic jsval type, and do your own checking.

Special Return: JSBool

If you set the return type on a function to JSBool (this is different from bool!), you can return JS_TRUE or JS_FALSE for function success.  jsgen won't convert this special return type.  (If you also want to return a value, read further down about jsval *rval.)

Special Arguments

jsgen will look for special arguments which allow you access to the internals of SpiderMonkey, but are hidden from the actual JavaScript functions.

Since these special functions are all hidden from JavaScript, they aren't actual arguments when called from JavaScript.  This could cause unexpected problems when using overloaded functions.  For example, a(JSContext *cx) and a() will be the same functions to JavaScript after the JSContext * is hidden.

Special Arguments: uintN argc and jsval *argv

If either of these arguments are included in your function (I highly recommend placing them as the last two arguments), then the argument count and values from the JavaScript function call will be passed straight to your function.

The argument type and names must be exactly (case-sensitive) uintN argc and jsval *argv (or jsval* argv), or jsgen will not recognize them as special arguments.

Overloading with Special Arguments argc and argv

Because using argc/argv will allow you dynamic variable types, when overloading, functions/constructors with these two special arguments will be called after variables have first been matched functions/constructors without either of these arguments. 

This allows you use these special arguments in a "catch-all" function/constructor that can handle any combination of arguments after first checking more specific argument combinations.

You can also mix these special arguments with regular arguments.  jsgen will first check if argc is greater or equal to the number of non-special arguments, then match the first entries in argv to the non-special arguments and call the function.

Note: jsgen will start with the most specific overloaded function/constructor, then move to the least specific.

Special Argument: jsval *rval

If you have any parameter of type jsval*, but isn't called argv (rval is the recommended name), it will be given the pointer to the return value.

With this argument, you should either return void or JSBool, otherwise jsgen will overwrite any changes to the pointer after the function call returns.

Special Argument: JSContext *

This argument will be passed the current context from the function call.  The placement doesn't matter, but I recommend placing this as the very first argument.

Special Argument: JSObject *_this

This argument will be passed the current "this" object from the function call.  Again, the placement doesn't matter, but I recommend as either the first argument, or if you have a JSContext * argument, after that.

Double/Float/Int, the Number pseudotype

Note: JavaScript internally uses double, so if you use the float type, jsgen will just cast between double and float as necessary.  This may be a good reason to use double in your code.

To make life easier, jsgen pretends double and int are all the same type, and converts between them as necessary.  This is so the user can use a non-decimal as an argument to a function that takes a float/double, or a decimal to a function that takes a int

Note: I found it a requirement for int to be automatically converted to double, because, for example, JavaScript considers 1.0 an int, and it can't be used if the function only accepts double.  However, I can see the argument to not automatically convert from double to int. It is convenient, though, and I'll only remove it if recommended.

These are the only auto-converted types, it's up to the user to use parseInt/parseFloat to convert other types (such as a string) to a number. 

With overloading, int/float/double are considered the same type, and thus will probably cause problems when overloading.

STL String Support

The autoconversion for string type uses STL strings.  If you don't use STL strings, consider them, they're quite nice.  If you need otherwise, you can use jsval type as an argument and convert manually.

Back to Top


API - Generated Functions

To use classes that are wrapped with jsgen doesn't require any includes to shared code, and all the functions used by jsgen are generated per each class.

By beta 8 there are enough generated functions to warrant documenting them specifically.  You can always look in the generated .h file for the functions generated by jsgen, all the headers are there.

The following functions are the ones you would typically call from your own code:
static JSObject *JSInit(JSContext *cx, JSObject *obj = NULL);
This static function initializes the class into a specified object by calling JS_InitClass, it will automatically call JSInit on a parent class with the same context and object.

JSInit first checks if the class (or something of the same name) already exists in the obj, and can safely be called multiple times.

The obj is the object that the class is part of, in general this will be your global object.  If obj is NULL, JS_GetGlobalObject is used to retrieve the global object associated with the context.

JSInit returns the result of JS_InitClass, or the previously created class.

Example Usage:
MyClass::JSInit(cx,globalObj);
JSObject *getJSObject(JSContext *cx);
This function returns the JSObject * associated with the instance of the C++ class.  If there is none, one is created using the newJSObject function and associated for future calls to getJSObject.

This function is used by jsgen code to automatically convert C++ classes to JSObject *'s.

Example Usage:
MyClass foo;
JS_DefineProperty(cx, globalObj, "foo", OBJECT_TO_JSVAL(foo.getJSObject(cx)), NULL, NULL, JSPROP_ENUMERATE);

The following function you may call from your own code, but generally shouldn't:

static JSObject *newJSObject(JSContext *cx);
This function is a wrapper for JS_NewObject, and will automatically create the prototype as necessary.  This is used by getJSObject when a new object is needed.
jsgen will generate other functions, but they shouldn't be called by your own code.

Back to Top


Examples

The files example directory demonstrate jsgen in action.  It also shows you how you could use jsgen in a Makefile to autogenerate code whenever the class definition file changes.

After compiling, type test test.js to try it out.  Most of the output will be gibberish, so open test.js up in a text editor to see what's going on.

The JSTest class is a fairly useless class that demonstrates a number of different function formats (most of which print to standard out), variables formats, and the ability to have another class as a variable.

The Vector2d class is a demonstration of an actual class that might be used.

The JSTestChild/JSTestChild2/Vector2dChild classes demonstrate prototyping.

Back to Top


Makefile Pointers

Makefiles are one method helping manage development of large projects with many source files and custom compile commands. 

Rather than provide a Makefile example to pick apart, I'll outline a number of points for customizing your makefile to generate with jsgen automatically.

Due to the nature of jsgen requiring classes to be in their own .h/.cpp files, the make process is much smoother with this convention.
That about covers the makefile tips, but stay tuned for...

mergeJS Tips!

Back to Top


Known problems/incomplete features

The various problems are noted in the Usage section.

There are no intentionally incomplete features at the moment. 

Possible features in the future:

There also aren't any more immediate plans for features in the future.  Basically as soon as I need something for my own project, I add it.

Back to Top


Credits

jsgen was written in entirely TextPad using Mingw32 (make/g++, for building/compiling) by Marcello Bastéa-Forte.

SpiderMonkey is a mozilla.org project by Brendan Eich.

A special thanks to Brendan Eich for helping me out on the JavaScript newsgroup so I could bring this program to what it is now.

Back to Top


Comments?

I'm discussing this on and off the allegro.cc forums, so please post if you see my thread.

I've also posted this on the Mozilla.org JavaScript newsgroup, so please post comments there.

Last of all you can email me at marcello@cellosoft.com.

Back to Top