Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ option(PISA_SYSTEM_ONETBB "Use system installation of oneTBB" OFF)
option(PISA_SYSTEM_BOOST "Use system installation of Boost" OFF)
option(PISA_SYSTEM_CLI11 "Use system headers for CLI11" OFF)

# ICU is an OPTIONAL, system-provided dependency. When it is disabled or
# not found, PISA builds normally and InternationalTokenizer throws at runtime.
option(PISA_ENABLE_ICU "Look for a system ICU and enable InternationalTokenizer if found" ON)

if(PISA_USE_PIC)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(GUMBO_CFLAGS "-fPIC")
Expand Down Expand Up @@ -104,6 +108,11 @@ if (PISA_SYSTEM_ONETBB)
find_package(TBB REQUIRED)
endif()

# Optional: only the common library (uc) is needed for icu::BreakIterator.
if(PISA_ENABLE_ICU)
find_package(ICU COMPONENTS uc)
endif()

file(GLOB_RECURSE PISA_SRC_FILES FOLLOW_SYMLINKS "src/*cpp")
list(SORT PISA_SRC_FILES)

Expand Down Expand Up @@ -147,6 +156,23 @@ target_link_libraries(pisa
trecpp
nlohmann_json::nlohmann_json
)

if(PISA_ENABLE_ICU AND ICU_FOUND)
# The macro is PUBLIC so tests and tools can tell whether
# InternationalTokenizer is functional. ICU itself is PRIVATE
target_compile_definitions(pisa PUBLIC PISA_HAS_ICU=1)
target_link_libraries(pisa PRIVATE ICU::uc)
message(STATUS "ICU ${ICU_VERSION} found: InternationalTokenizer enabled")
elseif(PISA_ENABLE_ICU)
message(WARNING
"ICU not found: InternationalTokenizer will throw at runtime. "
"Install the ICU development files (libicu-dev on Debian/Ubuntu, "
"libicu-devel on Fedora) or configure with -DPISA_ENABLE_ICU=OFF "
"to silence this warning.")
else()
message(STATUS "PISA_ENABLE_ICU=OFF: InternationalTokenizer will throw at runtime")
endif()

if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0)
target_link_libraries(pisa PUBLIC stdc++fs)
Expand Down
2 changes: 1 addition & 1 deletion docs/book/cli/compute_intersection.html
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ <h2 id="usage"><a class="header" href="#usage">Usage</a></h2>
-w,--wand TEXT REQUIRED WAND data filename
--compressed-wand Needs: --wand
Compressed WAND data file
--tokenizer TEXT:{english,whitespace} [english]
--tokenizer TEXT:{english,international,whitespace} [english]
Tokenizer
-H,--html Strip HTML
-F,--token-filters TEXT:{krovetz,lowercase,porter2} ...
Expand Down
2 changes: 1 addition & 1 deletion docs/book/cli/count-postings.html
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ <h2 id="usage"><a class="header" href="#usage">Usage</a></h2>
-h,--help Print this help message and exit
-e,--encoding TEXT REQUIRED Index encoding
-i,--index TEXT REQUIRED Inverted index filename
--tokenizer TEXT:{english,whitespace} [english]
--tokenizer TEXT:{english,international,whitespace} [english]
Tokenizer
-H,--html Strip HTML
-F,--token-filters TEXT:{krovetz,lowercase,porter2} ...
Expand Down
2 changes: 1 addition & 1 deletion docs/book/cli/evaluate_queries.html
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ <h2 id="usage"><a class="header" href="#usage">Usage</a></h2>
-w,--wand TEXT REQUIRED WAND data filename
--compressed-wand Needs: --wand
Compressed WAND data file
--tokenizer TEXT:{english,whitespace} [english]
--tokenizer TEXT:{english,international,whitespace} [english]
Tokenizer
-H,--html Strip HTML
-F,--token-filters TEXT:{krovetz,lowercase,porter2} ...
Expand Down
2 changes: 1 addition & 1 deletion docs/book/cli/extract-maxscores.html
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ <h2 id="usage"><a class="header" href="#usage">Usage</a></h2>
-w,--wand TEXT REQUIRED WAND data filename
--compressed-wand Needs: --wand
Compressed WAND data file
--tokenizer TEXT:{english,whitespace} [english]
--tokenizer TEXT:{english,international,whitespace} [english]
Tokenizer
-H,--html Strip HTML
-F,--token-filters TEXT:{krovetz,lowercase,porter2} ...
Expand Down
2 changes: 1 addition & 1 deletion docs/book/cli/kth_threshold.html
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ <h2 id="usage"><a class="header" href="#usage">Usage</a></h2>
-w,--wand TEXT REQUIRED WAND data filename
--compressed-wand Needs: --wand
Compressed WAND data file
--tokenizer TEXT:{english,whitespace} [english]
--tokenizer TEXT:{english,international,whitespace} [english]
Tokenizer
-H,--html Strip HTML
-F,--token-filters TEXT:{krovetz,lowercase,porter2} ...
Expand Down
2 changes: 1 addition & 1 deletion docs/book/cli/map_queries.html
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ <h2 id="usage"><a class="header" href="#usage">Usage</a></h2>

Options:
-h,--help Print this help message and exit
--tokenizer TEXT:{english,whitespace} [english]
--tokenizer TEXT:{english,international,whitespace} [english]
Tokenizer
-H,--html Strip HTML
-F,--token-filters TEXT:{krovetz,lowercase,porter2} ...
Expand Down
2 changes: 1 addition & 1 deletion docs/book/cli/parse_collection.html
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ <h2 id="usage"><a class="header" href="#usage">Usage</a></h2>
-L,--log-level TEXT:{critical,debug,err,info,off,trace,warn} [info]
Log level
-j,--threads UINT Number of threads
--tokenizer TEXT:{english,whitespace} [english]
--tokenizer TEXT:{english,international,whitespace} [english]
Tokenizer
-H,--html Strip HTML
-F,--token-filters TEXT:{krovetz,lowercase,porter2} ...
Expand Down
2 changes: 1 addition & 1 deletion docs/book/cli/queries.html
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ <h2 id="usage"><a class="header" href="#usage">Usage</a></h2>
-w,--wand TEXT WAND data filename
--compressed-wand Needs: --wand
Compressed WAND data file
--tokenizer TEXT:{english,whitespace} [english]
--tokenizer TEXT:{english,international,whitespace} [english]
Tokenizer
-H,--html Strip HTML
-F,--token-filters TEXT:{krovetz,lowercase,porter2} ...
Expand Down
2 changes: 1 addition & 1 deletion docs/book/cli/selective_queries.html
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ <h2 id="usage"><a class="header" href="#usage">Usage</a></h2>
-h,--help Print this help message and exit
-e,--encoding TEXT REQUIRED Index encoding
-i,--index TEXT REQUIRED Inverted index filename
--tokenizer TEXT:{english,whitespace} [english]
--tokenizer TEXT:{english,international,whitespace} [english]
Tokenizer
-H,--html Strip HTML
-F,--token-filters TEXT:{krovetz,lowercase,porter2} ...
Expand Down
2 changes: 1 addition & 1 deletion docs/book/cli/taily-thresholds.html
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ <h2 id="usage"><a class="header" href="#usage">Usage</a></h2>

