Skip to content

Instantly share code, notes, and snippets.

@Lupus

Lupus/Makefile Secret

Created October 1, 2020 09:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Lupus/7980a1211ce51a1bdb0f7c05070eddb0 to your computer and use it in GitHub Desktop.
Save Lupus/7980a1211ce51a1bdb0f7c05070eddb0 to your computer and use it in GitHub Desktop.
Half-arsed highly-PoC level typescript => gen_js_api bindings generator
all:
node index.js > out.mli
ocamlformat -i out.mli
ocamlfind gen_js_api/gen_js_api out.mli
ocamlformat -i out.ml
{
"name": "ts_to_mli",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"@types/node": "^13.7.4",
"cassandra-driver": "^4.4.0",
"dts-bundle": "^0.7.3",
"dts-generator": "^3.0.0",
"flowgen": "^1.10.0",
"glob": "^7.1.6",
"gulp": "^4.0.2",
"json-cyclic": "^0.0.3",
"json-stringify-safe": "^5.0.1",
"long": "^4.0.0",
"ts-dox": "^0.0.7",
"tsc": "^1.20150623.0",
"typescript": "^3.8.2"
}
}
const path = require("path");
const glob = require("glob");
const ts = require("typescript");
const crypto = require("crypto");
var program = ts.createProgram(glob.sync("sample-ts/**/*.d.ts"), {});
var checker = program.getTypeChecker();
let indent = " ";
var output = "";
var header = "";
function out_start(level = 0) {
output += indent.repeat(level);
}
function out(s) {
output += s;
}
function out_end(s = "") {
output += s + "\n";
}
function outl(s, level = 0) {
out_start(level);
out_end(s);
}
function hdr_start(level = 0) {
header += indent.repeat(level);
}
function hdr(s) {
header += s;
}
function hdr_end(s = "") {
header += s + "\n";
}
function hdrl(s, level = 0) {
hdr_start(level);
hdr_end(s);
}
function to_snake_case(str, upper_case_first_char = false) {
var str = str
.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
.map(x => x.toLowerCase())
.join("_");
if (upper_case_first_char) {
return str.replace(/^[a-z]/, x => x.toUpperCase());
} else {
return str;
}
}
const keywords = {
and: true,
as: true,
assert: true,
asr: true,
begin: true,
class: true,
constraint: true,
do: true,
done: true,
downto: true,
else: true,
end: true,
exception: true,
externalfalse: true,
for: true,
fun: true,
function: true,
functor: true,
if: true,
in: true,
include: true,
inherit: true,
initializer: true,
land: true,
lazy: true,
let: true,
lor: true,
lsl: true,
lsr: true,
lxor: true,
match: true,
method: true,
mod: true,
module: true,
mutable: true,
new: true,
nonrec: true,
object: true,
of: true,
open: true,
or: true,
private: true,
rec: true,
sig: true,
struct: true,
then: true,
to: true,
true: true,
try: true,
type: true,
val: true,
virtual: true,
when: true,
while: true,
with: true
};
function escape_keyword(s) {
if (keywords[s.toLowerCase()]) return s + "_";
return s;
}
var get_flags = function(e, flags) {
var selected = [];
for (var flag in e) {
if (typeof flag == "string") {
var val = e[flag];
if ((flags & val) === val) selected.push(flag);
}
}
return selected.join(" | ");
};
var get_symbol_flags = function(flags) {
return get_flags(ts.SymbolFlags, flags);
};
var get_type_flags = function(flags) {
return get_flags(ts.TypeFlags, flags);
};
var get_object_flags = function(flags) {
return get_flags(ts.ObjectFlags, flags);
};
function ordered_for_each(x, f) {
var a = [];
x.forEach(v => a.push(v));
a.sort(function(a, b) {
return a.declarations[0].pos - b.declarations[0].pos;
}).forEach(f);
}
var this_module_escaped_name;
var module_path = [];
var module_aliases = {};
var module_requires = {};
var current_js_path = "";
var js_path_aliases = {};
function prepend_js_path(x) {
if (current_js_path != "") {
return current_js_path + "." + x;
}
return x;
}
function resolve_js_path(x) {
for (const s in js_path_aliases) {
if (x.startsWith(s)) {
return js_path_aliases[s] + x.substr(s.length);
}
}
return x;
}
function resolve_js_path_for_symbol(s) {
var qn = resolve_js_path(checker.getFullyQualifiedName(s));
var parts = qn.split(".");
var mdl = parts.shift().replace(/["]/g, "");
if (!module_requires[mdl]) {
var hash = crypto.createHash("md5");
hash.update(mdl);
var require_id = `__ts2mli_${to_snake_case(mdl)}_${hash
.digest("hex")
.substr(26)}`;
hdrl(`let () = Ojs.set Ojs.global "${require_id}" (node_require "${mdl}")`);
module_requires[mdl] = require_id;
}
parts.unshift(module_requires[mdl]);
return parts.join(".");
}
function symbol_path(s, last_part) {
var file_modules = s.valueDeclaration
? s.valueDeclaration.getSourceFile().ambientModuleNames
: [];
var ambients = checker.getAmbientModules();
file_modules = file_modules.map(fm => {
var found = ambients.find(m => m.valueDeclaration.name.text == fm);
if (!found) {
throw "unable to find ambient module for " + fm;
}
return found;
});
var parts = [s.escapedName];
var parent = s.parent;
while (parent) {
var m = file_modules.find(m => {
var alias = m.exports.get("export=");
if (alias && alias.flags & ts.SymbolFlags.Alias) {
return checker.getAliasedSymbol(alias) == parent;
} else {
return false;
}
});
if (m) {
parts.unshift(m.escapedName);
} else {
parts.unshift(parent.escapedName);
}
parent = parent.parent;
}
var path = parts
.filter(x => x != this_module_escaped_name)
.map(x => x.replace(/["]/g, ""))
.map(x =>
x.startsWith(process.cwd()) ? x.substring(process.cwd().length) : x
)
.map(x => to_snake_case(x, true))
.join(".");
for (const s in module_aliases) {
if (path.startsWith(s)) {
path = module_aliases[s] + path.substr(s.length);
break;
}
}
if (last_part && path != "") path = `${path}.${last_part}`;
for (const s of module_path) {
if (path.startsWith(s + ".")) {
path = path.substr(s.length + 1);
}
}
return path;
}
function map_type(typ) {
if (typ.flags & ts.TypeFlags.Object) {
if (
typ.objectFlags & ts.ObjectFlags.Reference ||
typ.objectFlags & ts.ObjectFlags.ClassOrInterface
) {
if (
(typ.symbol.escapedName == "Array" ||
typ.symbol.escapedName == "Set") &&
checker.getTypeArguments(typ).length == 1
) {
return `(${map_type(checker.getTypeArguments(typ)[0])} list)`;
}
return `${symbol_path(typ.symbol, "t")}`;
}
} else if (
typ.flags & ts.TypeFlags.Any ||
typ.flags & ts.TypeFlags.UnknownAny
) {
return `Ojs.t (* any *)`;
} else if (
typ.flags & ts.TypeFlags.String ||
typ.flags & ts.TypeFlags.StringLike
) {
return `string`;
} else if (
typ.flags & ts.TypeFlags.Number ||
typ.flags & ts.TypeFlags.NumberLike
) {
return `int`;
} else if (
typ.flags & ts.TypeFlags.Undefined ||
typ.flags & ts.TypeFlags.Null ||
typ.flags & ts.TypeFlags.Void ||
typ.flags & ts.TypeFlags.VoidLike ||
typ.flags & ts.TypeFlags.Unit ||
typ.flags & ts.TypeFlags.Never
) {
return `unit`;
} else if (
typ.flags & ts.TypeFlags.Boolean ||
typ.flags & ts.TypeFlags.BooleanLike
) {
return `bool`;
} else if (typ.flags & ts.TypeFlags.TypeParameter) {
return symbol_path(typ.symbol, "t");
}
/*
Enum = 32,
BigInt = 64,
StringLiteral = 128,
NumberLiteral = 256,
BooleanLiteral = 512,
EnumLiteral = 1024,
BigIntLiteral = 2048,
ESSymbol = 4096,
UniqueESSymbol = 8192,
TypeParameter = 262144,
Object = 524288,
Union = 1048576,
Intersection = 2097152,
Index = 4194304,
IndexedAccess = 8388608,
Conditional = 16777216,
Substitution = 33554432,
NonPrimitive = 67108864,
Literal = 2944,
StringOrNumberLiteral = 384,
PossiblyFalsy = 117724,
BigIntLike = 2112,
BooleanLike = 528,
EnumLike = 1056,
ESSymbolLike = 12288,
UnionOrIntersection = 3145728,
StructuredType = 3670016,
TypeVariable = 8650752,
InstantiableNonPrimitive = 58982400,
InstantiablePrimitive = 4194304,
Instantiable = 63176704,
StructuredOrInstantiable = 66846720,
Narrowable = 133970943,
NotUnionOrUnit = 67637251,
*/
return `Ojs.t (* ${get_type_flags(typ.flags)} *)`;
}
function dump_property(p, level) {
var typ = checker.getTypeOfSymbolAtLocation(p, p.valueDeclaration);
outl(
`(* ${checker.symbolToString(p)} : ${checker.typeToString(typ)} *)`,
level
);
outl(
`method ${escape_keyword(to_snake_case(p.escapedName))} : ${map_type(
typ
)} [@@js.get "${p.escapedName}"]`,
level
);
}
function dump_heritage(x, level) {
var n = 0;
if (x.valueDeclaration && x.valueDeclaration.heritageClauses) {
x.valueDeclaration.heritageClauses.forEach(clause => {
if (clause.token == ts.SyntaxKind.ExtendsKeyword) {
clause.types.forEach(t => {
var ts = ts;
var typ = checker.getTypeAtLocation(t.expression);
outl(`inherit ${symbol_path(typ.symbol, "t")}`, level);
n++;
});
}
});
}
if (n == 0) {
outl(`inherit Ojs.obj`, level);
}
}
function serialize_symbol(symbol) {
return (
symbol.getName() +
(checker.isOptionalParameter(symbol.valueDeclaration) ? "?" : "") +
": " +
checker.typeToString(
checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration)
)
);
}
function serialize_signature(signature) {
var parts = signature.parameters.map(p => {
var name = escape_keyword(to_snake_case(p.getName()));
var q = checker.isOptionalParameter(p.valueDeclaration) ? "?" : "";
var typ = checker.getTypeOfSymbolAtLocation(p, p.valueDeclaration);
if (typ.members && typ.members.has("__call")) {
return `${q}${name}:(${serialize_signature(typ.callSignatures[0])})`;
} else {
return `${q}${name}:${map_type(typ)}`;
}
});
if (parts.length == 0 || parts[parts.length - 1].startsWith("?")) {
parts.push("unit");
}
parts.push(map_type(signature.getReturnType()));
return parts.join(" -> ");
}
function dump_indexes(s, level) {
var typ = checker.getDeclaredTypeOfSymbol(s);
var str_index = checker.getIndexInfoOfType(typ, ts.IndexKind.String);
if (str_index) {
outl(`module String_index : sig`, level);
{
var level = level + 1;
outl(`val get : t -> string -> ${map_type(str_index.type)}`, level);
outl(`[@@js.custom`, level);
{
var level = level + 1;
outl(
`val cast_output_val: Ojs.t -> ${map_type(
str_index.type
)} [@@js.cast]`,
level
);
outl(
`let get t prop = Ojs.get (t |> js_of_t) prop |> cast_output_val`,
level
);
}
outl(`]`, level);
}
outl(`end`, level);
}
var num_index = checker.getIndexInfoOfType(typ, ts.IndexKind.Number);
if (num_index) {
outl(`module Number_index : sig`, level);
{
var level = level + 1;
outl(`val get : t -> string -> Ojs.t`, level);
outl(`[@@js.custom`, level);
{
var level = level + 1;
outl(`let get t idx = Ojs.array_get (t |> js_of_t) idx`, level);
}
outl(`]`, level);
}
outl(`end`, level);
}
}
function dump_constructors(cls, level) {
var c;
cls.members.forEach(m => {
if (m.flags & ts.SymbolFlags.Constructor) c = m;
});
if (!c) return;
outl(`(* constructors *)`, level);
let parent_type = checker.getTypeOfSymbolAtLocation(
c.parent,
c.parent.valueDeclaration
);
var signatures = parent_type.getConstructSignatures();
var n = signatures.length > 1 ? 1 : undefined;
signatures.forEach(s => {
outl(`(* signature: ${checker.signatureToString(s)} *)`, level);
outl(
`val create${n ? "_" + n : ""} : ${serialize_signature(
s
)}[@@js.new "${resolve_js_path_for_symbol(cls)}"]`,
level
);
if (n) n++;
});
}
function dump_members(x, level) {
ordered_for_each(x.members, m => {
if (m.flags & ts.SymbolFlags.Property) {
dump_property(m, level);
} else if (m.flags & ts.SymbolFlags.Method) {
outl(`(* method '${m.escapedName}' *)`, level);
var typ = checker.getTypeOfSymbolAtLocation(m, m.valueDeclaration);
var signatures = checker.getSignaturesOfType(typ, ts.SignatureKind.Call);
var n = signatures.length > 1 ? 1 : undefined;
signatures.forEach(s => {
outl(`(* signature: ${checker.signatureToString(s)} *)`, level);
outl(
`method ${escape_keyword(to_snake_case(m.escapedName))}${
n ? "_" + n : ""
} : ${serialize_signature(s)}[@@js.call "${m.escapedName}"]`,
level
);
if (n) n++;
});
} else if (m.flags & ts.SymbolFlags.Constructor) {
} else if (
m.flags & ts.SymbolFlags.Signature &&
m.escapedName == "__index"
) {
} else {
outl(
`(* member? ${m.escapedName}, flags: ${get_symbol_flags(m.flags)} *)`,
level
);
}
});
}
function out_class(level, cb) {
outl(`class t :`, level + 1);
outl(`Ojs.t`, level + 2);
outl(`-> object`, level + 2);
cb(level + 3);
outl(`end`, level + 2);
}
function out_module(s, level, cb) {
var name = to_snake_case(s.escapedName, true);
outl(`module ${name} : sig`, level);
module_path.push(name);
cb(level + 1);
module_path.pop();
outl(`end`, level);
}
function dump_symbol(s, level) {
if (s.flags & ts.SymbolFlags.Function) {
outl(`(* function '${s.escapedName}' *)`, level);
var typ = checker.getTypeOfSymbolAtLocation(s, s.valueDeclaration);
var signatures = checker.getSignaturesOfType(typ, ts.SignatureKind.Call);
var n = signatures.length > 1 ? 1 : undefined;
signatures.forEach(sig => {
outl(`(* signature: ${checker.signatureToString(sig)} *)`, level);
outl(
`val ${escape_keyword(to_snake_case(s.escapedName))}${
n ? "_" + n : ""
} : ${serialize_signature(
sig
)}[@@js.global "${resolve_js_path_for_symbol(s)}"]`,
level
);
if (n) n++;
});
} else if (s.flags & ts.SymbolFlags.Interface) {
outl(`(* interface '${s.escapedName}' *)`, level);
out_module(s, level, level => {
out_class(level + 1, level => {
dump_heritage(s, level);
dump_members(s, level);
});
outl(`val js_of_t : t -> Ojs.t [@@js.cast]`, level + 1);
outl(`val t_of_js : Ojs.t -> t [@@js.cast]`, level + 1);
dump_indexes(s, level);
});
} else if (s.flags & ts.SymbolFlags.Class) {
outl(`(* class '${s.escapedName}' *)`, level);
out_module(s, level, level => {
out_class(level, level => {
dump_heritage(s, level);
dump_members(s, level);
});
outl(`val js_of_t : t -> Ojs.t [@@js.cast]`, level + 1);
outl(`val t_of_js : Ojs.t -> t [@@js.cast]`, level + 1);
dump_constructors(s, level);
dump_indexes(s, level);
});
} else if (s.flags & ts.SymbolFlags.Alias) {
var a = checker.getAliasedSymbol(s);
if (a.flags & ts.SymbolFlags.ValueModule) {
outl(
`(* re-export of '${checker.getFullyQualifiedName(
a
)}' as '${symbol_path(s)}' *)`,
level
);
out_module(s, level, level => {
dump_module_symbol(a, level);
});
} else {
outl(
`(* Alias??? ${s.escapedName}, flags: ${get_symbol_flags(s.flags)} *)`,
level
);
}
} else if (s.flags & ts.SymbolFlags.ValueModule) {
outl(`(* ValueModule '${s.escapedName}' *)`, level);
out_module(s, level, level => {
dump_module_symbol(s, level);
});
} else {
outl(
`(* ??? ${s.escapedName}, flags: ${get_symbol_flags(s.flags)} *)`,
level
);
}
}
function extract_alias(s) {
if (s.flags & ts.SymbolFlags.Alias) {
var a = checker.getAliasedSymbol(s);
if (a.flags & ts.SymbolFlags.ValueModule) {
module_aliases[symbol_path(a)] = symbol_path(s);
js_path_aliases[checker.getFullyQualifiedName(a)] = resolve_js_path(
checker.getFullyQualifiedName(s)
);
}
}
}
function dump_module_symbol(m, level = 0) {
var exports = checker.getExportsOfModule(m);
exports.forEach(e => {
extract_alias(e);
});
ordered_for_each(exports, e => {
dump_symbol(e, level + 1);
});
}
function dump_ambient_module(name) {
var m = checker
.getAmbientModules()
.find(m => m.valueDeclaration.name.text == name);
if (m) {
this_module_escaped_name = m.escapedName;
dump_module_symbol(m);
}
}
function dump_file_module(filename) {
var filename = path.resolve(filename);
var m = program.getSourceFiles().find(f => f.path == filename).symbol;
if (m) {
this_module_escaped_name = m.escapedName;
dump_module_symbol(m);
}
}
const cassandra_ts = "sample-ts/cassandra/index.d.ts";
const policies_ts = "sample-ts/cassandra/lib/policies/index.d.ts";
const http_ts = "sample-ts/node-v12/http.d.ts";
hdrl(`[@@@js.implem`);
hdrl(
`val node_require : string -> Ojs.t [@@js.global "process.mainModule.require"]`
);
dump_file_module(cassandra_ts);
//dump_ambient_module("http");
hdrl(`]`);
process.stdout.write(header);
process.stdout.write(output);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment