Skip to content

Commit c8f5c4e

Browse files
committed
Refactor parsing
- Treat unrecognized attributes as text. - Add tokenizer, expand attribute, simplify code flow. - Make CMakeLists.txt more generic.
1 parent d83d842 commit c8f5c4e

File tree

2 files changed

+132
-120
lines changed

2 files changed

+132
-120
lines changed

CMakeLists.txt

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.17 FATAL_ERROR)
33
enable_language(CXX)
44

55
set(CMAKE_DEBUG_POSTFIX "-d")
6+
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
67
set(CMAKE_CXX_EXTENSIONS OFF) # disable compiler extensions
78
set(CMAKE_CXX_STANDARD 17)
89
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -21,6 +22,20 @@ set(cxx_standard cxx_std_17)
2122

2223
project(${project_name} VERSION "${${project_name}_version}")
2324
set(${project_name}_soversion ${PROJECT_VERSION_MAJOR})
25+
set(${project_name}_version_file_in cmake/${PROJECT_NAME}_version.hpp.in)
26+
set(${project_name}_version_file_out ${PROJECT_NAME}_version.hpp)
27+
28+
function(configure_version_file)
29+
set(in "${CMAKE_CURRENT_SOURCE_DIR}/${${project_name}_version_file_in}")
30+
31+
if(EXISTS "${in}")
32+
set(out "${CMAKE_CURRENT_BINARY_DIR}/include/${PROJECT_NAME}/${${project_name}_version_file_out}")
33+
message(STATUS "Configuring ${out}")
34+
configure_file("${in}" "${out}" @ONLY)
35+
source_group(TREE "${CMAKE_CURRENT_BINARY_DIR}" FILES "${out}")
36+
endif()
37+
endfunction()
38+
2439
set(is_root_project OFF) # indicate if this is the top-level project
2540

2641
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
@@ -44,7 +59,7 @@ if(is_root_project)
4459
set(build_config "Multi-config")
4560
endif()
4661

47-
message(STATUS "[${CMAKE_SYSTEM_PROCESSOR}] [${PLATFORM}] [${CMAKE_GENERATOR}] [${CMAKE_CXX_COMPILER_ID}] [${build_config}]")
62+
message(STATUS "[${CMAKE_SYSTEM_PROCESSOR}] [${CMAKE_SYSTEM_NAME}] [${CMAKE_GENERATOR}] [${CMAKE_CXX_COMPILER_ID}] [${build_config}]")
4863
endif()
4964

5065
# compile commands
@@ -63,9 +78,10 @@ target_include_directories(${PROJECT_NAME} INTERFACE
6378
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>"
6479
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
6580
)
66-
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES include/forest/forest.hpp)
81+
get_target_property(sources ${PROJECT_NAME} SOURCES)
82+
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${sources})
6783

68-
configure_file(cmake/forest_version.hpp.in "${CMAKE_CURRENT_BINARY_DIR}/include/forest/forest_version.hpp" @ONLY)
84+
configure_version_file()
6985

7086
# examples
7187
if(FOREST_BUILD_EXAMPLE)
@@ -82,7 +98,7 @@ if(FOREST_INSTALL)
8298
include(CMakePackageConfigHelpers)
8399

84100
# install targets
85-
install(TARGETS forest EXPORT forest-targets
101+
install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}-targets
86102
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
87103
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
88104
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
@@ -101,33 +117,33 @@ if(FOREST_INSTALL)
101117
FILES_MATCHING PATTERN "*.hpp"
102118
)
103119

104-
# install export
105-
install(EXPORT forest-targets
106-
FILE forest-targets.cmake
107-
NAMESPACE forest::
108-
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/forest
120+
# install and export targets
121+
install(EXPORT ${PROJECT_NAME}-targets
122+
FILE ${PROJECT_NAME}-targets.cmake
123+
NAMESPACE ${PROJECT_NAME}::
124+
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
109125
)
110126

111-
# configure package config
127+
# configure ${PROJECT_NAME}-config.cmake
112128
configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/config.cmake.in"
113-
"${CMAKE_CURRENT_BINARY_DIR}/forest-config.cmake"
114-
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/forest
129+
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake"
130+
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
115131
)
116132

