diff --git a/docs/UsersManual.md b/docs/UsersManual.md
index bd4865f9..0c45a91a 100644
--- a/docs/UsersManual.md
+++ b/docs/UsersManual.md
@@ -39,6 +39,7 @@ There is also experimental support for these JavaScript-related targets:
- N-API (Node.js)
- QuickJS
- TypeScript
+- Emscripten
# 3. Native Targets
diff --git a/src/CLI/CLI.cs b/src/CLI/CLI.cs
index 54b2ed6c..b15b86b7 100644
--- a/src/CLI/CLI.cs
+++ b/src/CLI/CLI.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using CppSharp.Generators;
using Mono.Options;
namespace CppSharp
@@ -15,11 +16,11 @@ namespace CppSharp
{
var showHelp = false;
- optionSet.Add("I=", "the {PATH} of a folder to search for include files", (i) => { AddIncludeDirs(i, errorMessages); });
+ optionSet.Add("I=", "the {PATH} of a folder to search for include files", i => { AddIncludeDirs(i, errorMessages); });
optionSet.Add("l=", "{LIBRARY} that that contains the symbols of the generated code", l => options.Libraries.Add(l));
optionSet.Add("L=", "the {PATH} of a folder to search for additional libraries", l => options.LibraryDirs.Add(l));
optionSet.Add("D:", "additional define with (optional) value to add to be used while parsing the given header files", (n, v) => AddDefine(n, v, errorMessages));
- optionSet.Add("A=", "additional Clang arguments to pass to the compiler while parsing the given header files", (v) => AddArgument(v, errorMessages));
+ optionSet.Add("A=", "additional Clang arguments to pass to the compiler while parsing the given header files", v => AddArgument(v, errorMessages));
optionSet.Add("o=|output=", "the {PATH} for the generated bindings file (doesn't need the extension since it will depend on the generator)", v => HandleOutputArg(v, errorMessages));
optionSet.Add("on=|outputnamespace=", "the {NAMESPACE} that will be used for the generated code", on => options.OutputNamespace = on);
@@ -30,8 +31,8 @@ namespace CppSharp
optionSet.Add("d|debug", "enables debug mode which generates more verbose code to aid debugging", v => options.Debug = true);
optionSet.Add("c|compile", "enables automatic compilation of the generated code", v => options.Compile = true);
optionSet.Add("g=|gen=|generator=", "the {TYPE} of generated code: 'csharp' or 'cli' ('cli' supported only for Windows)", g => { GetGeneratorKind(g, errorMessages); });
- optionSet.Add("p=|platform=", "the {PLATFORM} that the generated code will target: 'win', 'osx' or 'linux'", p => { GetDestinationPlatform(p, errorMessages); });
- optionSet.Add("a=|arch=", "the {ARCHITECTURE} that the generated code will target: 'x86' or 'x64'", a => { GetDestinationArchitecture(a, errorMessages); });
+ optionSet.Add("p=|platform=", "the {PLATFORM} that the generated code will target: 'win', 'osx' or 'linux' or 'emscripten'", p => { GetDestinationPlatform(p, errorMessages); });
+ optionSet.Add("a=|arch=", "the {ARCHITECTURE} that the generated code will target: 'x86' or 'x64' or 'wasm32' or 'wasm64'", a => { GetDestinationArchitecture(a, errorMessages); });
optionSet.Add("prefix=", "sets a string prefix to the names of generated files", a => { options.Prefix = a; });
optionSet.Add("exceptions", "enables support for C++ exceptions in the parser", v => { options.EnableExceptions = true; });
@@ -208,26 +209,29 @@ namespace CppSharp
switch (generator.ToLower())
{
case "csharp":
- options.Kind = CppSharp.Generators.GeneratorKind.CSharp;
+ options.Kind = GeneratorKind.CSharp;
return;
case "cli":
- options.Kind = CppSharp.Generators.GeneratorKind.CLI;
+ options.Kind = GeneratorKind.CLI;
return;
case "c":
- options.Kind = CppSharp.Generators.GeneratorKind.C;
+ options.Kind = GeneratorKind.C;
return;
case "cpp":
- options.Kind = CppSharp.Generators.GeneratorKind.CPlusPlus;
+ options.Kind = GeneratorKind.CPlusPlus;
return;
case "napi":
- options.Kind = CppSharp.Generators.GeneratorKind.NAPI;
+ options.Kind = GeneratorKind.NAPI;
return;
case "qjs":
- options.Kind = CppSharp.Generators.GeneratorKind.QuickJS;
+ options.Kind = GeneratorKind.QuickJS;
return;
case "ts":
case "typescript":
- options.Kind = CppSharp.Generators.GeneratorKind.TypeScript;
+ options.Kind = GeneratorKind.TypeScript;
+ return;
+ case "emscripten":
+ options.Kind = GeneratorKind.Emscripten;
return;
}
@@ -247,6 +251,9 @@ namespace CppSharp
case "linux":
options.Platform = TargetPlatform.Linux;
return;
+ case "emscripten":
+ options.Platform = TargetPlatform.Emscripten;
+ return;
}
errorMessages.Add($"Unknown target platform: {platform}. Defaulting to {options.Platform}");
@@ -262,6 +269,12 @@ namespace CppSharp
case "x64":
options.Architecture = TargetArchitecture.x64;
return;
+ case "wasm32":
+ options.Architecture = TargetArchitecture.WASM32;
+ return;
+ case "wasm64":
+ options.Architecture = TargetArchitecture.WASM64;
+ return;
}
errorMessages.Add($"Unknown target architecture: {architecture}. Defaulting to {options.Architecture}");
@@ -286,7 +299,7 @@ namespace CppSharp
return;
}
- Generator gen = new Generator(options);
+ var gen = new Generator(options);
bool validOptions = gen.ValidateOptions(errorMessages);
PrintErrorMessages(errorMessages);
diff --git a/src/CLI/Generator.cs b/src/CLI/Generator.cs
index 5c6f72be..55b1c406 100644
--- a/src/CLI/Generator.cs
+++ b/src/CLI/Generator.cs
@@ -43,28 +43,51 @@ namespace CppSharp
{
var tripleBuilder = new StringBuilder();
- if (options.Architecture == TargetArchitecture.x64)
- tripleBuilder.Append("x86_64-");
- else if (options.Architecture == TargetArchitecture.x86)
- tripleBuilder.Append("i686-");
-
- if (options.Platform == TargetPlatform.Windows)
- {
- tripleBuilder.Append("pc-win32-msvc");
- abi = CppAbi.Microsoft;
- }
- else if (options.Platform == TargetPlatform.MacOS)
+ switch (options.Architecture)
{
- tripleBuilder.Append("apple-darwin12.4.0");
- abi = CppAbi.Itanium;
+ case TargetArchitecture.x64:
+ tripleBuilder.Append("x86_64-");
+ break;
+ case TargetArchitecture.x86:
+ tripleBuilder.Append("i686-");
+ break;
+ case TargetArchitecture.WASM32:
+ tripleBuilder.Append("wasm32-");
+ break;
+ case TargetArchitecture.WASM64:
+ tripleBuilder.Append("wasm64-");
+ break;
}
- else if (options.Platform == TargetPlatform.Linux)
- {
- tripleBuilder.Append("linux-gnu");
- abi = CppAbi.Itanium;
- if (options.Cpp11ABI)
- tripleBuilder.Append("-cxx11abi");
+ switch (options.Platform)
+ {
+ case TargetPlatform.Windows:
+ tripleBuilder.Append("pc-win32-msvc");
+ abi = CppAbi.Microsoft;
+ break;
+ case TargetPlatform.MacOS:
+ tripleBuilder.Append("apple-darwin12.4.0");
+ abi = CppAbi.Itanium;
+ break;
+ case TargetPlatform.Linux:
+ {
+ tripleBuilder.Append("linux-gnu");
+ abi = CppAbi.Itanium;
+
+ if (options.Cpp11ABI)
+ tripleBuilder.Append("-cxx11abi");
+ break;
+ }
+ case TargetPlatform.Emscripten:
+ {
+ if (options.Architecture != TargetArchitecture.WASM32 &&
+ options.Architecture != TargetArchitecture.WASM64)
+ throw new Exception("Emscripten target is only compatible with WASM architectures");
+
+ tripleBuilder.Append("unknown-emscripten");
+ abi = CppAbi.Itanium;
+ break;
+ }
}
triple = tripleBuilder.ToString();
diff --git a/src/CLI/Options.cs b/src/CLI/Options.cs
index f679287c..bbcb2f38 100644
--- a/src/CLI/Options.cs
+++ b/src/CLI/Options.cs
@@ -6,7 +6,9 @@ namespace CppSharp
enum TargetArchitecture
{
x86,
- x64
+ x64,
+ WASM32,
+ WASM64
}
class Options
diff --git a/src/Core/Platform.cs b/src/Core/Platform.cs
index d0500f29..90740dfc 100644
--- a/src/Core/Platform.cs
+++ b/src/Core/Platform.cs
@@ -11,7 +11,8 @@ namespace CppSharp
MacOS,
iOS,
WatchOS,
- TVOS
+ TVOS,
+ Emscripten,
}
public static class Platform
diff --git a/src/Generator/Driver.cs b/src/Generator/Driver.cs
index 3f9a847e..726cb7cd 100644
--- a/src/Generator/Driver.cs
+++ b/src/Generator/Driver.cs
@@ -8,6 +8,7 @@ using CppSharp.Generators.C;
using CppSharp.Generators.CLI;
using CppSharp.Generators.Cpp;
using CppSharp.Generators.CSharp;
+using CppSharp.Generators.Emscripten;
using CppSharp.Generators.TS;
using CppSharp.Parser;
using CppSharp.Passes;
@@ -43,6 +44,8 @@ namespace CppSharp
return new CLIGenerator(Context);
case GeneratorKind.CSharp:
return new CSharpGenerator(Context);
+ case GeneratorKind.Emscripten:
+ return new EmscriptenGenerator(Context);
case GeneratorKind.QuickJS:
return new QuickJSGenerator(Context);
case GeneratorKind.NAPI:
diff --git a/src/Generator/Generator.cs b/src/Generator/Generator.cs
index e9bcc3e8..cf414b48 100644
--- a/src/Generator/Generator.cs
+++ b/src/Generator/Generator.cs
@@ -14,6 +14,7 @@ namespace CppSharp.Generators
CSharp = 2,
C,
CPlusPlus,
+ Emscripten,
ObjectiveC,
Java,
Swift,
diff --git a/src/Generator/Generators/Emscripten/EmscriptenGenerator.cs b/src/Generator/Generators/Emscripten/EmscriptenGenerator.cs
new file mode 100644
index 00000000..f2f7e71a
--- /dev/null
+++ b/src/Generator/Generators/Emscripten/EmscriptenGenerator.cs
@@ -0,0 +1,73 @@
+using System.Collections.Generic;
+using System.Linq;
+using CppSharp.AST;
+using CppSharp.Generators.Cpp;
+
+namespace CppSharp.Generators.Emscripten
+{
+ ///
+ /// Emscripten generator responsible for driving the generation of binding files.
+ /// Embind documentation: https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html
+ ///
+ public class EmscriptenGenerator : CppGenerator
+ {
+ public EmscriptenGenerator(BindingContext context) : base(context)
+ {
+ }
+
+ public override List Generate()
+ {
+ var outputs = base.Generate();
+
+ foreach (var module in Context.Options.Modules)
+ {
+ if (module == Context.Options.SystemModule)
+ continue;
+
+ var output = GenerateModule(module);
+ if (output != null)
+ {
+ OnUnitGenerated(output);
+ outputs.Add(output);
+ }
+ }
+
+ return outputs;
+ }
+
+ public override List Generate(IEnumerable units)
+ {
+ var outputs = new List();
+
+ var header = new EmscriptenHeaders(Context, units);
+ outputs.Add(header);
+
+ var source = new EmscriptenSources(Context, units);
+ outputs.Add(source);
+
+ return outputs;
+ }
+
+ public override GeneratorOutput GenerateModule(Module module)
+ {
+ if (module == Context.Options.SystemModule)
+ return null;
+
+ var moduleGen = new EmscriptenModule(Context, module);
+
+ var output = new GeneratorOutput
+ {
+ TranslationUnit = new TranslationUnit
+ {
+ FilePath = $"{module.LibraryName}_embind_module.cpp",
+ Module = module
+ },
+ Outputs = new List { moduleGen }
+ };
+
+ output.Outputs[0].Process();
+
+ return output;
+ }
+ }
+}
diff --git a/src/Generator/Generators/Emscripten/EmscriptenHeaders.cs b/src/Generator/Generators/Emscripten/EmscriptenHeaders.cs
new file mode 100644
index 00000000..5d618e82
--- /dev/null
+++ b/src/Generator/Generators/Emscripten/EmscriptenHeaders.cs
@@ -0,0 +1,48 @@
+using System.Collections.Generic;
+using CppSharp.AST;
+
+namespace CppSharp.Generators.Emscripten
+{
+ ///
+ /// Generates Emscripten Embind C/C++ header files.
+ /// Embind documentation: https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html
+ ///
+ public class EmscriptenHeaders : EmscriptenCodeGenerator
+ {
+ public EmscriptenHeaders(BindingContext context, IEnumerable units)
+ : base(context, units)
+ {
+ CTypePrinter.PushContext(TypePrinterContextKind.Managed);
+ }
+
+ //public override bool ShouldGenerateNamespaces => false;
+
+ public override void Process()
+ {
+ GenerateFilePreamble(CommentKind.BCPL);
+
+ PushBlock(BlockKind.Includes);
+ WriteLine("#pragma once");
+ NewLine();
+ PopBlock();
+
+ var name = GetTranslationUnitName(TranslationUnit);
+ WriteLine($"extern \"C\" void embind_init_{name}();");
+ }
+
+ public override bool VisitClassDecl(Class @class)
+ {
+ return true;
+ }
+
+ public override bool VisitEvent(Event @event)
+ {
+ return true;
+ }
+
+ public override bool VisitFieldDecl(Field field)
+ {
+ return true;
+ }
+ }
+}
diff --git a/src/Generator/Generators/Emscripten/EmscriptenMarshal.cs b/src/Generator/Generators/Emscripten/EmscriptenMarshal.cs
new file mode 100644
index 00000000..0e05a740
--- /dev/null
+++ b/src/Generator/Generators/Emscripten/EmscriptenMarshal.cs
@@ -0,0 +1,21 @@
+using CppSharp.Generators.C;
+
+namespace CppSharp.Generators.Emscripten
+{
+ public class EmscriptenMarshalNativeToManagedPrinter : MarshalPrinter
+ {
+ public EmscriptenMarshalNativeToManagedPrinter(MarshalContext marshalContext)
+ : base(marshalContext)
+ {
+ }
+ }
+
+ public class EmscriptenMarshalManagedToNativePrinter : MarshalPrinter
+ {
+ public EmscriptenMarshalManagedToNativePrinter(MarshalContext ctx)
+ : base(ctx)
+ {
+ Context.MarshalToNative = this;
+ }
+ }
+}
diff --git a/src/Generator/Generators/Emscripten/EmscriptenModule.cs b/src/Generator/Generators/Emscripten/EmscriptenModule.cs
new file mode 100644
index 00000000..e92c4bcf
--- /dev/null
+++ b/src/Generator/Generators/Emscripten/EmscriptenModule.cs
@@ -0,0 +1,89 @@
+using System.IO;
+using System.Linq;
+using CppSharp.AST;
+using CppSharp.Generators.C;
+
+namespace CppSharp.Generators.Emscripten
+{
+ ///
+ /// Generates Emscripten module init files.
+ /// Embind documentation: https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html
+ ///
+ public class EmscriptenModule : EmscriptenCodeGenerator
+ {
+ private readonly Module module;
+
+ public EmscriptenModule(BindingContext context, Module module)
+ : base(context, module.Units.GetGenerated())
+ {
+ this.module = module;
+ }
+
+ public override string FileExtension { get; } = "cpp";
+
+ public override void Process()
+ {
+ GenerateFilePreamble(CommentKind.BCPL);
+ NewLine();
+
+ PushBlock(BlockKind.Includes);
+ {
+ WriteInclude(new CInclude()
+ {
+ File = "emscripten/bind.h",
+ Kind = CInclude.IncludeKind.Angled
+ });
+
+ foreach (var unit in TranslationUnits)
+ WriteInclude(GetIncludeFileName(Context, unit), CInclude.IncludeKind.Quoted);
+ }
+ PopBlock(NewLineKind.Always);
+
+ WriteLine("extern \"C\" {");
+ NewLine();
+
+ PushBlock(BlockKind.ForwardReferences);
+ {
+ foreach (var unit in TranslationUnits.Where(unit => unit.IsGenerated))
+ {
+ var name = GetTranslationUnitName(unit);
+ WriteLine($"void embind_init_{name}();");
+ }
+ }
+ PopBlock(NewLineKind.Always);
+
+ var moduleName = module.LibraryName;
+ WriteLine($"void embind_init_{moduleName}()");
+ WriteOpenBraceAndIndent();
+
+ foreach (var unit in TranslationUnits)
+ {
+ var name = GetTranslationUnitName(unit);
+ WriteLine($"embind_init_{name}();");
+ }
+
+ UnindentAndWriteCloseBrace();
+
+ NewLine();
+ WriteLine("}");
+ NewLine();
+
+ WriteLine($"static struct EmBindInit_{moduleName} : emscripten::internal::InitFunc {{");
+ WriteLineIndent($"EmBindInit_{moduleName}() : InitFunc(embind_init_{moduleName}) {{}}");
+ WriteLine($"}} EmBindInit_{moduleName}_instance;");
+ }
+
+ public static string GetIncludeFileName(BindingContext context, TranslationUnit unit)
+ {
+ // TODO: Replace with GetIncludePath
+ string file;
+ if (context.Options.GenerateName != null)
+ file = context.Options.GenerateName(unit);
+ else
+ file = Path.GetFileNameWithoutExtension(unit.FileName)
+ .Replace('\\', '/');
+
+ return $"{file}.h";
+ }
+ }
+}
diff --git a/src/Generator/Generators/Emscripten/EmscriptenSources.cs b/src/Generator/Generators/Emscripten/EmscriptenSources.cs
new file mode 100644
index 00000000..4e786b04
--- /dev/null
+++ b/src/Generator/Generators/Emscripten/EmscriptenSources.cs
@@ -0,0 +1,202 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using CppSharp.AST;
+using CppSharp.AST.Extensions;
+using CppSharp.Generators.C;
+using CppSharp.Generators.Cpp;
+
+namespace CppSharp.Generators.Emscripten
+{
+ public class EmscriptenCodeGenerator : MethodGroupCodeGenerator
+ {
+ protected EmscriptenCodeGenerator(BindingContext context, IEnumerable units)
+ : base(context, units)
+ {
+ }
+
+ public virtual MarshalPrinter GetMarshalManagedToNativePrinter(MarshalContext ctx)
+ {
+ return new EmscriptenMarshalManagedToNativePrinter(ctx);
+ }
+
+ public virtual MarshalPrinter GetMarshalNativeToManagedPrinter(MarshalContext ctx)
+ {
+ return new EmscriptenMarshalNativeToManagedPrinter(ctx);
+ }
+
+ public override bool VisitClassTemplateDecl(ClassTemplate template)
+ {
+ return true;
+ }
+
+ public override bool VisitFunctionTemplateDecl(FunctionTemplate template)
+ {
+ return true;
+ }
+ }
+
+ ///
+ /// Generates Emscripten C/C++ source files.
+ /// Embind documentation: https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html
+ ///
+ public class EmscriptenSources : EmscriptenCodeGenerator
+ {
+ public override string FileExtension => "cpp";
+
+ public EmscriptenSources(BindingContext context, IEnumerable units)
+ : base(context, units)
+ {
+ }
+
+ public override void Process()
+ {
+ GenerateFilePreamble(CommentKind.BCPL);
+
+ PushBlock(BlockKind.Includes);
+ {
+ WriteInclude(new CInclude()
+ {
+ File = "emscripten/bind.h",
+ Kind = CInclude.IncludeKind.Angled
+ });
+
+ foreach (var unit in TranslationUnits)
+ {
+ WriteInclude(unit.IncludePath, CInclude.IncludeKind.Angled);
+ }
+ }
+ PopBlock(NewLineKind.Always);
+
+ var name = GetTranslationUnitName(TranslationUnit);
+ WriteLine($"extern \"C\" void embind_init_{name}()");
+ WriteOpenBraceAndIndent();
+ VisitNamespace(TranslationUnit);
+ UnindentAndWriteCloseBrace();
+ }
+
+ public override bool VisitClassDecl(Class @class)
+ {
+ if (@class.IsIncomplete)
+ return true;
+
+ PushBlock();
+ Write($"emscripten::class_<{@class.QualifiedOriginalName}");
+ if (@class.HasBaseClass)
+ Write($", emscripten::base<{@class.BaseClass.QualifiedOriginalName}>");
+ WriteLine($">(\"{@class.Name}\")");
+
+ VisitClassDeclContext(@class);
+
+ WriteLineIndent(";");
+ PopBlock(NewLineKind.BeforeNextBlock);
+ return true;
+ }
+
+ public override void VisitClassConstructors(IEnumerable ctors)
+ {
+ var overloadCheck = new HashSet();
+ foreach (var ctor in ctors)
+ {
+ if (overloadCheck.Contains(ctor.Parameters.Count))
+ {
+ Console.WriteLine($"Ignoring overloaded ctor: {ctor.QualifiedOriginalName}");
+ continue;
+ }
+
+ var cppTypePrinter = new CppTypePrinter(Context)
+ {
+ PrintFlavorKind = CppTypePrintFlavorKind.Cpp
+ };
+ cppTypePrinter.PushContext(TypePrinterContextKind.Native);
+
+ var parameters = ctor.Parameters.Select(p => p.Type.Visit(cppTypePrinter).Type);
+ WriteLineIndent($".constructor<{string.Join(", ", parameters)}>()");
+
+ overloadCheck.Add(ctor.Parameters.Count);
+ }
+ }
+
+ public override bool VisitMethodDecl(Method method)
+ {
+ Indent();
+ var ret = VisitFunctionDecl(method);
+ Unindent();
+ return ret;
+ }
+
+ public override bool VisitFieldDecl(Field field)
+ {
+ WriteLineIndent($".field(\"{field.Name}\", &{field.Class.QualifiedOriginalName}::{field.OriginalName})");
+ return true;
+ }
+
+ public override void GenerateMethodGroup(List @group)
+ {
+ if (@group.Count > 1)
+ {
+ Console.WriteLine($"Ignoring method group: {@group.First().QualifiedOriginalName}");
+ return;
+ }
+
+ base.GenerateMethodGroup(@group);
+ }
+
+ public override void GenerateFunctionGroup(List @group)
+ {
+ if (@group.Count > 1)
+ {
+ Console.WriteLine($"Ignoring function group: {@group.First().QualifiedOriginalName}");
+ return;
+ }
+
+ base.GenerateFunctionGroup(@group);
+ }
+
+ public override void VisitDeclContextFunctions(DeclarationContext context)
+ {
+ PushBlock();
+ base.VisitDeclContextFunctions(context);
+ PopBlock(NewLineKind.BeforeNextBlock);
+ }
+
+ public override bool VisitFunctionDecl(Function function)
+ {
+ var prefix = function is Method ? ".function" : "emscripten::function";
+ Write($"{prefix}(\"{function.Name}\", &{function.QualifiedOriginalName}");
+
+ var hasPointers = function.ReturnType.Type.IsPointer() ||
+ function.Parameters.Any(p => p.Type.IsPointer());
+ if (hasPointers)
+ Write(", emscripten::allow_raw_pointers()");
+
+ WriteLine(function is not Method ? ");" : $")");
+ return true;
+ }
+
+ public override bool VisitEnumDecl(Enumeration @enum)
+ {
+ if (@enum.IsIncomplete)
+ return false;
+
+ PushBlock();
+
+ WriteLine($"emscripten::enum_<{@enum.Name}>(\"{@enum.QualifiedOriginalName}\")");
+ foreach (var item in @enum.Items)
+ {
+ WriteLineIndent(@enum.IsScoped
+ ? $".value(\"{item.Name}\", {@enum.QualifiedOriginalName}::{item.OriginalName})"
+ : $".value(\"{item.Name}\", {item.QualifiedOriginalName})");
+ }
+ WriteLineIndent(";");
+
+ PopBlock(NewLineKind.BeforeNextBlock);
+ return true;
+ }
+
+ public override bool VisitTypedefDecl(TypedefDecl typedef)
+ {
+ return true;
+ }
+ }
+}
diff --git a/src/Generator/Generators/Emscripten/EmscriptenTypePrinter.cs b/src/Generator/Generators/Emscripten/EmscriptenTypePrinter.cs
new file mode 100644
index 00000000..b5d5cd88
--- /dev/null
+++ b/src/Generator/Generators/Emscripten/EmscriptenTypePrinter.cs
@@ -0,0 +1,11 @@
+using CppSharp.Generators.C;
+
+namespace CppSharp.Generators.Emscripten
+{
+ public class EmscriptenTypePrinter : CppTypePrinter
+ {
+ public EmscriptenTypePrinter(BindingContext context) : base(context)
+ {
+ }
+ }
+}
diff --git a/src/Generator/Passes/CheckDuplicatedNamesPass.cs b/src/Generator/Passes/CheckDuplicatedNamesPass.cs
index 1ccefa42..0a2d7622 100644
--- a/src/Generator/Passes/CheckDuplicatedNamesPass.cs
+++ b/src/Generator/Passes/CheckDuplicatedNamesPass.cs
@@ -7,6 +7,7 @@ using CppSharp.Generators;
using CppSharp.Generators.C;
using CppSharp.Generators.CLI;
using CppSharp.Generators.CSharp;
+using CppSharp.Generators.Emscripten;
using CppSharp.Types;
namespace CppSharp.Passes
@@ -204,6 +205,9 @@ namespace CppSharp.Passes
case GeneratorKind.C:
typePrinter = new CppTypePrinter(Context) { PrintFlavorKind = CppTypePrintFlavorKind.C };
break;
+ case GeneratorKind.Emscripten:
+ typePrinter = new EmscriptenTypePrinter(Context);
+ break;;
case GeneratorKind.CPlusPlus:
case GeneratorKind.QuickJS:
case GeneratorKind.NAPI:
diff --git a/tests2/Builtins.h b/tests2/Builtins.h
index 3aa8015c..9b270e53 100644
--- a/tests2/Builtins.h
+++ b/tests2/Builtins.h
@@ -1,3 +1,4 @@
+#include
#include
void ReturnsVoid () { }
@@ -40,8 +41,10 @@ int16_t ReturnsInt16 () { return -5; }
uint16_t ReturnsUInt16 () { return 5; }
int32_t ReturnsInt32 () { return -5; }
uint32_t ReturnsUInt32 () { return 5; }
+#if !defined(__EMSCRIPTEN__)
int64_t ReturnsInt64 () { return -5; }
uint64_t ReturnsUInt64 () { return 5; }
+#endif
int8_t PassAndReturnsInt8 (int8_t v) { return v; }
uint8_t PassAndReturnsUInt8 (uint8_t v) { return v; }
diff --git a/tests2/emscripten/.gitignore b/tests2/emscripten/.gitignore
new file mode 100644
index 00000000..36316b01
--- /dev/null
+++ b/tests2/emscripten/.gitignore
@@ -0,0 +1,4 @@
+gen
+*.so
+*.dylib
+*.dll
diff --git a/tests2/emscripten/premake5.lua b/tests2/emscripten/premake5.lua
new file mode 100644
index 00000000..8fa7a738
--- /dev/null
+++ b/tests2/emscripten/premake5.lua
@@ -0,0 +1,21 @@
+
+workspace "emscripten"
+ configurations { "debug", "release" }
+ location "gen"
+ symbols "On"
+ optimize "Off"
+
+ project "test"
+ kind "SharedLib"
+ language "C++"
+ files
+ {
+ "gen/**.cpp",
+ }
+ includedirs
+ {
+ "..",
+ "../../include"
+ }
+ linkoptions { "--bind -sENVIRONMENT=node -sMODULARIZE=1 -sEXPORT_ALL -sEXPORT_ES6=1 -sUSE_ES6_IMPORT_META=1" }
+ targetextension ".mjs"
\ No newline at end of file
diff --git a/tests2/emscripten/test.mjs b/tests2/emscripten/test.mjs
new file mode 100644
index 00000000..95b8b0bf
--- /dev/null
+++ b/tests2/emscripten/test.mjs
@@ -0,0 +1,114 @@
+import wasmModule from "./gen/bin/debug/libtest.mjs";
+import { eq, ascii, floateq } from "./utils.mjs"
+
+const test = await wasmModule({
+ onRuntimeInitialized() {
+
+ }
+});
+
+function builtins() {
+ eq(test.ReturnsVoid(), undefined)
+
+ eq(test.ReturnsBool(), true)
+ eq(test.PassAndReturnsBool(false), false)
+
+ eq(test.ReturnsNullptr(), null)
+ eq(test.PassAndReturnsNullptr(null), null)
+
+ eq(test.ReturnsChar(), ascii('a'));
+ eq(test.ReturnsSChar(), ascii('a'));
+ eq(test.ReturnsUChar(), ascii('a'));
+
+ eq(test.PassAndReturnsChar(ascii('a')), ascii('a'));
+ eq(test.PassAndReturnsSChar(ascii('b')), ascii('b'));
+ eq(test.PassAndReturnsUChar(ascii('c')), ascii('c'));
+
+ // TODO: add wchar_t tests
+
+ eq(test.ReturnsFloat(), 5.0);
+ eq(test.ReturnsDouble(), -5.0);
+ //eq(test.ReturnsLongDouble(), -5.0);
+
+ floateq(test.PassAndReturnsFloat(1.32), 1.32);
+ floateq(test.PassAndReturnsDouble(1.32), 1.32);
+ //float(test.PassAndReturnsLongDouble(1.32), 1.32);
+
+ eq(test.ReturnsInt8(), -5);
+ eq(test.ReturnsUInt8(), 5);
+ eq(test.ReturnsInt16(), -5);
+ eq(test.ReturnsUInt16(), 5);
+ eq(test.ReturnsInt32(), -5);
+ eq(test.ReturnsUInt32(), 5);
+
+ // TODO:
+ // https://github.com/WebAssembly/proposals/issues/7
+ // https://github.com/emscripten-core/emscripten/issues/11140
+ //eq(test.ReturnsInt64(), -5n);
+ //eq(test.ReturnsUInt64(), 5n);
+
+ const int8 = { min: -(2 ** 7), max: (2 ** 7) - 1 };
+ eq(test.PassAndReturnsInt8(int8.min), int8.min);
+ eq(test.PassAndReturnsInt8(int8.max), int8.max);
+
+ const uint8 = { min: 0, max: (2 ** 8) - 1 };
+ eq(test.PassAndReturnsUInt8(uint8.min), uint8.min);
+ eq(test.PassAndReturnsUInt8(uint8.max), uint8.max);
+
+ const int16 = { min: -(2 ** 15), max: (2 ** 15) - 1 };
+ eq(test.PassAndReturnsInt16(int16.min), int16.min);
+ eq(test.PassAndReturnsInt16(int16.max), int16.max);
+
+ const uint16 = { min: 0, max: (2 ** 16) - 1 };
+ eq(test.PassAndReturnsUInt16(uint16.min), uint16.min);
+ eq(test.PassAndReturnsUInt16(uint16.max), uint16.max);
+
+ const int32 = { min: -(2 ** 31), max: (2 ** 31) - 1 };
+ eq(test.PassAndReturnsInt32(int32.min), int32.min);
+ eq(test.PassAndReturnsInt32(int32.max), int32.max);
+
+ const uint32 = { min: 0, max: (2 ** 32) - 1 };
+ eq(test.PassAndReturnsUInt32(uint32.min), uint32.min);
+ eq(test.PassAndReturnsUInt32(uint32.max), uint32.max);
+
+ //const int64 = { min: BigInt(2 ** 63) * -1n, max: BigInt(2 ** 63) - 1n };
+ //eq(test.PassAndReturnsInt64(int64.min), int64.min);
+ //eq(test.PassAndReturnsInt64(int64.max), int64.max);
+
+ //const uint64 = { min: BigInt(0), max: BigInt(2 ** 64) - 1n };
+ //eq(test.PassAndReturnsUInt64(uint64.min), uint64.min);
+ //eq(test.PassAndReturnsUInt64(uint64.max), uint64.max);
+}
+
+function enums() {
+ eq(test.Enum0.Item0.value, 0);
+ eq(test.Enum0.Item1.value, 1);
+ eq(test.Enum0.Item2.value, 5);
+
+ eq(test.ReturnsEnum(), test.Enum0.Item0);
+ eq(test.PassAndReturnsEnum(test.Enum0.Item1), test.Enum0.Item1);
+}
+
+function classes() {
+ var c = new test.Class();
+ eq(typeof (c), "object")
+ eq(c.ReturnsVoid(), undefined)
+ eq(c.ReturnsInt(), 0)
+ eq(c.PassAndReturnsClassPtr(null), null)
+
+ var c1 = new test.ClassWithSingleInheritance();
+ eq(c1.__proto__.constructor.name, 'ClassWithSingleInheritance')
+ eq(c1.__proto__.__proto__.constructor.name, 'Class')
+ eq(c1.ReturnsVoid(), undefined);
+ eq(c1.ReturnsInt(), 0);
+ eq(c1.ChildMethod(), 2);
+
+ var classWithField = new test.ClassWithField();
+ eq(classWithField.ReturnsField(), 10);
+ eq(classWithField.Field, 10);
+}
+
+
+builtins();
+enums();
+classes();
\ No newline at end of file
diff --git a/tests2/emscripten/test.sh b/tests2/emscripten/test.sh
new file mode 100755
index 00000000..52807e87
--- /dev/null
+++ b/tests2/emscripten/test.sh
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+set -e
+dir=$(cd "$(dirname "$0")"; pwd)
+rootdir="$dir/../.."
+dotnet_configuration=Release
+configuration=debug
+platform=x64
+jsinterp=node
+
+red=`tput setaf 1`
+green=`tput setaf 2`
+reset=`tput sgr0`
+
+generate=true
+
+if [ $generate = true ]; then
+ echo "${green}Generating bindings${reset}"
+ dotnet $rootdir/bin/${dotnet_configuration}_${platform}/CppSharp.CLI.dll \
+ --gen=emscripten --platform=emscripten --arch=wasm32 \
+ -I$dir/.. -I$rootdir/include -o $dir/gen -m tests $dir/../*.h
+fi
+
+echo "${green}Building generated binding files${reset}"
+premake=$rootdir/build/premake.sh
+config=$configuration $premake --file=$dir/premake5.lua gmake
+emmake make -C $dir/gen
+echo
+
+echo "${green}Executing JS tests with Node${reset}"
+$jsinterp $dir/test.mjs
\ No newline at end of file
diff --git a/tests2/emscripten/utils.mjs b/tests2/emscripten/utils.mjs
new file mode 100644
index 00000000..fb0eb6c7
--- /dev/null
+++ b/tests2/emscripten/utils.mjs
@@ -0,0 +1,21 @@
+export function assert(actual, expected, message) {
+ if (arguments.length == 1)
+ expected = true;
+
+ if (actual === expected)
+ return;
+
+ if (actual !== null && expected !== null
+ && typeof actual == 'object' && typeof expected == 'object'
+ && actual.toString() === expected.toString())
+ return;
+
+ throw Error("assertion failed: got |" + actual + "|" +
+ ", expected |" + expected + "|" +
+ (message ? " (" + message + ")" : ""));
+}
+
+export const eq = assert;
+export const floateq = (actual, expected) => { assert(Math.abs(actual - expected) < 0.01) }
+
+export const ascii = v => v.charCodeAt(0)
diff --git a/tests2/test.sh b/tests2/test.sh
index 622401ef..f5abc712 100755
--- a/tests2/test.sh
+++ b/tests2/test.sh
@@ -4,3 +4,4 @@ dir=$(cd "$(dirname "$0")"; pwd)
$dir/napi/test.sh
$dir/quickjs/test.sh
+$dir/emscripten/test.sh