From f6caf807a02dddfe2931e2ffb03b49a36133a54d Mon Sep 17 00:00:00 2001 From: srydell Date: Tue, 29 Mar 2022 19:10:13 +0200 Subject: [PATCH 1/2] Improve readability of docs on mobile * Update Parser, frontend.{wasm,py} to latest --- CMakeLists.txt | 8 +- docs/ReleaseNotes/v0.6.0.md | 53 ++++++ .../docs/guides/translating_a_cpp_library.md | 93 ----------- docs/packaging/docs/index.md | 4 +- .../packaging/docs/webassembly/conversions.md | 1 + docs/packaging/docs/webassembly/examples.md | 151 ++++++++++++++++-- .../overloaded_functions_naming_convention.md | 2 +- .../webassembly/template_naming_convention.md | 2 +- docs/packaging/mkdocs.yml | 4 +- .../tolc_theme/css/tolc/markdown.css | 1 + 10 files changed, 202 insertions(+), 117 deletions(-) create mode 100644 docs/ReleaseNotes/v0.6.0.md delete mode 100644 docs/packaging/docs/guides/translating_a_cpp_library.md diff --git a/CMakeLists.txt b/CMakeLists.txt index e2168b1..edec55d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.17) project( tolc - VERSION 0.5.0 + VERSION 0.6.0 LANGUAGES CXX) configure_file(docs/ReleaseNotes/version.in @@ -31,17 +31,17 @@ include(${modules}/Sanitizers.cmake) include(${modules}/StaticAnalyzers.cmake) include(${modules}/GetFrontend.cmake) -get_frontend(NAME Frontend.py VERSION v0.5.0) +get_frontend(NAME Frontend.py VERSION v0.6.0) copy_frontend_docs(NAME Frontend.py SRC_DIR ${frontend.py_SOURCE_DIR} COPY_TO ${CMAKE_CURRENT_LIST_DIR}/docs/packaging/docs/python) -get_frontend(NAME Frontend.wasm VERSION v0.4.5) +get_frontend(NAME Frontend.wasm VERSION v0.5.0) copy_frontend_docs( NAME Frontend.wasm SRC_DIR ${frontend.wasm_SOURCE_DIR} COPY_TO ${CMAKE_CURRENT_LIST_DIR}/docs/packaging/docs/webassembly) include(${modules}/GetParser.cmake) -get_parser(VERSION v0.3.0) +get_parser(VERSION v0.4.1) # Set the include path for the system library in the variable # We are using the standard library shipped diff --git a/docs/ReleaseNotes/v0.6.0.md b/docs/ReleaseNotes/v0.6.0.md new file mode 100644 index 0000000..9438340 --- /dev/null +++ b/docs/ReleaseNotes/v0.6.0.md @@ -0,0 +1,53 @@ +# News # + +## Bindings ## + +### Python ### + +* Add support for transferring documentation from C++ namespaces to pybind11 modules + +* Add support for the following operators: + +| C++ operator | Python operator | +|:----------------- |:--------------------- | +| operator+ | \_\_add\_\_ | +| operator- | \_\_sub\_\_ | +| operator* | \_\_mul\_\_ | +| operator/ | \_\_truediv\_\_ | +| operator% | \_\_mod\_\_ | +| operator+= | \_\_iadd\_\_ | +| operator-= | \_\_isub\_\_ | +| operator*= | \_\_imul\_\_ | +| operator/= | \_\_itruediv\_\_ | +| operator%= | \_\_imod\_\_ | +| operator== | \_\_eq\_\_ | +| operator!= | \_\_ne\_\_ | +| operator> | \_\_gt\_\_ | +| operator>= | \_\_ge\_\_ | +| operator< | \_\_lt\_\_ | +| operator<= | \_\_ge\_\_ | +| operator[] | \_\_getitem\_\_ | +| operator() | \_\_call\_\_ | + + +Overloading is also supported. There are more extensive documentation in the examples section. + +* Added support for polymorphic classes + * Inherit from virtual C++ classes in python + * Override virtual C++ member functions in python + * Call functions via C++ base class on derived class from python + +* Improve readability of generated output + +### WebAssembly ### + +* Added support for inheriting from C++ classes from javascript +* Automatic downcasting +* Improved documentation +* Improved readability of output somewhat +* Supports raw pointers when necessary + * Trusts the C++ side to know when to deallocate + +## Minor ## + +* Improve accessibility of documentation site on mobile diff --git a/docs/packaging/docs/guides/translating_a_cpp_library.md b/docs/packaging/docs/guides/translating_a_cpp_library.md deleted file mode 100644 index 7d0d835..0000000 --- a/docs/packaging/docs/guides/translating_a_cpp_library.md +++ /dev/null @@ -1,93 +0,0 @@ -# Translating a C++ library # - -In this tutorial we are going to use `tolc` to create `python` bindings to the `C++` library [tolc-demo](https://github.com/Tolc-Software/tolc-demo). Start by cloning the library - -```shell -git clone https://github.com/Tolc-Software/tolc-demo.git -cd tolc-demo -``` - -In the [`include/Demo/demo.hpp`](https://github.com/Tolc-Software/tolc-demo/blob/main/include/Demo/demo.hpp) we can find the API that we want to use from `python` - -```cpp -// include/Demo/demo.hpp -#pragma once - -#include -#include -#include -#include - -namespace Demo { -int add(int a, int b); -int sum(std::vector const &numbers); -std::map merge(std::map const &m1, - std::map const &m2); -std::set difference(std::set const &s1, - std::set const &s2); -} -``` - -We can see that the library is built using `CMake` by the presence of the `CMakeLists.txt` file in the project root. Within this file, the library `Math` is defined - -```cmake -# CMakeLists.txt -add_library(Math src/Demo/demo.cpp) -target_include_directories(Math PUBLIC include) -``` - -Below this code we download and install `tolc` locally into the project from [the release page](https://github.com/Tolc-Software/tolc/releases/tag/main-release) - -```cmake -# Can be ["latest", "v0.2.0", ...] -set(tolc_version latest) -include(FetchContent) -FetchContent_Declare( - tolc_entry - URL https://github.com/Tolc-Software/tolc/releases/download/${tolc_version}/tolc-${CMAKE_HOST_SYSTEM_NAME}.tar.gz -) -FetchContent_Populate(tolc_entry) - -set(tolc_DIR ${tolc_entry_SOURCE_DIR}/lib/cmake/tolc) -find_package( - tolc - CONFIG - REQUIRED -) -``` - -After the call to `find_package` we are free to use the `CMake` functions available in the `tolc` installation. To create bindings for `Math` we have to call the `tolc_create_bindings` function - -```cmake -# CMakeLists.txt -tolc_create_bindings( - TARGET Math - LANGUAGE python - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/python-bindings) -``` - -This will read the public API of `Math`, create bindings to `python` and put them in `${CMAKE_CURRENT_BINARY_DIR}/python-bindings` (typically `build/python-bindings`). It also created the target `Math_python` (`_`) for the bindings, and is just a normal build target. Configuring the project - -```shell -cmake -S. -Bbuild -``` - -We can build the project - -```shell -cmake --build build -``` - -Now `tolc` has read the public API of `Math`, created a set of `python` bindings, and then we used our normal compiler to build a `CPython` extension. We can now find and use the extension from `build/tolc` - -```python -cd build/tolc -python3 ->>> import Math ->>> Math.Demo.merge({"tolc": 0}, {"demo": 1}) -{'demo': 1, 'tolc': 0} -``` - -We see that our `C++` namespaces translates to `python` submodules, and our `python` dictionaries cleanly translates into `C++`'s `std::map`. - -We can now call all the public `C++` functions of `Math` from `python`. diff --git a/docs/packaging/docs/index.md b/docs/packaging/docs/index.md index 5c3df60..576b981 100644 --- a/docs/packaging/docs/index.md +++ b/docs/packaging/docs/index.md @@ -10,5 +10,5 @@ From here you can try any of the supported languages: -* [Python](./python/introduction.md) -* [WebAssembly](./webassembly/introduction.md) +* [Python](./python/quickstart.md) +* [WebAssembly](./webassembly/quickstart.md) diff --git a/docs/packaging/docs/webassembly/conversions.md b/docs/packaging/docs/webassembly/conversions.md index 1219b01..5df11a8 100644 --- a/docs/packaging/docs/webassembly/conversions.md +++ b/docs/packaging/docs/webassembly/conversions.md @@ -14,6 +14,7 @@ Note that any restriction this poses only applies to the public interface of you | Class | Class | | Public function | Class function | | Private function | Not converted | +| Virtual function | Overridable function | | Static member function | Static class function | | Static member variable | Static member variable | | Public const member variable | Read only property | diff --git a/docs/packaging/docs/webassembly/examples.md b/docs/packaging/docs/webassembly/examples.md index 04a446b..641fc77 100644 --- a/docs/packaging/docs/webassembly/examples.md +++ b/docs/packaging/docs/webassembly/examples.md @@ -5,7 +5,7 @@ Each example is taken from the test suite for `Tolc` and, given that you use the To use `WebAssembly` from `javascript`, one has to load it in asynchronously. When using `Tolc` this is done with a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) on the `javascript` side. Each library named `MyLib` exports a `Promise` called `loadMyLib`, in every test the name is simply `m` for brevity. All tests use [`jest`](https://jestjs.io/), and the `javascript` test boiler plate is omitted: ```javascript -var loadm = require('./build/m'); +const loadm = require('./build/m'); test('Tolc Test', () => { loadm().then(m => { @@ -87,14 +87,14 @@ expect(m.WithConstructor.getStatic()).toBe(55); // Static variable expect(m.WithConstructor.i).toBe(5); -var withConstructor = new m.WithConstructor("Hello"); +const withConstructor = new m.WithConstructor("Hello"); expect(withConstructor.getS()).toBe("Hello"); // Classes need to be deleted manually withConstructor.delete(); // Const properties are read-only -var withMembers = new m.WithMembers(); +const withMembers = new m.WithMembers(); expect(withMembers.i).toBe(5); try { withMembers.i = 10; @@ -106,12 +106,12 @@ expect(withMembers.s).toBe("hello"); withMembers.delete(); // Public functions are available -var withFunction = new m.WithFunction(); +const withFunction = new m.WithFunction(); expect(withFunction.add(5, 10)).toBe(15); withFunction.delete(); // Cannot access private functions -var withPrivateFunction = new m.WithPrivateFunction(); +const withPrivateFunction = new m.WithPrivateFunction(); try { withPrivateFunction.multiply(5, 10); expect(true).toBe(false); @@ -121,12 +121,12 @@ try { withPrivateFunction.delete(); // Classes can be found under their namespace -var nested = new m.MyNamespace.Nested(); +const nested = new m.MyNamespace.Nested(); expect(nested.i).toBe(42); nested.delete(); // Ok to nest Enums within classes -var withEnum = new m.WithEnum(); +const withEnum = new m.WithEnum(); expect(withEnum.i).toBe(m.WithEnum.Instrument.Flute); withEnum.delete(); @@ -335,6 +335,129 @@ expect(m.MyLib.We.Are.Going.Pretty.Deep.meaningOfLife()).toBe('42'); +## Overriding virtual in javascript ## + + +```cpp + +#include + +class Animal { +public: + virtual ~Animal() { } + virtual std::string sound(int n_times, bool grumpy) = 0; +}; + +class Dog : public Animal { +public: + std::string sound(int n_times, bool grumpy) override { + if (grumpy) { + return "No."; + } + + std::string result; + for (int i = 0; i < n_times; ++i) { + result += "woof! "; + } + return result; + } +}; + +std::string call_sound(Animal *animal) { + return animal->sound(3, false); +} + +``` + + +```javascript + +const fido = new m.Dog(); +const grumpy = true; + +// Overloaded function in C++ +expect(fido.sound(1, grumpy)).toBe("No.") +expect(fido.sound(1, !grumpy)).toBe("woof! ") + +// Polymorphic function in C++ +expect(m.call_sound(fido)).toBe("woof! woof! woof! ") +fido.delete(); + +// Inherit from virtual C++ classes in javascript +const Cat = m.Animal.extend("Animal", { + // Override C++ function + sound: function(n_times, grumpy) { + return grumpy ? "No." : "meow! ".repeat(n_times); + }, +}); + +const whiskers = new Cat(); + +// Overloaded C++ function in javascript +expect(whiskers.sound(1, grumpy)).toBe("No.") +expect(whiskers.sound(1, !grumpy)).toBe("meow! ") + +// Polymorphic function in C++ called with javascript object +// Automatic downcasting +expect(m.call_sound(whiskers)).toBe("meow! meow! meow! ") + +whiskers.delete(); + +// Another way is to just provide what is needed +// to implement the Animal interface +const tiger = m.Animal.implement({ + // Put only the functions you want to implement here + sound: function(n_times, grumpy) { + return grumpy ? "No." : "roar! ".repeat(n_times); + }, +}); + +expect(tiger.sound(1, grumpy)).toBe("No.") +expect(tiger.sound(1, !grumpy)).toBe("roar! ") + +// Automatic downcasting works the same +expect(m.call_sound(tiger)).toBe("roar! roar! roar! ") + +tiger.delete(); + +``` + + + +## Simple inheritence ## + + +```cpp + +#include + +struct Pet { + Pet(const std::string &name) : name(name) { } + std::string name; +}; + +struct Dog : public Pet { + Dog(const std::string &name) : Pet(name) { } + std::string bark() const { return "woof!"; } +}; + +``` + + +```javascript + +const fido = new m.Dog("Fido"); + +// Inherits public properties +expect(fido.name).toBe("Fido") + +// But has its new functions +expect(fido.bark()).toBe("woof!") + +``` + + + ## Smart Pointers ## @@ -403,14 +526,14 @@ std::array getData2() { ```javascript -var data3 = m.getData3(); +const data3 = m.getData3(); // It's just a normal JS array expect(data3.length).toBe(3); expect(data3).toStrictEqual([0, 1, 2]); -var data2 = m.getData2(); +const data2 = m.getData2(); expect(data2.length).toBe(2); expect(data2).toStrictEqual([0, 1]); @@ -439,7 +562,7 @@ std::map getData() { ```javascript -var data = m.getData(); +const data = m.getData(); expect(data.size()).toBe(1); @@ -534,7 +657,7 @@ public: // Tuple converts from javascript array const myArray = ["Hello World", 42]; -var myClass = new m.MyClass(myArray); +const myClass = new m.MyClass(myArray); expect(myClass.getTuple()).toStrictEqual(myArray); // The array still need to match the underlying std::tuple structure @@ -548,7 +671,7 @@ try { myClass.delete(); // Can handle different Number types -var withFunction = new m.WithFunction(); +const withFunction = new m.WithFunction(); expect(withFunction.sum([1, 2, 3.3, 2.0])).toBeCloseTo(8.3, 5); withFunction.delete(); @@ -574,11 +697,11 @@ std::vector getData() { ```javascript -var data = m.getData(); +const data = m.getData(); expect(data.size()).toBe(3); -for (var i = 0; i < data.size(); i++) { +for (let i = 0; i < data.size(); i++) { expect(data.get(i)).toBe(i); } diff --git a/docs/packaging/docs/webassembly/overloaded_functions_naming_convention.md b/docs/packaging/docs/webassembly/overloaded_functions_naming_convention.md index e35a89b..f65e588 100644 --- a/docs/packaging/docs/webassembly/overloaded_functions_naming_convention.md +++ b/docs/packaging/docs/webassembly/overloaded_functions_naming_convention.md @@ -13,7 +13,7 @@ double f(int); The two functions will be available from `javascript` as `f` and `f_int` respectively. ```python -var loadMyLib = require('./myLib.js'); +const loadMyLib = require('./myLib.js'); loadMyLib().then(MyLib => { const result0 = MyLib.f(); diff --git a/docs/packaging/docs/webassembly/template_naming_convention.md b/docs/packaging/docs/webassembly/template_naming_convention.md index 279ea97..75e3f80 100644 --- a/docs/packaging/docs/webassembly/template_naming_convention.md +++ b/docs/packaging/docs/webassembly/template_naming_convention.md @@ -18,7 +18,7 @@ template class Example; The specialized `class` `Example` will be available from `javascript` as `Example_int`: ```javascript -var loadMyLib = require('./build/MyLib'); +const loadMyLib = require('./build/MyLib'); loadm().then(MyLib => { example = new MyLib.Example_int(); diff --git a/docs/packaging/mkdocs.yml b/docs/packaging/mkdocs.yml index 4eb5f8c..e09ae40 100644 --- a/docs/packaging/mkdocs.yml +++ b/docs/packaging/mkdocs.yml @@ -8,12 +8,12 @@ nav: - Installing: 'installing.md' - Usage: 'usage.md' - Python Interface: - - Introduction: 'python/introduction.md' + - Quickstart: 'python/quickstart.md' - Examples: 'python/examples.md' - Conversions: 'python/conversions.md' - Template Naming Convention: 'python/template_naming_convention.md' - WebAssembly Interface: - - Introduction: 'webassembly/introduction.md' + - Quickstart: 'webassembly/quickstart.md' - Examples: 'webassembly/examples.md' - Conversions: 'webassembly/conversions.md' - CMake: diff --git a/docs/packaging/tolc_theme/css/tolc/markdown.css b/docs/packaging/tolc_theme/css/tolc/markdown.css index 1e2af85..aac3a00 100644 --- a/docs/packaging/tolc_theme/css/tolc/markdown.css +++ b/docs/packaging/tolc_theme/css/tolc/markdown.css @@ -106,6 +106,7 @@ #main-markdown p { hyphens: auto; + word-break: break-word; } #main-markdown p > a { From 257dfede9512738b2f3421271786ac14f949be95 Mon Sep 17 00:00:00 2001 From: srydell Date: Tue, 29 Mar 2022 19:52:40 +0200 Subject: [PATCH 2/2] Add a link to encourage users to submit bug reports on errors --- src/TolcInternal/run.cpp | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/TolcInternal/run.cpp b/src/TolcInternal/run.cpp index d08bb9e..1de6794 100644 --- a/src/TolcInternal/run.cpp +++ b/src/TolcInternal/run.cpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace TolcInternal { @@ -53,8 +54,36 @@ void writeFile(TolcInternal::Config const& config, } } +enum class ErrorOrigin { + Parser, + FrontendPy, + FrontendWasm, +}; + +ErrorOrigin getOrigin(Config::Language language) { + switch (language) { + case Config::Language::Python: return ErrorOrigin::FrontendPy; + case Config::Language::Wasm: return ErrorOrigin::FrontendWasm; + } + // Should never happen :) + return ErrorOrigin::Parser; +} + +void logError(ErrorOrigin origin) { + std::string repository; + switch (origin) { + case ErrorOrigin::Parser: repository = "Parser"; break; + case ErrorOrigin::FrontendPy: repository = "frontend.py"; break; + case ErrorOrigin::FrontendWasm: repository = "frontend.wasm"; break; + } + spdlog::error( + "If this error is something Tolc can solve, please open a feature request or a bug report here: https://github.com/Tolc-Software/{}/issues/new", + repository); +} + int run(int argc, const char** argv) { Log::Data logData {}; + spdlog::set_pattern("[Tolc][%l]: %v"); if (auto maybeResult = CommandLine::parse(argc, argv)) { auto cliResult = maybeResult.value(); @@ -81,7 +110,11 @@ int run(int argc, const char** argv) { logData.success = true; Log::logTimeTaken(logData); return 0; + } else { + logError(getOrigin(config.language)); } + } else { + logError(ErrorOrigin::Parser); } } }