Options:
-h,--help Print this help message and exit
--tokenizer TEXT:{english,whitespace} [english]
--tokenizer TEXT:{english,international,whitespace} [english]
Tokenizer
-H,--html Strip HTML
-F,--token-filters TEXT:{krovetz,lowercase,porter2} ...
Expand Down
2 changes: 1 addition & 1 deletion docs/book/cli/thresholds.html
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ <h2 id="usage"><a class="header" href="#usage">Usage</a></h2>
-w,--wand TEXT REQUIRED WAND data filename
--compressed-wand Needs: --wand
Compressed WAND data file
--tokenizer TEXT:{english,whitespace} [english]
--tokenizer TEXT:{english,international,whitespace} [english]
Tokenizer
-H,--html Strip HTML
-F,--token-filters TEXT:{krovetz,lowercase,porter2} ...
Expand Down
22 changes: 11 additions & 11 deletions docs/book/print.html
Original file line number Diff line number Diff line change
Expand Up @@ -1054,7 +1054,7 @@ <h2 id="usage-1"><a class="header" href="#usage-1">Usage</a></h2>
-w,--wand TEXT REQUIRED WAND data filename
--compressed-wand Needs: --wand
Compressed WAND data file
--tokenizer TEXT:{english,whitespace} [english]
--tokenizer TEXT:{english,international,whitespace} [english]
Tokenizer
-H,--html Strip HTML
-F,--token-filters TEXT:{krovetz,lowercase,porter2} ...
Expand Down Expand Up @@ -1096,7 +1096,7 @@ <h2 id="usage-2"><a class="header" href="#usage-2">Usage</a></h2>
-h,--help Print this help message and exit
-e,--encoding TEXT REQUIRED Index encoding
-i,--index TEXT REQUIRED Inverted index filename
--tokenizer TEXT:{english,whitespace} [english]
--tokenizer TEXT:{english,international,whitespace} [english]
Tokenizer
-H,--html Strip HTML
-F,--token-filters TEXT:{krovetz,lowercase,porter2} ...
Expand Down Expand Up @@ -1174,7 +1174,7 @@ <h2 id="usage-4"><a class="header" href="#usage-4">Usage</a></h2>
-w,--wand TEXT REQUIRED WAND data filename
--compressed-wand Needs: --wand
Compressed WAND data file
--tokenizer TEXT:{english,whitespace} [english]
--tokenizer TEXT:{english,international,whitespace} [english]
Tokenizer
-H,--html Strip HTML
-F,--token-filters TEXT:{krovetz,lowercase,porter2} ...
Expand Down Expand Up @@ -1224,7 +1224,7 @@ <h2 id="usage-5"><a class="header" href="#usage-5">Usage</a></h2>
-w,--wand TEXT REQUIRED WAND data filename
--compressed-wand Needs: --wand
Compressed WAND data file
--tokenizer TEXT:{english,whitespace} [english]
--tokenizer TEXT:{english,international,whitespace} [english]
Tokenizer
-H,--html Strip HTML
-F,--token-filters TEXT:{krovetz,lowercase,porter2} ...
Expand Down Expand Up @@ -1285,7 +1285,7 @@ <h2 id="usage-8"><a class="header" href="#usage-8">Usage</a></h2>
-w,--wand TEXT REQUIRED WAND data filename
--compressed-wand Needs: --wand
Compressed WAND data file
--tokenizer TEXT:{english,whitespace} [english]
--tokenizer TEXT:{english,international,whitespace} [english]
Tokenizer
-H,--html Strip HTML
-F,--token-filters TEXT:{krovetz,lowercase,porter2} ...
Expand Down Expand Up @@ -1341,7 +1341,7 @@ <h2 id="usage-10"><a class="header" href="#usage-10">Usage</a></h2>

Options:
-h,--help Print this help message and exit
--tokenizer TEXT:{english,whitespace} [english]
--tokenizer TEXT:{english,international,whitespace} [english]
Tokenizer
-H,--html Strip HTML
-F,--token-filters TEXT:{krovetz,lowercase,porter2} ...
Expand All @@ -1366,7 +1366,7 @@ <h2 id="usage-11"><a class="header" href="#usage-11">Usage</a></h2>
-L,--log-level TEXT:{critical,debug,err,info,off,trace,warn} [info]
Log level
-j,--threads UINT Number of threads
--tokenizer TEXT:{english,whitespace} [english]
--tokenizer TEXT:{english,international,whitespace} [english]
Tokenizer
-H,--html Strip HTML
-F,--token-filters TEXT:{krovetz,lowercase,porter2} ...
Expand Down Expand Up @@ -1412,7 +1412,7 @@ <h2 id="usage-13"><a class="header" href="#usage-13">Usage</a></h2>
-w,--wand TEXT WAND data filename
--compressed-wand Needs: --wand
Compressed WAND data file
--tokenizer TEXT:{english,whitespace} [english]
--tokenizer TEXT:{english,international,whitespace} [english]
Tokenizer
-H,--html Strip HTML
-F,--token-filters TEXT:{krovetz,lowercase,porter2} ...
Expand Down Expand Up @@ -1603,7 +1603,7 @@ <h2 id="usage-17"><a class="header" href="#usage-17">Usage</a></h2>
-h,--help Print this help message and exit
-e,--encoding TEXT REQUIRED Index encoding
-i,--index TEXT REQUIRED Inverted index filename
--tokenizer TEXT:{english,whitespace} [english]
--tokenizer TEXT:{english,international,whitespace} [english]
Tokenizer
-H,--html Strip HTML
-F,--token-filters TEXT:{krovetz,lowercase,porter2} ...
Expand Down Expand Up @@ -1683,7 +1683,7 @@ <h2 id="usage-21"><a class="header" href="#usage-21">Usage</a></h2>

Options:
-h,--help Print this help message and exit
--tokenizer TEXT:{english,whitespace} [english]
--tokenizer TEXT:{english,international,whitespace} [english]
Tokenizer
-H,--html Strip HTML
-F,--token-filters TEXT:{krovetz,lowercase,porter2} ...
Expand All @@ -1710,7 +1710,7 @@ <h2 id="usage-22"><a class="header" href="#usage-22">Usage</a></h2>
-w,--wand TEXT REQUIRED WAND data filename
--compressed-wand Needs: --wand
Compressed WAND data file
--tokenizer TEXT:{english,whitespace} [english]
--tokenizer TEXT:{english,international,whitespace} [english]
Tokenizer
-H,--html Strip HTML
-F,--token-filters TEXT:{krovetz,lowercase,porter2} ...
Expand Down
2 changes: 1 addition & 1 deletion docs/book/searchindex.js

Large diffs are not rendered by default.

60 changes: 60 additions & 0 deletions include/pisa/tokenizer.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <cctype>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
Expand Down Expand Up @@ -56,6 +57,37 @@ class EnglishTokenStream: public TokenStream {
virtual auto next() -> std::optional<std::string> override;
};

/**
* Token stream that splits text into words using ICU's `icu::BreakIterator`.
*
* ICU is an *optional* build-time dependency (see `PISA_ENABLE_ICU` in CMake). This header
* intentionally includes no ICU headers: all ICU state lives in a pimpl defined in
* `tokenizer.cpp`, so including this file never requires ICU.
*
* When PISA is built without ICU, the constructors throw `std::runtime_error`.
*
* Copying is not supported: streams are handed out as `std::unique_ptr<TokenStream>`, so it is
* not needed, and a correct copy would have to re-bind the break iterator to the copied text.
*
* Tokens are case-folded (Unicode full case folding), and whitespace and punctuation are skipped.
*/
class InternationalTokenStream: public TokenStream {
public:
explicit InternationalTokenStream(std::string_view input);
explicit InternationalTokenStream(std::string input);
explicit InternationalTokenStream(CowString input);
InternationalTokenStream(InternationalTokenStream const&) = delete;
InternationalTokenStream(InternationalTokenStream&&) noexcept;
InternationalTokenStream& operator=(InternationalTokenStream const&) = delete;
InternationalTokenStream& operator=(InternationalTokenStream&&) noexcept;
virtual ~InternationalTokenStream();
virtual auto next() -> std::optional<std::string> override;

private:
struct Impl;
std::unique_ptr<Impl> m_impl;
};

/**
* Tokenizer abstraction.
*/
Expand Down Expand Up @@ -121,4 +153,32 @@ class EnglishTokenizer: public Tokenizer {
-> std::unique_ptr<TokenStream> override;
};

/**
* International tokenizer.
*
* Uses icu::BreakIterator to split text into words. This tokenizer is suitable for languages
* that do not use whitespace to separate words, such as Chinese, Japanese, and Thai.
* Also supports mixed language text.
*
* Requires PISA to be built with ICU (optional dependency, `PISA_ENABLE_ICU`, on by default and
* auto-detected). Without ICU, `tokenize` throws `std::runtime_error`. The macro `PISA_HAS_ICU`
* is defined when ICU support is compiled in.
*/
class InternationalTokenizer: public Tokenizer {
public:
InternationalTokenizer();
InternationalTokenizer(InternationalTokenizer const&);
InternationalTokenizer(InternationalTokenizer&&);
InternationalTokenizer& operator=(InternationalTokenizer const&);
InternationalTokenizer& operator=(InternationalTokenizer&&);
virtual ~InternationalTokenizer();

[[nodiscard]] virtual auto tokenize(std::string_view input) const
-> std::unique_ptr<TokenStream> override;
[[nodiscard]] virtual auto tokenize(std::string input) const
-> std::unique_ptr<TokenStream> override;
[[nodiscard]] virtual auto tokenize(CowString input) const
-> std::unique_ptr<TokenStream> override;
};

} // namespace pisa
2 changes: 1 addition & 1 deletion pisactl/src/pisactl/cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
)

ALGORITHMS = ["or", "and", "ranked_or", "ranked_and", "wand", "block_max_wand", "maxscore"]
TOKENIZERS = ["english", "whitespace"]
TOKENIZERS = ["english", "whitespace", "international"]
TOKEN_FILTERS = ["lowercase", "porter2", "krovetz"]


Expand Down
Loading