├── 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 | 
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