117-
# install forest-config.cmake
118-
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/forest-config.cmake"
119-
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/forest
133+
# install ${PROJECT_NAME}-config.cmake
134+
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake"
135+
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
120136
)
121137

122-
# configure forest-version.cmake
138+
# configure ${PROJECT_NAME}-config-version.cmake
123139
write_basic_package_version_file(
124-
"${CMAKE_CURRENT_BINARY_DIR}/forest-config-version.cmake"
140+
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake"
125141
VERSION ${build_version}
126142
COMPATIBILITY AnyNewerVersion
127143
)
128144

129-
# install forest-version.cmake
130-
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/forest-config-version.cmake"
131-
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/forest
145+
# install ${PROJECT_NAME}-config-version.cmake
146+
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake"
147+
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
132148
)
133149
endif()

include/forest/forest.hpp

Lines changed: 96 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,15 @@ struct rgb_t {
6565
rgb_type type{rgb_type::foreground};
6666
};
6767

68+
enum class op_type : std::uint8_t { open, close };
69+
template <typename T>
70+
struct attr_op_t {
71+
T t{};
72+
op_type op{};
73+
};
74+
75+
enum class attr_type : std::uint8_t { style, rgb };
76+
6877
constexpr std::uint8_t make_rgb(float r, float g, float b) {
6978
auto const ir = static_cast<int>(r * 5.0f);
7079
auto const ig = static_cast<int>(g * 5.0f);
@@ -88,6 +97,9 @@ constexpr float clamp(float const a, float const lo, float const hi) {
8897
return a;
8998
}
9099

100+
constexpr bool is_foreground(std::string_view str) { return str == "rgb"; }
101+
constexpr bool is_background(std::string_view str) { return str == "bg" || str == "background"; }
102+
91103
constexpr bool to_rgb(rgb_t& out, std::string_view const str) {
92104
if (str.size() != 3) { return false; }
93105
auto const r = clamp(static_cast<float>(str[0] - '0') / 5.0f, 0.0f, 1.0f);
@@ -97,30 +109,46 @@ constexpr bool to_rgb(rgb_t& out, std::string_view const str) {
97109
return true;
98110
}
99111

100-
enum class token_type : std::uint8_t { text, style, rgb };
101-
enum class op_type : std::uint8_t { open, close };
112+
struct tokenizer_t {
113+
std::string_view text{};
102114

103-
template <typename T>
104-
struct attribute_t {
105-
T t{};
106-
op_type op{};
107-
};
115+
constexpr std::pair<std::size_t, std::size_t> find_attribute() const {
116+
if (auto const open = text.find('<'); open != std::string_view::npos) { return {open, text.find('>', open + 1)}; }
117+
return {std::string_view::npos, std::string_view::npos};
118+
}
108119

109-
struct token_t {
110-
std::string_view text{};
111-
style_t style{};
112-
rgb_t rgb{};
113-
token_type type{};
114-
op_type op{};
115-
};
120+
constexpr bool try_attribute(std::string_view& out, std::size_t const close) {
121+
if (close != std::string_view::npos) {
122+
out = text.substr(0, close + 1);
123+
text = text.substr(close + 1);
124+
return true;
125+
}
126+
return false;
127+
}
116128

117-
struct scanner_t {
118-
struct assign_t {
119-
std::string_view lhs{};
120-
std::string_view rhs{};
121-
};
129+
constexpr bool operator()(std::string_view& out) {
130+
if (text.empty()) { return false; }
131+
auto const [open, close] = find_attribute();
122132

123-
std::string_view text{};
133+
if (open == 0 && close != std::string_view::npos) {
134+
if (try_attribute(out, close)) { return true; }
135+
}
136+
137+
if (close != std::string_view::npos) {
138+
out = text.substr(0, open);
139+
text = text.substr(open);
140+
} else {
141+
out = text;
142+
text = {};
143+
}
144+
145+
return true;
146+
}
147+
};
148+
149+
struct key_value_t {
150+
std::string_view key{};
151+
std::string_view value{};
124152

125153
static constexpr bool is_space(char const ch) { return ch == ' ' || ch == '\t' || ch == '\n'; }
126154

@@ -130,105 +158,68 @@ struct scanner_t {
130158
return in;
131159
}
132160

133-
static constexpr bool is_foreground(std::string_view str) { return str == "rgb"; }
134-
static constexpr bool is_background(std::string_view str) { return str == "bg" || str == "background"; }
135-
136-
static constexpr assign_t assignment(std::string_view const str) {
161+
static constexpr key_value_t make(std::string_view const str) {
137162
if (auto eq = str.find('='); eq != std::string_view::npos) { return {trim(str.substr(0, eq)), trim(str.substr(eq + 1))}; }
138163
return {str};
139164
}
165+
};
140166

141-
constexpr std::string_view attribute_str() {
142-
auto const close = text.find('>');
143-
auto ret = std::string_view{};
144-
if (close == std::string_view::npos) {
145-
text = {};
146-
ret = {};
147-
} else {
148-
ret = text.substr(1, close - 1);
149-
text = text.substr(close + 1);
150-
}
151-
return ret;
152-
}
167+
struct attribute_t {
168+
style_t style{};
169+
rgb_t rgb{};
170+
attr_type type{};
171+
op_type op{};
153172

154-
constexpr bool make_closing(token_t& out_token, std::string_view const str) {
155-
if (out_token.op != op_type::close) { return false; }
156-
if (is_background(str)) {
157-
out_token.type = token_type::rgb;
158-
out_token.rgb.type = rgb_type::background;
173+
constexpr bool make_close(key_value_t const& kvp) {
174+
if (op != op_type::close) { return false; }
175+
if (is_background(kvp.key)) {
176+
type = attr_type::rgb;
177+
rgb.type = rgb_type::background;
159178
return true;
160179
}
161-
if (is_foreground(str)) {
162-
out_token.type = token_type::rgb;
163-
out_token.rgb.type = rgb_type::foreground;
180+
if (is_foreground(kvp.key)) {
181+
type = attr_type::rgb;
182+
rgb.type = rgb_type::foreground;
164183
return true;
165184
}
166185
return false;
167186
}
168187

169-
constexpr bool make_assign(token_t& out_token, assign_t const assign) {
170-
if (out_token.op != op_type::open) { return false; }
171-
if (is_background(assign.lhs) && to_rgb(out_token.rgb, assign.rhs)) {
172-
out_token.type = token_type::rgb;
173-
out_token.rgb.type = rgb_type::background;
188+
constexpr bool make_key_value(key_value_t const& kvp) {
189+
if (op != op_type::open) { return false; }
190+
if (is_background(kvp.key) && to_rgb(rgb, kvp.value)) {
191+
type = attr_type::rgb;
192+
rgb.type = rgb_type::background;
174193
return true;
175194
}
176-
if (is_foreground(assign.lhs) && to_rgb(out_token.rgb, assign.rhs)) {
177-
out_token.type = token_type::rgb;
178-
out_token.rgb.type = rgb_type::foreground;
195+
if (is_foreground(kvp.key) && to_rgb(rgb, kvp.value)) {
196+
type = attr_type::rgb;
197+
rgb.type = rgb_type::foreground;
179198
return true;
180199
}
181200
return false;
182201
}
183202

184-
constexpr bool make_attribute(token_t& out_token, std::string_view const str) {
203+
constexpr bool make_attribute(std::string_view const str) {
185204
// try style
186-
if (to_style(out_token.style, str)) {
187-
out_token.type = token_type::style;
205+
if (to_style(style, str)) {
206+
type = attr_type::style;
188207
return true;
189208
}
190209

191210
// try assign
192-
auto const assign = assignment(str);
193-
if (assign.rhs.empty()) { return make_closing(out_token, assign.lhs); }
194-
return make_assign(out_token, assign);
211+
auto const kvp = key_value_t::make(str);
212+
return kvp.value.empty() ? make_close(kvp) : make_key_value(kvp);
195213
}
196214

197-
constexpr bool attribute(token_t& out_token, std::string_view str) {
198-
out_token.op = op_type::open;
215+
constexpr bool try_make(std::string_view str) {
216+
op = op_type::open;
199217
if (!str.empty() && str.front() == '/') {
200-
out_token.op = op_type::close;
218+
op = op_type::close;
201219
str = str.substr(1);
202220
}
203221
if (str.empty()) { return false; }
204-
return make_attribute(out_token, str);
205-
}
206-
207-
constexpr bool next(token_t& out_token) {
208-
if (text.empty()) { return false; }
209-
out_token.text = {};
210-
out_token.type = token_type::text;
211-
auto const open = text.find('<');
212-
if (open == std::string_view::npos) {
213-
// no attributes in text
214-
out_token.text = text;
215-
text = {};
216-
return true;
217-
}
218-
219-
if (open > 0) {
220-
// remaining text before attribute
221-
out_token.text = text.substr(0, open);
222-
text = text.substr(open);
223-
return true;
224-
}
225-
226-
auto str = attribute_str();
227-
if (str.empty()) { return false; }
228-
if (attribute(out_token, str)) { return true; }
229-
230-
// skip
231-
return next(out_token);
222+
return make_attribute(str);
232223
}
233224
};
234225

@@ -276,7 +267,7 @@ struct pen_t {
276267
return *this;
277268
}
278269

279-
constexpr pen_t& write(attribute_t<rgb_t> rgb) {
270+
constexpr pen_t& write(attr_op_t<rgb_t> rgb) {
280271
write(attribute_start_v);
281272
if (rgb.t.type == rgb_type::foreground) {
282273
if (rgb.op == op_type::close) { return write("39m"); }
@@ -289,7 +280,7 @@ struct pen_t {
289280
return write("m");
290281
}
291282

292-
constexpr pen_t& write(attribute_t<style_t> style) {
283+
constexpr pen_t& write(attr_op_t<style_t> style) {
293284
if (style.t == style_t::clear) { write({style_t::reset}); }
294285
write(attribute_start_v);
295286
if (style.op == op_type::close) {
@@ -310,17 +301,22 @@ struct null_writer {
310301

311302
template <typename Out>
312303
constexpr pen_t<Out>& write(pen_t<Out>& out_pen, std::string_view const text, std::size_t capacity = std::string_view::npos) {
313-
auto scanner = scanner_t{text};
314-
auto token = token_t{};
315304
auto vacant = [capacity, &out_pen] { return capacity == std::string_view::npos || out_pen.written < capacity; };
316-
while (scanner.next(token) && vacant()) {
317-
switch (token.type) {
318-
case token_type::rgb: out_pen.write({token.rgb, token.op}); break;
319-
case token_type::style: out_pen.write({token.style, token.op}); break;
320-
default:
321-
case token_type::text: out_pen.write(token.text); break;
305+
auto write_token = [&out_pen](std::string_view const token) -> pen_t<Out>& {
306+
if (token.front() == '<' && token.back() == '>') {
307+
auto attr = attribute_t{};
308+
if (attr.try_make(token.substr(1, token.size() - 2))) {
309+
switch (attr.type) {
310+
case attr_type::rgb: return out_pen.write({attr.rgb, attr.op});
311+
default: return out_pen.write({attr.style, attr.op});
312+
}
313+
}
322314
}
323-
}
315+
return out_pen.write(token);
316+
};
317+
auto tokenizer = tokenizer_t{text};
318+
auto token = std::string_view{};
319+
while (vacant() && tokenizer(token)) { write_token(token); }
324320
return out_pen;
325321
}
326322
} // namespace detail

0 commit comments

Comments
 (0)