Skip to content

Commit beadd60

Browse files
lemireMoLow
andauthored
feat: emscripten support (#412)
* 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]>
1 parent 5598049 commit beadd60

File tree

7 files changed

+149
-22
lines changed

7 files changed

+149
-22
lines changed

.github/workflows/emscripten.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: emscripten
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, reopened, ready_for_review]
6+
paths-ignore:
7+
- '**.md'
8+
- 'docs/**'
9+
push:
10+
branches:
11+
- main
12+
paths-ignore:
13+
- '**.md'
14+
- 'docs/**'
15+
16+
permissions:
17+
contents: read
18+
19+
concurrency:
20+
group: ${{ github.workflow }}-${{ github.ref }}
21+
cancel-in-progress: true
22+
23+
24+
jobs:
25+
build:
26+
runs-on: ubuntu-latest
27+
steps:
28+
- uses: actions/setup-node@v3
29+
- uses: mymindstorm/setup-emsdk@v12
30+
- name: Verify
31+
run: emcc -v
32+
- name: Checkout
33+
uses: actions/checkout@v3
34+
- name: Configure
35+
run: emcmake cmake -B buildwasm
36+
- name: Build
37+
run: cmake --build buildwasm
38+
- name: Test
39+
run: ctest --test-dir buildwasm

CMakeLists.txt

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,27 @@ if((BUILD_TESTING OR ADA_BENCHMARKS) AND (CMAKE_SIZEOF_VOID_P EQUAL 8))
3333
add_dependency(gtest)
3434
endif()
3535

36-
if (BUILD_TESTING)
36+
if (BUILD_TESTING AND NOT EMSCRIPTEN)
3737
message(STATUS "The tests are enabled.")
3838
add_subdirectory(tests)
3939
else()
4040
if(is_top_project)
4141
message(STATUS "The tests are disabled.")
4242
endif()
43-
endif(BUILD_TESTING)
43+
endif(BUILD_TESTING AND NOT EMSCRIPTEN)
4444

45-
If(ADA_BENCHMARKS)
45+
If(ADA_BENCHMARKS AND NOT EMSCRIPTEN)
4646
message(STATUS "Ada benchmarks enabled.")
4747
add_subdirectory(benchmarks)
48-
else(ADA_BENCHMARKS)
48+
else(ADA_BENCHMARKS AND NOT EMSCRIPTEN)
4949
if(is_top_project)
5050
message(STATUS "Ada benchmarks disabled. Set ADA_BENCHMARKS=ON to enable them.")
5151
endif()
52-
endif(ADA_BENCHMARKS)
52+
endif(ADA_BENCHMARKS AND NOT EMSCRIPTEN)
53+
54+
if (BUILD_TESTING AND EMSCRIPTEN)
55+
add_subdirectory(tests-wasm)
56+
endif(BUILD_TESTING AND EMSCRIPTEN)
5357

5458
add_library(ada::ada ALIAS ada)
5559

@@ -60,16 +64,10 @@ set_target_properties(
6064
WINDOWS_EXPORT_ALL_SYMBOLS YES
6165
)
6266

63-
include (TestBigEndian)
64-
TEST_BIG_ENDIAN(IS_BIG_ENDIAN)
65-
if(IS_BIG_ENDIAN)
66-
message(STATUS "Big-endian system detected.")
67-
endif()
68-
6967
include(CMakePackageConfigHelpers)
7068
include(GNUInstallDirs)
7169

72-
if(NOT ADA_COVERAGE)
70+
if(NOT ADA_COVERAGE AND NOT EMSCRIPTEN)
7371
add_subdirectory(singleheader)
7472
add_subdirectory(tools)
7573
endif()

