Contents:
jsgen is a program that allows you to easily interface C++ classes from JavaScript in SpiderMonkey. SpiderMonkey is a JavaScript engine for C/C++.
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.
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.)
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;This is also useful if you want to hide constructors from JavaScript, but allow the user to access some instance created in C++.
JS_DefineProperty(cx, globalObj, "test", OBJECT_TO_JSVAL(test.getJSObject(cx)), NULL, NULL, JSPROP_ENUMERATE);
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.
JS_LookupProperty instead of JS_GetPropertyJSObject *newJSObject(JSContext *)
getJSObject to call newJSObject instead of JS_NewObject, and doesn't call JSInit anymore
getContext() function, in favor of a special argument of type JSContext*uintN argc and jsval* argv special arguments, so they can be used in combination with other argumentsthis" JSObject* with special argument JSObject* _thisJSString* special typeJSObject* to be converted with OBJECT_TO_JSVAL and JSVAL_TO_OBJECT/* javascript variables */ regionsObject* o will function like a regular object by reference-Wall -W -Werror -Wno-unused to makefile, and updated jsgen's output to produce no errors in this settingvariables in docsvirtual keyword is no longer default for get/set accessor functionsvirtual, const, static
JSInit(...)Added support for static variables and functionsenum blocks read as static const int readonly normal variables.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:
makeTo compile just jsgen:
make jsgen.exemake mergeJS.exeBack to Top
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:
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).#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.)class yourclass : public/protected anotherclass,"
with whitespace around the :, then jsgen will assume inheritance and automatically generate code to deal with prototypes. private, jsgen will treat the class if it had no parent (and generate no code for dealing with prototype)./* 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.variables (or variable, vars, var, v): jsgen reads to the end of the comment and parses them as variable definitions.[brackets] is optional, without the []):[static] [virtual] [const] type name [readonly] [normal] [const];
; and assumes that's a variable
definition. name starts with *, it'll be stripped off and appended to the type, this will make int *i into type=int*, and name=i.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.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.)/* javascript functions */ /*
end */ block will confuse the parser, so keep them outside the block.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.)( as the
method name. type *name), jsgen will move * and & from the name to the type so as not to cause problems.) as the arguments, (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.enums (or enum, e): grabs everything the same way as with functions, (comma), then strips off = and anything after it if found, this is used as the variable name.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
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.
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.jsval), it gives you full control over your variables.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.
Because JavaScript functions can have a variable number of
arguments, jsgen handles overloaded constructors and methods by
checking the argument types.
jsval type, and do your own checking.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.)
a(JSContext *cx) and a() will be the same functions to JavaScript after the JSContext * is hidden.uintN argc and jsval *argvIf 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.
argc and argvBecause 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.
jsval *rvalIf 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.
JSContext * 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.JSObject *_thisJSContext * argument, after that.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.
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.static JSObject *JSInit(JSContext *cx, JSObject *obj = NULL);
This static function initializes the class into a specified object by callingJS_InitClass, it will automatically callJSIniton a parent class with the same context and object.
JSInitfirst checks if the class (or something of the same name) already exists in theobj, and can safely be called multiple times.
Theobjis the object that the class is part of, in general this will be your global object. IfobjisNULL,JS_GetGlobalObjectis used to retrieve the global object associated with the context.
JSInitreturns the result ofJS_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 thenewJSObjectfunction and associated for future calls togetJSObject.
This function is used by jsgen code to automatically convert C++ classes toJSObject *'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:jsgen will generate other functions, but they shouldn't be called by your own code.static JSObject *newJSObject(JSContext *cx);
This function is a wrapper forJS_NewObject, and will automatically create the prototype as necessary. This is used bygetJSObjectwhen a new object is needed.
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.
js in your source directory to store the files generated by jsgen.
js/%.h: %.h $(JSGEN)
$(JSGEN) $< js/$< js/$(subst .h,.cpp,$<)JSGEN earlier in your makefile.)JS_MODULES= \
SomeClass \
AnotherClass \
LastClassJS_HFILES= $(addprefix js/,$(addsuffix .h, $(notdir $(JS_MODULES))))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). $(OUTEXE): $(JS_HFILES) $(OBJS)
$(CXX) $(OBJS) -o $@ $(LIBDIR) $(LIBS)js/include.h: $(JS_HFILES) $(MERGEJS)
$(MERGEJS) js/include.h $(JS_MODULES)
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."
The various problems are noted in the Usage section.
JSContexts, but I haven't got a good way to test, so I can't say for sure.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
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.
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