Created
June 1, 2019 00:13
-
-
Save CrazyPython/8289454842948b07344f28c94d5cecb7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 { | |
/++ | |
+/ | |
_Initialize, | |
/++ | |
+/ | |
emptyPrototype | |
} | |
// @ConstructionProtocol | |
enum AllocationType { | |
/++JavaScript Instances of this struct will use `this.o` to know where to store | |
their data. +/ | |
offset, | |
/++Instances of this struct will be allocated on a contiguous section of memory. | |
Implicit for root structs. +/ | |
array, | |
/*js_destructor_gc, | |
/+we can allocate references as a function of the parent structure's location | |
reference*/ | |
} | |
/++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; | |
else | |
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); | |
f.writeln(definePropertyExpression); | |
} 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 | |
Allocated | |
exports.subclass = function subclass(Class) { | |
class Allocated extends Class {} | |
exports.wrap(Allocated) | |
return Allocated; | |
}`; | |
const tuples = generateJavaScript!(m_type)(dataViewName); | |
auto allMembers = [__traits(allMembers, m_type)]; | |
` | |
.subca | |
` | |
`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)); | |
} | |
} | |
}} | |
f.writeln('}'); | |
f.writeln(addendum); | |
// __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"); | |
generateJavaScript!(Entity); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment