Skip to content

Instantly share code, notes, and snippets.

@CrazyPython CrazyPython/dngen.d
Created Jun 1, 2019

What would you like to do?
import std.stdio, std.traits, std.string, std.meta, std.algorithm, std.range;
// todo: make vim fold comments so I can write doc.d inline in the file
// support JS getters and setters for nested structs
// generate each involved struct to its own file
/++Write @DataViewName(<string literal>) before a struct to specify the
DataView a struct will be allocated on. Only applicable to
AllocationType.array +/
struct DataViewName {
string name;
enum NullConstructorProtocol {
// @ConstructionProtocol
enum AllocationType {
/++JavaScript Instances of this struct will use `this.o` to know where to store
their data. +/
/++Instances of this struct will be allocated on a contiguous section of memory.
Implicit for root structs. +/
/+we can allocate references as a function of the parent structure's location
/++Specifies what happens when a JavaScript program takes a reference of a
sub-structure +/
enum JavaScriptSemantics {
/++Discards values unknown to the sub-struct declaration. The JavaScript type
for the sub-struct is not stored on the parent JavaScript object. Copy
values by name when the sub-structure is assigned to as a whole. +/
value_struct = "value_struct"
/// Reserved option names.
static string outputdir;
/// Reserved words.
struct exposedToJS;
struct notExposedToJS;
string offsetSubclassPrefix = "Allocated";
struct Vector
double x, y;
@DataViewName("ev") struct Entity
double x, y;
@(JavaScriptSemantics.value_struct) {
Vector velocity;
Vector accel;
bool function(Entity*) physics_fn_pointer;
mixin template declareNameForType(type1, int sizeInBytes, type2, string name) {
static if (type1.sizeof == sizeInBytes) {
// generate code like double.sizeof == 8
static if (__traits(isSame, type1, type2)) {
const string typedArrayName = name;
* Given a type, if the type is a type directly accessible using a DataView,
* return the JavaScript method for accessing that data type.
* Returns: `""` if there is no method. Otherwise, return the JavaScript method
* used to access the data type.
* Examples:
* getNumericJSArrayName!(int) // => "getInt32"
* getNumericJSArrayName!(long) // => "" (JavaScript has no 64-bit integer type)
* getNumericJSArrayName!(string) // => ""
string getNumericDataViewMethodPostfix(T)() {
mixin declareNameForType!(T, 8, double, "Float64");
mixin declareNameForType!(T, 4, float, "Float32");
mixin declareNameForType!(T, 4, int, "Int32");
mixin declareNameForType!(T, 4, uint, "Uint32");
mixin declareNameForType!(T, 2, short, "Int16");
mixin declareNameForType!(T, 2, ushort, "Uint16");
mixin declareNameForType!(T, 1, byte, "Int8");
mixin declareNameForType!(T, 1, ubyte, "Uint8");
static if (__traits(compiles, typedArrayName))
return typedArrayName;
return "";
// Returns: an array of tuples in the format (dataViewName, methodPostfix, byteIndexExpr)
// Side effects: writes a generated javascript file to cstructs/{T.stringof}.js
string[3][] generateJavaScript(T)(string dataViewName="", string addendum="") {
debug pragma(msg, T.stringof);
auto f = File(format!"cstructs/%s.js"(T.stringof), "w");
f.writeln(`exports.wrap = function wrap(Class, baseclass) {`)
string[3][] returnValue;
alias dataViewNameTuple = getUDAs!(T, DataViewName);
static if (dataViewNameTuple.length == 1) {
const allocationType = AllocationType.array;
dataViewName = dataViewNameTuple[0].name;
} else
const allocationType = AllocationType.offset;
assert(dataViewName != "", "The DataViewName attribute was not found on"
~ T.stringof ~ " and it is not a child struct.");
static if (allocationType == AllocationType.array)
string parentStructStartOffset = format!"this.i*%d"(T.sizeof);
else static if (allocationType == AllocationType.offset)
string parentStructStartOffset = "this.o";
static foreach (m_name; __traits(allMembers, T)) {{
debug pragma(msg, m_name);
alias member = __traits(getMember, T, m_name);
const m_offset = cast(int)__traits(getMember, T, m_name).offsetof;
const m_sizeof = __traits(getMember, T, m_name).sizeof;
alias m_type = typeof(__traits(getMember, T, m_name));
/* property name, get string, set string*/
const definePropertyTemplate = `
Object.defineProperty(Class.prototype, '%s', {
enumerable: true,
configurable: false,
get() { return %s },
set(v) { %s },
static if (getNumericDataViewMethodPostfix!(m_type) != "") {
const methodPostfix = getNumericDataViewMethodPostfix!(m_type);
const byteIndexExpr = format!"%s+%d"(parentStructStartOffset, m_offset);
alias fieldData = AliasSeq!(dataViewName, methodPostfix, byteIndexExpr);
returnValue ~= [ fieldData ];
const jsGetExpr = format!"%s.get%s(%s)"(fieldData);
const jsSetExpr = format!"%s.set%s(%s, v)"(fieldData);
const definePropertyExpression =
format!definePropertyTemplate(m_name, jsGetExpr, jsSetExpr);
} else static if (__traits(compiles, __traits(allMembers, m_type))) { // check if m_type is a struct
alias javaScriptSemanticsTuple = getUDAs!(member, JavaScriptSemantics);
static assert(javaScriptSemanticsTuple.length == 1, "Reference semantics must be explicitly specified");
if (javaScriptSemanticsTuple[0] == "value_struct") {
/* Bridging the gap between JavaScript and C requires some hacks:
* JavaScript doesn't have C's compile-time forward declaration.
* JavaScript also does not have widespread adopted class static variable
* syntax, so simply making an empty object prototype construction will
* not because some people will be setting constants in the constructor.
* This combination of factors forces us to resort to dependency
* inversion: during construction, the JavaScript struct adaptor class
* will depend on the substruct adaptor class, instead of the other way
* around. This strategy is neither traditional C nor traditional
* JavaScript, but sacr
auto addendum =
let Allocated
exports.subclass = function subclass(Class) {
class Allocated extends Class {}
return Allocated;
const tuples = generateJavaScript!(m_type)(dataViewName);
auto allMembers = [__traits(allMembers, m_type)];
`import { AllocatedFrom } `
const jsGetExpr = format!
`const obj = new Class(_Initialize=false)
obj.i = this.i + %s;`(offset/*m_type.stringof,*/ m_offset);
const jsSetExpr = zip(tuples, allMembers).map!((pair) {
const tuple = pair[0];
const name = pair[1];
return format!
"%s.set%s(%s, v.%s);"(tuple[0], tuple[1], tuple[2], name);
})().fold!((a, b) => a ~ b);
f.writeln(format!definePropertyTemplate(m_name, jsGetExpr, jsSetExpr));
// __AllocatedVector
// new
// use the source on FieldNameTuple to figure out how to tell if smth is a struct
//RepresentationTypeTuple? Does that recurse?
return returnValue;
void main() {
debug writeln("Program execution start");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.