mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-04-25 07:02:13 +00:00
544 lines
16 KiB
TypeScript
544 lines
16 KiB
TypeScript
/**
|
|
* Definition builders for WebIDL and TypeScript.
|
|
* @module definitions
|
|
*//***/
|
|
|
|
import {
|
|
CommonFlags
|
|
} from "./common";
|
|
|
|
import {
|
|
Program,
|
|
Element,
|
|
ElementKind,
|
|
Global,
|
|
Enum,
|
|
EnumValue,
|
|
Field,
|
|
Function,
|
|
FunctionPrototype,
|
|
Class,
|
|
ClassPrototype,
|
|
Namespace,
|
|
ConstantValueKind,
|
|
Interface,
|
|
Property
|
|
} from "./program";
|
|
|
|
import {
|
|
Type,
|
|
TypeKind
|
|
} from "./types";
|
|
|
|
import {
|
|
indent
|
|
} from "./util";
|
|
|
|
/** Walker base class. */
|
|
abstract class ExportsWalker {
|
|
|
|
/** Program reference. */
|
|
program: Program;
|
|
/** Whether to include private members */
|
|
includePrivate: bool;
|
|
/** Elements still to do. */
|
|
todo: Element[] = [];
|
|
/** Already seen elements. */
|
|
seen: Set<Element> = new Set();
|
|
|
|
/** Constructs a new Element walker. */
|
|
constructor(program: Program, includePrivate: bool = false) {
|
|
this.program = program;
|
|
this.includePrivate;
|
|
}
|
|
|
|
/** Walks all exports and calls the respective handlers. */
|
|
walk(): void {
|
|
for (let moduleExport of this.program.moduleLevelExports.values()) {
|
|
// FIXME: doesn't honor the actual externally visible name
|
|
this.visitElement(moduleExport.element);
|
|
}
|
|
var todo = this.todo;
|
|
for (let i = 0; i < todo.length; ) this.visitElement(todo[i]);
|
|
}
|
|
|
|
/** Visits an element.*/
|
|
visitElement(element: Element): void {
|
|
if (element.is(CommonFlags.PRIVATE) && !this.includePrivate) return;
|
|
if (this.seen.has(element)) return;
|
|
this.seen.add(element);
|
|
switch (element.kind) {
|
|
case ElementKind.GLOBAL: {
|
|
if (element.is(CommonFlags.COMPILED)) this.visitGlobal(<Global>element);
|
|
break;
|
|
}
|
|
case ElementKind.ENUM: {
|
|
if (element.is(CommonFlags.COMPILED)) this.visitEnum(<Enum>element);
|
|
break;
|
|
}
|
|
case ElementKind.FUNCTION_PROTOTYPE: {
|
|
this.visitFunctionInstances(<FunctionPrototype>element);
|
|
break;
|
|
}
|
|
case ElementKind.CLASS_PROTOTYPE: {
|
|
this.visitClassInstances(<ClassPrototype>element);
|
|
break;
|
|
}
|
|
case ElementKind.FIELD: {
|
|
if ((<Field>element).is(CommonFlags.COMPILED)) this.visitField(<Field>element);
|
|
break;
|
|
}
|
|
case ElementKind.PROPERTY: {
|
|
let prop = <Property>element;
|
|
let getter = prop.getterPrototype;
|
|
if (getter) this.visitFunctionInstances(getter);
|
|
let setter = prop.setterPrototype;
|
|
if (setter) this.visitFunctionInstances(setter);
|
|
break;
|
|
}
|
|
case ElementKind.NAMESPACE: {
|
|
if (hasCompiledMember(element)) this.visitNamespace(element);
|
|
break;
|
|
}
|
|
default: assert(false);
|
|
}
|
|
}
|
|
|
|
private visitFunctionInstances(element: FunctionPrototype): void {
|
|
for (let instances of element.instances.values()) {
|
|
for (let instance of instances.values()) {
|
|
if (instance.is(CommonFlags.COMPILED)) this.visitFunction(<Function>instance);
|
|
}
|
|
}
|
|
}
|
|
|
|
private visitClassInstances(element: ClassPrototype): void {
|
|
for (let instance of element.instances.values()) {
|
|
if (instance.is(CommonFlags.COMPILED)) this.visitClass(<Class>instance);
|
|
}
|
|
}
|
|
|
|
abstract visitGlobal(element: Global): void;
|
|
abstract visitEnum(element: Enum): void;
|
|
abstract visitFunction(element: Function): void;
|
|
abstract visitClass(element: Class): void;
|
|
abstract visitInterface(element: Interface): void;
|
|
abstract visitField(element: Field): void;
|
|
abstract visitNamespace(element: Element): void;
|
|
}
|
|
|
|
/** A WebIDL definitions builder. */
|
|
export class IDLBuilder extends ExportsWalker {
|
|
|
|
/** Builds WebIDL definitions for the specified program. */
|
|
static build(program: Program): string {
|
|
return new IDLBuilder(program).build();
|
|
}
|
|
|
|
private sb: string[] = [];
|
|
private indentLevel: i32 = 0;
|
|
|
|
/** Constructs a new WebIDL builder. */
|
|
constructor(program: Program, includePrivate: bool = false) {
|
|
super(program, includePrivate);
|
|
}
|
|
|
|
visitGlobal(element: Global): void {
|
|
var sb = this.sb;
|
|
var isConst = element.is(CommonFlags.INLINED);
|
|
indent(sb, this.indentLevel);
|
|
if (isConst) sb.push("const ");
|
|
sb.push(this.typeToString(element.type));
|
|
sb.push(" ");
|
|
sb.push(element.simpleName);
|
|
if (isConst) {
|
|
switch (element.constantValueKind) {
|
|
case ConstantValueKind.INTEGER: {
|
|
sb.push(" = ");
|
|
sb.push(i64_to_string(element.constantIntegerValue));
|
|
break;
|
|
}
|
|
case ConstantValueKind.FLOAT: {
|
|
sb.push(" = ");
|
|
sb.push(element.constantFloatValue.toString());
|
|
break;
|
|
}
|
|
default: assert(false);
|
|
}
|
|
}
|
|
sb.push(";\n");
|
|
}
|
|
|
|
visitEnum(element: Enum): void {
|
|
var sb = this.sb;
|
|
indent(sb, this.indentLevel++);
|
|
sb.push("interface ");
|
|
sb.push(element.simpleName);
|
|
sb.push(" {\n");
|
|
var members = element.members;
|
|
if (members) {
|
|
for (let [name, member] of members) {
|
|
if (member.kind == ElementKind.ENUMVALUE) {
|
|
let isConst = (<EnumValue>member).is(CommonFlags.INLINED);
|
|
indent(sb, this.indentLevel);
|
|
if (isConst) sb.push("const ");
|
|
else sb.push("readonly ");
|
|
sb.push("unsigned long ");
|
|
sb.push(name);
|
|
if (isConst) {
|
|
sb.push(" = ");
|
|
sb.push((<EnumValue>member).constantValue.toString(10));
|
|
}
|
|
sb.push(";\n");
|
|
}
|
|
}
|
|
for (let member of members.values()) {
|
|
if (member.kind != ElementKind.ENUMVALUE) this.visitElement(member);
|
|
}
|
|
}
|
|
indent(sb, --this.indentLevel);
|
|
sb.push("}\n");
|
|
}
|
|
|
|
visitFunction(element: Function): void {
|
|
var sb = this.sb;
|
|
var signature = element.signature;
|
|
indent(sb, this.indentLevel);
|
|
sb.push(this.typeToString(signature.returnType));
|
|
sb.push(" ");
|
|
sb.push(element.simpleName);
|
|
sb.push("(");
|
|
var parameters = signature.parameterTypes;
|
|
var numParameters = parameters.length;
|
|
// var requiredParameters = signature.requiredParameters;
|
|
for (let i = 0; i < numParameters; ++i) {
|
|
if (i) sb.push(", ");
|
|
// if (i >= requiredParameters) sb.push("optional ");
|
|
sb.push(this.typeToString(parameters[i]));
|
|
sb.push(" ");
|
|
sb.push(signature.getParameterName(i));
|
|
}
|
|
sb.push(");\n");
|
|
var members = element.members;
|
|
if (members && members.size) {
|
|
indent(sb, this.indentLevel);
|
|
sb.push("interface ");
|
|
sb.push(element.simpleName);
|
|
sb.push(" {\n");
|
|
for (let member of members.values()) this.visitElement(member);
|
|
indent(sb, --this.indentLevel);
|
|
sb.push("}\n");
|
|
}
|
|
}
|
|
|
|
visitClass(element: Class): void {
|
|
var sb = this.sb;
|
|
indent(sb, this.indentLevel++);
|
|
sb.push("interface ");
|
|
sb.push(element.simpleName);
|
|
sb.push(" {\n");
|
|
// TODO
|
|
indent(sb, --this.indentLevel);
|
|
sb.push("}\n");
|
|
}
|
|
|
|
visitInterface(element: Interface): void {
|
|
this.visitClass(element);
|
|
}
|
|
|
|
visitField(element: Field): void {
|
|
// TODO
|
|
}
|
|
|
|
visitNamespace(element: Namespace): void {
|
|
var sb = this.sb;
|
|
indent(sb, this.indentLevel++);
|
|
sb.push("interface ");
|
|
sb.push(element.simpleName);
|
|
sb.push(" {\n");
|
|
var members = element.members;
|
|
if (members) {
|
|
for (let member of members.values()) this.visitElement(member);
|
|
}
|
|
indent(sb, --this.indentLevel);
|
|
sb.push("}\n");
|
|
}
|
|
|
|
typeToString(type: Type): string {
|
|
switch (type.kind) {
|
|
case TypeKind.I8: return "byte";
|
|
case TypeKind.I16: return "short";
|
|
case TypeKind.I32: return "long";
|
|
case TypeKind.I64: return "long long";
|
|
case TypeKind.ISIZE: return this.program.options.isWasm64 ? "long long" : "long";
|
|
case TypeKind.U8: return "octet";
|
|
case TypeKind.U16: return "unsigned short";
|
|
case TypeKind.U32: return "unsigned long";
|
|
// ^ TODO: function types
|
|
case TypeKind.U64: return "unsigned long long";
|
|
case TypeKind.USIZE: return this.program.options.isWasm64 ? "unsigned long long" : "unsigned long";
|
|
// ^ TODO: class types
|
|
case TypeKind.BOOL: return "boolean";
|
|
case TypeKind.F32: return "unrestricted float";
|
|
case TypeKind.F64: return "unrestricted double";
|
|
case TypeKind.VOID: return "void";
|
|
default: {
|
|
assert(false);
|
|
return "";
|
|
}
|
|
}
|
|
}
|
|
|
|
build(): string {
|
|
var sb = this.sb;
|
|
sb.push("interface ASModule {\n");
|
|
++this.indentLevel;
|
|
this.walk();
|
|
--this.indentLevel;
|
|
sb.push("}\n");
|
|
return sb.join("");
|
|
}
|
|
}
|
|
|
|
/** A TypeScript definitions builder. */
|
|
export class TSDBuilder extends ExportsWalker {
|
|
|
|
/** Builds TypeScript definitions for the specified program. */
|
|
static build(program: Program): string {
|
|
return new TSDBuilder(program).build();
|
|
}
|
|
|
|
private sb: string[] = [];
|
|
private indentLevel: i32 = 0;
|
|
|
|
/** Constructs a new WebIDL builder. */
|
|
constructor(program: Program, includePrivate: bool = false) {
|
|
super(program, includePrivate);
|
|
}
|
|
|
|
visitGlobal(element: Global): void {
|
|
var sb = this.sb;
|
|
var isConst = element.is(CommonFlags.INLINED);
|
|
indent(sb, this.indentLevel);
|
|
if (element.is(CommonFlags.STATIC)) {
|
|
if (isConst) sb.push("static readonly ");
|
|
else sb.push("static ");
|
|
} else {
|
|
if (isConst) sb.push("const ");
|
|
else sb.push("var ");
|
|
}
|
|
sb.push(element.simpleName);
|
|
sb.push(": ");
|
|
sb.push(this.typeToString(element.type));
|
|
sb.push(";\n");
|
|
this.visitNamespace(element);
|
|
}
|
|
|
|
visitEnum(element: Enum): void {
|
|
var sb = this.sb;
|
|
indent(sb, this.indentLevel++);
|
|
sb.push("enum ");
|
|
sb.push(element.simpleName);
|
|
sb.push(" {\n");
|
|
var members = element.members;
|
|
if (members) {
|
|
let numMembers = members.size;
|
|
for (let [name, member] of members) {
|
|
if (member.kind == ElementKind.ENUMVALUE) {
|
|
indent(sb, this.indentLevel);
|
|
sb.push(name);
|
|
if (member.is(CommonFlags.INLINED)) {
|
|
sb.push(" = ");
|
|
sb.push((<EnumValue>member).constantValue.toString(10));
|
|
}
|
|
sb.push(",\n");
|
|
--numMembers;
|
|
}
|
|
}
|
|
if (numMembers) this.visitNamespace(element);
|
|
}
|
|
indent(sb, --this.indentLevel);
|
|
sb.push("}\n");
|
|
}
|
|
|
|
visitFunction(element: Function): void {
|
|
if (element.isAny(CommonFlags.PRIVATE | CommonFlags.SET)) return;
|
|
var sb = this.sb;
|
|
var signature = element.signature;
|
|
indent(sb, this.indentLevel);
|
|
if (element.is(CommonFlags.PROTECTED)) sb.push("protected ");
|
|
if (element.is(CommonFlags.STATIC)) sb.push("static ");
|
|
if (element.is(CommonFlags.GET)) {
|
|
sb.push(element.prototype.declaration.name.text); // 'get:funcName' internally
|
|
sb.push(": ");
|
|
sb.push(this.typeToString(signature.returnType));
|
|
sb.push(";\n");
|
|
return;
|
|
} else {
|
|
if (!element.isAny(CommonFlags.STATIC | CommonFlags.INSTANCE)) sb.push("function ");
|
|
sb.push(element.simpleName);
|
|
}
|
|
sb.push("(");
|
|
var parameters = signature.parameterTypes;
|
|
var numParameters = parameters.length;
|
|
// var requiredParameters = signature.requiredParameters;
|
|
for (let i = 0; i < numParameters; ++i) {
|
|
if (i) sb.push(", ");
|
|
// if (i >= requiredParameters) sb.push("optional ");
|
|
sb.push(signature.getParameterName(i));
|
|
sb.push(": ");
|
|
sb.push(this.typeToString(parameters[i]));
|
|
}
|
|
if (element.isAny(CommonFlags.CONSTRUCTOR | CommonFlags.SET)) {
|
|
sb.push(")");
|
|
} else {
|
|
sb.push("): ");
|
|
sb.push(this.typeToString(signature.returnType));
|
|
}
|
|
sb.push(";\n");
|
|
this.visitNamespace(element);
|
|
}
|
|
|
|
visitClass(element: Class): void {
|
|
var sb = this.sb;
|
|
var isInterface = element.kind == ElementKind.INTERFACE;
|
|
indent(sb, this.indentLevel++);
|
|
if (isInterface) {
|
|
sb.push("interface ");
|
|
} else {
|
|
if (element.is(CommonFlags.ABSTRACT)) sb.push("abstract ");
|
|
sb.push("class ");
|
|
}
|
|
sb.push(element.simpleName);
|
|
var base = element.base;
|
|
if (base && base.is(CommonFlags.COMPILED | CommonFlags.MODULE_EXPORT)) {
|
|
sb.push(" extends ");
|
|
sb.push(base.simpleName); // TODO: fqn
|
|
}
|
|
sb.push(" {\n");
|
|
var members = element.prototype.members; // static
|
|
if (members) {
|
|
for (let member of members.values()) {
|
|
this.visitElement(member);
|
|
}
|
|
}
|
|
var ctor = element.constructorInstance;
|
|
if (ctor) this.visitFunction(ctor);
|
|
members = element.members; // instance
|
|
if (members) {
|
|
for (let member of members.values()) this.visitElement(member);
|
|
}
|
|
indent(sb, --this.indentLevel);
|
|
sb.push("}\n");
|
|
}
|
|
|
|
visitInterface(element: Interface): void {
|
|
this.visitClass(element);
|
|
}
|
|
|
|
visitField(element: Field): void {
|
|
if (element.is(CommonFlags.PRIVATE)) return;
|
|
var sb = this.sb;
|
|
indent(sb, this.indentLevel);
|
|
if (element.is(CommonFlags.PROTECTED)) sb.push("protected ");
|
|
if (element.is(CommonFlags.STATIC)) sb.push("static ");
|
|
if (element.is(CommonFlags.READONLY)) sb.push("readonly ");
|
|
sb.push(element.simpleName);
|
|
sb.push(": ");
|
|
sb.push(this.typeToString(element.type));
|
|
sb.push(";\n");
|
|
}
|
|
|
|
visitNamespace(element: Element): void {
|
|
var members = element.members;
|
|
if (members && members.size) {
|
|
let sb = this.sb;
|
|
indent(sb, this.indentLevel++);
|
|
sb.push("namespace ");
|
|
sb.push(element.simpleName);
|
|
sb.push(" {\n");
|
|
for (let member of members.values()) this.visitElement(member);
|
|
indent(sb, --this.indentLevel);
|
|
sb.push("}\n");
|
|
}
|
|
}
|
|
|
|
typeToString(type: Type): string {
|
|
switch (type.kind) {
|
|
case TypeKind.I8: return "i8";
|
|
case TypeKind.I16: return "i16";
|
|
case TypeKind.I32: return "i32";
|
|
case TypeKind.I64: return "I64";
|
|
case TypeKind.ISIZE: return this.program.options.isWasm64 ? "I64" : "i32";
|
|
case TypeKind.U8: return "u8";
|
|
case TypeKind.U16: return "u16";
|
|
case TypeKind.U32: return "u32";
|
|
// ^ TODO: function types
|
|
case TypeKind.U64: return "U64";
|
|
case TypeKind.USIZE: return this.program.options.isWasm64 ? "U64" : "u32";
|
|
// ^ TODO: class types
|
|
case TypeKind.BOOL: return "bool";
|
|
case TypeKind.F32: return "f32";
|
|
case TypeKind.F64: return "f64";
|
|
case TypeKind.V128: return "v128";
|
|
case TypeKind.VOID: return "void";
|
|
default: {
|
|
assert(false);
|
|
return "";
|
|
}
|
|
}
|
|
}
|
|
|
|
build(): string {
|
|
var sb = this.sb;
|
|
sb.push("declare module ASModule {\n");
|
|
sb.push(" type i8 = number;\n");
|
|
sb.push(" type i16 = number;\n");
|
|
sb.push(" type i32 = number;\n");
|
|
sb.push(" type u8 = number;\n");
|
|
sb.push(" type u16 = number;\n");
|
|
sb.push(" type u32 = number;\n");
|
|
sb.push(" type f32 = number;\n");
|
|
sb.push(" type f64 = number;\n");
|
|
sb.push(" type bool = any;\n");
|
|
++this.indentLevel;
|
|
this.walk();
|
|
--this.indentLevel;
|
|
sb.push("}\n");
|
|
sb.push("export default ASModule;\n");
|
|
return this.sb.join("");
|
|
}
|
|
}
|
|
|
|
// helpers
|
|
|
|
/** Tests if a namespace-like element has at least one compiled member. */
|
|
function hasCompiledMember(element: Element): bool {
|
|
var members = element.members;
|
|
if (members) {
|
|
for (let member of members.values()) {
|
|
switch (member.kind) {
|
|
case ElementKind.FUNCTION_PROTOTYPE: {
|
|
for (let instances of (<FunctionPrototype>member).instances.values()) {
|
|
for (let instance of instances.values()) {
|
|
if (instance.is(CommonFlags.COMPILED)) return true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case ElementKind.CLASS_PROTOTYPE: {
|
|
for (let instance of (<ClassPrototype>member).instances.values()) {
|
|
if (instance.is(CommonFlags.COMPILED)) return true;
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
if (member.is(CommonFlags.COMPILED) || hasCompiledMember(member)) return true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|