LIMA
Libre Multilingual Analyzer — C++ API
Loading...
Searching...
No Matches
mwt_expander.h
Go to the documentation of this file.
1// Copyright 2021 CEA LIST
2// SPDX-FileCopyrightText: 2026 CEA LIST <gael.de-chalendar@cea.fr>
3//
4// SPDX-License-Identifier: MIT
5
6#ifndef DEEPLIMA_MWT_EXPANDER_H
7#define DEEPLIMA_MWT_EXPANDER_H
8
9#include <cstdint>
10#include <deque>
11#include <fstream>
12#include <functional>
13#include <stdexcept>
14#include <string>
15#include <unordered_map>
16#include <vector>
17
19#include "deeplima/token_type.h"
20
21namespace deeplima
22{
23
41{
42public:
43 typedef std::function<void(const std::vector<segmentation::token_pos>&, uint32_t)> callback_t;
44
45 explicit MwtExpander(const std::string& dict_fn)
46 {
47 load(dict_fn);
48 }
49
51 {
52 m_next = fn;
53 }
54
55 size_t size() const
56 {
57 return m_dict.size();
58 }
59
60 // When true (the default), only surface tokens the segmenter flagged as
61 // multiword (token_flags_t::multiword) are expanded — the model decides, in
62 // context, which ambiguous forms (e.g. French "des") are contractions. Set
63 // false for a non-MWT-aware tokenizer to fall back to expanding every dict
64 // match (the original dictionary-only behaviour).
65 void set_require_flag(bool require_flag)
66 {
67 m_require_flag = require_flag;
68 }
69
70 void operator()(const std::vector<segmentation::token_pos>& tokens, uint32_t len)
71 {
72 m_out.clear();
73 m_storage.clear();
74
75 for (uint32_t i = 0; i < len; ++i)
76 {
77 const segmentation::token_pos& t = tokens[i];
78 const std::string surface(t.m_pch, t.m_len);
79 const auto it = m_dict.find(surface);
80 const bool flagged = (t.m_flags & token_flags_t::multiword);
81 if (m_dict.end() == it || (m_require_flag && !flagged))
82 {
83 // Not a known multiword token, or (when gating on the model) not flagged
84 // as one by the segmenter: pass through unchanged.
85 m_out.push_back(t);
86 continue;
87 }
88
89 const std::vector<std::string>& words = it->second;
90
91 // Own the surface bytes for the range line. std::deque keeps element
92 // pointers/references valid across push_back, so m_pch stays valid until
93 // the downstream analyzer has interned everything (it does so
94 // synchronously inside m_next, before we clear m_storage on the next call).
95 m_storage.push_back(surface);
96 const char* surf_pch = m_storage.back().c_str();
97
98 for (size_t k = 0; k < words.size(); ++k)
99 {
100 m_storage.push_back(words[k]);
101
103 sub.m_pch = m_storage.back().c_str();
104 sub.m_len = uint16_t(words[k].size());
105 // The surface's leading whitespace attaches to the first sub-word; a
106 // sentence/paragraph break of the surface attaches to the last. The
107 // multiword flag belongs to the surface only, never to its sub-words.
108 sub.m_offset = (0 == k) ? t.m_offset : 0;
109 sub.m_flags = (k + 1 == words.size())
112 if (0 == k)
113 {
114 sub.m_mwt_len = uint8_t(words.size());
115 sub.m_mwt_surface_pch = surf_pch;
116 sub.m_mwt_surface_len = uint16_t(surface.size());
117 }
118 m_out.push_back(sub);
119 }
120 }
121
122 if (m_next)
123 {
124 m_next(m_out, uint32_t(m_out.size()));
125 }
126 }
127
128private:
129 void load(const std::string& fn)
130 {
131 std::ifstream f(fn);
132 if (!f.is_open())
133 {
134 throw std::runtime_error("MwtExpander: can't open dictionary \"" + fn + "\"");
135 }
136
137 std::string line;
138 while (std::getline(f, line))
139 {
140 if (line.empty() || '#' == line[0])
141 {
142 continue;
143 }
144
145 // surface <TAB> count <TAB> word1 <TAB> word2 ...
146 std::vector<std::string> fields;
147 size_t start = 0;
148 for (;;)
149 {
150 const size_t tab = line.find('\t', start);
151 fields.push_back(line.substr(start, std::string::npos == tab ? std::string::npos : tab - start));
152 if (std::string::npos == tab)
153 {
154 break;
155 }
156 start = tab + 1;
157 }
158
159 if (fields.size() < 4)
160 {
161 // need surface, count, and at least 2 words for a real expansion
162 continue;
163 }
164 const std::string& surface = fields[0];
165 std::vector<std::string> words(fields.begin() + 2, fields.end());
166 if (surface.empty() || words.size() < 2)
167 {
168 continue;
169 }
170 m_dict.emplace(surface, std::move(words));
171 }
172 }
173
174 std::unordered_map<std::string, std::vector<std::string>> m_dict;
175 callback_t m_next;
176 bool m_require_flag = true;
177
178 // Scratch reused on every call.
179 std::vector<segmentation::token_pos> m_out;
180 std::deque<std::string> m_storage; // owns sub-word/surface bytes; pointer-stable
181};
182
183} // namespace deeplima
184
185#endif
Multiword-token (MWT) expansion stage.
void set_require_flag(bool require_flag)
void operator()(const std::vector< segmentation::token_pos > &tokens, uint32_t len)
MwtExpander(const std::string &dict_fn)
size_t size() const
void register_handler(const callback_t &fn)
std::function< void(const std::vector< segmentation::token_pos > &, uint32_t)> callback_t