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./~cello/jsgen/download/
The website for jsgen is: http://www.cs./~cello/jsgen/
Back to Top
Changelog
- 2004-1-6: beta 8
- Updated JSInit to use
JS_LookupProperty instead of JS_GetProperty - Fixed a bug with inheritance by
- Added Credits and API sections to docs
- 2004-1-3: beta 7
- Fixed year on previous entry of changelog
- Rewrote quite a few bits of jsgen:
- Cleaned up code in general
- Commented somewhat complex code in jsgenjs.cpp, and added some descriptions in jsgen.cpp
- Removed object caching on JSInit (may be slightly slower, but fixes bugs)
- Reworked how function/constructor arguments work so that it supports "special" arguments more cleanly
- Removed context caching and related
getContext() function, in favor of a special argument of type JSContext*
- Made
uintN argc and jsval* argv special arguments, so they can be used in combination with other arguments
- Added ability to get the "
this " JSObject* with special argument JSObject* _this - Added
JSString* special type
- Restructured docs
- Cleaned up docs a bit
- Added Usage Notes section
- Added Makefile Pointers section
- 2004-1-1: beta 6 (cont.):
- Happy New Years! (Here I am, coding.)
- Added support for special type
JSObject* to be converted with OBJECT_TO_JSVAL and JSVAL_TO_OBJECT
- Minor doc updates
- 2003-12-20: beta 6:
- Finally started using jsgen in an actual program, so more bugs are popping up:
- Fixed bug with string conversion
- Fixed bug in JSClass definition when there are no get or set property functions
- Fixed bug with pointer variables in
/* javascript variables */ regions
- Updated how object pointers work, so
Object* o will function like a regular object by reference
- Minor doc updates
- Added alternative keywords for javascript regions
- 2003-12-19: beta 5:
- Added
-Wall -W -Werror -Wno-unused to makefile, and updated jsgen's output to produce no errors in this setting
- Updated vars to
variables in docs
virtual keyword is no longer default for get/set accessor functions
- Added new variable options:
virtual , const , static
- Added support for inheritance, this will automatically deal with prototypes in
JSInit(...)
Added support for static variables and functions
- Added support for
enum blocks read as static const int readonly normal variables.
- 2003-12-18: beta 4:
- Cleaned up documentation a bit
- Updated website
- Added code for pointers/references in function return types (it had been described in this doc., but wasn't actually done)
- 2003-12-18: beta 3:
- Added function overloading
- Added constructor support (with overloading)
- 2003-12-17: severe beta 2:
- 2003-12-16: severe beta 1:
- Preliminary version
- Working variable support
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:
- 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.
- Type
make
To compile just jsgen:
- Type
make jsgen.exe
To compile just mergeJS:
- 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:
- 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 #include d
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.
- 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).
- 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:
variables (or variable , vars , var , v ): jsgen reads to the end of the comment and parses them as variable definitions.
- The following format is used (text in
[brackets] is optional, without the [] ):
[static] [virtual] [const] type name [readonly] [normal] [const];
- It reads to each
; and assumes that's a variable
definition.
- 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 .
- 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.
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 .)
- jsgen tries to read real C++ functions, unlike the custom format for variables.
Inline functions may cause problems.
- Keep in mind that comments inside the
/* javascript functions */ /*
end */ block will confuse the parser, so keep them outside the block.
- 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.)
- 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.
- jsgen then reads to the next
) as the arguments
- 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.
constructors (or constructor , c ): grabs everything the same way as with functions .
- jsgen tries to read real constructors,
following the same algorithm as used for functions, except with the lack of return types to read.
enums (or enum , e
): grabs everything the same way as with functions
- jsgen tries to read real enums, and although it doesn't care
about assignment of specific enums, it can read enums of that format.
- jsgen reads everything until the next
, (comma), then strips off = and anything after it if found, this is used as the variable name.
- 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.
- First jsgen checks
the number of arguments passed.
- 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.
- First, to reduce clutter, you should make a folder named
js in your source directory to store the files generated by jsgen.
- Use the following make target to automatically generate code into the js directory with jsgen:
js/%.h: %.h $(JSGEN)
$(JSGEN) $< js/$< js/$(subst .h,.cpp,$<)
(Define the path to jsgen, JSGEN earlier in your makefile.)
- With a list of the classes that jsgen should generate for in the following format,
JS_MODULES= \
SomeClass \
AnotherClass \
LastClass
you can then build a list of .h files to build in js/ with the following
code:
JS_HFILES= $(addprefix js/,$(addsuffix .h, $(notdir $(JS_MODULES))))
- You should have all the jsgen files generated before you actually
start compiling classes to avoid errors.
Assuming you make a similar variable MODULES in the same format as JS_MODULES , use the following code:
OBJS= $(addprefix objs/, $(addsuffix .o, $(notdir $(MODULES) $(JS_MODULES))))
to merge the classes from the MODULES list with the JS_MODULES list into a list of object files (in a directory called objs, in this example).
Then, using a target, such as:
$(OUTEXE): $(JS_HFILES) $(OBJS)
$(CXX) $(OBJS) -o $@ $(LIBDIR) $(LIBS)
You can generate all the source files with jsgen before compiling the objects themselves.
That about covers the makefile tips, but stay tuned for...
mergeJS Tips!
- The extremely simple utility mergeJS.cpp can be used in the following way:
js/include.h: $(JS_HFILES) $(MERGEJS)
$(MERGEJS) js/include.h $(JS_MODULES)
This will generate a header file that includes all the classes from JS_MODULES , and builds a function (inline static void loadJS(JSContext *cx, JSObject *obj) ) that calls JSInit(cx,obj) on all the classes. This is handy if you want to add classes "on the fly."
Back to Top
Known problems/incomplete features
The various problems are noted in the Usage section.
- This code might now actually work with multiple
JSContext s, but I haven't got a good way to test, so I can't say for sure.
- jsgen doesn't support unicode, and I'm not sure how this would be implemented.
There are no intentionally incomplete features at the moment.
Possible features in the future:
- A variables block that's more similar to the functions/constructors (ie uses real variables)
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 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@.
Back to Top
|