src/helpers.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ ada_really_inline size_t find_next_host_delimiter_special(
238238
has_zero_byte(xor3) | has_zero_byte(xor4) |
239239
has_zero_byte(xor5);
240240
if (is_match) {
241-
return i + index_of_first_set_byte(is_match);
241+
return size_t(i + index_of_first_set_byte(is_match));
242242
}
243243
}
244244
if (i < view.size()) {
@@ -256,7 +256,7 @@ ada_really_inline size_t find_next_host_delimiter_special(
256256
has_zero_byte(xor3) | has_zero_byte(xor4) |
257257
has_zero_byte(xor5);
258258
if (is_match) {
259-
return i + index_of_first_set_byte(is_match);
259+
return size_t(i + index_of_first_set_byte(is_match));
260260
}
261261
}
262262
return view.size();
@@ -300,7 +300,7 @@ ada_really_inline size_t find_next_host_delimiter(std::string_view view,
300300
uint64_t is_match = has_zero_byte(xor1) | has_zero_byte(xor2) |
301301
has_zero_byte(xor4) | has_zero_byte(xor5);
302302
if (is_match) {
303-
return i + index_of_first_set_byte(is_match);
303+
return size_t(i + index_of_first_set_byte(is_match));
304304
}
305305
}
306306
if (i < view.size()) {
@@ -318,7 +318,7 @@ ada_really_inline size_t find_next_host_delimiter(std::string_view view,
318318
uint64_t is_match = has_zero_byte(xor1) | has_zero_byte(xor2) |
319319
has_zero_byte(xor4) | has_zero_byte(xor5);
320320
if (is_match) {
321-
return i + index_of_first_set_byte(is_match);
321+
return size_t(i + index_of_first_set_byte(is_match));
322322
}
323323
}
324324
return view.size();
@@ -625,7 +625,7 @@ find_authority_delimiter_special(std::string_view view) noexcept {
625625
uint64_t is_match = has_zero_byte(xor1) | has_zero_byte(xor2) |
626626
has_zero_byte(xor3) | has_zero_byte(xor4);
627627
if (is_match) {
628-
return i + index_of_first_set_byte(is_match);
628+
return size_t(i + index_of_first_set_byte(is_match));
629629
}
630630
}
631631

@@ -640,7 +640,7 @@ find_authority_delimiter_special(std::string_view view) noexcept {
640640
uint64_t is_match = has_zero_byte(xor1) | has_zero_byte(xor2) |
641641
has_zero_byte(xor3) | has_zero_byte(xor4);
642642
if (is_match) {
643-
return i + index_of_first_set_byte(is_match);
643+
return size_t(i + index_of_first_set_byte(is_match));
644644
}
645645
}
646646

@@ -673,7 +673,7 @@ find_authority_delimiter(std::string_view view) noexcept {
673673
uint64_t is_match =
674674
has_zero_byte(xor1) | has_zero_byte(xor2) | has_zero_byte(xor3);
675675
if (is_match) {
676-
return i + index_of_first_set_byte(is_match);
676+
return size_t(i + index_of_first_set_byte(is_match));
677677
}
678678
}
679679

@@ -687,7 +687,7 @@ find_authority_delimiter(std::string_view view) noexcept {
687687
uint64_t is_match =
688688
has_zero_byte(xor1) | has_zero_byte(xor2) | has_zero_byte(xor3);
689689
if (is_match) {
690-
return i + index_of_first_set_byte(is_match);
690+
return size_t(i + index_of_first_set_byte(is_match));
691691
}
692692
}
693693

src/parser.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@ result_type parse_url(std::string_view user_input,
3636

3737
// We refuse to parse URL strings that exceed 4GB. Such strings are almost
3838
// surely the result of a bug or are otherwise a security concern.
39-
if (user_input.size() >=
40-
std::string_view::size_type(std::numeric_limits<uint32_t>::max)) {
39+
if (user_input.size() > std::numeric_limits<uint32_t>::max()) {
4140
url.is_valid = false;
4241
}
4342
// Going forward, user_input.size() is in [0,

tests-wasm/CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
link_libraries(ada)
2+
add_executable(wasm wasm.cpp)
3+
set_target_properties(wasm PROPERTIES LINK_FLAGS "-Os -s WASM=1 -s ENVIRONMENT=node -s EXPORT_NAME=loadWASM -s MODULARIZE=1 --bind --no-entry")
4+
configure_file(test.js.in test.js)
5+
6+
find_program(NODEJS_BINARY NAMES node nodejs)
7+
if(NODEJS_BINARY)
8+
add_test(NAME wasmtest
9+
COMMAND "${NODEJS_BINARY}" "${CMAKE_CURRENT_BINARY_DIR}/test.js")
10+
endif(NODEJS_BINARY)
11+

tests-wasm/test.js.in

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const child_process = require('child_process');
2+
const assert = require('assert');
3+
const test = require('node:test');
4+
const wasm = require('${CMAKE_CURRENT_BINARY_DIR}/wasm');
5+
6+
function toJS(obj) {
7+
const result = {};
8+
for (const key of Object.keys(obj.__proto__)) {
9+
result[key] = typeof obj[key] === "object" ? toJS(obj[key]) : obj[key];
10+
}
11+
return result;
12+
}
13+
14+
const expected = {
15+
"result": "success",
16+
"href": "https://google.com/?q=Yagiz#Nizipli",
17+
"type": 2,
18+
"components": {
19+
"protocol_end": 6,
20+
"username_end": 8,
21+
"host_start": 8,
22+
"host_end": 18,
23+
"port": 4294967295,
24+
"pathname_start": 18,
25+
"search_start": 19,
26+
"hash_start": 27
27+
}
28+
};
29+
30+
test('wasm', async () => {
31+
const { parse } = await wasm();
32+
console.log();
33+
assert.deepStrictEqual(toJS(parse('https://google.com/?q=Yagiz#Nizipli')), expected);
34+
});
35+

tests-wasm/wasm.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#include "ada.h"
2+
#include <emscripten/emscripten.h>
3+
#include <emscripten/bind.h>
4+
5+
using namespace emscripten;
6+
7+
struct parse_result {
8+
std::string result;
9+
std::string href;
10+
uint32_t type;
11+
ada::url_components components;
12+
};
13+
14+
parse_result parse(const std::string &input) {
15+
auto out = ada::parse<ada::url_aggregator>(input);
16+
parse_result result;
17+
if (!out.has_value()) {
18+
result.result = "fail";
19+
} else {
20+
result.result = "success";
21+
result.href = std::string(out->get_href());
22+
result.type = out->type;
23+
result.components = out->get_components();
24+
}
25+
return result;
26+
}
27+
28+
EMSCRIPTEN_BINDINGS(url_components) {
29+
class_<parse_result>("Result")
30+
.property("result", &parse_result::result)
31+
.property("href", &parse_result::href)
32+
.property("type", &parse_result::type)
33+
.property("components", &parse_result::components);
34+
class_<ada::url_components>("URLComponents")
35+
.property("protocol_end", &ada::url_components::protocol_end)
36+
.property("username_end", &ada::url_components::username_end)
37+
.property("host_start", &ada::url_components::host_start)
38+
.property("host_end", &ada::url_components::host_end)
39+
.property("port", &ada::url_components::port)
40+
.property("pathname_start", &ada::url_components::pathname_start)
41+
.property("search_start", &ada::url_components::search_start)
42+
.property("hash_start", &ada::url_components::hash_start);
43+
44+
function("parse", &parse);
45+
}

0 commit comments

Comments
 (0)