├── ts2cpp.js ├── banner.png ├── .gitignore ├── examples ├── cycle.d.ts ├── parentBase.d.ts └── memberCycle.d.ts ├── cheerp ├── webgl.h ├── function.h ├── coroutine.h └── jshelper.h ├── package.json ├── src ├── type │ ├── genericType.ts │ ├── declaredType.ts │ ├── literalExpression.ts │ ├── memberType.ts │ ├── functionType.ts │ ├── namedType.ts │ ├── type.ts │ ├── qualifiedType.ts │ ├── expression.ts │ ├── compoundExpression.ts │ └── templateType.ts ├── parser │ ├── typeAlias.ts │ ├── variable.ts │ ├── name.ts │ ├── library.ts │ ├── parser.ts │ ├── typeParser.ts │ ├── node.ts │ └── generics.ts ├── declaration │ ├── variable.ts │ ├── typeAlias.ts │ ├── templateDeclaration.ts │ ├── namespace.ts │ ├── function.ts │ ├── declaration.ts │ └── class.ts ├── utility.ts ├── writer.ts ├── file.ts ├── index.ts ├── error.ts └── target.ts ├── README.md ├── LICENSE.txt └── tsconfig.json /ts2cpp.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require("./build/index.js"); 4 | -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leaningtech/ts2cpp/HEAD/banner.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /node_modules 3 | /cheerp/jsobject.h 4 | /cheerp/types.h 5 | /cheerp/clientlib.h 6 | /install.sh 7 | /.vimrc 8 | /compile_commands.json 9 | -------------------------------------------------------------------------------- /examples/cycle.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare interface Outer extends Outer.Inner {} 4 | 5 | declare namespace Outer { 6 | interface Inner {} 7 | } 8 | -------------------------------------------------------------------------------- /examples/parentBase.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare interface Outer {} 4 | 5 | declare namespace Outer { 6 | interface Inner extends Outer {} 7 | } 8 | -------------------------------------------------------------------------------- /cheerp/webgl.h: -------------------------------------------------------------------------------- 1 | #include "clientlib.h" 2 | 3 | namespace client [[cheerp::genericjs]] { 4 | /// deprecated 5 | using WebGLVertexArrayOES = WebGLVertexArrayObjectOES; 6 | 7 | /// deprecated 8 | using OESVertexArrayObject = OES_vertex_array_object; 9 | } 10 | -------------------------------------------------------------------------------- /examples/memberCycle.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare interface Outer { 4 | getInnest1(): Outer.Inner1.Innest1; 5 | } 6 | 7 | declare namespace Outer { 8 | interface Inner1 { 9 | getInnest2(): Outer.Inner2.Innest2; 10 | } 11 | 12 | interface Inner2 extends Outer {} 13 | 14 | namespace Inner1 { 15 | interface Innest1 {} 16 | } 17 | 18 | namespace Inner2 { 19 | interface Innest2 {} 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@leaningtech/ts2cpp", 3 | "version": "1.0.1", 4 | "description": "TypeScript declaration to C++ header transpiler for Cheerp", 5 | "bin": { 6 | "ts2cpp": "ts2cpp.js" 7 | }, 8 | "scripts": { 9 | "build": "npx tsc" 10 | }, 11 | "main": "build/index.js", 12 | "files": [ 13 | "build", 14 | "ts2cpp.js" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/leaningtech/ts2cpp.git" 19 | }, 20 | "keywords": [ 21 | "c++", 22 | "c", 23 | "typescript", 24 | "compiler", 25 | "cheerp", 26 | "leaningtech" 27 | ], 28 | "author": "Leaning Technologies", 29 | "license": "Apache-2.0", 30 | "dependencies": { 31 | "@types/node": "^20.8.2", 32 | "commander": "^11.1.0", 33 | "typescript": "^5.2.2" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/type/genericType.ts: -------------------------------------------------------------------------------- 1 | import { UnqualifiedType } from "./type.js"; 2 | import { Dependency, State, Dependencies } from "../target.js"; 3 | import { Writer } from "../writer.js"; 4 | import { Namespace } from "../declaration/namespace.js"; 5 | 6 | // A `GenericType` is a type that reference a type argument of a template 7 | // declaration. 8 | export class GenericType extends UnqualifiedType { 9 | private readonly name: string; 10 | 11 | private constructor(name: string) { 12 | super(); 13 | this.name = name; 14 | } 15 | 16 | public getName(): string { 17 | return this.name; 18 | } 19 | 20 | public getDependencies(reason: Dependency, innerState?: State): Dependencies { 21 | return new Dependencies; 22 | } 23 | 24 | public write(writer: Writer, namespace?: Namespace): void { 25 | writer.write(this.name); 26 | } 27 | 28 | public key(): string { 29 | return `N${this.name};`; 30 | } 31 | 32 | public isVoidLike(): boolean { 33 | return this.name === "void"; 34 | } 35 | 36 | public static create(name: string): GenericType { 37 | return new GenericType(name).intern(); 38 | } 39 | } 40 | 41 | export const ARGS = GenericType.create("_Args"); 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![ts2cpp banner](banner.png) 2 | 3 | A tool to generate C++ headers from typescript declaration files (.d.ts) for use with [Cheerp](https://github.com/leaningtech/cheerp-meta). 4 | 5 | ``` 6 | Usage: ts2cpp [options] 7 | 8 | Options: 9 | --pretty format output files 10 | --default-lib generate headers for the default library 11 | --out, -o path to output file 12 | --ignore-errors ignore errors 13 | --list-files write a list of all included .d.ts files 14 | --verbose, -v verbose output 15 | --verbose-progress verbose progress 16 | --namespace wrap output in a namespace 17 | --no-constraints do not use std::enable_if or static_asserts 18 | --full-names always use fully qualified names 19 | -h, --help display help for command 20 | ``` 21 | 22 | ## Setup 23 | 24 | ``` 25 | git clone https://github.com/leaningtech/ts2cpp.git 26 | cd ts2cpp 27 | npm i && npx tsc 28 | ``` 29 | 30 | Or run with `npx`: 31 | 32 | ``` 33 | npx @leaningtech/ts2cpp --help 34 | ``` 35 | 36 | ## Examples 37 | 38 | Generating clientlib headers 39 | 40 | ``` 41 | mkdir -p cheerp 42 | npx ts2cpp --default-lib --pretty 43 | ``` 44 | 45 | Generating headers from a custom declaration file 46 | 47 | ``` 48 | npx ts2cpp --pretty test.d.ts -o test.h 49 | ``` 50 | -------------------------------------------------------------------------------- /cheerp/function.h: -------------------------------------------------------------------------------- 1 | #ifndef CHEERP_FUNCTION_H 2 | #define CHEERP_FUNCTION_H 3 | #include "cheerp/types.h" 4 | namespace [[cheerp::genericjs]] client { 5 | class EventListener; 6 | } 7 | namespace [[cheerp::genericjs]] cheerp { 8 | template 9 | struct FunctionTypeImpl; 10 | template 11 | struct FunctionTypeImpl { 12 | using type = R(Args...); 13 | }; 14 | template 15 | struct FunctionTypeImpl { 16 | using type = R(Args...); 17 | }; 18 | template 19 | using FunctionType = typename FunctionTypeImpl::operator())>::type; 20 | template 21 | client::EventListener* Callback(T&& func); 22 | template 23 | client::EventListener* Callback(R(*func)(Args...)); 24 | } 25 | namespace [[cheerp::genericjs]] client { 26 | template 27 | class _Function : public Function { 28 | public: 29 | template>, _Function>>> 30 | _Function(T&& func) : Function(cheerp::Callback(func)) { 31 | } 32 | template, _Function>>> 33 | _Function(R(*func)(Args...)) : Function(cheerp::Callback(func)) { 34 | } 35 | [[gnu::always_inline]] 36 | _Function(const EventListener* listener) : Function(listener) { 37 | } 38 | }; 39 | } 40 | #endif 41 | -------------------------------------------------------------------------------- /cheerp/coroutine.h: -------------------------------------------------------------------------------- 1 | #ifndef CHEERP_ASYNC_H 2 | #define CHEERP_ASYNC_H 3 | 4 | #include "cheerp/client.h" 5 | #include "cheerp/clientlib.h" 6 | 7 | #include 8 | #include 9 | 10 | template 11 | struct std::coroutine_traits*, Args...> { 12 | struct [[cheerp::genericjs]] promise_type { 13 | client::Promise* get_return_object() { 14 | return client::Promise::template _New([this](client::Function* resolve) { 15 | this->resolve = resolve; 16 | }); 17 | } 18 | 19 | auto initial_suspend() const noexcept { 20 | return std::suspend_never(); 21 | } 22 | 23 | auto final_suspend() const noexcept { 24 | return std::suspend_never(); 25 | } 26 | 27 | void return_value(T value) { 28 | resolve->call(nullptr, value); 29 | } 30 | 31 | void unhandled_exception() { 32 | // TODO 33 | } 34 | 35 | private: 36 | client::Function* resolve; 37 | }; 38 | }; 39 | 40 | template 41 | [[cheerp::genericjs]] 42 | auto operator co_await(client::Promise& promise) { 43 | struct promise_awaiter { 44 | promise_awaiter(client::Promise* promise) { 45 | this->promise = promise; 46 | } 47 | 48 | bool await_ready() const noexcept { 49 | return false; 50 | } 51 | 52 | void await_suspend(std::coroutine_handle<> handle) { 53 | promise->template then([this, handle](T value) { 54 | this->value = value; 55 | handle.resume(); 56 | }); 57 | } 58 | 59 | T await_resume() const { 60 | return value; 61 | } 62 | 63 | private: 64 | client::Promise* promise; 65 | T value; 66 | }; 67 | 68 | return promise_awaiter(&promise); 69 | } 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /src/parser/typeAlias.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "./parser.js"; 2 | import { TypeAlias } from "../declaration/typeAlias.js"; 3 | import { Generics, DefaultResolve } from "./generics.js"; 4 | import { Namespace } from "../declaration/namespace.js"; 5 | import { TemplateType } from "../type/templateType.js"; 6 | import * as ts from "typescript"; 7 | 8 | export function parseTypeAlias(parser: Parser, declaration: ts.TypeAliasDeclaration, object: TypeAlias, generics: Generics, parent?: Namespace): void { 9 | // 1. If we're not going to emit this declaration anyways, there's no point 10 | // in parsing it. 11 | if (!parser.includesDeclaration(declaration)) { 12 | return; 13 | } 14 | 15 | generics = generics.clone(); 16 | 17 | // 2. Parse the type parameters with `createParameters` and add them to the 18 | // type alias. 19 | const [parameters, constraints] = generics.createParameters(parser, [declaration], DefaultResolve.None); 20 | parameters.forEach(({ type, defaultType }) => object.addTypeParameter(type.getName(), defaultType)); 21 | 22 | // 3. Parse and set the aliased type of the type alias, possibly 23 | // making an `std::enable_if_t` template using constraints returned 24 | // from `createParameters`. 25 | const info = parser.getTypeNodeInfo(declaration.type, generics); 26 | object.setType(TemplateType.makeConstraint(info.asTypeAlias(), constraints)); 27 | 28 | // 4. Some post processing: 29 | // - Mark the type alias as coming from the declaration `declaration`. 30 | // - Remove unused type parameters. 31 | object.setDeclaration(declaration); 32 | object.removeUnusedTypeParameters(); 33 | 34 | // 5. Add it to the parent declaration. 35 | parser.addDeclaration(object, parent); 36 | } 37 | -------------------------------------------------------------------------------- /src/type/declaredType.ts: -------------------------------------------------------------------------------- 1 | import { UnqualifiedType } from "./type.js"; 2 | import { Declaration } from "../declaration/declaration.js"; 3 | import { Dependency, State, Dependencies } from "../target.js"; 4 | import { Writer } from "../writer.js"; 5 | import { Namespace } from "../declaration/namespace.js"; 6 | 7 | // A declared type is one for which we have a declaration. 8 | // 9 | // Don't be fooled by the small implementation of `DeclaredType`, this is 10 | // *the most important* type to make dependency resolution work. If a function 11 | // declaration references `String`, we *must* be able to get the declaration 12 | // of `String` so we can order its generation before the function. All of 13 | // dependency resolution eventually reaches a call to `getDependencies` in this 14 | // type. 15 | export class DeclaredType extends UnqualifiedType { 16 | private readonly declaration: Declaration; 17 | 18 | private constructor(declaration: Declaration) { 19 | super(); 20 | this.declaration = declaration; 21 | } 22 | 23 | public getDeclaration(): Declaration { 24 | return this.declaration; 25 | } 26 | 27 | public getDependencies(reason: Dependency, innerState?: State): Dependencies { 28 | return new Dependencies([[this.declaration, reason]]); 29 | } 30 | 31 | public write(writer: Writer, namespace?: Namespace): void { 32 | writer.write(this.declaration.getPath(namespace)); 33 | } 34 | 35 | public key(): string { 36 | return `D${this.declaration.getId()}`; 37 | } 38 | 39 | public getName(): string { 40 | return this.declaration.getName(); 41 | } 42 | 43 | public static create(declaration: Declaration): DeclaredType { 44 | return new DeclaredType(declaration).intern(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/declaration/variable.ts: -------------------------------------------------------------------------------- 1 | import { Declaration } from "./declaration.js"; 2 | import { Namespace, Flags } from "./namespace.js"; 3 | import { State, Dependency, Dependencies, ReasonKind, ResolverContext } from "../target.js"; 4 | import { Type } from "../type/type.js"; 5 | import { Writer } from "../writer.js"; 6 | 7 | export class Variable extends Declaration { 8 | private type: Type; 9 | 10 | public constructor(name: string, type: Type, namespace?: Namespace) { 11 | super(name, namespace); 12 | this.type = type; 13 | } 14 | 15 | public getType(): Type { 16 | return this.type; 17 | } 18 | 19 | public maxState(): State { 20 | return State.Partial; 21 | } 22 | 23 | public getChildren(): ReadonlyArray { 24 | return new Array; 25 | } 26 | 27 | // The dependencies of a variable are: 28 | // - partial for the type of the variable. 29 | protected getDirectDependencies(state: State): Dependencies { 30 | return this.type.getDependencies(new Dependency(State.Partial, this, ReasonKind.VariableType)); 31 | } 32 | 33 | protected getDirectReferencedTypes(): ReadonlyArray { 34 | return this.type.getReferencedTypes(); 35 | } 36 | 37 | protected writeImpl(context: ResolverContext, writer: Writer, state: State, namespace?: Namespace): void { 38 | const flags = this.getFlags(); 39 | 40 | if (flags & Flags.Extern) { 41 | writer.write("extern"); 42 | writer.writeSpace(); 43 | } 44 | 45 | if (flags & Flags.Static) { 46 | writer.write("static"); 47 | writer.writeSpace(); 48 | } 49 | 50 | this.type.write(writer, namespace); 51 | writer.writeSpace(); 52 | writer.write(this.getName()); 53 | writer.write(";"); 54 | writer.writeLine(false); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/declaration/typeAlias.ts: -------------------------------------------------------------------------------- 1 | import { Declaration } from "./declaration.js"; 2 | import { TemplateDeclaration } from "./templateDeclaration.js"; 3 | import { Namespace } from "./namespace.js"; 4 | import { Type } from "../type/type.js"; 5 | import { State, Dependencies, ReasonKind, Dependency, ResolverContext } from "../target.js"; 6 | import { Writer } from "../writer.js"; 7 | 8 | export class TypeAlias extends TemplateDeclaration { 9 | private type: Type; 10 | 11 | public constructor(name: string, type: Type, namespace?: Namespace) { 12 | super(name, namespace); 13 | this.type = type; 14 | } 15 | 16 | public getType(): Type { 17 | return this.type; 18 | } 19 | 20 | public setType(type: Type): void { 21 | this.type = type; 22 | } 23 | 24 | public maxState(): State { 25 | return State.Partial; 26 | } 27 | 28 | public getChildren(): ReadonlyArray { 29 | return new Array; 30 | } 31 | 32 | // The dependencies of a type alias are: 33 | // - partial for the target type. 34 | protected getDirectDependencies(state: State): Dependencies { 35 | return this.type.getDependencies(new Dependency(State.Partial, this, ReasonKind.TypeAliasType)); 36 | } 37 | 38 | protected getDirectReferencedTypes(): ReadonlyArray { 39 | return this.type.getReferencedTypes(); 40 | } 41 | 42 | protected writeImpl(context: ResolverContext, writer: Writer, state: State, namespace?: Namespace): void { 43 | this.writeTemplate(writer, state, namespace); 44 | writer.write("using"); 45 | writer.writeSpace(); 46 | writer.write(this.getName()); 47 | writer.writeSpace(false); 48 | writer.write("="); 49 | writer.writeSpace(false); 50 | this.type.write(writer, namespace); 51 | writer.write(";"); 52 | writer.writeLine(false); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/type/literalExpression.ts: -------------------------------------------------------------------------------- 1 | import { Expression } from "./expression.js"; 2 | import { Dependency, State, Dependencies } from "../target.js"; 3 | import { Type } from "./type.js"; 4 | import { Writer } from "../writer.js"; 5 | import { Namespace } from "../declaration/namespace.js"; 6 | 7 | // A literal expression is just text. Any dependencies it may have are assumed 8 | // to exist. The token is written literally and should be valid C++. 9 | // 10 | // This is mostly used for simple terminal types like "true" and "false", but 11 | // it can also be used when you want any quick and dirty type and don't need to 12 | // bother with dependency or reference resolution. 13 | export class LiteralExpression extends Expression { 14 | private readonly token: string; 15 | 16 | private constructor(token: string) { 17 | super(); 18 | this.token = token; 19 | } 20 | 21 | public getDependencies(reason: Dependency, innerState?: State): Dependencies { 22 | return new Dependencies; 23 | } 24 | 25 | public getReferencedTypes(): ReadonlyArray { 26 | return []; 27 | } 28 | 29 | public write(writer: Writer, namespace?: Namespace): void { 30 | writer.write(this.token); 31 | } 32 | 33 | public key(): string { 34 | return `L${this.token};`; 35 | } 36 | 37 | // "true" is always true. We also say "..." is true, this may be a bug, but 38 | // it hasn't caused any issues yet. 39 | public isAlwaysTrue(): boolean { 40 | return this.token === "true" || this.token === "..."; 41 | } 42 | 43 | public static create(token: string): LiteralExpression { 44 | return new LiteralExpression(token).intern(); 45 | } 46 | } 47 | 48 | export const ELLIPSES = LiteralExpression.create("..."); 49 | export const TRUE = LiteralExpression.create("true"); 50 | export const FALSE = LiteralExpression.create("false"); 51 | -------------------------------------------------------------------------------- /src/type/memberType.ts: -------------------------------------------------------------------------------- 1 | import { Expression } from "./expression.js"; 2 | import { Type, UnqualifiedType } from "./type.js"; 3 | import { Dependency, State, Dependencies } from "../target.js"; 4 | import { Writer } from "../writer.js"; 5 | import { Namespace } from "../declaration/namespace.js"; 6 | 7 | // A member type (sometimes also called an associated type) is an inner type of 8 | // a class, such as "typename Container::iterator". 9 | // 10 | // A class that contains a member type looks like this: 11 | // ``` 12 | // class Container { 13 | // using iterator = int*; 14 | // }; 15 | // ``` 16 | export class MemberType extends UnqualifiedType { 17 | private readonly inner: Type; 18 | private readonly name: string; 19 | 20 | private constructor(inner: Type, name: string) { 21 | super(); 22 | this.inner = inner; 23 | this.name = name; 24 | } 25 | 26 | public getInner(): Type { 27 | return this.inner; 28 | } 29 | 30 | public getName(): string { 31 | return this.name; 32 | } 33 | 34 | // The dependencies of a member type are: 35 | // - complete for the parent of the member type. 36 | public getDependencies(reason: Dependency, innerState?: State): Dependencies { 37 | return this.inner.getDependencies(reason.withState(State.Complete)); 38 | } 39 | 40 | public getReferencedTypes(): ReadonlyArray { 41 | return [this, ...this.inner.getReferencedTypes()]; 42 | } 43 | 44 | public write(writer: Writer, namespace?: Namespace): void { 45 | // We must write "typename" in case `inner` is a dependent type. 46 | // Thanks C++ :D :D :D 47 | writer.write("typename"); 48 | writer.writeSpace(); 49 | this.inner.write(writer, namespace); 50 | writer.write("::"); 51 | writer.write(this.name); 52 | } 53 | 54 | public key(): string { 55 | return `Y${this.inner.key()}${this.name};`; 56 | } 57 | 58 | public static create(inner: Type, name: string): MemberType { 59 | return new MemberType(inner, name).intern(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/parser/variable.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "./parser.js"; 2 | import { Generics } from "./generics.js"; 3 | import { Namespace, Flags } from "../declaration/namespace.js"; 4 | import { Class } from "../declaration/class.js"; 5 | import { VOID_TYPE } from "../type/namedType.js"; 6 | import { getName } from "./name.js"; 7 | import { Variable } from "../declaration/variable.js"; 8 | import * as ts from "typescript"; 9 | 10 | export function parseVariable(parser: Parser, declaration: ts.VariableDeclaration | ts.PropertySignature | ts.PropertyDeclaration, generics: Generics, parent?: Namespace): void { 11 | // 1. If we're not going to emit this declaration anyways, there's no point 12 | // in parsing it. 13 | if (!parser.includesDeclaration(declaration)) { 14 | return; 15 | } 16 | 17 | const isMember = parent instanceof Class; 18 | const [interfaceName, escapedName] = getName(declaration); 19 | 20 | // 2. Parse the type of the variable. 21 | const info = parser.getTypeNodeInfo(declaration.type, generics); 22 | 23 | // 3. If this is an optional property, we mark the type as optional, this 24 | // may have an effect on the qualifiers or attributes of the final C++ 25 | // type. An example of an optional property: 26 | // ``` 27 | // declare interface Foo { 28 | // bar?: number; 29 | // } 30 | // ``` 31 | if (ts.isPropertySignature(declaration) && declaration.questionToken) { 32 | info.setOptional(); 33 | } 34 | 35 | // 4. Create the variable object. 36 | const object = new Variable(escapedName, info.asVariableType(isMember)); 37 | 38 | // 5. Variables of type `void` are not allowed in C++, we just don't 39 | // generate them at all. Another option might've beeen to generate them 40 | // with type `_Any*`. 41 | if (object.getType() === VOID_TYPE) { 42 | return; 43 | } 44 | 45 | // 6. Some post processing: 46 | // - Mark the variable as coming from the declaration `declaration`. 47 | // - Add `static` to member variables and `extern` to global variables. 48 | object.setDeclaration(declaration); 49 | object.addFlags(isMember ? Flags.Static : Flags.Extern); 50 | 51 | // 7. Add it to the parent declaration. 52 | parser.addDeclaration(object, parent); 53 | } 54 | -------------------------------------------------------------------------------- /src/utility.ts: -------------------------------------------------------------------------------- 1 | // Some utility functions, including: 2 | // - Parsing command line options. 3 | // - Benchmarking. 4 | 5 | import { program } from "commander"; 6 | 7 | export let options: Options; 8 | 9 | export interface Options { 10 | isPretty: boolean, 11 | isDefaultLib: boolean, 12 | isVerbose: boolean, 13 | isVerboseProgress: boolean, 14 | ignoreErrors: boolean, 15 | listFiles: boolean, 16 | useConstraints: boolean, 17 | useFullNames: boolean, 18 | outputFile?: string, 19 | namespace?: string, 20 | } 21 | 22 | export function parseOptions(): Array { 23 | program 24 | .option("--pretty", "format output files") 25 | .option("--default-lib", "generate headers for the default library") 26 | .option("--out, -o ", "path to output file") 27 | .option("--ignore-errors", "ignore errors") 28 | .option("--list-files", "write a list of all included .d.ts files") 29 | .option("--verbose, -v", "verbose output") 30 | .option("--verbose-progress", "verbose progress") 31 | .option("--namespace ", "wrap output in a namespace") 32 | .option("--no-constraints", "do not use std::enable_if or static_asserts") 33 | .option("--full-names", "always use fully qualified names"); 34 | 35 | program.parse(); 36 | 37 | const opts = program.opts(); 38 | 39 | options = { 40 | isPretty: !!opts.pretty, 41 | isDefaultLib: !!opts.defaultLib, 42 | isVerbose: !!opts.V, 43 | isVerboseProgress: !!opts.verboseProgress, 44 | ignoreErrors: !!opts.ignoreErrors, 45 | listFiles: !!opts.listFiles, 46 | useConstraints: !!opts.constraints, 47 | useFullNames: !!opts.fullNames, 48 | outputFile: opts.O, 49 | namespace: opts.namespace, 50 | }; 51 | 52 | return program.args; 53 | } 54 | 55 | // Times the given function, forwarding the return value. Timing information is 56 | // only output to console if "--verbose" is set. 57 | export function withTimer(name: string, func: () => T): T { 58 | if (options.isVerbose) { 59 | console.time(name); 60 | } 61 | 62 | const result = func(); 63 | 64 | if (options.isVerbose) { 65 | console.timeEnd(name); 66 | 67 | const memoryUsage = process.memoryUsage(); 68 | 69 | console.info(`memory usage: ${JSON.stringify(memoryUsage)}`); 70 | } 71 | 72 | return result; 73 | } 74 | -------------------------------------------------------------------------------- /src/type/functionType.ts: -------------------------------------------------------------------------------- 1 | import { Expression } from "./expression.js"; 2 | import { Type } from "./type.js"; 3 | import { Dependency, State, Dependencies } from "../target.js"; 4 | import { Writer } from "../writer.js"; 5 | import { Namespace } from "../declaration/namespace.js"; 6 | 7 | // A C-style function type of the form "int(const char*)" (a function taking a 8 | // const char* and returning an int). This is used, for example, as a type 9 | // parameter to `_Function`. 10 | export class FunctionType extends Type { 11 | private readonly returnType: Type; 12 | private parameters?: Array; 13 | 14 | private constructor(returnType: Type) { 15 | super(); 16 | this.returnType = returnType; 17 | } 18 | 19 | public getReturnType(): Type { 20 | return this.returnType; 21 | } 22 | 23 | public getParameters(): ReadonlyArray { 24 | return this.parameters ?? []; 25 | } 26 | 27 | // The dependencies of a function type are: 28 | // - partial for the parameter types. 29 | // - partial for the return type. 30 | public getDependencies(reason: Dependency, innerState?: State): Dependencies { 31 | const partialReason = reason.withState(State.Partial); 32 | 33 | return new Dependencies( 34 | this.getParameters() 35 | .flatMap(typeParameter => [...typeParameter.getDependencies(partialReason)]) 36 | .concat([...this.returnType.getDependencies(partialReason)]) 37 | ); 38 | } 39 | 40 | public getReferencedTypes(): ReadonlyArray { 41 | return this.getParameters() 42 | .flatMap(parameter => [...parameter.getReferencedTypes()]) 43 | .concat([this, ...this.returnType.getReferencedTypes()]); 44 | } 45 | 46 | public write(writer: Writer, namespace?: Namespace): void { 47 | let first = true; 48 | this.returnType.write(writer, namespace); 49 | writer.write("("); 50 | 51 | for (const parameter of this.getParameters()) { 52 | if (!first) { 53 | writer.write(","); 54 | writer.writeSpace(false); 55 | } 56 | 57 | parameter.write(writer, namespace); 58 | first = false; 59 | } 60 | 61 | writer.write(")"); 62 | } 63 | 64 | public key(): string { 65 | const parameters = this.getParameters() 66 | .map(parameter => parameter.key()).join(""); 67 | 68 | return `f${this.returnType.key()}${parameters};`; 69 | } 70 | 71 | public static create(returnType: Type, ...parameters: ReadonlyArray): FunctionType { 72 | const result = new FunctionType(returnType); 73 | 74 | if (parameters.length > 0) { 75 | result.parameters = [...parameters]; 76 | } 77 | 78 | return result.intern(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/type/namedType.ts: -------------------------------------------------------------------------------- 1 | import { UnqualifiedType } from "./type.js"; 2 | import { Dependency, State, Dependencies } from "../target.js"; 3 | import { Writer } from "../writer.js"; 4 | import { Namespace } from "../declaration/namespace.js"; 5 | 6 | // `NamedType` is much like `LiteralExpression` but for types. The difference 7 | // in name is due to a combination of poor foresight and laziness. 8 | export class NamedType extends UnqualifiedType { 9 | private readonly name: string; 10 | 11 | private constructor(name: string) { 12 | super(); 13 | this.name = name; 14 | } 15 | 16 | public getName(): string { 17 | return this.name; 18 | } 19 | 20 | public getDependencies(reason: Dependency, innerState?: State): Dependencies { 21 | return new Dependencies; 22 | } 23 | 24 | public write(writer: Writer, namespace?: Namespace): void { 25 | writer.write(this.name); 26 | } 27 | 28 | public key(): string { 29 | return `N${this.name};`; 30 | } 31 | 32 | public isVoidLike(): boolean { 33 | return this.name === "void"; 34 | } 35 | 36 | public static create(name: string): NamedType { 37 | return new NamedType(name).intern(); 38 | } 39 | } 40 | 41 | export const VOID_TYPE = NamedType.create("void"); 42 | export const BOOL_TYPE = NamedType.create("bool"); 43 | export const DOUBLE_TYPE = NamedType.create("double"); 44 | export const FLOAT_TYPE = NamedType.create("float"); 45 | export const LONG_TYPE = NamedType.create("long"); 46 | export const UNSIGNED_LONG_TYPE = NamedType.create("unsigned long"); 47 | export const INT_TYPE = NamedType.create("int"); 48 | export const UNSIGNED_INT_TYPE = NamedType.create("unsigned int"); 49 | export const SHORT_TYPE = NamedType.create("short"); 50 | export const UNSIGNED_SHORT_TYPE = NamedType.create("unsigned short"); 51 | export const CHAR_TYPE = NamedType.create("char"); 52 | export const WCHAR_TYPE = NamedType.create("wchar_t"); 53 | export const UNSIGNED_CHAR_TYPE = NamedType.create("unsigned char"); 54 | export const CONST_CHAR_POINTER_TYPE = CHAR_TYPE.constPointer(); 55 | export const CONST_WCHAR_POINTER_TYPE = WCHAR_TYPE.constPointer(); 56 | export const NULLPTR_TYPE = NamedType.create("cheerp::Nullptr"); 57 | export const STRING_TYPE = NamedType.create("std::string"); 58 | export const ENABLE_IF = NamedType.create("cheerp::EnableIf"); 59 | export const IS_SAME = NamedType.create("cheerp::IsSame"); 60 | export const CAN_CAST = NamedType.create("cheerp::CanCast"); 61 | export const CAN_CAST_ARGS = NamedType.create("cheerp::CanCastArgs"); 62 | export const CHECK_TEMPLATE = NamedType.create("cheerp::CheckTemplate"); 63 | export const ARRAY_ELEMENT_TYPE = NamedType.create("cheerp::ArrayElementType"); 64 | export const ANY_TYPE = NamedType.create("_Any"); 65 | export const UNION_TYPE = NamedType.create("_Union"); 66 | export const FUNCTION_TYPE = NamedType.create("_Function"); 67 | -------------------------------------------------------------------------------- /src/type/type.ts: -------------------------------------------------------------------------------- 1 | import { Expression } from "./expression.js"; 2 | 3 | // A C++ type, like `TArray*`. Types are not too different from 4 | // expressions, they just have some utility functions for operations that can 5 | // only be applied to types, and having a separate type for types gives some 6 | // type safety. `Type` being a cubclass of `Expression` is convenient when 7 | // both are acceptable, for example the type parameters of a `TemplateType`, 8 | // then we can just use `Expression`. 9 | export abstract class Type extends Expression { 10 | // Add qualifier to the type. For example, this turns 11 | // `NamedType.create("String").qualify(Pointer)` into `String*`. 12 | public qualify(qualifier: TypeQualifier): QualifiedType { 13 | return QualifiedType.create(this, qualifier); 14 | } 15 | 16 | public pointer(): QualifiedType { 17 | return this.qualify(TypeQualifier.Pointer); 18 | } 19 | 20 | public constPointer(): QualifiedType { 21 | return this.qualify(TypeQualifier.ConstPointer); 22 | } 23 | 24 | public reference(): QualifiedType { 25 | return this.qualify(TypeQualifier.Reference); 26 | } 27 | 28 | public constReference(): QualifiedType { 29 | return this.qualify(TypeQualifier.ConstReference); 30 | } 31 | 32 | public expand(): QualifiedType { 33 | return this.qualify(TypeQualifier.Variadic); 34 | } 35 | 36 | public rValueReference(): QualifiedType { 37 | return this.qualify(TypeQualifier.RValueReference); 38 | } 39 | 40 | // Get a member type of this type. For example 41 | // `typename Container::iterator`. 42 | public getMemberType(name: string): MemberType { 43 | return MemberType.create(this, name); 44 | } 45 | 46 | // A type always references itself. 47 | // 48 | // Don't confuse this with `getDependencies`! When we call 49 | // `getReferencedTypes` on `String` we *want* it to return `String`. 50 | public getReferencedTypes(): ReadonlyArray { 51 | return [this]; 52 | } 53 | 54 | // Get a name for this type, if available. Subclasses should override this 55 | // if they can give a reasonable name for the type. 56 | public getName(): string | undefined { 57 | return undefined; 58 | } 59 | 60 | // Check recursively if the type references any type arguments of a 61 | // template declaration. 62 | public hasGenerics(): boolean { 63 | return this.getReferencedTypes().some(type => type instanceof GenericType); 64 | } 65 | } 66 | 67 | // A type to mark that this type has no qualifiers, this is used to restrict 68 | // what types are allowed in some functions for extra type safety. For example, 69 | // we can't create a `TArray*`, because `TemplateType` does not accept 70 | // qualified types. 71 | export abstract class UnqualifiedType extends Type { 72 | } 73 | 74 | import { TypeQualifier, QualifiedType } from "./qualifiedType.js"; 75 | import { MemberType } from "./memberType.js"; 76 | import { GenericType } from "./genericType.js"; 77 | -------------------------------------------------------------------------------- /src/writer.ts: -------------------------------------------------------------------------------- 1 | // Utilities for automatically formatting code as it's being written to a file. 2 | // Also supports writing to strings. 3 | 4 | import * as fs from "fs"; 5 | import { Writable } from "stream"; 6 | 7 | export interface Options { 8 | pretty: boolean; 9 | tab: string; 10 | line: string; 11 | space: string; 12 | } 13 | 14 | export abstract class Writer { 15 | private depth: number = 0; 16 | private line: boolean = true; 17 | 18 | private readonly options: Options = { 19 | pretty: false, 20 | tab: "\t", 21 | line: "\n", 22 | space: " ", 23 | }; 24 | 25 | public constructor(options?: Partial) { 26 | Object.assign(this.options, options); 27 | } 28 | 29 | public abstract writeStream(string: string): void; 30 | 31 | public write(string: string, depth: number = 0): void { 32 | if (this.line && this.options.pretty) { 33 | this.writeStream(this.options.tab.repeat(Math.max(0, this.depth + depth))); 34 | } 35 | 36 | this.writeStream(string); 37 | this.line = false; 38 | } 39 | 40 | public writeLine(required: boolean = true): void { 41 | if (required || this.options.pretty) { 42 | this.writeStream(this.options.line); 43 | this.line = true; 44 | } 45 | } 46 | 47 | public writeLineStart(required: boolean = true): void { 48 | if (!this.line) { 49 | this.writeLine(required); 50 | } 51 | } 52 | 53 | public writeSpace(required: boolean = true): void { 54 | if (required || this.options.pretty) { 55 | this.writeStream(this.options.space); 56 | } 57 | } 58 | 59 | public indent(count: number = 1): void { 60 | this.depth += count; 61 | } 62 | 63 | public dedent(count: number = 1): void { 64 | this.depth -= count; 65 | } 66 | 67 | public writeBlockOpen(): void { 68 | this.writeSpace(false); 69 | this.write("{"); 70 | this.writeLine(false); 71 | this.indent(); 72 | } 73 | 74 | public writeBlockClose(semicolon: boolean = false): void { 75 | this.dedent(); 76 | this.write(semicolon ? "};" : "}"); 77 | this.writeLine(false); 78 | } 79 | 80 | public writeText(text: string): void { 81 | for (const line of text.trim().split("\n")) { 82 | this.writeLineStart(line.startsWith("#")); 83 | this.write(this.options.pretty ? line : line.trim()); 84 | this.writeLine(line.startsWith("#")); 85 | } 86 | } 87 | 88 | public writeBody(body: string, semicolon: boolean = false): void { 89 | this.writeBlockOpen(); 90 | 91 | if (body !== "") { 92 | this.writeText(body); 93 | } 94 | 95 | this.writeBlockClose(semicolon); 96 | } 97 | } 98 | 99 | export class StreamWriter extends Writer { 100 | private readonly stream: Writable; 101 | 102 | public constructor(path: fs.PathLike, options?: Partial) { 103 | super(options); 104 | this.stream = fs.createWriteStream(path); 105 | } 106 | 107 | public writeStream(string: string): void { 108 | this.stream.write(string); 109 | } 110 | } 111 | 112 | export class StringWriter extends Writer { 113 | private data: string = ""; 114 | 115 | public writeStream(string: string): void { 116 | this.data += string; 117 | } 118 | 119 | public getString(): string { 120 | return this.data; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/parser/name.ts: -------------------------------------------------------------------------------- 1 | // Utilities for escaping names that may not be valid identifiers in c++. 2 | 3 | import * as ts from "typescript"; 4 | 5 | const DIGITS = "0123456789"; 6 | const CHARSET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; 7 | 8 | const RESERVED = [ 9 | // keywords 10 | "alignas", 11 | "alignof", 12 | "and", 13 | "and_eq", 14 | "asm", 15 | "atomic_cancel", 16 | "atomic_commit", 17 | "atomic_noexcept", 18 | "auto", 19 | "bitand", 20 | "bitor", 21 | "bool", 22 | "break", 23 | "case", 24 | "catch", 25 | "char", 26 | "char8_t", 27 | "char16_t", 28 | "char32_t", 29 | "class", 30 | "compl", 31 | "concept", 32 | "const", 33 | "consteval", 34 | "constexpr", 35 | "constinit", 36 | "const_cast", 37 | "continue", 38 | "co_await", 39 | "co_return", 40 | "co_yield", 41 | "decltype", 42 | "default", 43 | "delete", 44 | "do", 45 | "double", 46 | "dynamic_cast", 47 | "else", 48 | "enum", 49 | "explicit", 50 | "export", 51 | "extern", 52 | "false", 53 | "float", 54 | "for", 55 | "friend", 56 | "goto", 57 | "if", 58 | "inline", 59 | "int", 60 | "long", 61 | "mutable", 62 | "namespace", 63 | "new", 64 | "noexcept", 65 | "not", 66 | "not_eq", 67 | "nullptr", 68 | "operator", 69 | "or", 70 | "or_eq", 71 | "private", 72 | "protected", 73 | "public", 74 | "reflexpr", 75 | "register", 76 | "reinterpret_cast", 77 | "requires", 78 | "return", 79 | "short", 80 | "signed", 81 | "sizeof", 82 | "static", 83 | "static_assert", 84 | "static_cast", 85 | "struct", 86 | "switch", 87 | "synchronized", 88 | "template", 89 | "this", 90 | "thread_local", 91 | "throw", 92 | "true", 93 | "try", 94 | "typedef", 95 | "typeid", 96 | "typename", 97 | "union", 98 | "unsigned", 99 | "using", 100 | "virtual", 101 | "void", 102 | "volatile", 103 | "wchar_t", 104 | "while", 105 | "xor", 106 | "xor_eq", 107 | // reserved identifiers 108 | "assert", 109 | "EOF", 110 | "F_OK", 111 | "R_OK", 112 | "W_OK", 113 | "X_OK", 114 | "COPYFILE_EXCL", 115 | "COPYFILE_FICLONE", 116 | "COPYFILE_FICLONE_FORCE", 117 | "O_RDONLY", 118 | "O_WRONLY", 119 | "O_RDWR", 120 | "O_CREAT", 121 | "O_EXCL", 122 | "O_NOCTTY", 123 | "O_TRUNC", 124 | "O_APPEND", 125 | "O_DIRECTORY", 126 | "O_NOATIME", 127 | "O_NOFOLLOW", 128 | "O_SYNC", 129 | "O_DSYNC", 130 | "O_SYMLINK", 131 | "O_DIRECT", 132 | "O_NONBLOCK", 133 | "S_IFMT", 134 | "S_IFREG", 135 | "S_IFDIR", 136 | "S_IFCHR", 137 | "S_IFBLK", 138 | "S_IFIFO", 139 | "S_IFLNK", 140 | "S_IFSOCK", 141 | "S_IRWXU", 142 | "S_IRUSR", 143 | "S_IWUSR", 144 | "S_IXUSR", 145 | "S_IRWXG", 146 | "S_IRGRP", 147 | "S_IWGRP", 148 | "S_IXGRP", 149 | "S_IRWXO", 150 | "S_IROTH", 151 | "S_IWOTH", 152 | "S_IXOTH", 153 | "UV_FS_O_FILEMAP", 154 | ]; 155 | 156 | // If a character is not in the charset, or it is a digit at the start of the 157 | // identifier, then it is replaced with `_${charCode}_`. If the result is a 158 | // reserved word it will have an underscore prepended to it. 159 | export function escapeName(name: string): string { 160 | let result = ""; 161 | 162 | for (const char of name) { 163 | if (CHARSET.includes(char) && (result !== "" || !DIGITS.includes(char))) { 164 | result += char; 165 | } else { 166 | result += `_${char.charCodeAt(0)}_`; 167 | } 168 | } 169 | 170 | if (RESERVED.includes(result)) { 171 | result = result + "_"; 172 | } 173 | 174 | return result; 175 | } 176 | 177 | // Returns both the unescaped and escaped name of a declaration. 178 | export function getName(declaration: ts.NamedDeclaration): [string, string] { 179 | const name = declaration.name!.getText(); 180 | return [name, escapeName(name)]; 181 | } 182 | -------------------------------------------------------------------------------- /src/file.ts: -------------------------------------------------------------------------------- 1 | import { Declaration } from "./declaration/declaration.js"; 2 | import { StreamWriter } from "./writer.js"; 3 | import { Namespace } from "./declaration/namespace.js"; 4 | 5 | // A reference to a file that should be included 6 | export class Include { 7 | // The name of the file, this is the exact string that will appear between 8 | // <> or "" in the include directive. 9 | private readonly name: string; 10 | 11 | // If the `system` flag is set, we use <> instead of "" 12 | private readonly system: boolean; 13 | 14 | // If `lean` is false, this include will be wrapped in an 15 | // `#ifndef LEAN_CXX_LIB` block. 16 | private readonly lean: boolean; 17 | 18 | // A reference to the `File` instance, only set if we are also generating 19 | // this file. This is used to order the generation of declarations, see the 20 | // comments on `LibraryWriter` for more info. 21 | private readonly file?: File; 22 | 23 | public constructor(name: string, system: boolean, lean: boolean, file?: File) { 24 | this.name = name; 25 | this.system = system; 26 | this.lean = lean; 27 | this.file = file; 28 | } 29 | 30 | public getName(): string { 31 | return this.name; 32 | } 33 | 34 | public isSystem(): boolean { 35 | return this.system; 36 | } 37 | 38 | public isLean(): boolean { 39 | return this.lean; 40 | } 41 | 42 | public getFile(): File | undefined { 43 | return this.file; 44 | } 45 | } 46 | 47 | export class File { 48 | // The name of the file, this is the path where it will be output to. It 49 | // is also used to generate the name for the include guard. 50 | private readonly name: string; 51 | 52 | // Files that need to be included. 53 | private readonly includes: Array = new Array; 54 | 55 | // A list of declarations that belong in this file. 56 | private readonly declarations: Array = new Array; 57 | 58 | public constructor(name: string) { 59 | this.name = name; 60 | } 61 | 62 | public getName(): string { 63 | return this.name; 64 | } 65 | 66 | public getIncludes(): ReadonlyArray { 67 | return this.includes; 68 | } 69 | 70 | public addInclude(name: string, system: boolean, lean: boolean, file?: File): void { 71 | this.includes.push(new Include(name, system, lean, file)); 72 | } 73 | 74 | public getDeclarations(): ReadonlyArray { 75 | return this.declarations; 76 | } 77 | 78 | public addDeclaration(declaration: Declaration): void { 79 | this.declarations.push(declaration); 80 | } 81 | } 82 | 83 | export class FileWriter { 84 | private readonly file: File; 85 | private readonly writer: StreamWriter; 86 | 87 | // The namespace which the file is currently in. 88 | private namespace?: Namespace; 89 | 90 | // The number of declarations that still need to be written in this file. 91 | // This is used to track when to move on to the next file, see the comments 92 | // on `LibraryWriter` for more info. 93 | private target: number = 0; 94 | 95 | public constructor(file: File, writer: StreamWriter) { 96 | this.file = file; 97 | this.writer = writer; 98 | } 99 | 100 | public getFile(): File { 101 | return this.file; 102 | } 103 | 104 | public getWriter(): StreamWriter { 105 | return this.writer; 106 | } 107 | 108 | public incrementTarget(): void { 109 | this.target += 1; 110 | } 111 | 112 | public decrementTarget(): void { 113 | this.target -= 1; 114 | } 115 | 116 | public isDone(): boolean { 117 | return this.target === 0; 118 | } 119 | 120 | // `setNamespace` writes a change in namespace to the file. 121 | public setNamespace(namespace?: Namespace): void { 122 | Namespace.writeChange(this.writer, this.namespace, namespace); 123 | this.namespace = namespace; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/type/qualifiedType.ts: -------------------------------------------------------------------------------- 1 | import { Expression } from "./expression.js"; 2 | import { Type } from "./type.js"; 3 | import { Dependency, State, Dependencies } from "../target.js"; 4 | import { Writer } from "../writer.js"; 5 | import { Namespace } from "../declaration/namespace.js"; 6 | 7 | export enum TypeQualifier { 8 | Pointer = 1, 9 | Reference = 2, 10 | Const = 4, 11 | Variadic = 8, 12 | RValueReference = 16, 13 | ConstPointer = Const | Pointer, 14 | ConstReference = Const | Reference, 15 | } 16 | 17 | // A `QualifiedType` is a type that may have type qualifiers. 18 | // 19 | // Note that in C++, pointers are *not* considered qualified, they are a 20 | // completely distinct type in the type system. But for us it is easier to 21 | // represent it as a qualifier anyways. 22 | // 23 | // There is also a `Variadic` qualifier, used when expanding variadic template 24 | // arguments, such as `T...` in the following example: 25 | // 26 | // ``` 27 | // template 28 | // void foo(T... args); 29 | // ``` 30 | // 31 | // Multiple qualifiers can be combined, in this case the order in which they 32 | // are applied is as follows: 33 | // - const 34 | // - pointer 35 | // - reference 36 | // - rvalue reference 37 | // - variadic 38 | export class QualifiedType extends Type { 39 | private readonly inner: Type; 40 | private readonly qualifier: TypeQualifier; 41 | 42 | private constructor(inner: Type, qualifier: TypeQualifier) { 43 | super(); 44 | this.inner = inner; 45 | this.qualifier = qualifier; 46 | } 47 | 48 | public getInner(): Type { 49 | return this.inner; 50 | } 51 | 52 | public getQualifier(): TypeQualifier { 53 | return this.qualifier; 54 | } 55 | 56 | // The dependencies of a qualified type are: 57 | // - the inner type. 58 | // 59 | // The required state depends on if the type is pointer, reference, or 60 | // rvalue-reference qualified, and whether `innerState` is set to 61 | // `Complete`. See "src/type/expression.ts" for a description of 62 | // `innerState`. 63 | public getDependencies(reason: Dependency, innerState?: State): Dependencies { 64 | if (this.qualifier & (TypeQualifier.Pointer | TypeQualifier.Reference | TypeQualifier.RValueReference)) { 65 | return this.inner.getDependencies(reason.withState(innerState ?? State.Partial), innerState); 66 | } else { 67 | return this.inner.getDependencies(reason); 68 | } 69 | } 70 | 71 | public getReferencedTypes(): ReadonlyArray { 72 | return [this, ...this.inner.getReferencedTypes()]; 73 | } 74 | 75 | public write(writer: Writer, namespace?: Namespace): void { 76 | if (this.qualifier & TypeQualifier.Const) { 77 | writer.write("const"); 78 | writer.writeSpace(); 79 | } 80 | 81 | this.getInner().write(writer, namespace); 82 | 83 | if (this.qualifier & TypeQualifier.Pointer) { 84 | writer.write("*"); 85 | } 86 | 87 | if (this.qualifier & TypeQualifier.Reference) { 88 | writer.write("&"); 89 | } 90 | 91 | if (this.qualifier & TypeQualifier.RValueReference) { 92 | writer.write("&&"); 93 | } 94 | 95 | if (this.qualifier & TypeQualifier.Variadic) { 96 | writer.write("..."); 97 | } 98 | } 99 | 100 | public key(): string { 101 | return `Q${this.qualifier}${this.inner.key()}`; 102 | } 103 | 104 | // Remove all qualifiers, except when this type is variadic. 105 | // 106 | // This implementation is questionable. Should other qualifiers still be 107 | // removed if the type is variadic? Why not remove variadic in the first 108 | // place? I don't think this function is ever called on a variadic type. 109 | // I don't have a good reason for any of this, but I'm not changing it 110 | // until something breaks. 111 | public removeQualifiers(): Expression { 112 | if (!(this.qualifier & TypeQualifier.Variadic)) { 113 | return this.inner.removeQualifiers(); 114 | } else { 115 | return this; 116 | } 117 | } 118 | 119 | public static create(inner: Type, qualifier: TypeQualifier): QualifiedType { 120 | return new QualifiedType(inner, qualifier).intern(); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/parser/library.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "./parser.js"; 2 | import { Node } from "./node.js"; 3 | import { Namespace } from "../declaration/namespace.js"; 4 | import { Generics } from "./generics.js"; 5 | import { Class } from "../declaration/class.js"; 6 | import { parseClass } from "./class.js"; 7 | import { parseTypeAlias } from "./typeAlias.js"; 8 | import { parseFunction } from "./function.js"; 9 | import { parseVariable } from "./variable.js"; 10 | 11 | const EMPTY = new Generics; 12 | 13 | // Get a list of "global" classes. Global classes are classes that represent 14 | // the global scope in some context. For example, `Window`, or 15 | // `WorkerGlobalScope`. 16 | function getGlobalClasses(parser: Parser): ReadonlyArray { 17 | const globalClasses = []; 18 | globalClasses.push(parser.getRootClass("Window")); 19 | globalClasses.push(parser.getRootClass("WorkerGlobalScope")); 20 | return globalClasses.filter((globalClass): globalClass is Class => !!globalClass); 21 | } 22 | 23 | // `parseLibrary` is the main entry point for converting the tree structure 24 | // described in "src/parser/node.ts" into an AST of C++ declarations. This 25 | // function recursively calls itself for namespace nodes (nodes that do not 26 | // have any other declaration). For non-namespace nodes, it calls any of the 27 | // following functions: 28 | // - classes: `parseClass` in "src/parser/class.ts" 29 | // - functions: `parseFunction` in "src/parser/function.ts" 30 | // - variables: `parseVariable` in "src/parser/variable.ts" 31 | // - type aliases: `parseTypeAlias` in "src/parser/typeAlias.ts" 32 | export function parseLibrary(parser: Parser, node: Node, parent?: Namespace): void { 33 | // `incrementTarget` and `incrementProgress` provide some very primitive 34 | // progress information that is output only in verbose mode. 35 | parser.incrementTarget(node.getSize()); 36 | 37 | for (const child of node.getChildren()) { 38 | parser.incrementProgress(child); 39 | 40 | const functionDeclarations = child.getFunctionDeclarations(); 41 | 42 | if (child.classObject) { 43 | // Classes are parsed using `parseClass`. 44 | parseClass(parser, child, child.classObject, EMPTY, parent); 45 | } else if (functionDeclarations.length > 0 && child.getSize() === 0) { 46 | // Functions are parsed using `parseFunction`. 47 | for (const declaration of functionDeclarations) { 48 | parseFunction(parser, declaration, EMPTY, false, parent); 49 | 50 | // If this is a global function, we also add it to "global" 51 | // classes. For example, the `eval` function is only declared 52 | // as a global function, but should still be possible to call 53 | // `window.eval`. So we need functions like `eval` to be added 54 | // to the `Window` class. 55 | if (parent === parser.getRootNamespace()) { 56 | for (const globalClass of getGlobalClasses(parser)) { 57 | parseFunction(parser, declaration, EMPTY, false, globalClass); 58 | } 59 | } 60 | } 61 | } else if (child.variableDeclaration) { 62 | // Variables are parsed using `parseVariable`. 63 | parseVariable(parser, child.variableDeclaration, EMPTY, parent); 64 | 65 | // The variable node might also have children, this matches the 66 | // following typescript declaration: 67 | // ``` 68 | // declare var Foo: FooConstructor; 69 | // 70 | // declare namespace Foo { 71 | // function func(): void; 72 | // } 73 | // ``` 74 | // 75 | // The variable is generated as normal, and we also generate a 76 | // namespace, with an extra "_" at the end of its name. Because of 77 | // the extra "_", there is currently no way to actually use this 78 | // namespace. 79 | // 80 | // TODO: add an interface name for the namespace 81 | if (child.getSize() > 0) { 82 | parseLibrary(parser, child, new Namespace(`${child.getName()}_`, parent)); 83 | } 84 | } else if (child.typeAliasObject && child.typeAliasDeclaration) { 85 | // Type aliases are parsed using `parseTypeAlias`. 86 | parseTypeAlias(parser, child.typeAliasDeclaration, child.typeAliasObject, EMPTY, parent); 87 | } else { 88 | // No declarations for this node, this must be a namespace node. 89 | parseLibrary(parser, child, new Namespace(child.getName(), parent)); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/type/expression.ts: -------------------------------------------------------------------------------- 1 | import { Dependency, State, Dependencies } from "../target.js"; 2 | import { Type } from "./type.js"; 3 | import { Writer, StringWriter } from "../writer.js"; 4 | import { Namespace } from "../declaration/namespace.js"; 5 | import { options } from "../utility.js"; 6 | 7 | const EXPRESSIONS = new Map; 8 | 9 | // An ordinary C++ expression, like `std::is_same_v`. Types are also 10 | // expressions, see "src/type/type.ts" for more info. 11 | // 12 | // Expressions are interned (using the `intern` function), meaning that every 13 | // distinct expression shares one instance. The `EXPRESSIONS` map tracks 14 | // expression instances for the purpose of interning. 15 | export abstract class Expression { 16 | public static getCount(): number { 17 | return EXPRESSIONS.size; 18 | } 19 | 20 | // Return all dependencies of this type. Some compound types depend on 21 | // multiple declarations, for example, `TArray*` depends on both 22 | // `TArray` and `String`. 23 | // 24 | // `reason` includes the following information: 25 | // - Is this dependency on the complete type or just a forward declaration? 26 | // - What declaration is the *dependent* of this dependency? 27 | // - What is the dependency needed for? Return type? Base class? etc. 28 | // 29 | // `innerState` is a hack to force pointer types to depend on a complete 30 | // declaration of their inner type. Usually a pointer type, such as 31 | // `String*` only requires a forward declaration of it's inner type, 32 | // `String`. But when trying to cast to a base class, for example, the cast 33 | // will fail if we don't have a complete declaration. When the type is used 34 | // in such an expression, `innerState` will be set to `Complete`, and 35 | // pointer types will return a complete dependency on its inner type. 36 | // 37 | // It's not perfect, but it works well enough. 38 | public abstract getDependencies(reason: Dependency, innerState?: State): Dependencies; 39 | 40 | // All the types referenced in this expression. This is like a simpler 41 | // version of `getDependencies`. Some declarations call this to compute 42 | // their referenced types, see `getReferencedTypes` in 43 | // "src/declaration/declaration.ts" for more info. 44 | public abstract getReferencedTypes(): ReadonlyArray; 45 | 46 | // Write the type. The `namespace` is the namespace in which the expression 47 | // is being written, and can be used to abbreviate class paths. 48 | public abstract write(writer: Writer, namespace?: Namespace): void; 49 | 50 | // Returns a key that identifies this expression, it is used for removing 51 | // duplicate expressions. The key should be specific enough so we don't 52 | // remove any expressions that aren't actually duplicates, but it should 53 | // should not allow conflicting overloads to exist together. 54 | public abstract key(): string; 55 | 56 | // This function is used to evaluate some simple expressions at compile 57 | // time. For example, any `std::enable_if_t, U>` can 58 | // be replaced with just `U`. 59 | public isAlwaysTrue(): boolean { 60 | return false; 61 | } 62 | 63 | // `isVoidLike` is used in an attempt to omit the `return` statement when 64 | // generating code in a function that is supposed to return this type. This 65 | // function can almost definitely return incorrect values for more complex 66 | // type expressions, but it works well enough for now. 67 | public isVoidLike(): boolean { 68 | return false; 69 | } 70 | 71 | // Return the raw type with any qualifiers removed. This turns 72 | // `const TArray*` into `TArray`. 73 | public removeQualifiers(): Expression { 74 | return this; 75 | } 76 | 77 | // Turn the type into a string, the string should (hopefully) be valid C++. 78 | public toString(namespace?: Namespace): string { 79 | const writer = new StringWriter({ pretty: options.isPretty }); 80 | this.write(writer, namespace); 81 | return writer.getString(); 82 | } 83 | 84 | protected intern(): this { 85 | const key = this.key(); 86 | const value = EXPRESSIONS.get(key); 87 | 88 | if (value) { 89 | return value; 90 | } 91 | 92 | EXPRESSIONS.set(key, this); 93 | return this; 94 | } 95 | } 96 | 97 | // Returns a new array where every key occurs at most once. 98 | export function removeDuplicateExpressions(expressions: ReadonlyArray): Array { 99 | return [...new Set(expressions)]; 100 | } 101 | -------------------------------------------------------------------------------- /src/declaration/templateDeclaration.ts: -------------------------------------------------------------------------------- 1 | import { Namespace } from "./namespace.js"; 2 | import { Declaration } from "./declaration.js"; 3 | import { Writer } from "../writer.js"; 4 | import { GenericType } from "../type/genericType.js"; 5 | import { Type } from "../type/type.js"; 6 | import { State } from "../target.js"; 7 | 8 | export class TypeParameter { 9 | private readonly name: string; 10 | private readonly variadic: boolean; 11 | private readonly defaultType?: Type; 12 | 13 | public constructor(name: string, variadic: boolean, defaultType?: Type) { 14 | this.name = name; 15 | this.variadic = variadic; 16 | this.defaultType = defaultType; 17 | } 18 | 19 | public getName(): string { 20 | return this.name; 21 | } 22 | 23 | public isVariadic(): boolean { 24 | return this.variadic; 25 | } 26 | 27 | public getDefaultType(): Type | undefined { 28 | return this.defaultType; 29 | } 30 | } 31 | 32 | // A declaration that may be templated. 33 | export abstract class TemplateDeclaration extends Declaration { 34 | private typeParameters?: Array; 35 | 36 | // For some declarations we generate both basic and generic (prefixed with 37 | // "T") versions. For the generic versions of these declarations, 38 | // `basicVersion` stores a reference to the basic version. 39 | private basicVersion?: this; 40 | 41 | // This is set for the generic version of a declaration after it is 42 | // discovered, so that when we parse it we know that this is the generic 43 | // version and we should add type parameters. The basic version will not 44 | // have type parameters. 45 | private generic: boolean = false; 46 | 47 | public getTypeParameters(): ReadonlyArray { 48 | return this.typeParameters ?? []; 49 | } 50 | 51 | public addTypeParameter(name: string, defaultType?: Type): void { 52 | this.typeParameters ??= []; 53 | this.typeParameters.push(new TypeParameter(name, false, defaultType)); 54 | } 55 | 56 | public addVariadicTypeParameter(name: string): void { 57 | this.typeParameters ??= []; 58 | this.typeParameters.push(new TypeParameter(name, true)); 59 | } 60 | 61 | // We only check the last parameter for if it's variadic. 62 | public isVariadic(): boolean { 63 | return !!this.typeParameters && this.typeParameters.length > 0 && this.typeParameters[this.typeParameters.length - 1]?.isVariadic(); 64 | } 65 | 66 | public getBasicVersion(): this | undefined { 67 | return this.basicVersion; 68 | } 69 | 70 | public setBasicVersion(declaration: this): void { 71 | this.basicVersion = declaration; 72 | } 73 | 74 | public isGeneric(): boolean { 75 | return this.generic; 76 | } 77 | 78 | public setGeneric(generic: boolean): void { 79 | this.generic = generic; 80 | } 81 | 82 | private writeParameters(writer: Writer, state: State, namespace?: Namespace): void { 83 | let first = true; 84 | writer.write("<"); 85 | 86 | for (const typeParameter of this.getTypeParameters()) { 87 | if (!first) { 88 | writer.write(","); 89 | writer.writeSpace(false); 90 | } 91 | 92 | if (typeParameter.isVariadic()) { 93 | writer.write("class..."); 94 | } else { 95 | writer.write("class"); 96 | } 97 | 98 | writer.writeSpace(); 99 | writer.write(typeParameter.getName()); 100 | 101 | const defaultType = typeParameter.getDefaultType(); 102 | 103 | if (defaultType && state >= this.maxState()) { 104 | writer.writeSpace(false); 105 | writer.write("="); 106 | writer.writeSpace(false); 107 | defaultType.write(writer, namespace); 108 | } 109 | 110 | first = false; 111 | } 112 | 113 | writer.write(">"); 114 | } 115 | 116 | public writeTemplate(writer: Writer, state: State, namespace?: Namespace): void { 117 | if (this.getTypeParameters().length > 0) { 118 | writer.write("template"); 119 | this.writeParameters(writer, state, namespace); 120 | writer.writeLine(false); 121 | } 122 | } 123 | 124 | public removeUnusedTypeParameters(): void { 125 | // Get all referenced types. 126 | const referencedTypes = new Set( 127 | this.getReferencedTypes() 128 | .filter((type): type is GenericType => type instanceof GenericType) 129 | .map(type => type.getName()) 130 | ); 131 | 132 | // Filter out template parameters that aren't referenced. 133 | const typeParameters = this.getTypeParameters().filter(typeParameter => { 134 | return referencedTypes.has(typeParameter.getName()); 135 | }); 136 | 137 | if (this.typeParameters) { 138 | this.typeParameters.splice(0, this.typeParameters.length, ...typeParameters); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/type/compoundExpression.ts: -------------------------------------------------------------------------------- 1 | import { Expression } from "./expression.js"; 2 | import { Dependency, State, Dependencies } from "../target.js"; 3 | import { Type } from "./type.js"; 4 | import { Writer } from "../writer.js"; 5 | import { Namespace } from "../declaration/namespace.js"; 6 | 7 | export enum ExpressionKind { 8 | // "&&" 9 | LogicalAnd, 10 | // "||" 11 | LogicalOr, 12 | }; 13 | 14 | // A `CompoundExpression` is a list of subexpressions separated by a binary 15 | // operator, such as `A || B || C`. All operators are the same, to mix them 16 | // you must construct nested `CompoundExpressions`. 17 | export class CompoundExpression extends Expression { 18 | private children?: Array; 19 | private readonly kind: ExpressionKind; 20 | 21 | private constructor(kind: ExpressionKind) { 22 | super(); 23 | this.kind = kind; 24 | } 25 | 26 | public getChildren(): ReadonlyArray { 27 | return this.children ?? []; 28 | } 29 | 30 | public getKind(): ExpressionKind { 31 | return this.kind; 32 | } 33 | 34 | // The dependencies of a compound expression are: 35 | // - partial for all children. 36 | public getDependencies(reason: Dependency, innerState?: State): Dependencies { 37 | return new Dependencies(this.getChildren().flatMap(expression => [...expression.getDependencies(reason)])); 38 | } 39 | 40 | public getReferencedTypes(): ReadonlyArray { 41 | return this.getChildren().flatMap(expression => [...expression.getReferencedTypes()]); 42 | } 43 | 44 | public write(writer: Writer, namespace?: Namespace): void { 45 | if (this.getChildren().length === 0) { 46 | // Special case when there are no children. 47 | switch (this.kind) { 48 | case ExpressionKind.LogicalAnd: 49 | writer.write("true"); 50 | break; 51 | case ExpressionKind.LogicalOr: 52 | writer.write("false"); 53 | break; 54 | } 55 | } else if (this.getChildren().length === 1) { 56 | // Omit parentheses if there is only one child. 57 | this.getChildren()[0].write(writer, namespace); 58 | } else { 59 | // We must write the full expression with parentheses. 60 | let first = true; 61 | writer.write("("); 62 | 63 | for (const expression of this.getChildren()) { 64 | if (!first) { 65 | writer.writeSpace(false); 66 | 67 | switch (this.kind) { 68 | case ExpressionKind.LogicalAnd: 69 | writer.write("&&"); 70 | break; 71 | case ExpressionKind.LogicalOr: 72 | writer.write("||"); 73 | break; 74 | } 75 | 76 | writer.writeSpace(false); 77 | } 78 | 79 | expression.write(writer, namespace); 80 | first = false; 81 | } 82 | 83 | writer.write(")"); 84 | } 85 | } 86 | 87 | public key(): string { 88 | const children = this.getChildren().map(child => child.key()).join(""); 89 | 90 | switch (this.kind) { 91 | case ExpressionKind.LogicalAnd: 92 | return `&${children};`; 93 | case ExpressionKind.LogicalOr: 94 | return `|${children};`; 95 | } 96 | } 97 | 98 | public isAlwaysTrue(): boolean { 99 | switch (this.kind) { 100 | case ExpressionKind.LogicalAnd: 101 | return this.getChildren().every(child => child.isAlwaysTrue()); 102 | case ExpressionKind.LogicalOr: 103 | return this.getChildren().some(child => child.isAlwaysTrue()); 104 | } 105 | } 106 | 107 | // Create a new compound expression. 108 | public static create(kind: ExpressionKind, ...members: ReadonlyArray): CompoundExpression { 109 | const result = new CompoundExpression(kind); 110 | 111 | if (members.length > 0) { 112 | result.children = [...members]; 113 | } 114 | 115 | return result.intern(); 116 | } 117 | 118 | // Create a new compound expression, collapsing multiple compound 119 | // expressions of the same kind into one. For "&&" and "||", this does not 120 | // change the meaning of the expression. 121 | public static combine(kind: ExpressionKind, ...members: ReadonlyArray): CompoundExpression { 122 | const children = []; 123 | 124 | for (const member of members) { 125 | if (member instanceof CompoundExpression && member.getKind() === kind && !member.getChildren().includes(ELLIPSES)) { 126 | children.push(...member.getChildren()); 127 | } else { 128 | children.push(member); 129 | } 130 | } 131 | 132 | return CompoundExpression.create(kind, ...children); 133 | } 134 | 135 | public static or(...children: ReadonlyArray): CompoundExpression { 136 | return CompoundExpression.combine(ExpressionKind.LogicalOr, ...children); 137 | } 138 | 139 | public static and(...children: ReadonlyArray): CompoundExpression { 140 | return CompoundExpression.combine(ExpressionKind.LogicalAnd, ...children); 141 | } 142 | } 143 | 144 | import { ELLIPSES } from "./literalExpression.js"; 145 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "./parser/parser.js"; 2 | import { catchErrors } from "./error.js"; 3 | import { Library } from "./library.js"; 4 | import { withTimer, parseOptions, options } from "./utility.js"; 5 | import { Declaration } from "./declaration/declaration.js"; 6 | import { Expression } from "./type/expression.js"; 7 | import * as ts from "typescript"; 8 | 9 | // 1. Parse command line options. 10 | const args = parseOptions(); 11 | 12 | // 2. Add default library files if "--default-lib" is specified. 13 | if (options.isDefaultLib) { 14 | args.push("node_modules/typescript/lib/lib.es5.d.ts"); 15 | args.push("node_modules/typescript/lib/lib.es2015.d.ts"); 16 | args.push("node_modules/typescript/lib/lib.es2016.d.ts"); 17 | args.push("node_modules/typescript/lib/lib.es2017.d.ts"); 18 | args.push("node_modules/typescript/lib/lib.es2018.d.ts"); 19 | args.push("node_modules/typescript/lib/lib.es2019.d.ts"); 20 | args.push("node_modules/typescript/lib/lib.es2020.d.ts"); 21 | args.push("node_modules/typescript/lib/lib.es2015.core.d.ts"); 22 | args.push("node_modules/typescript/lib/lib.es2015.collection.d.ts"); 23 | args.push("node_modules/typescript/lib/lib.es2015.generator.d.ts"); 24 | args.push("node_modules/typescript/lib/lib.es2015.iterable.d.ts"); 25 | args.push("node_modules/typescript/lib/lib.es2015.promise.d.ts"); 26 | args.push("node_modules/typescript/lib/lib.es2015.proxy.d.ts"); 27 | args.push("node_modules/typescript/lib/lib.es2015.reflect.d.ts"); 28 | args.push("node_modules/typescript/lib/lib.es2015.symbol.d.ts"); 29 | args.push("node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts"); 30 | args.push("node_modules/typescript/lib/lib.es2016.array.include.d.ts"); 31 | args.push("node_modules/typescript/lib/lib.es2017.date.d.ts"); 32 | args.push("node_modules/typescript/lib/lib.es2017.object.d.ts"); 33 | args.push("node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts"); 34 | args.push("node_modules/typescript/lib/lib.es2017.string.d.ts"); 35 | args.push("node_modules/typescript/lib/lib.es2017.intl.d.ts"); 36 | args.push("node_modules/typescript/lib/lib.es2017.typedarrays.d.ts"); 37 | args.push("node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts"); 38 | args.push("node_modules/typescript/lib/lib.es2018.asynciterable.d.ts"); 39 | args.push("node_modules/typescript/lib/lib.es2018.intl.d.ts"); 40 | args.push("node_modules/typescript/lib/lib.es2018.promise.d.ts"); 41 | args.push("node_modules/typescript/lib/lib.es2018.regexp.d.ts"); 42 | args.push("node_modules/typescript/lib/lib.es2019.array.d.ts"); 43 | args.push("node_modules/typescript/lib/lib.es2019.object.d.ts"); 44 | args.push("node_modules/typescript/lib/lib.es2019.string.d.ts"); 45 | args.push("node_modules/typescript/lib/lib.es2019.symbol.d.ts"); 46 | args.push("node_modules/typescript/lib/lib.es2019.intl.d.ts"); 47 | args.push("node_modules/typescript/lib/lib.es2020.bigint.d.ts"); 48 | args.push("node_modules/typescript/lib/lib.es2020.date.d.ts"); 49 | args.push("node_modules/typescript/lib/lib.es2020.promise.d.ts"); 50 | args.push("node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts"); 51 | args.push("node_modules/typescript/lib/lib.es2020.string.d.ts"); 52 | args.push("node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts"); 53 | args.push("node_modules/typescript/lib/lib.es2020.intl.d.ts"); 54 | args.push("node_modules/typescript/lib/lib.es2020.number.d.ts"); 55 | args.push("node_modules/typescript/lib/lib.esnext.intl.d.ts"); 56 | args.push("node_modules/typescript/lib/lib.decorators.d.ts"); 57 | args.push("node_modules/typescript/lib/lib.decorators.legacy.d.ts"); 58 | args.push("node_modules/typescript/lib/lib.dom.d.ts"); 59 | args.push("node_modules/typescript/lib/lib.webworker.d.ts"); 60 | args.push("node_modules/typescript/lib/lib.webworker.importscripts.d.ts"); 61 | args.push("node_modules/typescript/lib/lib.scripthost.d.ts"); 62 | } 63 | 64 | // 3. Parse the typescript declaration files. 65 | const tsProgram = withTimer("create program", () => { 66 | return ts.createProgram(args, {}); 67 | }); 68 | 69 | // 4. List which files were used if "--list-files" is specified. This could be 70 | // more than only the files which were specified, if they include other files. 71 | if (options.listFiles) { 72 | for (const sourceFile of tsProgram.getSourceFiles()) { 73 | console.log(sourceFile.fileName); 74 | } 75 | } 76 | 77 | // 5. Create the library instance. 78 | const library = new Library(options.outputFile ?? "cheerp/clientlib.h", args); 79 | 80 | // 6. If this isn't "clientlib.h", add an include for "clientlib.h". 81 | if (!options.isDefaultLib) { 82 | library.addGlobalInclude("cheerp/clientlib.h", true, true); 83 | } 84 | 85 | // 7. Convert the typescript AST into a C++ AST. 86 | const parser = withTimer("parse", () => { 87 | return new Parser(tsProgram, library).run(options.isDefaultLib); 88 | }); 89 | 90 | // 8. Write everything into C++ headers. 91 | catchErrors(() => { 92 | withTimer("write", () => { 93 | library.write({ pretty: options.isPretty }); 94 | }); 95 | }); 96 | 97 | // 9. Write debug info in verbose mode. 98 | if (options.isVerbose) { 99 | console.log(`declaration count: ${Declaration.getCount()}`); 100 | console.log(`expression count: ${Expression.getCount()}`); 101 | } 102 | -------------------------------------------------------------------------------- /src/error.ts: -------------------------------------------------------------------------------- 1 | // Utilities for printing errors. 2 | // 3 | // A `Reason` is a linked list where each element is a declaration that 4 | // explains why the previous element is required. The first element will always 5 | // refer to the same declaration as a later element, meaning that declaration 6 | // eventually depends on itself, and completing the dependency cycle. 7 | // 8 | // For example, the linked list "A -> B -> C -> A" means that: 9 | // - "A" must appear before "B" 10 | // - "B" must appear before "C" 11 | // - "C" must appear before "A" 12 | // 13 | // Another example of a dependency cycle can be found in "examples/cycle.d.ts". 14 | // This would generate the following invalid c++ header. 15 | // 16 | // ``` 17 | // class Outer: public Outer::Inner { 18 | // public: 19 | // class Inner: public Object { 20 | // }; 21 | // }; 22 | // ``` 23 | // 24 | // When generating an inner class, the outer class usually only needs to 25 | // forward declare it, and the actual implementation can come after. This makes 26 | // it possible for an inner class to have its parent as a base class. 27 | // The code in "examples/parentBase.d.ts" generates this valid c++ header. 28 | // 29 | // ``` 30 | // class Outer: public Object { 31 | // public: 32 | // class Inner; 33 | // }; 34 | // class Outer::Inner: public Outer { 35 | // }; 36 | // ``` 37 | // 38 | // But sometimes a member of `Outer` might require `Inner` to be complete, then 39 | // forward declaring `Inner` is not possible and it must be fully implemented 40 | // immediately within the outer class. When this causes a dependency cycle, the 41 | // member that requires `Inner` to be complete is also stored. This forms 42 | // a linked list where each class member requires the complete declaration of 43 | // another class, which in turn has a member that requires the complete 44 | // declaration of the previous class in the list. This linked list, if present, 45 | // is the "reference data" of a declaration. 46 | // 47 | // An example of this can be found in "examples/memberCycle.d.ts". The error 48 | // here is that `Inner2` has `Outer` as a base class, but `Outer` cannot be 49 | // completed before the complete declaration of `Inner2`. The reference data 50 | // describes why the complete declaration of `Inner2` must exist within 51 | // `Outer`, rather than only being forward declared as was the case in the 52 | // previous example. 53 | // 54 | // ``` 55 | // class Outer: public Object { 56 | // public: 57 | // class Inner2: public Outer { 58 | // public: 59 | // class Innest2; 60 | // }; 61 | // class Inner1: public Object { 62 | // public: 63 | // Inner2::Innest2* getInnest2(); 64 | // class Innest1; 65 | // }; 66 | // Inner1::Innest1* getInnest1(); 67 | // }; 68 | // class Outer::Inner1::Innest1: public Object { 69 | // }; 70 | // class Outer::Inner2::Innest2: public Object { 71 | // }; 72 | // ``` 73 | 74 | import { Library } from "./library.js"; 75 | import { Options } from "./writer.js"; 76 | import { ReasonKind, Reason } from "./target.js"; 77 | import { ReferenceData } from "./declaration/declaration.js"; 78 | 79 | function getReferencedString(referenceData: ReferenceData, prevPath: string): string { 80 | const path = referenceData.getReferencedBy().getPath(); 81 | 82 | switch (referenceData.getReasonKind()) { 83 | case ReasonKind.BaseClass: 84 | return ` because [${prevPath}] is referenced as a *base class* of [${path}]`; 85 | case ReasonKind.VariableType: 86 | return ` because [${prevPath}] is referenced as the *type* of [${path}]`; 87 | case ReasonKind.ReturnType: 88 | return ` because [${prevPath}] is referenced as the *return type* of [${path}]`; 89 | case ReasonKind.ParameterType: 90 | return ` because [${prevPath}] is referenced as a *parameter type* of [${path}]`; 91 | case ReasonKind.TypeAliasType: 92 | return ` because [${prevPath}] is referenced as the *alias type* of [${path}]`; 93 | case ReasonKind.Constraint: 94 | return ` because [${prevPath}] is referenced as a *constraint* of [${path}]`; 95 | default: 96 | return ` because [${prevPath}] is referenced by [${path}]`; 97 | } 98 | } 99 | 100 | function getRequiredString(reason: Reason, path: string): string { 101 | switch (reason.getKind()) { 102 | case ReasonKind.Inner: 103 | return `required to generate [${path}]`; 104 | case ReasonKind.Member: 105 | return `required as part of the declaration of [${path}]`; 106 | case ReasonKind.BaseClass: 107 | return `required as a *base class* of [${path}]`; 108 | case ReasonKind.VariableType: 109 | return `required as the *type* of [${path}]`; 110 | case ReasonKind.ReturnType: 111 | return `required as the *return type* of [${path}]`; 112 | case ReasonKind.ParameterType: 113 | return `required as a *parameter type* of [${path}]`; 114 | case ReasonKind.TypeAliasType: 115 | return `required as the *alias type* of [${path}]`; 116 | case ReasonKind.Constraint: 117 | return `required as a *constraint* of [${path}]`; 118 | default: 119 | return `required by ${path}`; 120 | } 121 | } 122 | 123 | function printReason(reason: Reason): void { 124 | let nextReason = reason.getNext(); 125 | console.error(`dependency cycle detected while generating [${reason.getDeclaration().getPath()}]`); 126 | 127 | while (nextReason) { 128 | let path = reason.getDeclaration().getPath(); 129 | console.error(getRequiredString(reason, nextReason.getDeclaration().getPath())); 130 | 131 | if (reason.getKind() === ReasonKind.Member) { 132 | let referenceData = reason.getDeclaration().getReferenceData(); 133 | 134 | while (referenceData) { 135 | console.error(getReferencedString(referenceData, path)); 136 | path = referenceData.getReferencedIn().getPath(); 137 | referenceData = referenceData.getReferencedIn().getReferenceData(); 138 | } 139 | } 140 | 141 | reason = nextReason; 142 | nextReason = reason.getNext(); 143 | } 144 | } 145 | 146 | export function catchErrors(func: () => void): void { 147 | try { 148 | func(); 149 | } catch (reason) { 150 | if (reason instanceof Reason) { 151 | printReason(reason); 152 | } else { 153 | throw reason; 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/declaration/namespace.ts: -------------------------------------------------------------------------------- 1 | import { Writer } from "../writer.js"; 2 | import { options } from "../utility.js"; 3 | 4 | export enum Flags { 5 | Static = 1, 6 | Extern = 2, 7 | Explicit = 4, 8 | Const = 8, 9 | Inline = 16, 10 | Noexcept = 32, 11 | } 12 | 13 | // A `Namespace` is anything that has a name, it is the root base class of all 14 | // other declarations. Even declarations that do not have any children, such as 15 | // `Function`, are still subclasses of `Namespace`. When used directly this 16 | // class represents a c++ namespace. 17 | // 18 | // A `Namespace` does not store its children, in this sense it is better to 19 | // think of a namespace as an element in a linked list that describes the 20 | // namespace of a declaration, rather than as an AST node. 21 | // 22 | // If "A <- B" means that "A.parent === B" then "A <- B <- C" refers to the C++ 23 | // path "A::B::C". 24 | export class Namespace { 25 | // The [[cheerp::interface_name]] attribute is stored separately so that 26 | // it can be freely modified, and can be omited at write time when it turns 27 | // out to be the same as the real name. 28 | private interfaceName?: string; 29 | 30 | private name: string; 31 | private flags: Flags = 0 as Flags; 32 | private parent?: Namespace; 33 | private attributes?: Array; 34 | 35 | public constructor(name: string, parent?: Namespace) { 36 | this.name = name; 37 | this.parent = parent; 38 | } 39 | 40 | public getName(): string { 41 | return this.name; 42 | } 43 | 44 | public setName(name: string): void { 45 | this.name = name; 46 | } 47 | 48 | public setInterfaceName(name?: string): void { 49 | this.interfaceName = name; 50 | } 51 | 52 | public getFlags(): Flags { 53 | return this.flags; 54 | } 55 | 56 | public addFlags(flags: Flags): void { 57 | this.flags |= flags; 58 | } 59 | 60 | // The path of a declaration starting at the namespace `namespace`. 61 | public getPathSafe(namespace?: Namespace): string { 62 | return this.parent && this.parent !== namespace ? `${this.parent.getPathSafe(namespace)}::${this.name}` : this.name; 63 | } 64 | 65 | // `getPathSafe` generates a full path when this is not a descendant of 66 | // `namespace`. This function tries to generate a shorter path by using 67 | // the common ancestor as the base. 68 | public getPath(namespace?: Namespace): string { 69 | // TODO: check for name conflicts 70 | 71 | if (options.useFullNames) { 72 | return this.getPathSafe(namespace); 73 | } 74 | 75 | if (this === namespace) { 76 | return this.name; 77 | } 78 | 79 | return this.getPathSafe(Namespace.getCommonAncestor(this, namespace)); 80 | } 81 | 82 | public isDescendantOf(ancestor: Namespace): boolean { 83 | return !!this.parent && (this.parent === ancestor || this.parent.isDescendantOf(ancestor)); 84 | } 85 | 86 | public getParent(): Namespace | undefined { 87 | return this.parent; 88 | } 89 | 90 | public getDepth(): number { 91 | return this.parent ? this.parent.getDepth() + 1 : 0; 92 | } 93 | 94 | public setParent(parent?: Namespace): void { 95 | this.parent = parent; 96 | } 97 | 98 | public getAttributes(): ReadonlyArray { 99 | return this.attributes ?? []; 100 | } 101 | 102 | public addAttribute(attribute: string): void { 103 | this.attributes ??= []; 104 | this.attributes.push(attribute); 105 | } 106 | 107 | public writeInterfaceName(writer: Writer): void { 108 | if (this.interfaceName && this.name !== this.interfaceName) { 109 | const interfaceName = this.interfaceName.replace(/"/g, "\\\""); 110 | writer.write(`[[cheerp::interface_name(("${interfaceName}"))]]`); 111 | writer.writeLine(false); 112 | } 113 | } 114 | 115 | public writeAttributes(writer: Writer): void { 116 | let first = true; 117 | writer.write("[["); 118 | 119 | for (const attribute of this.getAttributes()) { 120 | if (!first) { 121 | writer.write(","); 122 | writer.writeSpace(false); 123 | } 124 | 125 | writer.write(attribute); 126 | first = false; 127 | } 128 | 129 | writer.write("]]"); 130 | } 131 | 132 | public writeAttributesOrSpace(writer: Writer): void { 133 | if (this.getAttributes().length > 0) { 134 | writer.writeSpace(false); 135 | this.writeAttributes(writer); 136 | writer.writeSpace(false); 137 | } else { 138 | writer.writeSpace(); 139 | } 140 | } 141 | 142 | public static getDepth(namespace?: Namespace): number { 143 | return namespace ? Namespace.getDepth(namespace.parent) + 1 : 0; 144 | } 145 | 146 | public static getCommonAncestor(lhs?: Namespace, rhs?: Namespace): Namespace | undefined { 147 | let lhsDepth = Namespace.getDepth(lhs); 148 | let rhsDepth = Namespace.getDepth(rhs); 149 | 150 | while (lhsDepth > rhsDepth) { 151 | lhs = lhs!.parent; 152 | lhsDepth -= 1; 153 | } 154 | 155 | while (rhsDepth > lhsDepth) { 156 | rhs = rhs!.parent; 157 | rhsDepth -= 1; 158 | } 159 | 160 | while (lhs !== rhs) { 161 | lhs = lhs!.parent; 162 | rhs = rhs!.parent; 163 | } 164 | 165 | return lhs; 166 | } 167 | 168 | // Global declarations may be reordered such that not all members of a 169 | // namespace are consecutive. The `writeOpen`, `writeClose`, and 170 | // `writeChange` functions are used to efficiently change from one 171 | // namespace to another when they have a common ancestor. 172 | 173 | public static writeOpen(writer: Writer, from?: Namespace, to?: Namespace): void { 174 | if (to && to !== from) { 175 | Namespace.writeOpen(writer, from, to.parent); 176 | writer.write("namespace"); 177 | to.writeAttributesOrSpace(writer); 178 | writer.write(to.name); 179 | writer.writeBlockOpen(); 180 | } 181 | } 182 | 183 | public static writeClose(writer: Writer, from?: Namespace, to?: Namespace): void { 184 | if (from && from !== to) { 185 | writer.writeBlockClose(); 186 | Namespace.writeClose(writer, from.parent, to); 187 | } 188 | } 189 | 190 | public static writeChange(writer: Writer, from?: Namespace, to?: Namespace): void { 191 | const commonAncestor = Namespace.getCommonAncestor(from, to); 192 | Namespace.writeClose(writer, from, commonAncestor); 193 | Namespace.writeOpen(writer, commonAncestor, to); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/parser/parser.ts: -------------------------------------------------------------------------------- 1 | import { Namespace } from "../declaration/namespace.js"; 2 | import { Declaration } from "../declaration/declaration.js"; 3 | import { Class, Visibility } from "../declaration/class.js"; 4 | import { Function } from "../declaration/function.js"; 5 | import { Library } from "../library.js"; 6 | import { Type } from "../type/type.js"; 7 | import { NamedType } from "../type/namedType.js"; 8 | import { DeclaredType } from "../type/declaredType.js"; 9 | import { TypeInfo } from "./typeInfo.js"; 10 | import { withTimer, options } from "../utility.js"; 11 | import { addExtensions } from "../extensions.js"; 12 | import { Node, Child } from "./node.js"; 13 | import { TypeParser } from "./typeParser.js"; 14 | import { Generics } from "./generics.js"; 15 | import { parseLibrary } from "./library.js"; 16 | import * as ts from "typescript"; 17 | 18 | // `Parser` is the big class that stores all the information about a program 19 | // as it is being parsed. This class is only used to store information and 20 | // state of the parser, and the functions only get or update this state. This 21 | // class does no parsing by itself. 22 | export class Parser { 23 | // The typescript program. 24 | private readonly program: ts.Program; 25 | 26 | // Typescript TypeChecker instance from the above program. 27 | private readonly typeChecker: ts.TypeChecker; 28 | 29 | // All parsed declarations are stored in this `Library` instance. 30 | private readonly library: Library; 31 | 32 | // The root node where yet unparsed typescript declarations are stored. 33 | // See "src/parser/node.ts" for more info. 34 | private readonly root: Node = new Node; 35 | 36 | // The root namespace. 37 | private readonly namespace?: Namespace; 38 | 39 | // A map of declared types. Types are added here when generating the node 40 | // tree in "src/parser/node.ts". This map is used by TypeParser to get the 41 | // C++ equivalent of a typescript type, if we have one. 42 | private readonly declaredTypes: Map = new Map; 43 | 44 | // Lists of all class and function declarations. These are used after the 45 | // main parsing is done to run secondary passes on all classes and 46 | // functions. For example, to mark virtual base classes, or to remove 47 | // duplicate declarations. 48 | private readonly classes: Array = new Array; 49 | private readonly functions: Array = new Array; 50 | 51 | // Primitive progress counters. 52 | private target: number = 0; 53 | private progress: number = 0; 54 | 55 | public constructor(program: ts.Program, library: Library) { 56 | this.program = program; 57 | this.typeChecker = program.getTypeChecker(); 58 | this.library = library; 59 | this.namespace = new Namespace("client"); 60 | this.namespace.addAttribute("cheerp::genericjs"); 61 | } 62 | 63 | public run(defaultLib: boolean): void { 64 | // 1. Populate the `Node` tree `root` with typescript declarations. See 65 | // "src/parser/node.ts" for a detailed description of why and how. 66 | withTimer("discover", () => { 67 | this.program.getSourceFiles().forEach(sourceFile => this.root.discover(this, sourceFile)); 68 | }); 69 | 70 | // 2. Convert the typescript declarations in the `Node` tree into C++ 71 | // declarations, the resulting declarations are stored in `library`. 72 | withTimer("generate", () => { 73 | if (options.namespace) { 74 | parseLibrary(this, this.root, new Namespace(options.namespace, this.namespace)); 75 | } else { 76 | parseLibrary(this, this.root, this.namespace); 77 | } 78 | }); 79 | 80 | // 3. Remove duplicate declarations. 81 | withTimer("remove duplicates", () => { 82 | this.library.removeDuplicates(); 83 | this.classes.forEach(declaration => declaration.removeDuplicates()); 84 | }); 85 | 86 | // 4. Add extensions, if this is the default library. 87 | if (defaultLib) { 88 | addExtensions(this); 89 | } 90 | 91 | // 5. Compute virtual base classes. 92 | withTimer("compute virtual base classes", () => { 93 | this.classes.forEach(declaration => declaration.computeVirtualBaseClasses()); 94 | }); 95 | 96 | // 6. Add `using` declarations for base members that require them. 97 | withTimer("use base members", () => { 98 | this.classes.forEach(declaration => declaration.useBaseMembers()); 99 | }); 100 | 101 | // 7. Add `cheerp::client_layout` attribute to the `Object` class. 102 | const objectClass = this.getRootClass("Object"); 103 | 104 | if (objectClass) { 105 | objectClass.addAttribute("cheerp::client_layout"); 106 | } 107 | } 108 | 109 | public getTypeAtLocation(node: ts.Node): ts.Type { 110 | return this.typeChecker.getTypeAtLocation(node); 111 | } 112 | 113 | public getTypeFromTypeNode(node: ts.TypeNode): ts.Type { 114 | return this.typeChecker.getTypeFromTypeNode(node); 115 | } 116 | 117 | public getTypeArguments(typeReference: ts.TypeReference): ReadonlyArray { 118 | return this.typeChecker.getTypeArguments(typeReference); 119 | } 120 | 121 | public getLibrary(): Library { 122 | return this.library; 123 | } 124 | 125 | public getClasses(): ReadonlyArray { 126 | return this.classes; 127 | } 128 | 129 | public getFunctions(): ReadonlyArray { 130 | return this.functions; 131 | } 132 | 133 | public getRootNode(): Node { 134 | return this.root; 135 | } 136 | 137 | public getRootNamespace(): Namespace | undefined { 138 | return this.namespace; 139 | } 140 | 141 | // A quick and dirty way to get the a class in global scope. This is mostly 142 | // used in "src/extensions.ts", to modify specific classes that are known 143 | // to exist. 144 | public getRootClass(name: string): Class | undefined { 145 | return this.root.getChild(name)?.classObject; 146 | } 147 | 148 | // A quick and dirty way to get a type in global scope. This is used in 149 | // "src/extensions.ts" and "src/parser/typeParser.ts" to obtain a reference 150 | // to a specific class type that is known to exist. 151 | public getRootType(name: string): Type { 152 | const declaration = this.getRootClass(name); 153 | 154 | if (declaration) { 155 | return DeclaredType.create(declaration); 156 | } else { 157 | return NamedType.create(`client::${name}`); 158 | } 159 | } 160 | 161 | public setDeclaredType(type: ts.Type, declaredType: DeclaredType): void { 162 | this.declaredTypes.set(type, declaredType); 163 | } 164 | 165 | public getDeclaredType(type: ts.Type): DeclaredType | undefined { 166 | return this.declaredTypes.get(type); 167 | } 168 | 169 | // Check if a typescript node is to be included in the output file. This 170 | // check is used to prune declarations from files that are included 171 | // indirectly but whose declarations we do not want to emit. 172 | public includesDeclaration(node: ts.Node): boolean { 173 | return this.library.hasFile(node.getSourceFile().fileName); 174 | } 175 | 176 | public getTypeInfo(type: ts.Type, generics: Generics): TypeInfo { 177 | return new TypeParser(this, generics.getTypes()).getInfo(type); 178 | } 179 | 180 | public getTypeNodeInfo(node: ts.TypeNode | undefined, generics: Generics): TypeInfo { 181 | return new TypeParser(this, generics.getTypes()).getNodeInfo(node); 182 | } 183 | 184 | public getSymbol(type: ts.Type, generics: Generics): [ts.Symbol | undefined, Map] { 185 | return new TypeParser(this, generics.getTypes()).getSymbol(type); 186 | } 187 | 188 | public incrementTarget(count: number): void { 189 | this.target += count; 190 | } 191 | 192 | public incrementProgress(child: Child): void { 193 | this.progress += 1; 194 | 195 | // Output some progress information in verbose mode. 196 | if (options.isVerboseProgress) { 197 | console.log(`${this.progress}/${this.target} ${child.getName()}`); 198 | } 199 | } 200 | 201 | public registerDeclaration(declaration: Declaration): void { 202 | if (declaration instanceof Class) { 203 | this.classes.push(declaration) 204 | } else if (declaration instanceof Function) { 205 | this.functions.push(declaration); 206 | } 207 | } 208 | 209 | // Instead of manually adding a declaration to the library or parent class, 210 | // `addDeclaration` should be called for every declaration. This function 211 | // does a few things: 212 | // - Update the parent of the declaration. 213 | // - If the parent is a class, add the declaration as a member of the class 214 | // with public visibility. 215 | // - If the parent is a namespace, or if the declaration is itself a class, 216 | // add the global declaration to the *library*. Inner classes are also 217 | // added as global declarations so their complete definitions may appear 218 | // outside of their parent class, eg. as `class Foo::Bar {};`. 219 | // - If the declaration is a class in global scope, compute its references. 220 | // Computing the references of the global class will also recursive 221 | // compute references of all inner classes inside of it. 222 | // - Register the declaration using `registerDeclaration`. 223 | public addDeclaration(declaration: Declaration, parent?: Namespace): void { 224 | declaration.setParent(parent); 225 | 226 | if (parent instanceof Class) { 227 | if (declaration instanceof Class) { 228 | this.library.addGlobal(declaration); 229 | } 230 | 231 | parent.addMember(declaration, Visibility.Public); 232 | } else { 233 | if (declaration instanceof Class) { 234 | declaration.computeReferences(); 235 | } 236 | 237 | this.library.addGlobal(declaration); 238 | } 239 | 240 | this.registerDeclaration(declaration); 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/type/templateType.ts: -------------------------------------------------------------------------------- 1 | import { Type, UnqualifiedType } from "./type.js"; 2 | import { TypeQualifier, QualifiedType } from "./qualifiedType.js"; 3 | import { DeclaredType } from "./declaredType.js"; 4 | import { Expression, removeDuplicateExpressions } from "./expression.js"; 5 | import { Dependency, State, Dependencies } from "../target.js"; 6 | import { Class } from "../declaration/class.js"; 7 | import { Writer } from "../writer.js"; 8 | import { Namespace } from "../declaration/namespace.js"; 9 | import { LiteralExpression, TRUE } from "./literalExpression.js"; 10 | import { CompoundExpression } from "./compoundExpression.js"; 11 | import { FunctionType } from "./functionType.js"; 12 | import { VOID_TYPE, UNION_TYPE, FUNCTION_TYPE, ENABLE_IF, ANY_TYPE, ARRAY_ELEMENT_TYPE, IS_SAME, CAN_CAST, CAN_CAST_ARGS } from "./namedType.js"; 13 | 14 | // A template type is a generic type with template arguments 15 | // (`TArray`). 16 | export class TemplateType extends Type { 17 | private readonly inner: UnqualifiedType; 18 | private typeParameters?: Array; 19 | 20 | private constructor(inner: UnqualifiedType) { 21 | super(); 22 | this.inner = inner; 23 | } 24 | 25 | public getInner(): UnqualifiedType { 26 | return this.inner; 27 | } 28 | 29 | public getTypeParameters(): ReadonlyArray { 30 | return this.typeParameters ?? []; 31 | } 32 | 33 | // The dependencies of a template type are: 34 | // - the type parameters. 35 | // - the inner type. 36 | // 37 | // If the inner type is a class declaration with constraints, then any 38 | // pointer types declared in this template must have a complete dependency 39 | // on their inner type, so the contraints can be properly verified. this is 40 | // done by passing `Complete` as the `innerState` to `getDependencies` on 41 | // the type parameters. 42 | public getDependencies(reason: Dependency, innerState?: State): Dependencies { 43 | let state: State | undefined = undefined; 44 | 45 | if (this.inner instanceof DeclaredType) { 46 | const declaration = this.inner.getDeclaration(); 47 | 48 | if (declaration instanceof Class && declaration.hasConstraints()) { 49 | state = reason.getState(); 50 | } 51 | } 52 | 53 | return new Dependencies( 54 | this.getTypeParameters() 55 | .flatMap(typeParameter => [...typeParameter.getDependencies(reason, state)]) 56 | .concat([...this.inner.getDependencies(reason)]) 57 | ); 58 | } 59 | 60 | public getReferencedTypes(): ReadonlyArray { 61 | return this.getTypeParameters() 62 | .flatMap(typeParameter => [...typeParameter.getReferencedTypes()]) 63 | .concat([this, ...this.inner.getReferencedTypes()]); 64 | } 65 | 66 | public write(writer: Writer, namespace?: Namespace): void { 67 | let first = true; 68 | this.inner.write(writer, namespace); 69 | writer.write("<"); 70 | 71 | for (const typeParameter of this.getTypeParameters()) { 72 | if (!first) { 73 | writer.write(","); 74 | writer.writeSpace(false); 75 | } 76 | 77 | typeParameter.write(writer, namespace); 78 | first = false; 79 | } 80 | 81 | writer.write(">"); 82 | } 83 | 84 | public key(): string { 85 | const typeParameters = this.getTypeParameters() 86 | .map(typeParameter => typeParameter.key()).join(""); 87 | 88 | return `T${this.inner.key()}${typeParameters};`; 89 | } 90 | 91 | // A template type is always true if: 92 | // - it's `std::is_same_v` where T and U refer to the same type. 93 | // - it's `IsAcceptableV` where U includes the type `_Any*`. 94 | public isAlwaysTrue(): boolean { 95 | if (this.inner === IS_SAME) { 96 | return this.getTypeParameters()[0] === this.getTypeParameters()[1]; 97 | } else if (this.inner === CAN_CAST || this.inner === CAN_CAST_ARGS) { 98 | return this.getTypeParameters().slice(1).includes(ANY_TYPE.pointer()); 99 | } else { 100 | return false; 101 | } 102 | } 103 | 104 | // A template type is void-like if it's `std::enable_if_t` and `T` is 105 | // void-like. 106 | public isVoidLike(): boolean { 107 | if (this.inner === ENABLE_IF) { 108 | return this.getTypeParameters()[1].isVoidLike(); 109 | } else { 110 | return false; 111 | } 112 | } 113 | 114 | public getName(): string | undefined { 115 | return this.inner.getName(); 116 | } 117 | 118 | public static create(inner: UnqualifiedType, ...typeParameters: ReadonlyArray): TemplateType { 119 | const result = new TemplateType(inner); 120 | 121 | if (typeParameters.length > 0) { 122 | result.typeParameters = [...typeParameters]; 123 | } 124 | 125 | return result.intern(); 126 | } 127 | 128 | // Construct a `_Union` template. 129 | // 130 | // If the resulting union would contain a nested union, it is flattened. 131 | // If the resulting union would only have one type parameter, that type is 132 | // returned by itself and no union is constructed. 133 | // 134 | // TODO: merge derived type into base type 135 | public static createUnion(qualifier: TypeQualifier, ...types: ReadonlyArray): Type { 136 | types = removeDuplicateExpressions( 137 | types 138 | .flatMap(type => { 139 | const nakedType = type.removeQualifiers(); 140 | 141 | if (nakedType instanceof TemplateType) { 142 | if (nakedType.getInner() === UNION_TYPE) { 143 | return nakedType.getTypeParameters() as ReadonlyArray; 144 | } 145 | } 146 | 147 | return [type]; 148 | }) 149 | ); 150 | 151 | if (types.length === 1) { 152 | return types[0]; 153 | } else { 154 | return TemplateType.create(UNION_TYPE, ...types).qualify(qualifier); 155 | } 156 | } 157 | 158 | // Construct a `_Function` template. 159 | public static createFunction(returnType: Type, ...parameters: ReadonlyArray): TemplateType { 160 | return TemplateType.create(FUNCTION_TYPE, FunctionType.create(returnType, ...parameters)); 161 | } 162 | 163 | // Construct an `std::enable_if_t` template. 164 | // 165 | // If the condition is always true, we do not construct an 166 | // `std::enable_if_t` template, and instead just return the type. 167 | // 168 | // If `type` is also a `std::enable_if_t` template, we simply combine the 169 | // conditions. So `std::enable_if_t>` becomes 170 | // just `std::enable_if_t<(A && B), C>`. 171 | public static enableIf(condition: Expression, type?: Type): Type { 172 | if (condition.isAlwaysTrue()) { 173 | return type ?? VOID_TYPE; 174 | } 175 | 176 | if (type instanceof TemplateType && type.getInner() === ENABLE_IF) { 177 | const [otherCondition, otherType] = type.getTypeParameters(); 178 | condition = CompoundExpression.and(condition, otherCondition); 179 | type = otherType as Type; 180 | } 181 | 182 | if (type) { 183 | return TemplateType.create(ENABLE_IF, condition, type); 184 | } else { 185 | return TemplateType.create(ENABLE_IF, condition); 186 | } 187 | } 188 | 189 | // Construct an `ArrayElementTypeT` template. 190 | // 191 | // If `array` has the form `TArray`, we do not construct an 192 | // `ArrayElementTypeT`, and instead return `T`. 193 | // 194 | // If `array` is the `Array` type, we do not construct an 195 | // `ArrayElementTypeT`, and instead return `_Any`. 196 | // 197 | // This function assumes that any `DeclaredType` is an array type. 198 | public static arrayElementType(array: Type): Type { 199 | const rawArray = array.removeQualifiers(); 200 | 201 | if (rawArray instanceof TemplateType) { 202 | if (rawArray.getInner() instanceof DeclaredType) { 203 | return rawArray.getTypeParameters()[0] as Type; 204 | } 205 | } 206 | 207 | if (rawArray instanceof DeclaredType) { 208 | return ANY_TYPE.pointer(); 209 | } 210 | 211 | return TemplateType.create(ARRAY_ELEMENT_TYPE, array); 212 | } 213 | 214 | // Construct a `IsAcceptableV` template. 215 | // 216 | // If U includes the `_Any` type, we do not construct a `IsAcceptableV` 217 | // template, and instead return `true`. 218 | // 219 | // `IsAcceptableV` is much like `std::is_convertible_v`, but it allows some 220 | // extra conversions according to typescript rules. For example, 221 | // `IsAcceptableV` returns true, while 222 | // `std::is_convertible_v` returns false. 223 | public static canCast(from: Type, ...to: ReadonlyArray): Expression { 224 | if (to.includes(ANY_TYPE.pointer())) { 225 | return TRUE; 226 | } 227 | 228 | return TemplateType.create(CAN_CAST, from, ...removeDuplicateExpressions(to)); 229 | } 230 | 231 | // Construct a `IsAcceptableArgsV` template. 232 | // 233 | // If U includes the `_Any` type, we do not construct a `IsAcceptableV` 234 | // template, and instead return `true`. 235 | // 236 | // This template is identical to `IsAcceptableV`, except that it also 237 | // allows `T` to be `const char*` when `U` includes `String*`. The actual 238 | // conversion from `const char*` to `String*` is handled by 239 | // `cheerp::clientCast`, which is only called in variadic functions (for 240 | // now). 241 | public static canCastArgs(from: Type, ...to: ReadonlyArray): Expression { 242 | if (to.includes(ANY_TYPE.pointer())) { 243 | return TRUE; 244 | } 245 | 246 | return TemplateType.create(CAN_CAST_ARGS, from, ...removeDuplicateExpressions(to)); 247 | } 248 | 249 | // Construct an `std::enable_if_t` template whose condition is the 250 | // conjunction of all given constraints. 251 | public static makeConstraint(type: Type, constraints: ReadonlySet): Type { 252 | return TemplateType.enableIf(CompoundExpression.and(...constraints), type); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /src/parser/typeParser.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from "./parser.js"; 2 | import { Type } from "../type/type.js"; 3 | import { TypeInfo, TypeKind } from "./typeInfo.js"; 4 | import { QualifiedType } from "../type/qualifiedType.js"; 5 | import { TemplateType } from "../type/templateType.js"; 6 | import { NULLPTR_TYPE, ANY_TYPE, VOID_TYPE, DOUBLE_TYPE, BOOL_TYPE } from "../type/namedType.js"; 7 | import { FunctionType } from "../type/functionType.js"; 8 | import { isTypeReference } from "./generics.js"; 9 | import { getName } from "./name.js"; 10 | import * as ts from "typescript"; 11 | 12 | // The main job of a `TypeParser` is to create a `TypeInfo` object from a 13 | // typescript type. See "src/parser/typeInfo.ts" for more about `TypeInfo`. 14 | export class TypeParser { 15 | private readonly parser: Parser; 16 | 17 | // The `overrides` map stores types that the caller *already* has 18 | // information about. This allows us to return type information for types 19 | // which exist outside of the context of the parser. For example, the type 20 | // arguments of generic types are stored in the `overrides` map. 21 | private readonly overrides: ReadonlyMap; 22 | 23 | // The `visited` set is used to prevent infinite recursion in recursive 24 | // types. For example, to parse `type Foo = number | Array;`, we must 25 | // first parse `Foo`, which must first parse `Foo`, etc... 26 | // 27 | // Recursive types are not supported at the moment, and are always replaced 28 | // with `_Any*`. 29 | private readonly visited: Set = new Set; 30 | 31 | public constructor(parser: Parser, overrides: ReadonlyMap) { 32 | this.parser = parser; 33 | this.overrides = overrides; 34 | } 35 | 36 | // For every call signature of a type with call signatures, we generate a 37 | // `_Function` overload, where T is a C-style function type that 38 | // represents the call signature. 39 | // 40 | // The parameters and return type are both generated using 41 | // `asCallbackType`. 42 | private addCallInfo(info: TypeInfo, callSignatures: ReadonlyArray): void { 43 | // Also add EventListener overload for compatibility. 44 | if (callSignatures.length > 0) { 45 | info.addType(this.parser.getRootType("EventListener"), TypeKind.ClassOverload); 46 | } 47 | 48 | for (const signature of callSignatures) { 49 | const declaration = signature.getDeclaration(); 50 | const returnType = this.getNodeInfo(declaration.type).asCallbackType(); 51 | 52 | const parameterTypes = declaration.parameters 53 | .filter(parameter => getName(parameter)[0] !== "this") 54 | .map(parameter => this.getNodeInfo(parameter.type)) 55 | .map(info => info.asCallbackType()); 56 | 57 | info.addType(TemplateType.createFunction(returnType, ...parameterTypes), TypeKind.Class); 58 | } 59 | } 60 | 61 | // `addInfo` is where most of the work happens. The type `type` is parsed 62 | // and `info` is updated accordingly. 63 | // 64 | // This function takes in a `TypeInfo`, rather than returning it, to 65 | // support recursively parsing all types in nested unions into the same 66 | // `TypeInfo` instance. 67 | private addInfo(info: TypeInfo, type: ts.Type): void { 68 | const overrideType = this.overrides.get(type); 69 | const declaredType = this.parser.getDeclaredType(type); 70 | const callSignatures = type.getCallSignatures(); 71 | 72 | if (this.visited.has(type)) { 73 | // We've seen this type before, to prevent infinite recursing, use 74 | // `_Any`. 75 | info.addType(ANY_TYPE, TypeKind.Class); 76 | } else if (overrideType) { 77 | // This type is in the overrides map, add info from that map. 78 | info.merge(overrideType); 79 | } else if (type.flags & ts.TypeFlags.Undefined) { 80 | // The typescript `undefined` type does not directly correspond 81 | // to a type in C++. Instead, it often appears in union types, such 82 | // as `string | undefined` to mark that a value of that type is 83 | // optional. So if we encounter the `undefined` type, we simply 84 | // mark this type info as optional. 85 | info.setOptional(); 86 | } else if (type.flags & ts.TypeFlags.Any) { 87 | // Typescript `any` becomes C++ `_Any`. `any` can also be 88 | // undefined, so we mark the info as optional. 89 | info.addType(ANY_TYPE, TypeKind.Class); 90 | info.setOptional(); 91 | } else if (type.flags & ts.TypeFlags.VoidLike) { 92 | // Typescript `void` becomes C++ `void`. 93 | info.addType(VOID_TYPE, TypeKind.Primitive); 94 | } else if (type.flags & ts.TypeFlags.NumberLike) { 95 | // Typescript `number` becomes C++ `double`. 96 | info.addType(DOUBLE_TYPE, TypeKind.Primitive); 97 | } else if (type.flags & ts.TypeFlags.BooleanLike) { 98 | // Typescript `boolean` becomes C++ `bool`. 99 | info.addType(BOOL_TYPE, TypeKind.Primitive); 100 | } else if (type.flags & ts.TypeFlags.StringLike) { 101 | // Typescript `string` becomes C++ `client::String`. 102 | info.addType(this.parser.getRootType("String"), TypeKind.Class); 103 | } else if (type.flags & ts.TypeFlags.BigIntLike) { 104 | // Typescript `bigint` becomes C++ `client::BigInt`. 105 | info.addType(this.parser.getRootType("BigInt"), TypeKind.Class); 106 | } else if (type.flags & ts.TypeFlags.ESSymbolLike) { 107 | // Typescript `symbol` becomes C++ `client::Symbol`. 108 | info.addType(this.parser.getRootType("Symbol"), TypeKind.Class); 109 | } else if (callSignatures.length > 0) { 110 | // For function types, add their call signatures. 111 | // 112 | // TODO: use geenric parameters 113 | this.addCallInfo(info, callSignatures); 114 | } else if (declaredType && type.isClassOrInterface()) { 115 | // A typescript class for which we have a C++ declaration. This 116 | // happens in two cases: 117 | // - Basic (not generic) classes. 118 | // - Reference to a generic class within the class itself with the 119 | // same type parameters. Otherwise, generic classes are parsed as 120 | // `ts.TypeReference` instead. 121 | if (!declaredType.getDeclaration().isGeneric()) { 122 | info.addType(declaredType, TypeKind.Class); 123 | } else { 124 | this.visited.add(type); 125 | 126 | const templateType = TemplateType.create( 127 | declaredType, 128 | ...(type.typeParameters ?? []) 129 | .map(typeParameter => this.getInfo(typeParameter).asTypeParameter()) 130 | ); 131 | 132 | this.visited.delete(type); 133 | info.addType(templateType, TypeKind.Class); 134 | } 135 | } else if (type.isIntersection()) { 136 | // HACK: For intersection types, we only use the first variant. 137 | this.addInfo(info, type.types[0]); 138 | } else if (type.isUnion()) { 139 | // Union types are parsed by recursively adding all variants to the 140 | // same `TypeInfo` instance. 141 | type.types.forEach(inner => this.addInfo(info, inner)); 142 | } else if (isTypeReference(type)) { 143 | // A type reference usually has the form `T`, when this is 144 | // the case, we should have a generic declared class for the target 145 | // type. Tuple types of the form `[T, U]` seemingly also end up as 146 | // type references, these are currently not handled and are 147 | // translated into `client::Object`. 148 | // 149 | // To parse type references, we parse all the type parameters and 150 | // construct a `TemplateType` with the parsed type parameters. 151 | const target = this.parser.getDeclaredType(type.target); 152 | 153 | if (!target) { 154 | info.addType(this.parser.getRootType("Object"), TypeKind.Class); 155 | } else if (!target.getDeclaration().isGeneric()) { 156 | info.addType(target, TypeKind.Class); 157 | } else { 158 | this.visited.add(type); 159 | 160 | const templateType = TemplateType.create( 161 | target, 162 | ...this.parser.getTypeArguments(type) 163 | .filter(typeArgument => !(typeArgument as any).isThisType) 164 | .map(typeArgument => this.getInfo(typeArgument).asTypeParameter()) 165 | ); 166 | 167 | this.visited.delete(type); 168 | info.addType(templateType, TypeKind.Class); 169 | } 170 | } else if (type.isTypeParameter()) { 171 | // A type parameter that was not in the `overrides` map. We have no 172 | // idea what type this is so this should just be the same as `any`. 173 | info.addType(ANY_TYPE, TypeKind.Class); 174 | info.setOptional(); 175 | } else { 176 | // Any other type is just `client::Object`. 177 | info.addType(this.parser.getRootType("Object"), TypeKind.Class); 178 | } 179 | } 180 | 181 | // A simple wrapper around `addInfo` that constructs a new `TypeInfo`, 182 | // calls `addInfo` with it, and then returns the populated `TypeInfo`. 183 | public getInfo(type: ts.Type): TypeInfo { 184 | const info = new TypeInfo; 185 | this.addInfo(info, type); 186 | return info; 187 | } 188 | 189 | // Essentially equivalent to `getInfo(getTypeFromTypeNode(node))`, except: 190 | // - When `node` is undefined, return an optional `_Any`. 191 | // - When `node` is a `this` type node, `getContraint` returns the parent 192 | // class type, and we return info for the parent class type instead. 193 | public getNodeInfo(node?: ts.TypeNode): TypeInfo { 194 | if (node) { 195 | const type = this.parser.getTypeFromTypeNode(node); 196 | return this.getInfo(ts.isThisTypeNode(node) ? type.getConstraint()! : type); 197 | } else { 198 | const info = new TypeInfo; 199 | info.addType(ANY_TYPE, TypeKind.Class); 200 | info.setOptional(); 201 | return info; 202 | } 203 | } 204 | 205 | // `getSymbol` returns the symbol for the templated type of a type 206 | // reference of the form `T`, along with a map containg the type 207 | // arguments of the type reference. If `type` is not a type reference, we 208 | // return the symbol of the type itself, and there are no type arguments. 209 | // 210 | // The type arguments can be used, for example, as the `overrides` map of 211 | // another `TypeParser` instance. 212 | public getSymbol(type: ts.Type): [ts.Symbol | undefined, Map] { 213 | if (isTypeReference(type)) { 214 | const result = new Map( 215 | this.parser.getTypeArguments(type) 216 | .map((t, i) => [type.target.typeParameters![i], this.getInfo(t)]) 217 | ); 218 | 219 | return [type.target.getSymbol(), result]; 220 | } else { 221 | return [type.getSymbol(), new Map]; 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | -------------------------------------------------------------------------------- /src/parser/node.ts: -------------------------------------------------------------------------------- 1 | // Typescript declarations are a big mess. An interface can have multiple 2 | // declarations in different files that all contribute to the interface. 3 | // 4 | // For example, the following code is actually only one interface with two 5 | // methods, `hello` and `world`. 6 | // ``` 7 | // // one.d.ts 8 | // declare interface Foo { 9 | // hello(): void; 10 | // } 11 | // 12 | // // two.d.ts 13 | // declare interface Foo { 14 | // world(): void; 15 | // } 16 | // ``` 17 | // 18 | // The same name can even have multiple declarations of different types! 19 | // ``` 20 | // declare interface Foo { 21 | // hello(): void; 22 | // } 23 | // 24 | // declare namespace Foo { 25 | // function world(): void; 26 | // } 27 | // ``` 28 | // 29 | // The typescript compiler api parses these as separate files with separate 30 | // declarations, making the task of converting them into C++ classes a total 31 | // pain in the ass. 32 | // 33 | // This file exists to collect the scattered typescript declarations into a 34 | // tree structure where every node represents one name, and holds a list of 35 | // all the declarations of that name anywhere in any file. 36 | 37 | import { getName } from "./name.js"; 38 | import { Class, Visibility } from "../declaration/class.js"; 39 | import { TypeAlias } from "../declaration/typeAlias.js"; 40 | import { Parser } from "./parser.js"; 41 | import { VOID_TYPE } from "../type/namedType.js"; 42 | import { DeclaredType } from "../type/declaredType.js"; 43 | import * as ts from "typescript"; 44 | 45 | export type ClassDeclaration = ts.InterfaceDeclaration | ts.ClassDeclaration; 46 | export type FunctionDeclaration = ts.SignatureDeclarationBase; 47 | 48 | // The root node has no name or declarations, it only contains other nodes. 49 | export class Node { 50 | // The children of this node. The root node contains all top level names as 51 | // children. When a name has namespace declarations, the contents of the 52 | // namespace form the children of the node. 53 | private children?: Map; 54 | 55 | public getChildren(): Iterable { 56 | return this.children?.values() ?? []; 57 | } 58 | 59 | public getChild(name: string): Child | undefined { 60 | return this.children?.get(name); 61 | } 62 | 63 | public getSize(): number { 64 | return this.children?.size ?? 0; 65 | } 66 | 67 | // This function is called to get the node for a named declaration. If the 68 | // name was seen before, we return the already created note so that other 69 | // declarations of the same name will be added to the same node. If this is 70 | // a new name, we must create a new node. 71 | private getOrInsert(declaration: ts.NamedDeclaration): Child { 72 | const [_, name] = getName(declaration); 73 | let node = this.getChild(name); 74 | 75 | if (!node) { 76 | node = new Child(name); 77 | this.children ??= new Map; 78 | this.children.set(name, node); 79 | } 80 | 81 | return node; 82 | } 83 | 84 | // This function takes a `ts.Node` whose name matches this node (or does 85 | // not have a name in the case of the root node), and adds all child 86 | // declarations as children of this node. If multiple child declarations 87 | // have the same name they will only have one `Child` instance, through use 88 | // of the `getOrInsert` function. 89 | // 90 | // For class declarations, we already instantiate a C++ declaration here 91 | // and register it with the parser. In this way, when we get to generating 92 | // the rest of the declarations, we can get `DeclaredType` instances for 93 | // their types even if the declaration is generated before the class that 94 | // declares its type. 95 | public discover(parser: Parser, parent: ts.Node): void { 96 | ts.forEachChild(parent, node => { 97 | if (ts.isInterfaceDeclaration(node)) { 98 | // Add the interface declaration to the list of interface 99 | // declarations for this node. Also call `discoverClass`. 100 | const child = this.getOrInsert(node); 101 | child.interfaceDeclarations ??= []; 102 | child.interfaceDeclarations.push(node); 103 | child.discoverClass(parser, node); 104 | } else if (ts.isFunctionDeclaration(node)) { 105 | // Add the function declaration to the list of function 106 | // declarations for this node. 107 | const child = this.getOrInsert(node); 108 | child.functionDeclarations ??= []; 109 | child.functionDeclarations.push(node); 110 | } else if (ts.isVariableStatement(node)) { 111 | // For each declaration in the variable statement, set the 112 | // variable declaration of the corresponding node. 113 | if (parser.includesDeclaration(node)) { 114 | for (const declaration of node.declarationList.declarations) { 115 | const child = this.getOrInsert(declaration); 116 | child.variableDeclaration ??= declaration; 117 | } 118 | } 119 | } else if (ts.isTypeAliasDeclaration(node)) { 120 | // Set the type alias declaration of this node. 121 | // 122 | // We also instantiate a `TypeAlias` ahead of type just like we 123 | // do for classes, as explained in the comment above 124 | // `discover`. But it turns out this is not needed for type 125 | // aliases. Type aliases are not real types in typescript. 126 | // The compiler api offers no way to distinguish between `T` 127 | // and `U` when `type U = T;`. For example, when a function 128 | // declaration contains a type alias, we only see the aliased 129 | // type and not the type alias itself. So there is no point in 130 | // pre-instantiating `TypeAlias` because it will never be 131 | // referenced, but we still do it anyways. 132 | const child = this.getOrInsert(node); 133 | child.typeAliasDeclaration = node; 134 | child.typeAliasObject = new TypeAlias(child.getName(), VOID_TYPE); 135 | 136 | if (node.typeParameters && node.typeParameters.length > 0) { 137 | child.typeAliasObject.setGeneric(true); 138 | } 139 | } else if (ts.isModuleDeclaration(node)) { 140 | // For namespace declarations, add all child declarations of 141 | // the namespace as children of the node for that namespace by 142 | // recursively calling `discover`. 143 | // 144 | // `global` is a name for the global namespace, any 145 | // declarations inside of a namespace named `global` should 146 | // instead be added as children of the root node. 147 | if (node.name.text === "global") { 148 | parser.getRootNode().discover(parser, node.body!); 149 | } else { 150 | const child = this.getOrInsert(node); 151 | child.moduleDeclaration = node; 152 | child.discover(parser, node.body!); 153 | } 154 | } else if (ts.isClassDeclaration(node)) { 155 | // Set the class declaration for this node. Also call 156 | // `discoverClass`. 157 | const child = this.getOrInsert(node); 158 | child.classDeclaration = node; 159 | child.discoverClass(parser, node); 160 | } 161 | 162 | // other possible nodes: 163 | // ts.SyntaxKind.EndOfFileToken 164 | // ts.SyntaxKind.ExportDeclaration 165 | // ts.SyntaxKind.ImportDeclaration 166 | // ts.SyntaxKind.ExportAssignment 167 | // ts.SyntaxKind.ImportEqualsDeclaration 168 | }); 169 | } 170 | } 171 | 172 | // Every named thing is an instance of `Child`. `Child` extends `Node` and so 173 | // children can have their own children. 174 | // 175 | // It is important to note that even declarations that don't have children 176 | // (which is all of them except for namespace declarations) are instances of 177 | // `Child`. This is because a `Child` does not correspond with a single 178 | // declaration, but rather a set of declarations that all have the same name. 179 | // 180 | // So for example, these typescript declarations: 181 | // ``` 182 | // declare interface Foo {} 183 | // 184 | // declare namespace Foo { 185 | // declare interface Bar {} 186 | // } 187 | // ``` 188 | // 189 | // Turn into this tree structure: 190 | // ``` 191 | // { 192 | // children: { 193 | // "Foo": { 194 | // name: "Foo", 195 | // interfaceDeclarations: [], 196 | // children: { 197 | // "Bar": { 198 | // name: "Bar", 199 | // interfaceDeclarations: [], 200 | // } 201 | // } 202 | // } 203 | // } 204 | // } 205 | // ``` 206 | // 207 | // To reiterate: 208 | // - The root node only has children. 209 | // - The "Foo" and "Bar" node each have one interface declaration. 210 | // - The "Foo" node also has children, from the namespace declaration. 211 | export class Child extends Node { 212 | private readonly name: string; 213 | public interfaceDeclarations?: Array; 214 | public functionDeclarations?: Array; 215 | public moduleDeclaration?: ts.ModuleDeclaration; 216 | public classDeclaration?: ts.ClassDeclaration; 217 | public variableDeclaration?: ts.VariableDeclaration; 218 | public typeAliasDeclaration?: ts.TypeAliasDeclaration; 219 | public classObject?: Class; 220 | public typeAliasObject?: TypeAlias; 221 | 222 | public constructor(name: string) { 223 | super(); 224 | this.name = name; 225 | } 226 | 227 | public getName(): string { 228 | return this.name; 229 | } 230 | 231 | // Return all class *AND* interface declarations for this node. Class and 232 | // interface declarations have mostly the same structure and are treated 233 | // as equivalent in most cases. 234 | public getClassDeclarations(): ReadonlyArray { 235 | if (this.classDeclaration) { 236 | return [this.classDeclaration, ...this.interfaceDeclarations ?? []]; 237 | } else { 238 | return this.interfaceDeclarations ?? []; 239 | } 240 | } 241 | 242 | public getFunctionDeclarations(): ReadonlyArray { 243 | return this.functionDeclarations ?? []; 244 | } 245 | 246 | // `discoverClass` is called for both class and interface declarations. If 247 | // we find a class or interface declaration for this name we must 248 | // instantiate and register a C++ class declaration for it, as explained in 249 | // the comment above `discover`. If a class has already been instantiated, 250 | // this function does nothing. 251 | public discoverClass(parser: Parser, node: ts.Node): void { 252 | if (!this.classObject) { 253 | const type = parser.getTypeAtLocation(node) as ts.InterfaceType; 254 | this.classObject = new Class(this.name === "Array" ? "TArray" : this.name); 255 | parser.setDeclaredType(type, DeclaredType.create(this.classObject)); 256 | 257 | if (type.typeParameters && type.typeParameters.length > 0) { 258 | this.classObject.setGeneric(true); 259 | 260 | if (this.name === "Array") { 261 | this.classObject.setBasicVersion(new Class("Array")); 262 | } 263 | } 264 | } 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /cheerp/jshelper.h: -------------------------------------------------------------------------------- 1 | #ifndef CHEERP_JSHELPER_H 2 | #define CHEERP_JSHELPER_H 3 | namespace [[cheerp::genericjs]] client { 4 | class _Any; 5 | template 6 | class _Union; 7 | template 8 | class _Function; 9 | class Object; 10 | class String; 11 | template 12 | class TArray; 13 | } 14 | namespace [[cheerp::genericjs]] cheerp { 15 | using Nullptr = decltype(nullptr); 16 | struct FalseType { 17 | constexpr static bool value = false; 18 | }; 19 | struct TrueType { 20 | constexpr static bool value = true; 21 | }; 22 | template 23 | struct IsSameImpl : FalseType {}; 24 | template 25 | struct IsSameImpl : TrueType {}; 26 | template 27 | struct EnableIfImpl {}; 28 | template 29 | struct EnableIfImpl { 30 | using type = T; 31 | }; 32 | template 33 | struct ConditionalImpl { 34 | using type = T; 35 | }; 36 | template 37 | struct ConditionalImpl { 38 | using type = F; 39 | }; 40 | template 41 | struct RemoveConstImpl { 42 | using type = T; 43 | }; 44 | template 45 | struct RemoveConstImpl { 46 | using type = T; 47 | }; 48 | template 49 | struct RemoveVolatileImpl { 50 | using type = T; 51 | }; 52 | template 53 | struct RemoveVolatileImpl { 54 | using type = T; 55 | }; 56 | template 57 | struct RemovePointerImpl { 58 | using type = T; 59 | }; 60 | template 61 | struct RemovePointerImpl { 62 | using type = T; 63 | }; 64 | template 65 | struct RemoveReferenceImpl { 66 | using type = T; 67 | }; 68 | template 69 | struct RemoveReferenceImpl { 70 | using type = T; 71 | }; 72 | template 73 | struct RemoveReferenceImpl { 74 | using type = T; 75 | }; 76 | template 77 | struct RemoveExtentImpl { 78 | using type = T; 79 | }; 80 | template 81 | struct RemoveExtentImpl { 82 | using type = T; 83 | }; 84 | template 85 | struct RemoveExtentImpl { 86 | using type = T; 87 | }; 88 | template 89 | constexpr bool IsSame = IsSameImpl::value; 90 | template 91 | using EnableIf = typename EnableIfImpl::type; 92 | template 93 | using Conditional = typename ConditionalImpl::type; 94 | template 95 | using RemoveConst = typename RemoveConstImpl::type; 96 | template 97 | using RemoveVolatile = typename RemoveVolatileImpl::type; 98 | template 99 | using RemoveCv = RemoveVolatile>; 100 | template 101 | using RemovePointer = Conditional>::type, RemoveCv>, T, typename RemovePointerImpl>::type>; 102 | template 103 | using RemoveReference = typename RemoveReferenceImpl::type; 104 | template 105 | using RemoveExtent = typename RemoveExtentImpl::type; 106 | template 107 | constexpr bool IsSimilar = IsSame, RemoveCv>; 108 | template 109 | constexpr bool IsVoid = IsSimilar; 110 | template 111 | constexpr bool IsConst = !IsSame, T>; 112 | template 113 | constexpr bool IsVolatile = !IsSame, T>; 114 | template 115 | constexpr bool IsPointer = !IsSame, T>; 116 | template 117 | constexpr bool IsReference = !IsSame, T>; 118 | template 119 | constexpr bool IsArray = !IsSame, T>; 120 | template 121 | struct ArrayElementTypeImpl { 122 | using type = client::_Any*; 123 | }; 124 | template 125 | struct ArrayElementTypeImpl> { 126 | using type = T; 127 | }; 128 | template 129 | using ArrayElementType = typename ArrayElementTypeImpl::type; 130 | template 131 | using Normalize = RemoveCv>>; 132 | template 133 | constexpr bool IsPointerOrArrayOf = (IsPointer && IsSimilar, U>) || (IsArray && IsSimilar, U>); 134 | template 135 | constexpr bool IsCharPointer = IsPointerOrArrayOf || IsPointerOrArrayOf; 136 | template 137 | constexpr bool IsConstReference = IsReference && IsConst>; 138 | template 139 | constexpr bool IsPrimitive = IsSimilar || IsSimilar || IsSimilar || IsSimilar || IsSimilar || IsSimilar || IsSimilar || IsSimilar || IsSimilar || IsSimilar || IsSimilar || IsSimilar || IsSimilar || IsSimilar || IsSimilar; 140 | template 141 | struct CanCastHelper : FalseType {}; 142 | template && IsPrimitive> 143 | struct CanCastImpl { 144 | constexpr static TrueType testAny(client::_Any*); 145 | constexpr static FalseType testAny(...); 146 | constexpr static TrueType test(To*); 147 | constexpr static FalseType test(...); 148 | constexpr static bool value = ((IsVoid || IsSame) && (IsPrimitive || IsSame || decltype(testAny((To*) nullptr))::value)) || ((IsVoid || IsSame) && (IsPrimitive || IsSame || decltype(testAny((From*) nullptr))::value)) || decltype(test((From*) nullptr))::value || CanCastHelper::value; 149 | }; 150 | template 151 | struct CanCastImpl { 152 | constexpr static TrueType test(To); 153 | constexpr static FalseType test(...); 154 | constexpr static bool value = decltype(test(*(From*) nullptr))::value; 155 | }; 156 | template 157 | constexpr bool CanCast = (CanCastImpl, Normalize>::value || ...); 158 | template 159 | constexpr bool CanCastArgs = CanCast || (IsCharPointer && (CanCast || ...)); 160 | template class To, class... T> 161 | struct CanCastHelper> { 162 | template 163 | [[cheerp::genericjs]] 164 | constexpr static bool test(To*) { 165 | return CanCast*, To*>; 166 | } 167 | [[cheerp::genericjs]] 168 | constexpr static bool test(void*) { 169 | return false; 170 | } 171 | constexpr static bool value = test((From*) nullptr); 172 | }; 173 | template class Class, class... From, class... To> 174 | struct CanCastHelper, Class> { 175 | constexpr static bool value = (CanCast && ...); 176 | }; 177 | template 178 | struct CanCastHelper, client::_Function> { 179 | constexpr static bool value = CanCast; 180 | }; 181 | template 182 | struct CanCastHelper, client::_Function> { 183 | constexpr static bool value = CanCast && CanCastHelper, client::_Function>::value; 184 | }; 185 | template 186 | struct CanCastHelper, client::_Union> { 187 | constexpr static bool value = (CanCast> && ...); 188 | }; 189 | template 190 | struct CanCastHelper, To> { 191 | constexpr static bool value = (CanCast && ...); 192 | }; 193 | template 194 | struct CanCastHelper> { 195 | constexpr static bool value = (CanCast || ...); 196 | }; 197 | template 198 | constexpr bool CheckTemplate = ((IsPrimitive || IsPointer) && ...); 199 | template 200 | struct GetTypeHelper { 201 | static client::Object* getType(); 202 | }; 203 | template 204 | [[gnu::always_inline]] 205 | client::Object* getType() { 206 | return GetTypeHelper::getType(); 207 | } 208 | } 209 | namespace [[cheerp::genericjs]] client { 210 | class [[cheerp::client_layout]] _Any { 211 | struct [[cheerp::client_layout]] Cast { 212 | template>> 213 | [[gnu::always_inline]] 214 | operator T() const { 215 | T out; 216 | asm("%1" : "=r"(out) : "r"(this)); 217 | return out; 218 | } 219 | }; 220 | public: 221 | template 222 | [[cheerp::client_transparent]] 223 | _Any(T value) noexcept; 224 | template>> 225 | [[gnu::always_inline]] 226 | T cast() const { 227 | T out; 228 | asm("%1" : "=r"(out) : "r"(this)); 229 | return out; 230 | } 231 | [[gnu::always_inline]] 232 | const Cast& cast() const { 233 | return *reinterpret_cast(this); 234 | } 235 | template 236 | [[gnu::always_inline]] 237 | bool instanceof() const { 238 | bool out; 239 | asm("%1 instanceof %2" : "=r"(out) : "r"(this), "r"(cheerp::getType())); 240 | return out; 241 | } 242 | [[gnu::always_inline]] 243 | client::String* _typeof() const { 244 | client::String* out; 245 | asm("typeof %1" : "=r"(out) : "r"(this)); 246 | return out; 247 | } 248 | template>> 249 | [[gnu::always_inline]] 250 | explicit operator T() const { 251 | return this->cast(); 252 | } 253 | [[gnu::always_inline]] 254 | explicit operator int() const { 255 | return this->cast(); 256 | } 257 | }; 258 | template 259 | class [[cheerp::client_layout]] _Union { 260 | struct [[cheerp::client_layout]] Cast { 261 | template || ...)>> 262 | [[gnu::always_inline]] 263 | operator T() const { 264 | T out; 265 | asm("%1" : "=r"(out) : "r"(this)); 266 | return out; 267 | } 268 | }; 269 | public: 270 | template>>> 271 | [[cheerp::client_transparent]] 272 | _Union(T value) noexcept; 273 | template || ...)>> 274 | [[gnu::always_inline]] 275 | T cast() const { 276 | T out; 277 | asm("%1" : "=r"(out) : "r"(this)); 278 | return out; 279 | } 280 | [[gnu::always_inline]] 281 | const Cast& cast() const { 282 | return *reinterpret_cast(this); 283 | } 284 | template || ...)>> 285 | [[gnu::always_inline]] 286 | explicit operator T() const { 287 | return this->cast(); 288 | } 289 | }; 290 | } 291 | namespace [[cheerp::genericjs]] cheerp { 292 | [[gnu::always_inline]] 293 | inline client::String* makeString(const char* str); 294 | [[gnu::always_inline]] 295 | inline client::String* makeString(const wchar_t* str); 296 | template 297 | [[gnu::always_inline]] 298 | Conditional>, client::String*, Conditional, RemoveReference*, T&&>> clientCast(T&& value) { 299 | if constexpr (IsCharPointer>) 300 | return makeString(value); 301 | else if constexpr (IsConstReference) 302 | return &value; 303 | else 304 | return value; 305 | } 306 | } 307 | #endif 308 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "src/**/*", 4 | "package.json" 5 | ], 6 | "compilerOptions": { 7 | /* Visit https://aka.ms/tsconfig to read more about this file */ 8 | 9 | /* Projects */ 10 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 11 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 12 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 13 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 14 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 15 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 16 | 17 | /* Language and Environment */ 18 | "target": "es2019", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 19 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 20 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 21 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 22 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 23 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 24 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 25 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 26 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 27 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 28 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 29 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 30 | 31 | /* Modules */ 32 | "module": "commonjs", /* Specify what module code is generated. */ 33 | "rootDir": "./src", /* Specify the root folder within your source files. */ 34 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 35 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 36 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 37 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 38 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 39 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 40 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 41 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 42 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 43 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 44 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 45 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 46 | // "resolveJsonModule": true, /* Enable importing .json files. */ 47 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 48 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 49 | 50 | /* JavaScript Support */ 51 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 52 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 53 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 54 | 55 | /* Emit */ 56 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 57 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 58 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 59 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 60 | "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 62 | "outDir": "./build", /* Specify an output folder for all emitted files. */ 63 | // "removeComments": true, /* Disable emitting comments. */ 64 | // "noEmit": true, /* Disable emitting files from a compilation. */ 65 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 66 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 67 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 68 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 69 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 70 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 71 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 72 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 73 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 74 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 75 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 76 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 77 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 78 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 79 | 80 | /* Interop Constraints */ 81 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 82 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 83 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 84 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 85 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 86 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 87 | 88 | /* Type Checking */ 89 | "strict": true, /* Enable all strict type-checking options. */ 90 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 91 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 92 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 93 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 94 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 95 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 96 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 97 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 98 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 99 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 100 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 101 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 102 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 103 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 104 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 105 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 106 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 107 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 108 | 109 | /* Completeness */ 110 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 111 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/declaration/function.ts: -------------------------------------------------------------------------------- 1 | import { Declaration } from "./declaration.js"; 2 | import { TemplateDeclaration } from "./templateDeclaration.js"; 3 | import { Namespace, Flags } from "./namespace.js"; 4 | import { State, Dependency, Dependencies, ReasonKind, ResolverContext } from "../target.js"; 5 | import { Writer } from "../writer.js"; 6 | import { Type } from "../type/type.js"; 7 | import { TemplateType } from "../type/templateType.js"; 8 | import { TypeQualifier } from "../type/qualifiedType.js"; 9 | import { FunctionType } from "../type/functionType.js"; 10 | import { FUNCTION_TYPE, UNION_TYPE, VOID_TYPE } from "../type/namedType.js"; 11 | 12 | export class Parameter { 13 | private type: Type; 14 | private readonly name: string; 15 | private readonly defaultValue?: string; 16 | 17 | public constructor(type: Type, name: string, defaultValue?: string) { 18 | this.type = type; 19 | this.name = name; 20 | this.defaultValue = defaultValue; 21 | } 22 | 23 | public getType(): Type { 24 | return this.type; 25 | } 26 | 27 | public setType(type: Type): void { 28 | this.type = type; 29 | } 30 | 31 | public getName(): string { 32 | return this.name; 33 | } 34 | 35 | public getDefaultValue(): string | undefined { 36 | return this.defaultValue; 37 | } 38 | } 39 | 40 | export class Initializer { 41 | private readonly name: string; 42 | private readonly value: string; 43 | 44 | public constructor(name: string, value: string) { 45 | this.name = name; 46 | this.value = value; 47 | } 48 | 49 | public getName(): string { 50 | return this.name; 51 | } 52 | 53 | public getValue(): string { 54 | return this.value; 55 | } 56 | } 57 | 58 | export class Function extends TemplateDeclaration { 59 | // Function parameters. 60 | private parameters?: Array; 61 | 62 | // Constructor initializers, mostly used for extensions in 63 | // "src/extensions.ts". 64 | private initializers?: Array; 65 | 66 | // We can't track dependencies of the function body, they must be added 67 | // manually using `addExtraDependency`. 68 | private extraDependencies?: Dependencies; 69 | 70 | // The return type. 71 | private type?: Type; 72 | 73 | // The function body. This is mostly used for extensions in 74 | // "src/extensions.ts", but also for some automatically generated 75 | // forwarding helper functions. 76 | private body?: string; 77 | 78 | public constructor(name: string, type?: Type, namespace?: Namespace) { 79 | super(name, namespace); 80 | this.type = type; 81 | } 82 | 83 | public isConstructor(): boolean { 84 | return this.getName() === this.getParent()?.getName(); 85 | } 86 | 87 | public getParameters(): ReadonlyArray { 88 | return this.parameters ?? []; 89 | } 90 | 91 | public addParameter(type: Type, name: string, defaultValue?: string): void { 92 | this.parameters ??= []; 93 | this.parameters.push(new Parameter(type, name, defaultValue)); 94 | } 95 | 96 | public getInitializers(): ReadonlyArray { 97 | return this.initializers ?? []; 98 | } 99 | 100 | public addInitializer(name: string, value: string): void { 101 | this.initializers ??= []; 102 | this.initializers.push(new Initializer(name, value)); 103 | } 104 | 105 | public getExtraDependencies(): Dependencies { 106 | return this.extraDependencies ?? new Dependencies; 107 | } 108 | 109 | public addExtraDependency(declaration: Declaration, state: State, reason: ReasonKind = ReasonKind.Extra): void { 110 | this.extraDependencies ??= new Dependencies; 111 | this.extraDependencies.add(declaration, new Dependency(state, this, reason)); 112 | } 113 | 114 | public getType(): Type | undefined { 115 | return this.type; 116 | } 117 | 118 | public setType(type: Type | undefined): void { 119 | this.type = type; 120 | } 121 | 122 | public getBody(): string | undefined { 123 | return this.body; 124 | } 125 | 126 | public setBody(body: string): void { 127 | this.body = body; 128 | } 129 | 130 | public maxState(): State { 131 | return State.Partial; 132 | } 133 | 134 | public getChildren(): ReadonlyArray { 135 | return new Array; 136 | } 137 | 138 | // The dependencies of a function are: 139 | // - partial for types used in function parameters. 140 | // - partial for the return type. 141 | // - extra dependencies added using `addExtraDependency`. 142 | protected getDirectDependencies(state: State): Dependencies { 143 | const parameterReason = new Dependency(State.Partial, this, ReasonKind.ParameterType); 144 | const returnReason = new Dependency(State.Partial, this, ReasonKind.ReturnType); 145 | 146 | return new Dependencies( 147 | this.getParameters() 148 | .flatMap(parameter => [...parameter.getType().getDependencies(parameterReason)]) 149 | .concat([...this.type?.getDependencies(returnReason) ?? []]) 150 | .concat([...this.extraDependencies ?? []]) 151 | ); 152 | } 153 | 154 | protected getDirectReferencedTypes(): ReadonlyArray { 155 | return this.getParameters() 156 | .flatMap(parameter => [...parameter.getType().getReferencedTypes()]) 157 | .concat([...this.type?.getReferencedTypes() ?? []]); 158 | } 159 | 160 | protected writeImpl(context: ResolverContext, writer: Writer, state: State, namespace?: Namespace): void { 161 | // 1. Write the template<...> line, if needed. 162 | this.writeTemplate(writer, state, namespace); 163 | 164 | // 2. Write the interface name attribute, unless there is a body. 165 | if (this.body === undefined) { 166 | this.writeInterfaceName(writer); 167 | } 168 | 169 | // 3. Write attributes. 170 | if (this.getAttributes().length > 0) { 171 | this.writeAttributes(writer); 172 | writer.writeLine(false); 173 | } 174 | 175 | // 4. Write leading modifiers. 176 | const flags = this.getFlags(); 177 | 178 | if (flags & Flags.Explicit) { 179 | writer.write("explicit"); 180 | writer.writeSpace(); 181 | } 182 | 183 | if (flags & Flags.Static) { 184 | writer.write("static"); 185 | writer.writeSpace(); 186 | } 187 | 188 | if (flags & Flags.Inline) { 189 | writer.write("inline"); 190 | writer.writeSpace(); 191 | } 192 | 193 | // 5. Write return type. 194 | if (this.type) { 195 | this.type.write(writer, namespace); 196 | writer.writeSpace(); 197 | } 198 | 199 | // 6. Write function name. 200 | writer.write(this.getName()); 201 | writer.write("("); 202 | 203 | let first = true; 204 | 205 | // 7. Write function parameters 206 | for (const parameter of this.getParameters()) { 207 | const defaultValue = parameter.getDefaultValue(); 208 | 209 | if (!first) { 210 | writer.write(","); 211 | writer.writeSpace(false); 212 | } 213 | 214 | parameter.getType().write(writer, namespace); 215 | writer.writeSpace(); 216 | writer.write(parameter.getName()); 217 | 218 | if (defaultValue) { 219 | writer.writeSpace(false); 220 | writer.write("="); 221 | writer.writeSpace(false); 222 | writer.write(defaultValue); 223 | } 224 | 225 | first = false; 226 | } 227 | 228 | writer.write(")"); 229 | first = true; 230 | 231 | // 8. Write trailing modifiers. 232 | if (flags & Flags.Const) { 233 | writer.writeSpace(!first); 234 | writer.write("const"); 235 | first = false; 236 | } 237 | 238 | if (flags & Flags.Noexcept) { 239 | writer.writeSpace(!first); 240 | writer.write("noexcept"); 241 | first = false; 242 | } 243 | 244 | first = true; 245 | 246 | // 9. Write constructor initializers. 247 | for (const initializer of this.getInitializers()) { 248 | writer.write(first ? ":" : ","); 249 | writer.writeSpace(false); 250 | writer.write(initializer.getName()); 251 | writer.write("("); 252 | writer.write(initializer.getValue()); 253 | writer.write(")"); 254 | first = false; 255 | } 256 | 257 | // 10. Write body, if present. 258 | if (this.body !== undefined) { 259 | writer.writeBody(this.body); 260 | } else { 261 | writer.write(";"); 262 | writer.writeLine(false); 263 | } 264 | } 265 | 266 | // Merge function types to remove duplicate declarations and avoid 267 | // ambiguous overloads. Two functions are merged if and only if they have: 268 | // - An equal number of parameters. 269 | // - An equal number of template type parameters. 270 | // - Equal constness. 271 | // - Compatible parameter types. 272 | // 273 | // For each parameter type of this function and the corresponding type of 274 | // the other function: 275 | // - If both are the same type, it is used unchanged. 276 | // - If both are `_Function` types, they are merged using `mergeFunction`. 277 | // - If both are `_Union` types, they are merged by creating a new `_Union` 278 | // with all type parameters from both `_Union`s. 279 | // - Otherwise, the parameters types are incompatible. 280 | public merge(other: Declaration): boolean { 281 | // Cannot merge if the other declaration is not also a function. 282 | if (!(other instanceof Function)) { 283 | return false; 284 | } 285 | 286 | const thisParameters = this.getParameters(); 287 | const otherParameters = other.getParameters(); 288 | const parameters = new Array; 289 | let canMerge = true; 290 | 291 | canMerge &&= thisParameters.length === otherParameters.length; 292 | canMerge &&= this.getTypeParameters().length === other.getTypeParameters().length; 293 | canMerge &&= !((this.getFlags() ^ other.getFlags()) & Flags.Const); 294 | 295 | if (!canMerge) { 296 | return false; 297 | } 298 | 299 | for (let i = 0; i < thisParameters.length; i++) { 300 | if (thisParameters[i].getType() === otherParameters[i].getType()) { 301 | // Both parameters are the same type, use the type unchanged. 302 | parameters.push(thisParameters[i].getType()); 303 | continue; 304 | } 305 | 306 | const thisParameter = thisParameters[i].getType().removeQualifiers(); 307 | const otherParameter = otherParameters[i].getType().removeQualifiers(); 308 | 309 | if (!(thisParameter instanceof TemplateType && otherParameter instanceof TemplateType)) { 310 | return false; 311 | } 312 | 313 | const thisInner = thisParameter.getInner(); 314 | const otherInner = otherParameter.getInner(); 315 | 316 | if (thisInner === UNION_TYPE && otherInner === UNION_TYPE) { 317 | // Both parameters are union types, create a new union with all 318 | // type parameters from both unions. 319 | parameters.push(TemplateType.createUnion( 320 | TypeQualifier.ConstReference, 321 | ...thisParameter.getTypeParameters() as ReadonlyArray, 322 | ...otherParameter.getTypeParameters() as ReadonlyArray 323 | )); 324 | } else if (thisInner === FUNCTION_TYPE && otherInner === FUNCTION_TYPE) { 325 | // Both parameters are function types, use `mergeFunction` to 326 | // merge the function types. 327 | const thisFunction = thisParameter.getTypeParameters()[0] as FunctionType; 328 | const otherFunction = otherParameter.getTypeParameters()[0] as FunctionType; 329 | parameters.push(mergeFunction(thisFunction, otherFunction)); 330 | } else { 331 | // Parameter types are not compatible. 332 | return false; 333 | } 334 | } 335 | 336 | // At this point we know that the function declarations can be merged 337 | // for sure. We can now safely modify the declaration. 338 | this.type = mergeReturn(this.type, other.type); 339 | 340 | for (let i = 0; i < parameters.length; i++) { 341 | thisParameters[i].setType(parameters[i]); 342 | } 343 | 344 | return true; 345 | } 346 | } 347 | 348 | // Merge `_Function` types. The return type is made using `mergeReturn`. Every 349 | // parameter is a `_Union` type of the parameter type from the first function 350 | // and the corresponding parameter type of the second function. If one function 351 | // has more parameters than the other, the excess parameters are added to the 352 | // returned function unmodified. 353 | function mergeFunction(self: FunctionType, other: FunctionType): Type { 354 | const selfParameters = self.getParameters(); 355 | const otherParameters = other.getParameters(); 356 | 357 | if (otherParameters.length < selfParameters.length) { 358 | return mergeFunction(other, self); 359 | } 360 | 361 | const parameters = selfParameters 362 | .map((parameter, i) => TemplateType.createUnion(TypeQualifier.Pointer, parameter, otherParameters[i])) 363 | .concat(otherParameters.slice(selfParameters.length)); 364 | 365 | return TemplateType.createFunction( 366 | mergeReturn(self.getReturnType(), other.getReturnType())!, 367 | ...parameters 368 | ).constReference(); 369 | } 370 | 371 | // Merge return types. If either type is undefined or void, return the other 372 | // type. Otherwise, return a `_Union` template of both types. 373 | function mergeReturn(self?: Type, other?: Type): Type | undefined { 374 | if (self === undefined || self === VOID_TYPE) { 375 | return other; 376 | } else if (other === undefined || other === VOID_TYPE) { 377 | return self; 378 | } else { 379 | return TemplateType.createUnion(TypeQualifier.Pointer, self, other); 380 | } 381 | } 382 | -------------------------------------------------------------------------------- /src/parser/generics.ts: -------------------------------------------------------------------------------- 1 | // Functions that help with processing generic types 2 | 3 | import { Parser } from "./parser.js"; 4 | import { Type } from "../type/type.js"; 5 | import { GenericType } from "../type/genericType.js"; 6 | import { Expression } from "../type/expression.js"; 7 | import { options } from "../utility.js"; 8 | import { TypeInfo, TypeKind } from "./typeInfo.js"; 9 | import { ANY_TYPE } from "../type/namedType.js"; 10 | import * as ts from "typescript"; 11 | 12 | export function isObjectType(type: ts.Type): type is ts.ObjectType { 13 | return !!(type.flags & ts.TypeFlags.Object); 14 | } 15 | 16 | export function isTypeReference(type: ts.Type): type is ts.TypeReference { 17 | return isObjectType(type) && !!(type.objectFlags & ts.ObjectFlags.Reference); 18 | } 19 | 20 | // Which other types are used in this type? 21 | // 22 | // For class or interface types: The type parameters, if any. 23 | // For call function-like types: The return and parameter types. 24 | // For union `T | ...` and intersction `T & ...` types: The inner types `T`. 25 | // For type references `T`: The target type `T` and type arguments `U`. 26 | function *getUsedTypes(parser: Parser, type: ts.Type): IterableIterator { 27 | const callSignatures = type.getCallSignatures(); 28 | 29 | if (type.isClassOrInterface()) { 30 | yield *type.typeParameters ?? []; 31 | } else if (callSignatures.length > 0) { 32 | for (const signature of callSignatures) { 33 | const declaration = signature.getDeclaration(); 34 | yield parser.getTypeFromTypeNode(declaration.type!); 35 | 36 | for (const parameter of declaration.parameters) { 37 | yield parser.getTypeFromTypeNode(parameter.type!); 38 | } 39 | } 40 | } else if (type.isUnion() || type.isIntersection()) { 41 | yield *type.types; 42 | } else if (isTypeReference(type)) { 43 | yield type.target; 44 | yield *parser.getTypeArguments(type); 45 | } 46 | } 47 | 48 | function countTypes(parser: Parser, cache: Map, type: ts.Type): number { 49 | if (cache.has(type)) { 50 | return cache.get(type)!; 51 | } 52 | 53 | cache.set(type, 0); 54 | 55 | const count = [...getUsedTypes(parser, type)] 56 | .map(type => countTypes(parser, cache, type)) 57 | .reduce((a, b) => a + b, 0); 58 | 59 | cache.set(type, count); 60 | 61 | return count; 62 | } 63 | 64 | function usesType(parser: Parser, types: Iterable, other: ts.Type): number { 65 | const cache = new Map([[other, 1]]); 66 | 67 | return [...types] 68 | .map(type => countTypes(parser, cache, type)) 69 | .reduce((a, b) => a + b, 0); 70 | } 71 | 72 | function isFunctionLike(node: ts.Node): node is ts.SignatureDeclarationBase { 73 | return ts.isMethodDeclaration(node) || ts.isConstructSignatureDeclaration(node) || ts.isConstructorDeclaration(node) || ts.isIndexSignatureDeclaration(node) || ts.isMethodSignature(node) || ts.isMethodDeclaration(node) || ts.isFunctionDeclaration(node); 74 | } 75 | 76 | function isTypeAliasLike(node: ts.Node): node is ts.TypeAliasDeclaration { 77 | return ts.isTypeAliasDeclaration(node); 78 | } 79 | 80 | function isClassLike(node: ts.Node): node is ts.ClassDeclaration | ts.InterfaceDeclaration { 81 | return ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node); 82 | } 83 | 84 | export enum DefaultResolve { 85 | None, 86 | Explicit, 87 | Constraint, 88 | Any, 89 | } 90 | 91 | export interface Generic { 92 | readonly type: GenericType; 93 | readonly defaultType?: Type; 94 | } 95 | 96 | // This class stores a map of generic arguments and the C++ types we have made 97 | // for them. When parsing a declaration that may have generic arguments, the 98 | // parser calls either `createParameters` or `createConstraints`. See the 99 | // comments at those functions for information about what they do and when they 100 | // should be used. 101 | // 102 | // The `types` are used as overrides for `TypeParser` (see 103 | // "src/parser/typeParser.ts"), so that when the type parser encounters a 104 | // type argument, it will use the appropriate C++ type. 105 | // 106 | // The comments in this simplified example show the contents of `types` as the 107 | // declaration is being parsed. Showing how when parsing the parameters of 108 | // `bar`, we have a C++ type for both `T` and `U`: 109 | // ``` 110 | // // {} 111 | // declare interface Foo 112 | // // { T: } 113 | // { 114 | // bar 115 | // // { T: , U: } 116 | // (t: T, u: U): void; 117 | // // { T: } 118 | // } 119 | // // {} 120 | // ``` 121 | export class Generics { 122 | // A counter that is incremented every time we create a new type argument, 123 | // used to give them a unique name, _T0, _T1, _T2, etc... 124 | private nextId: number; 125 | 126 | private types?: Map; 127 | 128 | public constructor(nextId?: number, types?: ReadonlyMap) { 129 | this.nextId = nextId ?? 0; 130 | this.types = types && new Map(types); 131 | } 132 | 133 | // Create a copy of the type arguments map. This is called when we start 134 | // parsing a new generic declaration, the type arguments of that 135 | // declaration are then added using `createParameters` or 136 | // `createConstraints`, and the copy is discarded once the whole 137 | // declaration has been parsed. 138 | public clone(): Generics { 139 | return new Generics(this.nextId, this.types); 140 | } 141 | 142 | public getNextId(): number { 143 | return this.nextId; 144 | } 145 | 146 | public getTypes(): ReadonlyMap { 147 | return this.types ?? new Map; 148 | } 149 | 150 | private addType(type: ts.Type, override: TypeInfo): void { 151 | this.types ??= new Map; 152 | this.types.set(type, override); 153 | } 154 | 155 | // `createParameters` is one of two functions that updates the type 156 | // arguments map with the type arguments of a declaration. 157 | // 158 | // This function actually takes in a list of declarations rather than a 159 | // single declaration, this is to handle scattered interface declarations 160 | // for the same type having mismatched type parmaters. 161 | // 162 | // We return both a list of all the added type arguments, and a set of 163 | // expressions that represent the constraints on the type arguments. 164 | // 165 | // In the simplest case, we construct a `GenericType` for every type 166 | // argument and add it to the map. 167 | public createParameters(parser: Parser, declarations: ReadonlyArray, defaultResolve: DefaultResolve = DefaultResolve.Explicit): [Array, Set] { 168 | const types = new Array; 169 | const constraints = new Set; 170 | const aliasTypes = new Set; 171 | const usedTypes = new Array; 172 | 173 | // 1. Collect the types used by this declaration. This information is 174 | // used to ellide type arguments in some cases. Type arguments of 175 | // classes are ignored here and never ellided. 176 | // 177 | // For function types, we gather all types used in the function 178 | // signature. Type arguments for functions can be ellided if they 179 | // appear at most once in the signature. 180 | // 181 | // For example, consider the conversion of this typescript code: 182 | // ``` 183 | // declare function foo(arg: T): T; 184 | // declare function bar(arg: T): void; 185 | // ``` 186 | // 187 | // In the function `foo`, the type argument `T` must be present, 188 | // because otherwise we would not be able to accurately specify the 189 | // relation that the return type is the same as the argument type. 190 | // 191 | // In the function `bar` however, because `T` is not used in the return 192 | // type, or in any other argument, we can remove the whole type 193 | // parameter `T` and replace the type of `arg` with `any`. No type 194 | // information is lost because `T` could have been `any` to begin with, 195 | // and nothing is gained by using a type more specific than `any`. 196 | // 197 | // The same applies when `T` has a type constraint, except instead of 198 | // replacing `T` with `any` it is replaced with the constraint. 199 | // 200 | // For alias types, the type argument only needs to be used once. If it 201 | // is not used at all, then it can be ellided. 202 | for (const declaration of declarations) { 203 | if (isFunctionLike(declaration)) { 204 | usedTypes.push(parser.getTypeFromTypeNode(declaration.type!)); 205 | 206 | for (const parameter of declaration.parameters) { 207 | usedTypes.push(parser.getTypeFromTypeNode(parameter.type!)); 208 | } 209 | } else if (isTypeAliasLike(declaration)) { 210 | aliasTypes.add(parser.getTypeFromTypeNode(declaration.type)); 211 | } 212 | } 213 | 214 | for (const declaration of declarations) { 215 | const typeParameters = ts.getEffectiveTypeParameterDeclarations(declaration); 216 | 217 | // 2. Iterate over all type parameters of all "declarations" of 218 | // this declaration. 219 | for (const [i, typeParameter] of typeParameters.entries()) { 220 | const type = parser.getTypeAtLocation(typeParameter); 221 | const constraint = ts.getEffectiveConstraintOfTypeParameter(typeParameter); 222 | const info = constraint && parser.getTypeNodeInfo(constraint, this); 223 | 224 | // 3. If this is a class declaration or if the type parameter 225 | // is used in a way that cannot be ellided, we create a new 226 | // named type and add it to the type argument map. If the type 227 | // argument has a constraint, we also add it to the list of 228 | // constraints. 229 | // 230 | // To handle multiple interface declarations some of which may 231 | // only store a subset of the complete type arguments of this 232 | // declaration, we keep track of the index of the type argument 233 | // within this interface declaration and only create a new 234 | // named type if we have not seen the index before. 235 | // 236 | // If the type parameter can be ellided, we only add the 237 | // constraint to the type argument map, and we do not generate 238 | // a type argument at all. 239 | if (isClassLike(declaration) || usesType(parser, aliasTypes, type) > 0 || usesType(parser, usedTypes, type) > 1) { 240 | let defaultInfo; 241 | 242 | if (defaultResolve >= DefaultResolve.Explicit) { 243 | defaultInfo = typeParameter.default && parser.getTypeNodeInfo(typeParameter.default, this); 244 | } 245 | 246 | if (defaultResolve >= DefaultResolve.Constraint) { 247 | defaultInfo ||= info; 248 | } 249 | 250 | let defaultType = defaultInfo?.asTypeParameter(); 251 | 252 | if (defaultResolve >= DefaultResolve.Any) { 253 | defaultType ??= ANY_TYPE.pointer(); 254 | } 255 | 256 | types[i] ??= { type: GenericType.create(`_T${this.nextId++}`), defaultType }; 257 | this.addType(type, new TypeInfo(types[i].type, TypeKind.Generic)); 258 | 259 | if (info && options.useConstraints) { 260 | constraints.add(info.asTypeConstraint(types[i].type)); 261 | } 262 | } else if (info) { 263 | this.addType(type, info); 264 | } 265 | } 266 | } 267 | 268 | return [types.filter(type => type), constraints]; 269 | } 270 | 271 | // `createConstraints` is one of two functions that updates the type 272 | // arguments map with the type arguments of a declaration. 273 | // 274 | // This function actually takes in a list of declarations rather than a 275 | // single declaration, this is to handle scattered interface declarations 276 | // for the same type having mismatched type parmaters. 277 | // 278 | // This function is called when generating the non-generic versions of 279 | // generic declarations, eg. `Array` instead of `TArray`. It is much 280 | // simpler than `createParameters`. We do not create any named types for 281 | // the type parameters because the non-generic versions of classes do not 282 | // have type parameters. 283 | // 284 | // If the type argument has no constraints, we are forced to use `any`. But 285 | // if the type argument does have constraints, we can use the constraint as 286 | // as the type argument. 287 | // 288 | // For example, for this typescript declaration: 289 | // ``` 290 | // declare interface NodeListOf { 291 | // item(index: number): T; 292 | // } 293 | // ``` 294 | // The non-generic C++ class looks like this: 295 | // ``` 296 | // class NodeListOf: public Object { 297 | // Node* item(double index); 298 | // }; 299 | // ``` 300 | // 301 | // Where `Node*` in the return type of `item` comes from the constraint on 302 | // `T` in the typescript declaration. 303 | public createConstraints(parser: Parser, declarations: ReadonlyArray): void { 304 | for (const declaration of declarations) { 305 | const typeParameters = ts.getEffectiveTypeParameterDeclarations(declaration); 306 | 307 | for (const typeParameter of typeParameters) { 308 | const type = parser.getTypeAtLocation(typeParameter); 309 | const constraint = ts.getEffectiveConstraintOfTypeParameter(typeParameter); 310 | const info = constraint && parser.getTypeNodeInfo(constraint, this); 311 | 312 | if (info && !this.types?.get(type)) { 313 | this.addType(type, info); 314 | } 315 | } 316 | } 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /src/declaration/declaration.ts: -------------------------------------------------------------------------------- 1 | import { State, Dependencies, ReasonKind, ResolverContext } from "../target.js"; 2 | import { Namespace } from "./namespace.js"; 3 | import { Writer } from "../writer.js"; 4 | import { Type } from "../type/type.js"; 5 | import { removeDuplicateExpressions } from "../type/expression.js"; 6 | import type { Target } from "../target.js"; 7 | import * as ts from "typescript"; 8 | 9 | export class ReferenceData { 10 | // When a member in a class references another declaration, we say that the 11 | // declaration is referenced *in* the class, and that it is referenced *by* 12 | // the member. `referencedBy` and `referencedIn` can be the same when the 13 | // reference comes from the class itself, for example as a base class. 14 | // 15 | // Example: 16 | // ``` 17 | // class ReferencedIn { 18 | // class Declaration; 19 | // Declaration* referencedBy(); 20 | // }; 21 | private readonly referencedBy: Declaration; 22 | private readonly referencedIn: Declaration; 23 | private readonly reasonKind: ReasonKind; 24 | 25 | public constructor(referencedBy: Declaration, referencedIn: Declaration, reasonKind: ReasonKind) { 26 | this.referencedBy = referencedBy; 27 | this.referencedIn = referencedIn; 28 | this.reasonKind = reasonKind; 29 | } 30 | 31 | public getReferencedBy(): Declaration { 32 | return this.referencedBy; 33 | } 34 | 35 | public getReferencedIn(): Declaration { 36 | return this.referencedIn; 37 | } 38 | 39 | public getReasonKind(): ReasonKind { 40 | return this.reasonKind; 41 | } 42 | } 43 | 44 | // `Declaration` is the base class for all types of declarations. Declarations 45 | // form an AST-like tree structure that closely resembles the generated C++ 46 | // code. Note that namespaces are not seen as declarations, see 47 | // "src/declaration/namespace.ts" for more information about how namespaces 48 | // are stored. 49 | // 50 | // Like namespaces, `Declaration` does not store its own children, but it does 51 | // provide an interface for querying the children. Currently the only type of 52 | // declaration with children is `Class` (in "src/declaration/class.ts"). 53 | export abstract class Declaration extends Namespace { 54 | private referenceData?: ReferenceData; 55 | private id: number; 56 | private file?: string; 57 | 58 | // If `lean` is false, this declaration will be wrapped in an 59 | // `#ifndef LEAN_CXX_LIB` block. 60 | private lean: boolean = true; 61 | 62 | private static count: number = 0; 63 | 64 | public static getCount(): number { 65 | return Declaration.count; 66 | } 67 | 68 | public constructor(name: string, parent?: Namespace) { 69 | super(name, parent); 70 | this.id = Declaration.count++; 71 | } 72 | 73 | public getId(): number { 74 | return this.id; 75 | } 76 | 77 | public getFile(): string | undefined { 78 | return this.file; 79 | } 80 | 81 | public setFile(file: string): void { 82 | this.file = file; 83 | } 84 | 85 | public setDeclaration(declaration: ts.Node): void { 86 | this.file = declaration.getSourceFile().fileName; 87 | } 88 | 89 | public isLean(): boolean { 90 | return this.lean; 91 | } 92 | 93 | public setLean(lean: boolean): void { 94 | this.lean = lean; 95 | } 96 | 97 | // Return the first parent that is not a declaration. 98 | public getNamespace(): Namespace | undefined { 99 | const parent = this.getParent(); 100 | return parent instanceof Declaration ? parent.getNamespace() : parent; 101 | } 102 | 103 | // Return the parent, but only if it is a declaration. 104 | public getParentDeclaration(): Declaration | undefined { 105 | const parent = this.getParent(); 106 | return parent instanceof Declaration ? parent : undefined; 107 | } 108 | 109 | public isReferenced(): boolean { 110 | return this.referenceData !== undefined; 111 | } 112 | 113 | public getReferenceData(): ReferenceData | undefined { 114 | return this.referenceData; 115 | } 116 | 117 | // This function computes internal references. An internal reference is 118 | // when a child of this declaration references (read: depends on) another 119 | // child of this declaration. We also handle indirect internal references. 120 | // An indirect reference is when a declaration references another 121 | // declaration, which in turn references another declaration, and so on. 122 | // 123 | // This information is used when resolving dependencies (see 124 | // "src/target.ts"), to determine if an inner class needs a complete 125 | // declaration because one of its members is referenced internally. 126 | // It is also used for printing dependency cycle error messages (see 127 | // "src/error.ts"). 128 | public computeReferences(rootParam?: Declaration): void { 129 | // The `root` node is used when computing indirect references. It is 130 | // the declaration in which the original reference was made. An 131 | // internal indirect reference must not escape its root node, because 132 | // then it would not be internal. 133 | const root = rootParam ?? this; 134 | 135 | // 1. Visit references by this declaration, eg. base classes. 136 | for (const [declaration, dependency] of this.getDirectDependencies(State.Complete)) { 137 | const data = new ReferenceData(this, this, dependency.getReasonKind()); 138 | declaration.setReferenced(root, dependency.getState(), data); 139 | } 140 | 141 | // 2. Visit references by children, eg. method return types. 142 | for (const child of this.getChildren()) { 143 | for (const [declaration, dependency] of child.getDirectDependencies(State.Partial)) { 144 | const data = new ReferenceData(child, this, dependency.getReasonKind()); 145 | declaration.setReferenced(root, dependency.getState(), data); 146 | } 147 | } 148 | 149 | // 3. Also compute internal references of children. It is important 150 | // that this happens *after* computing indirect references of this 151 | // declaration, because these indirect references may reach further 152 | // (they have a higher root node), and we don't want them to be stopped 153 | // by the `!node.isReferenced()` check in `setReferenced`. 154 | for (const child of this.getChildren()) { 155 | if (!child.isReferenced()) { 156 | child.computeReferences(); 157 | } 158 | } 159 | } 160 | 161 | // When `computeReferences` finds a referenced declaration, it calls this 162 | // function, which will store the reference data and call 163 | // `computeReferences` again to handle indirect references. 164 | public setReferenced(root: Declaration, state: State, data: ReferenceData): void { 165 | // 1. Determine which declaration needs to be completely resolved for 166 | // the reference to be valid, if the reference needs only a partial 167 | // declaration, then the complete declaration of its parent is enough. 168 | let node = state === State.Complete ? this : this.getParentDeclaration(); 169 | 170 | // 2. Iterate over all parents of the node, until we reach the root 171 | // node. We stop at the root node because internal references should 172 | // not affect the world outside the declaration where they originated. 173 | for (; node && node.isDescendantOf(root); node = node.getParentDeclaration()) { 174 | if (!node.isReferenced()) { 175 | // 3. Store reference data in the referenced declaration. 176 | node.referenceData = data; 177 | 178 | // 4. Also compute internal references of the class we 179 | // referenced. This would otherwise also be done in 180 | // `computeReferences`, but here we keep the `root` value from 181 | // the current reference. This is important because an 182 | // indirect may also be the cause for needing a complete 183 | // declaration of an inner class. 184 | node.computeReferences(root); 185 | } 186 | } 187 | } 188 | 189 | // Return all external dependencies of this declaration, including 190 | // external dependencies from children. An external dependency is one that 191 | // reaches outside of this declaration, rather than referencing another 192 | // child of this declaration. 193 | public getDependencies(state: State): Dependencies { 194 | if (state === State.Complete) { 195 | return new Dependencies( 196 | this.getChildren() 197 | .map(child => Array.from(child.getDependencies(child.isReferenced() ? State.Complete : State.Partial))) 198 | .reduce((acc, dependencies) => acc.concat(dependencies), []) 199 | .filter(([declaration, dependency]) => !declaration.isDescendantOf(this)) 200 | .concat([...this.getDirectDependencies(State.Complete)]) 201 | .filter(([declaration, dependency]) => declaration !== this || dependency.getState() !== State.Partial) 202 | ); 203 | } else { 204 | // The dependencies of a partial declaration do not include 205 | // dependencies of its children. 206 | return this.getDirectDependencies(State.Partial); 207 | } 208 | } 209 | 210 | // Returns all the types that are referenced by this declaration, 211 | // recursively, including template arguments, pointer element types, etc. 212 | // This is used by `removeUnusedTypeParameters`. 213 | public getReferencedTypes(): ReadonlyArray { 214 | return removeDuplicateExpressions( 215 | this.getChildren() 216 | .flatMap(child => [...child.getReferencedTypes()]) 217 | .concat([...this.getDirectReferencedTypes()]) 218 | ); 219 | } 220 | 221 | // Same as `writeImpl`, but also wraps the declaration in an 222 | // `#ifndef LEAN_CXX_LIB` block, if needed. 223 | public write(context: ResolverContext, writer: Writer, state: State, namespace?: Namespace): void { 224 | if (!this.lean) { 225 | writer.write("#ifndef LEAN_CXX_LIB", -Infinity); 226 | writer.writeLine(); 227 | } 228 | 229 | this.writeImpl(context, writer, state, namespace); 230 | 231 | if (!this.lean) { 232 | writer.write("#endif", -Infinity); 233 | writer.writeLine(); 234 | } 235 | } 236 | 237 | // Returns the maximum state of a declaration, this is only Complete for 238 | // class declarations, and Partial for every other declaration, where a 239 | // forward declaration is all we ever generate. 240 | public abstract maxState(): State; 241 | 242 | // Returns the children (class members) of this declaration. 243 | public abstract getChildren(): ReadonlyArray; 244 | 245 | // The *direct* dependencies of a declaration do not include dependencies 246 | // of its children, this function is called by `getDependencies` and should 247 | // rarely be used otherwise. 248 | protected abstract getDirectDependencies(state: State): Dependencies; 249 | 250 | // The *direct* referenced types of a declaration do not include types 251 | // referenced by its children, this function is called by 252 | // `getReferencedTypes` and should rarely be used otherwise. 253 | protected abstract getDirectReferencedTypes(): ReadonlyArray; 254 | 255 | // Write this declaration to a file. If `state` is Partial, only generate 256 | // a forward declaration. The `namespace` is the namespace in which the 257 | // declaration is being written, and can be used to abbreviate class 258 | // paths. The `context` is passed because class declarations need to 259 | // construct a `DependencyResolver` to generate their members in the 260 | // correct order. 261 | protected abstract writeImpl(context: ResolverContext, writer: Writer, state: State, namespace?: Namespace): void; 262 | 263 | // Merge this declaration with another declaration, this is used by 264 | // `mergeDuplicateDeclarations` to remove duplicate declarations and avoid 265 | // ambiguous overloads. 266 | // 267 | // The return value indicates whether the declaration could be merged. 268 | // Subclasses should return true if the declaration was succesfully merged, 269 | // indicating that the other declaration can be removed. And should return 270 | // false if the declaration could not be merged, and the other declaration 271 | // will stay. 272 | public merge(other: Declaration): boolean { 273 | return false; 274 | } 275 | 276 | public isGeneric(): boolean { 277 | return false; 278 | } 279 | } 280 | 281 | // For each declaration, try to merge it with every previous declaration with 282 | // the same name, until one is found where the merge was successful. 283 | // 284 | // If the merge was successful, the declaration is not removed from the list of 285 | // declarations. If the declaration could not be merged into any previous 286 | // declaration, or there were no previous declarations, the declaration is 287 | // added to the list of declarations. 288 | // 289 | // The actual merging behaviour is implemented in the `merge` function in 290 | // subclasses of `Declaration`. 291 | export function mergeDuplicateDeclarations(targets: ReadonlyArray): ReadonlyArray { 292 | const map = new Map; 293 | 294 | for (const target of targets) { 295 | const declaration = target.getDeclaration(); 296 | const path = declaration.getPath(); 297 | let array = map.get(path); 298 | let merged = false; 299 | 300 | if (array === undefined) { 301 | array = new Array; 302 | map.set(path, array); 303 | } 304 | 305 | for (const other of array) { 306 | if (other.getDeclaration().merge(declaration)) { 307 | merged = true; 308 | break; 309 | } 310 | } 311 | 312 | if (!merged) { 313 | array.push(target); 314 | } 315 | } 316 | 317 | return [...map.values()].flat(); 318 | } 319 | -------------------------------------------------------------------------------- /src/declaration/class.ts: -------------------------------------------------------------------------------- 1 | import { Namespace, Flags } from "./namespace.js"; 2 | import { Declaration, mergeDuplicateDeclarations } from "./declaration.js"; 3 | import { TemplateDeclaration } from "./templateDeclaration.js"; 4 | import { State, Target, Dependency, ReasonKind, Dependencies, ResolverContext, resolveDependencies } from "../target.js"; 5 | import { Expression } from "../type/expression.js"; 6 | import { Type } from "../type/type.js"; 7 | import { DeclaredType } from "../type/declaredType.js"; 8 | import { TemplateType } from "../type/templateType.js"; 9 | import { Function } from "./function.js"; 10 | import { Writer } from "../writer.js"; 11 | import { options } from "../utility.js"; 12 | 13 | export enum Visibility { 14 | Public, 15 | Protected, 16 | Private, 17 | } 18 | 19 | export const VISIBILITY_STRINGS = { 20 | [Visibility.Public]: "public", 21 | [Visibility.Protected]: "protected", 22 | [Visibility.Private]: "private", 23 | }; 24 | 25 | export class Member implements Target { 26 | private readonly declaration: Declaration; 27 | private readonly visibility: Visibility; 28 | 29 | public constructor(declaration: Declaration, visibility: Visibility) { 30 | this.declaration = declaration; 31 | this.visibility = visibility; 32 | } 33 | 34 | public getDeclaration(): Declaration { 35 | return this.declaration; 36 | } 37 | 38 | public getVisibility(): Visibility { 39 | return this.visibility; 40 | } 41 | 42 | public getTargetState(): State { 43 | return this.declaration.isReferenced() ? State.Complete : State.Partial; 44 | } 45 | } 46 | 47 | export class Base { 48 | private readonly type: Type; 49 | private readonly visibility: Visibility; 50 | private virtual: boolean; 51 | 52 | public constructor(type: Type, visibility: Visibility, virtual: boolean = false) { 53 | this.type = type; 54 | this.visibility = visibility; 55 | this.virtual = virtual; 56 | } 57 | 58 | public getType(): Type { 59 | return this.type; 60 | } 61 | 62 | // Returns the base type without any template parameters. 63 | public getInnerType(): Type { 64 | let type = this.type; 65 | 66 | while (type instanceof TemplateType) { 67 | type = type.getInner(); 68 | } 69 | 70 | return type; 71 | } 72 | 73 | public getVisibility(): Visibility { 74 | return this.visibility; 75 | } 76 | 77 | public isVirtual(): boolean { 78 | return this.virtual; 79 | } 80 | 81 | public setVirtual(virtual: boolean): void { 82 | this.virtual = virtual; 83 | } 84 | } 85 | 86 | export class Class extends TemplateDeclaration { 87 | private members?: Array; 88 | private bases?: Array; 89 | 90 | // Class constraints are written using `static_assert`. 91 | private constraints?: Array; 92 | 93 | // Using declarations are required because method overloads can shadow 94 | // methods from the base class. 95 | private usingDeclarations?: Set; 96 | 97 | public getMembers(): ReadonlyArray { 98 | return this.members ?? []; 99 | } 100 | 101 | public hasStaticMembers(): boolean { 102 | return this.getMembers().some(member => member.getDeclaration().getFlags() & Flags.Static); 103 | } 104 | 105 | public hasConstructor(): boolean { 106 | return this.getMembers().some(member => member.getDeclaration().getName() === this.getName()); 107 | } 108 | 109 | public addMember(declaration: Declaration, visibility: Visibility): void { 110 | // A method cannot have the same name as its parent class. 111 | // TODO: emit a warning, maybe? 112 | if (declaration instanceof Function || declaration.getName() !== this.getName()) { 113 | this.members ??= []; 114 | this.members.push(new Member(declaration, visibility)); 115 | } 116 | } 117 | 118 | public getBases(): ReadonlyArray { 119 | return this.bases ?? []; 120 | } 121 | 122 | // Add `type` as a base class with the given `visibility`. 123 | // 124 | // Two things of note: 125 | // - Any attempt to add the class itself as a base class is ignored. 126 | // - Any attempt to add a duplicate base class is ignored. 127 | public addBase(type: Type, visibility: Visibility): void { 128 | if (!(type instanceof DeclaredType) || type.getDeclaration() !== this) { 129 | this.bases ??= []; 130 | 131 | if (!this.bases.some(base => base.getType() === type)) { 132 | this.bases.push(new Base(type, visibility)); 133 | } 134 | } 135 | } 136 | 137 | public getConstraints(): ReadonlyArray { 138 | return this.constraints ?? []; 139 | } 140 | 141 | public hasConstraints(): boolean { 142 | return this.getConstraints().length > 0; 143 | } 144 | 145 | public addConstraint(expression: Expression): void { 146 | this.constraints ??= []; 147 | this.constraints.push(expression); 148 | } 149 | 150 | public getUsingDeclarations(): ReadonlySet { 151 | return this.usingDeclarations ?? new Set; 152 | } 153 | 154 | public addUsingDeclaration(usingDeclaration: string): void { 155 | this.usingDeclarations ??= new Set; 156 | this.usingDeclarations.add(usingDeclaration); 157 | } 158 | 159 | public removeDuplicates(): void { 160 | this.members && this.members.splice(0, this.members.length, ...mergeDuplicateDeclarations(this.members)); 161 | } 162 | 163 | public removeMember(name: string): void { 164 | this.members && this.members.splice(0, this.members.length, ...this.members.filter(member => member.getDeclaration().getName() !== name)); 165 | } 166 | 167 | public maxState(): State { 168 | return State.Complete; 169 | } 170 | 171 | public getChildren(): ReadonlyArray { 172 | return this.getMembers().map(member => member.getDeclaration()); 173 | } 174 | 175 | // The dependencies of a class are: 176 | // - partial for types used in any constraints on this class. 177 | // - complete for types used as base classes. 178 | // This function does *not* include dependencies of class members, to get 179 | // those as well, call `getDependencies`. 180 | protected getDirectDependencies(state: State): Dependencies { 181 | if (state === State.Complete) { 182 | const constraintReason = new Dependency(State.Partial, this, ReasonKind.Constraint); 183 | const baseReason = new Dependency(State.Complete, this, ReasonKind.BaseClass); 184 | 185 | return new Dependencies( 186 | this.getConstraints() 187 | .flatMap(constraint => [...constraint.getDependencies(constraintReason)]) 188 | .concat(this.getBases().flatMap(base => [...base.getType().getDependencies(baseReason)])) 189 | ); 190 | } else { 191 | return new Dependencies; 192 | } 193 | } 194 | 195 | protected getDirectReferencedTypes(): ReadonlyArray { 196 | return this.getConstraints() 197 | .concat(this.getBases().map(base => base.getType())) 198 | .flatMap(type => [...type.getReferencedTypes()]); 199 | } 200 | 201 | protected writeImpl(context: ResolverContext, writer: Writer, state: State, namespace?: Namespace): void { 202 | // 1. Write the template<...> line, if needed. 203 | this.writeTemplate(writer, state, namespace); 204 | 205 | // 2. Write the class keyword. 206 | writer.write("class"); 207 | 208 | // 3. Write attributes. 209 | this.writeAttributesOrSpace(writer); 210 | 211 | // 4. Write the name of the class. 212 | writer.write(this.getPath(namespace)); 213 | 214 | if (state === State.Complete) { 215 | let first = true; 216 | let visibility = Visibility.Private; 217 | 218 | // 5. Write base class specifiers. 219 | for (const base of this.getBases()) { 220 | writer.write(first ? ":" : ","); 221 | first = false; 222 | writer.writeSpace(false); 223 | const baseVisibility = base.getVisibility(); 224 | 225 | if (baseVisibility !== Visibility.Private) { 226 | writer.write(VISIBILITY_STRINGS[baseVisibility]); 227 | writer.writeSpace(); 228 | } 229 | 230 | if (base.isVirtual()) { 231 | writer.write("virtual"); 232 | writer.writeSpace(); 233 | } 234 | 235 | base.getType().write(writer, this.getParent()); 236 | } 237 | 238 | writer.writeBlockOpen(); 239 | 240 | // 6. Write class constraints. 241 | if (options.useConstraints) { 242 | for (const constraint of this.getConstraints()) { 243 | writer.write("static_assert("); 244 | constraint.write(writer, namespace); 245 | writer.write(");"); 246 | writer.writeLine(false); 247 | } 248 | } 249 | 250 | // 7. Write class members. 251 | resolveDependencies(context, this.getMembers(), (context, member, state) => { 252 | const memberVisibility = member.getVisibility(); 253 | 254 | if (memberVisibility !== visibility) { 255 | writer.write(VISIBILITY_STRINGS[memberVisibility], -1); 256 | writer.write(":"); 257 | writer.writeLine(false); 258 | visibility = memberVisibility; 259 | } 260 | 261 | member.getDeclaration().write(context, writer, state, this); 262 | }); 263 | 264 | // 8. Write "using" declarations. 265 | if (this.getUsingDeclarations().size > 0) { 266 | writer.write(VISIBILITY_STRINGS[Visibility.Public], -1); 267 | writer.write(":"); 268 | writer.writeLine(false); 269 | 270 | for (const declaration of this.getUsingDeclarations()) { 271 | writer.write("using"); 272 | writer.writeSpace(true); 273 | writer.write(declaration); 274 | writer.write(";"); 275 | writer.writeLine(false); 276 | } 277 | } 278 | 279 | writer.writeBlockClose(true); 280 | } else { 281 | writer.write(";"); 282 | writer.writeLine(false); 283 | } 284 | } 285 | 286 | // Recursively find all base types, and count how many times each one 287 | // occurs. 288 | private countRecursiveBases(map: Map): void { 289 | for (const base of this.getBases()) { 290 | const type = base.getInnerType(); 291 | const value = map.get(type) ?? 0; 292 | map.set(type, value + 1); 293 | 294 | if (value === 0 && type instanceof DeclaredType) { 295 | const declaration = type.getDeclaration(); 296 | 297 | if (declaration instanceof Class) { 298 | declaration.countRecursiveBases(map); 299 | } 300 | } 301 | } 302 | } 303 | 304 | // Returns all bases, along with their class declarations. 305 | private getBaseClasses(): ReadonlyArray<[Base, Class]> { 306 | return this.getBases() 307 | .map(base => [base, base.getInnerType()]) 308 | .filter(([base, type]) => type instanceof DeclaredType) 309 | .map(([base, type]) => [base, (type as DeclaredType).getDeclaration()]) 310 | .filter(([base, declaration]) => declaration instanceof Class) as [Base, Class][]; 311 | } 312 | 313 | public computeVirtualBaseClasses(keys?: ReadonlySet): void { 314 | if (!keys) { 315 | // 1. Count how many times each base class appears in the 316 | // inheritance tree, recursively. 317 | const map = new Map; 318 | this.countRecursiveBases(map); 319 | 320 | // 2. Filter out bases that only occur once, they do not need to be 321 | // virtual. 322 | keys = new Set( 323 | [...map.entries()] 324 | .filter(([key, count]) => count >= 2) 325 | .map(([key, count]) => key) 326 | ); 327 | } 328 | 329 | // 3. If any of our base classes occurs anywhere else in the 330 | // inheritance tree, mark it as virtual. 331 | for (const base of this.getBases()) { 332 | if (keys.has(base.getInnerType())) { 333 | base.setVirtual(true); 334 | } 335 | } 336 | 337 | // 4. Repeat step 3-4 for all base classes. 338 | for (const [base, declaration] of this.getBaseClasses()) { 339 | declaration.computeVirtualBaseClasses(keys); 340 | } 341 | } 342 | 343 | // For each base class, find all its members and add the base class name 344 | // to a set of which classes declare that member. 345 | private findRecursiveBaseMembers(map: Map>): void { 346 | for (const [base, declaration] of this.getBaseClasses()) { 347 | for (const member of declaration.getMembers()) { 348 | const declaration = member.getDeclaration(); 349 | 350 | if (member.getVisibility() === Visibility.Public) { 351 | const name = declaration.getName(); 352 | let set = map.get(name); 353 | 354 | if (!set) { 355 | set = new Set; 356 | map.set(name, set); 357 | } 358 | 359 | set.add(base.getType().toString()); 360 | } 361 | } 362 | 363 | declaration.findRecursiveBaseMembers(map); 364 | } 365 | } 366 | 367 | // Generate the list of `usingDeclarations`. 368 | public useBaseMembers(): void { 369 | // A list of which base members to generate using declarations for. 370 | // 371 | // TODO: This could be more generic, instead of hardcoding the list. 372 | const useBaseMembers = [ 373 | "operator[]", 374 | ]; 375 | 376 | // 1. Get the names of all declarations in all base classes, 377 | // recursively. 378 | const baseMembers = new Map; 379 | this.findRecursiveBaseMembers(baseMembers); 380 | 381 | // 2. Iterate over all members of this class 382 | for (const member of this.getMembers()) { 383 | const name = member.getDeclaration().getName(); 384 | const set = baseMembers.get(name); 385 | 386 | // 3. Add using declarations for all base classes that declare a 387 | // member that is in the useBaseMembers list. 388 | if (useBaseMembers.includes(name) && set) { 389 | for (const baseName of set) { 390 | this.addUsingDeclaration(`${baseName}::${name}`); 391 | } 392 | } 393 | } 394 | } 395 | } 396 | -------------------------------------------------------------------------------- /src/target.ts: -------------------------------------------------------------------------------- 1 | // This poorly named file has everything to do with writing the AST such that 2 | // declarations appear in the right order. 3 | // 4 | // It is not always sufficient to just forward declare every class. Sometimes 5 | // one declaration requires the complete definition of another declaration. For 6 | // example, in the case of inheritance. This is further complicated by nested 7 | // classes whose complete definition may depend on or be depended on my another 8 | // (possibly nested) class. 9 | // 10 | // The algorithm implemented here involves a recursive depth-first search 11 | // through the dependency graph of a declaration with some extra state to 12 | // detect dependency cycles, and making sure not to write the same declaration 13 | // twice. 14 | // 15 | // A lengthy description of what sort of dependency cycles we might encounter 16 | // is found in "src/error.ts". 17 | 18 | import { Declaration } from "./declaration/declaration.js"; 19 | import { options } from "./utility.js"; 20 | import { Function } from "./declaration/function.js"; 21 | import { Flags } from "./declaration/namespace.js"; 22 | 23 | // This enum represents how much of a declaration is written. 24 | // Partial: it is only forward declared (eg. "class Object;"). 25 | // Complete: it is completely defined (eg. "class Object {};"). 26 | export enum State { 27 | Partial, 28 | Complete, 29 | } 30 | 31 | export enum ReasonKind { 32 | BaseClass, 33 | VariableType, 34 | ReturnType, 35 | ParameterType, 36 | TypeAliasType, 37 | Constraint, 38 | Root, 39 | Inner, 40 | Member, 41 | Extra, 42 | } 43 | 44 | export interface Target { 45 | getDeclaration(): Declaration; 46 | getTargetState(): State; 47 | } 48 | 49 | export class Dependency { 50 | // Do we need a complete definition, or is a forward declaration enough? 51 | private readonly state: State; 52 | 53 | // What is the declaration that requires this dependency? 54 | // This is *NOT* the dependency itself, it is the *dependent*. 55 | private readonly reasonDeclaration: Declaration; 56 | 57 | // Why is it required? 58 | private readonly reasonKind: ReasonKind; 59 | 60 | public constructor(state: State, reasonDeclaration: Declaration, reasonKind: ReasonKind) { 61 | this.state = state; 62 | this.reasonDeclaration = reasonDeclaration; 63 | this.reasonKind = reasonKind; 64 | } 65 | 66 | public getState(): State { 67 | return this.state; 68 | } 69 | 70 | public getReasonDeclaration(): Declaration { 71 | return this.reasonDeclaration; 72 | } 73 | 74 | public getReasonKind(): ReasonKind { 75 | return this.reasonKind; 76 | } 77 | 78 | public withState(state: State): Dependency { 79 | return new Dependency(state, this.reasonDeclaration, this.reasonKind); 80 | } 81 | } 82 | 83 | // A map of dependencies. In the case that one declaration is depended on 84 | // multiple times, the `add` function only stores the one with higher 85 | // completion state. 86 | export class Dependencies { 87 | private readonly map: Map = new Map; 88 | 89 | public constructor(entries?: ReadonlyArray<[Declaration, Dependency]>) { 90 | if (entries) { 91 | for (const [declaration, dependency] of entries) { 92 | this.add(declaration, dependency); 93 | } 94 | } 95 | } 96 | 97 | public add(declaration: Declaration, dependency: Dependency): void { 98 | const oldDependency = this.map.get(declaration); 99 | 100 | if (!oldDependency || dependency.getState() > oldDependency.getState()) { 101 | this.map.set(declaration, dependency); 102 | } 103 | } 104 | 105 | public [Symbol.iterator](): IterableIterator<[Declaration, Dependency]> { 106 | return this.map[Symbol.iterator](); 107 | } 108 | } 109 | 110 | // See "src/error.ts". 111 | export class Reason { 112 | // What is the declaration that we were trying to resolve? 113 | private readonly declaration: Declaration; 114 | 115 | // How much of that declaration did we need? 116 | private readonly state: State; 117 | 118 | // In what way was the declaration referenced? 119 | private readonly kind: ReasonKind; 120 | 121 | // It was referenced by which other declaration? 122 | private readonly next?: Reason; 123 | 124 | public constructor(declaration: Declaration, state: State, kind: ReasonKind, next?: Reason) { 125 | this.declaration = declaration; 126 | this.state = state; 127 | this.kind = kind; 128 | this.next = next; 129 | } 130 | 131 | public getDeclaration(): Declaration { 132 | return this.declaration; 133 | } 134 | 135 | public getState(): State { 136 | return this.state; 137 | } 138 | 139 | public getKind(): ReasonKind { 140 | return this.kind; 141 | } 142 | 143 | public getNext(): Reason | undefined { 144 | return this.next; 145 | } 146 | } 147 | 148 | export type ResolveFunction = (context: ResolverContext, dependency: T, state: State) => void; 149 | 150 | // The `ResolverContext` stores state that is persistent across multiple 151 | // instances of `DependencyResolver`. 152 | export class ResolverContext { 153 | // To which state is a particular declaration already resolved? 154 | // 155 | // This is used to implement a DFS search in `resolveDependency`, but 156 | // without visiting already-visited nodes. 157 | private readonly state: Map = new Map; 158 | 159 | public isResolved(declaration: Declaration, state: State): boolean { 160 | const declarationState = this.state.get(declaration); 161 | return declarationState !== undefined && declarationState >= state; 162 | } 163 | 164 | public getState(declaration: Declaration): State | undefined { 165 | return this.state.get(declaration); 166 | } 167 | 168 | public setState(declaration: Declaration, state: State): void { 169 | this.state.set(declaration, state); 170 | } 171 | } 172 | 173 | // This class stores the state required for the dependency resolution 174 | // algorithm. It is generic so it can be used for types that wrap 175 | // `Declaration`, for example the `Member` class in "src/declaration/class.ts". 176 | // 177 | // A separate `DependencyResolver` instance exists for every class to resolve 178 | // internal dependencies inside of that class. 179 | class DependencyResolver { 180 | private readonly context: ResolverContext; 181 | private readonly targets: Map; 182 | private readonly pending: Map> = new Map; 183 | private readonly resolve: ResolveFunction; 184 | 185 | public constructor(context: ResolverContext, targets: ReadonlyArray, resolve: ResolveFunction) { 186 | this.context = context; 187 | this.targets = new Map(targets.map(target => [target.getDeclaration(), target])); 188 | this.resolve = resolve; 189 | } 190 | 191 | private resolveDependency(declaration: Declaration, target: T, state: State, kind: ReasonKind, reason?: Reason): void { 192 | const parentDeclaration = declaration.getParentDeclaration(); 193 | const newReason = new Reason(declaration, state, kind, reason); 194 | 195 | // 1. If this is an inner class, we must first resolve its parent. 196 | // 197 | // For example, this is an error: 198 | // ``` 199 | // class Outer::Inner {}; // Error: `Outer` has not yet been defined. 200 | // class Outer { class Inner; }; 201 | // ``` 202 | if (parentDeclaration) { 203 | const parentTarget = this.targets.get(parentDeclaration); 204 | 205 | if (parentTarget) { 206 | this.resolveDependency(parentDeclaration, parentTarget, State.Complete, ReasonKind.Inner, newReason); 207 | } 208 | } 209 | 210 | if (!this.context.isResolved(declaration, state)) { 211 | let pendingStates = this.pending.get(declaration); 212 | 213 | if (!pendingStates) { 214 | pendingStates = new Array; 215 | this.pending.set(declaration, pendingStates); 216 | } 217 | 218 | const pendingState = pendingStates[pendingStates.length - 1]; 219 | 220 | // 2. Check for dependency cycles. 221 | // 222 | // This is done using a map of stacks for each declaration. Each 223 | // stack stores to which completion state we were trying to resolve 224 | // the declaration. 225 | // 226 | // If the stack is non-empty, it means we have seen this 227 | // declaration before, and we must be resolving it again because 228 | // it depends on itself. 229 | // 230 | // If the current target state is *less than* the pending state, 231 | // this is ok. For example, a Complete declaration may depend on 232 | // the Partial declaration of itself. 233 | // 234 | // If the current target state is *greater than or equal to* the 235 | // pending state, this is an error. A declaration may not depend 236 | // on an equivalent or greater completion state of itself. 237 | if (pendingState !== undefined && state >= pendingState) { 238 | if (options.ignoreErrors) { 239 | this.resolve(this.context, target, state); 240 | this.context.setState(declaration, state); 241 | return; 242 | } else { 243 | throw newReason; 244 | } 245 | } 246 | 247 | pendingStates.push(state); 248 | 249 | try { 250 | // 3. Iterate over all dependencies and resolve them first. 251 | // 252 | // When running dependency resolution on the members of a 253 | // class, it sometimes happens that the declaration we depend 254 | // on is not in the list of targets. This can happen for one 255 | // of two reasons. 256 | // 257 | // First, the dependency could be an "uncle" (sibling of a 258 | // parent) of the dependent. These kind of dependencies are 259 | // also considered dependencies of the parent (by 260 | // `getDependencies`). So as we are resolving one of the 261 | // parent's children, the uncle dependency must already have 262 | // been resolved. 263 | // 264 | // Example of an uncle dependency, note that `Dependency` is 265 | // not in the list of targets when generating `Parent` because 266 | // it is outside of the `Parent` class: 267 | // ``` 268 | // class Dependency; 269 | // class Parent { 270 | // Dependency* dependent(); 271 | // }; 272 | // ``` 273 | // 274 | // Second, the dependency could be a "nephew" (child of a 275 | // sibling) of the dependent. In this case we must find which 276 | // sibling the dependency is a child of and completely resolve 277 | // that sibling first. The complete resolution of that sibling 278 | // will imply the resolution of the nephew dependency. 279 | // 280 | // Example of a nephew dependency, note that `Dependency` is 281 | // not in the list of targets when generating `Parent` because 282 | // it is nested inside of the `Sibling` class: 283 | // ``` 284 | // class Parent { 285 | // class Sibling { 286 | // class Dependency; 287 | // }; 288 | // Sibling::Dependency* dependent(); 289 | // }; 290 | // ``` 291 | // 292 | // There is a tricky case where the dependency is a grandchild 293 | // of one of the siblings of the dependent. The complete 294 | // resolution of the sibling may only partially resolve its 295 | // child, and not resolve the grandchild at all! This is solved 296 | // because declarations store how they are referenced 297 | // internally, and when a class detects that one of its 298 | // children is referenced in this way, then it knows that the 299 | // resolution of that child must always be complete. 300 | // 301 | // Example of a tricky nephew dependency: 302 | // ``` 303 | // class Parent { 304 | // class Sibling { 305 | // class Nephew { 306 | // class Dependency; 307 | // }; 308 | // }; 309 | // Sibling::Nephew::Dependency* dependent(); 310 | // }; 311 | // ``` 312 | for (const [dependencyDeclaration, dependency] of declaration.getDependencies(state)) { 313 | let declaration: Declaration | undefined = dependencyDeclaration; 314 | let state = dependency.getState(); 315 | const dependencyReason = new Reason(dependency.getReasonDeclaration(), state, ReasonKind.Member, newReason); 316 | 317 | while (declaration) { 318 | const target = this.targets.get(declaration); 319 | 320 | if (target) { 321 | this.resolveDependency(declaration, target, state, dependency.getReasonKind(), dependencyReason); 322 | break; 323 | } 324 | 325 | declaration = declaration.getParentDeclaration(); 326 | state = State.Complete; 327 | } 328 | } 329 | 330 | // 4. Finally, after all dependencies have been resolved, we 331 | // resolve the original declaration. 332 | // 333 | // If, through a combination of recursion and magic, it turns 334 | // out that the declaration is already resolved to the required 335 | // completion state, then we don't need to do anything. 336 | if (!this.context.isResolved(declaration, state)) { 337 | this.resolve(this.context, target, state); 338 | this.context.setState(declaration, state); 339 | } 340 | } finally { 341 | pendingStates.pop(); 342 | } 343 | } 344 | } 345 | 346 | public resolveDependencies(): void { 347 | for (const [declaration, target] of this.targets) { 348 | this.resolveDependency(declaration, target, target.getTargetState(), ReasonKind.Root) 349 | } 350 | } 351 | } 352 | 353 | export function resolveDependencies(context: ResolverContext, targets: ReadonlyArray, resolve: ResolveFunction): void { 354 | new DependencyResolver(context, targets, resolve).resolveDependencies(); 355 | } 356 | --------------------------------------------------------------------------------