Skip to content

Commit

Permalink
feat: emscripten support (ada-url#412)
Browse files Browse the repository at this point in the history
* emscripten support

* Trying to fix the new CI test

* Trying to add test

* Only preduce wasm.js when building for wasm

* Automating the tests.

* Fix.

* Adding tests

Co-authored-by: Moshe Atlow <[email protected]>

* Reformat

* Fixing ci.

* Removing dead code.

---------

Co-authored-by: Moshe Atlow <[email protected]>
  • Loading branch information
lemire and MoLow authored May 17, 2023
1 parent 5598049 commit beadd60
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 22 deletions.
39 changes: 39 additions & 0 deletions .github/workflows/emscripten.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: emscripten

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
paths-ignore:
- '**.md'
- 'docs/**'
push:
branches:
- main
paths-ignore:
- '**.md'
- 'docs/**'

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true


jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v3
- uses: mymindstorm/setup-emsdk@v12
- name: Verify
run: emcc -v
- name: Checkout
uses: actions/checkout@v3
- name: Configure
run: emcmake cmake -B buildwasm
- name: Build
run: cmake --build buildwasm
- name: Test
run: ctest --test-dir buildwasm
22 changes: 10 additions & 12 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,27 @@ if((BUILD_TESTING OR ADA_BENCHMARKS) AND (CMAKE_SIZEOF_VOID_P EQUAL 8))
add_dependency(gtest)
endif()

if (BUILD_TESTING)
if (BUILD_TESTING AND NOT EMSCRIPTEN)
message(STATUS "The tests are enabled.")
add_subdirectory(tests)
else()
if(is_top_project)
message(STATUS "The tests are disabled.")
endif()
endif(BUILD_TESTING)
endif(BUILD_TESTING AND NOT EMSCRIPTEN)

If(ADA_BENCHMARKS)
If(ADA_BENCHMARKS AND NOT EMSCRIPTEN)
message(STATUS "Ada benchmarks enabled.")
add_subdirectory(benchmarks)
else(ADA_BENCHMARKS)
else(ADA_BENCHMARKS AND NOT EMSCRIPTEN)
if(is_top_project)
message(STATUS "Ada benchmarks disabled. Set ADA_BENCHMARKS=ON to enable them.")
endif()
endif(ADA_BENCHMARKS)
endif(ADA_BENCHMARKS AND NOT EMSCRIPTEN)

if (BUILD_TESTING AND EMSCRIPTEN)
add_subdirectory(tests-wasm)
endif(BUILD_TESTING AND EMSCRIPTEN)

add_library(ada::ada ALIAS ada)

Expand All @@ -60,16 +64,10 @@ set_target_properties(
WINDOWS_EXPORT_ALL_SYMBOLS YES
)

include (TestBigEndian)
TEST_BIG_ENDIAN(IS_BIG_ENDIAN)
if(IS_BIG_ENDIAN)
message(STATUS "Big-endian system detected.")
endif()

include(CMakePackageConfigHelpers)
include(GNUInstallDirs)

if(NOT ADA_COVERAGE)
if(NOT ADA_COVERAGE AND NOT EMSCRIPTEN)
add_subdirectory(singleheader)
add_subdirectory(tools)
endif()
Expand Down
16 changes: 8 additions & 8 deletions src/helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ ada_really_inline size_t find_next_host_delimiter_special(
has_zero_byte(xor3) | has_zero_byte(xor4) |
has_zero_byte(xor5);
if (is_match) {
return i + index_of_first_set_byte(is_match);
return size_t(i + index_of_first_set_byte(is_match));
}
}
if (i < view.size()) {
Expand All @@ -256,7 +256,7 @@ ada_really_inline size_t find_next_host_delimiter_special(
has_zero_byte(xor3) | has_zero_byte(xor4) |
has_zero_byte(xor5);
if (is_match) {
return i + index_of_first_set_byte(is_match);
return size_t(i + index_of_first_set_byte(is_match));
}
}
return view.size();
Expand Down Expand Up @@ -300,7 +300,7 @@ ada_really_inline size_t find_next_host_delimiter(std::string_view view,
uint64_t is_match = has_zero_byte(xor1) | has_zero_byte(xor2) |
has_zero_byte(xor4) | has_zero_byte(xor5);
if (is_match) {
return i + index_of_first_set_byte(is_match);
return size_t(i + index_of_first_set_byte(is_match));
}
}
if (i < view.size()) {
Expand All @@ -318,7 +318,7 @@ ada_really_inline size_t find_next_host_delimiter(std::string_view view,
uint64_t is_match = has_zero_byte(xor1) | has_zero_byte(xor2) |
has_zero_byte(xor4) | has_zero_byte(xor5);
if (is_match) {
return i + index_of_first_set_byte(is_match);
return size_t(i + index_of_first_set_byte(is_match));
}
}
return view.size();
Expand Down Expand Up @@ -625,7 +625,7 @@ find_authority_delimiter_special(std::string_view view) noexcept {
uint64_t is_match = has_zero_byte(xor1) | has_zero_byte(xor2) |
has_zero_byte(xor3) | has_zero_byte(xor4);
if (is_match) {
return i + index_of_first_set_byte(is_match);
return size_t(i + index_of_first_set_byte(is_match));
}
}

Expand All @@ -640,7 +640,7 @@ find_authority_delimiter_special(std::string_view view) noexcept {
uint64_t is_match = has_zero_byte(xor1) | has_zero_byte(xor2) |
has_zero_byte(xor3) | has_zero_byte(xor4);
if (is_match) {
return i + index_of_first_set_byte(is_match);
return size_t(i + index_of_first_set_byte(is_match));
}
}

Expand Down Expand Up @@ -673,7 +673,7 @@ find_authority_delimiter(std::string_view view) noexcept {
uint64_t is_match =
has_zero_byte(xor1) | has_zero_byte(xor2) | has_zero_byte(xor3);
if (is_match) {
return i + index_of_first_set_byte(is_match);
return size_t(i + index_of_first_set_byte(is_match));
}
}

Expand All @@ -687,7 +687,7 @@ find_authority_delimiter(std::string_view view) noexcept {
uint64_t is_match =
has_zero_byte(xor1) | has_zero_byte(xor2) | has_zero_byte(xor3);
if (is_match) {
return i + index_of_first_set_byte(is_match);
return size_t(i + index_of_first_set_byte(is_match));
}
}

Expand Down
3 changes: 1 addition & 2 deletions src/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ result_type parse_url(std::string_view user_input,

// We refuse to parse URL strings that exceed 4GB. Such strings are almost
// surely the result of a bug or are otherwise a security concern.
if (user_input.size() >=
std::string_view::size_type(std::numeric_limits<uint32_t>::max)) {
if (user_input.size() > std::numeric_limits<uint32_t>::max()) {
url.is_valid = false;
}
// Going forward, user_input.size() is in [0,
Expand Down
11 changes: 11 additions & 0 deletions tests-wasm/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
link_libraries(ada)
add_executable(wasm wasm.cpp)
set_target_properties(wasm PROPERTIES LINK_FLAGS "-Os -s WASM=1 -s ENVIRONMENT=node -s EXPORT_NAME=loadWASM -s MODULARIZE=1 --bind --no-entry")
configure_file(test.js.in test.js)

find_program(NODEJS_BINARY NAMES node nodejs)
if(NODEJS_BINARY)
add_test(NAME wasmtest
COMMAND "${NODEJS_BINARY}" "${CMAKE_CURRENT_BINARY_DIR}/test.js")
endif(NODEJS_BINARY)

35 changes: 35 additions & 0 deletions tests-wasm/test.js.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const child_process = require('child_process');
const assert = require('assert');
const test = require('node:test');
const wasm = require('${CMAKE_CURRENT_BINARY_DIR}/wasm');

function toJS(obj) {
const result = {};
for (const key of Object.keys(obj.__proto__)) {
result[key] = typeof obj[key] === "object" ? toJS(obj[key]) : obj[key];
}
return result;
}

const expected = {
"result": "success",
"href": "https://google.com/?q=Yagiz#Nizipli",
"type": 2,
"components": {
"protocol_end": 6,
"username_end": 8,
"host_start": 8,
"host_end": 18,
"port": 4294967295,
"pathname_start": 18,
"search_start": 19,
"hash_start": 27
}
};

test('wasm', async () => {
const { parse } = await wasm();
console.log();
assert.deepStrictEqual(toJS(parse('https://google.com/?q=Yagiz#Nizipli')), expected);
});

45 changes: 45 additions & 0 deletions tests-wasm/wasm.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#include "ada.h"
#include <emscripten/emscripten.h>
#include <emscripten/bind.h>

using namespace emscripten;

struct parse_result {
std::string result;
std::string href;
uint32_t type;
ada::url_components components;
};

parse_result parse(const std::string &input) {
auto out = ada::parse<ada::url_aggregator>(input);
parse_result result;
if (!out.has_value()) {
result.result = "fail";
} else {
result.result = "success";
result.href = std::string(out->get_href());
result.type = out->type;
result.components = out->get_components();
}
return result;
}

EMSCRIPTEN_BINDINGS(url_components) {
class_<parse_result>("Result")
.property("result", &parse_result::result)
.property("href", &parse_result::href)
.property("type", &parse_result::type)
.property("components", &parse_result::components);
class_<ada::url_components>("URLComponents")
.property("protocol_end", &ada::url_components::protocol_end)
.property("username_end", &ada::url_components::username_end)
.property("host_start", &ada::url_components::host_start)
.property("host_end", &ada::url_components::host_end)
.property("port", &ada::url_components::port)
.property("pathname_start", &ada::url_components::pathname_start)
.property("search_start", &ada::url_components::search_start)
.property("hash_start", &ada::url_components::hash_start);

function("parse", &parse);
}

0 comments on commit beadd60

Please sign in to comment.