| File: | _build/../src/parser-cat.cc |
| Warning: | line 807, column 39 Division by zero |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 1 | /* | |||
| 2 | * Copyright © 2017, 2018 Christian Persch | |||
| 3 | * | |||
| 4 | * This programme is free software; you can redistribute it and/or | |||
| 5 | * modify it under the terms of the GNU General Public | |||
| 6 | * License as published by the Free Software Foundation; either | |||
| 7 | * version 3 of the License, or (at your option) any later version. | |||
| 8 | * | |||
| 9 | * This programme is distributed in the hope that it will be useful, | |||
| 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
| 12 | * General Public License for more details. | |||
| 13 | * | |||
| 14 | * You should have received a copy of the GNU General Public License | |||
| 15 | * along with this program. If not, see <https://www.gnu.org/licenses/>. | |||
| 16 | */ | |||
| 17 | ||||
| 18 | #include "config.h" | |||
| 19 | ||||
| 20 | #include <glib.h> | |||
| 21 | #include <locale.h> | |||
| 22 | #include <unistd.h> | |||
| 23 | #include <fcntl.h> | |||
| 24 | ||||
| 25 | #include <cassert> | |||
| 26 | #include <cstring> | |||
| 27 | #include <cerrno> | |||
| 28 | #include <cstdio> | |||
| 29 | #include <cstdlib> | |||
| 30 | ||||
| 31 | #include <string> | |||
| 32 | ||||
| 33 | #include "debug.h" | |||
| 34 | #include "glib-glue.hh" | |||
| 35 | #include "libc-glue.hh" | |||
| 36 | #include "parser.hh" | |||
| 37 | #include "parser-glue.hh" | |||
| 38 | #include "utf8.hh" | |||
| 39 | ||||
| 40 | enum { | |||
| 41 | #define _BTE_SGR(...) | |||
| 42 | #define _BTE_NGR(name, value) BTE_SGR_##name = value, | |||
| 43 | #include "parser-sgr.hh" | |||
| 44 | #undef _BTE_SGR | |||
| 45 | #undef _BTE_NGR | |||
| 46 | }; | |||
| 47 | ||||
| 48 | using namespace std::literals; | |||
| 49 | ||||
| 50 | char* | |||
| 51 | bte::parser::Sequence::ucs4_to_utf8(gunichar const* str, | |||
| 52 | ssize_t len) const noexcept | |||
| 53 | { | |||
| 54 | return g_ucs4_to_utf8(str, len, nullptr, nullptr, nullptr); | |||
| 55 | } | |||
| 56 | ||||
| 57 | static constexpr char const* | |||
| 58 | seq_to_str(unsigned int type) noexcept | |||
| 59 | { | |||
| 60 | switch (type) { | |||
| 61 | case BTE_SEQ_NONE: return "NONE"; | |||
| 62 | case BTE_SEQ_IGNORE: return "IGNORE"; | |||
| 63 | case BTE_SEQ_GRAPHIC: return "GRAPHIC"; | |||
| 64 | case BTE_SEQ_CONTROL: return "CONTROL"; | |||
| 65 | case BTE_SEQ_ESCAPE: return "ESCAPE"; | |||
| 66 | case BTE_SEQ_CSI: return "CSI"; | |||
| 67 | case BTE_SEQ_DCS: return "DCS"; | |||
| 68 | case BTE_SEQ_OSC: return "OSC"; | |||
| 69 | case BTE_SEQ_SCI: return "SCI"; | |||
| 70 | case BTE_SEQ_APC: return "APC"; | |||
| 71 | case BTE_SEQ_PM: return "PM"; | |||
| 72 | case BTE_SEQ_SOS: return "SOS"; | |||
| 73 | default: | |||
| 74 | assert(false)(static_cast <bool> (false) ? void (0) : __assert_fail ( "false", __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__ )); | |||
| 75 | } | |||
| 76 | } | |||
| 77 | ||||
| 78 | static constexpr char const* | |||
| 79 | cmd_to_str(unsigned int command) noexcept | |||
| 80 | { | |||
| 81 | switch (command) { | |||
| 82 | #define _BTE_CMD(cmd) case BTE_CMD_##cmd: return #cmd; | |||
| 83 | #define _BTE_NOP(cmd) _BTE_CMD(cmd) | |||
| 84 | #include "parser-cmd.hh" | |||
| 85 | #undef _BTE_CMD | |||
| 86 | #undef _BTE_NOP | |||
| 87 | default: | |||
| 88 | return nullptr; | |||
| 89 | } | |||
| 90 | } | |||
| 91 | ||||
| 92 | #if 0 | |||
| 93 | static constexepr char const* | |||
| 94 | charset_alias_to_str(unsigned int cs) noexcept | |||
| 95 | { | |||
| 96 | switch (cs) { | |||
| 97 | #define _BTE_CHARSET_PASTE(name) | |||
| 98 | #define _BTE_CHARSET(name) _BTE_CHARSET_PASTE(name) | |||
| 99 | #define _BTE_CHARSET_ALIAS_PASTE(name1,name2) case BTE_CHARSET_##name1: return #name1 "(" ## #name2 ## ")"; | |||
| 100 | #define _BTE_CHARSET_ALIAS(name1,name2) | |||
| 101 | #include "parser-charset.hh" | |||
| 102 | #undef _BTE_CHARSET_PASTE | |||
| 103 | #undef _BTE_CHARSET | |||
| 104 | #undef _BTE_CHARSET_ALIAS_PASTE | |||
| 105 | #undef _BTE_CHARSET_ALIAS | |||
| 106 | default: | |||
| 107 | return nullptr; /* not an alias */ | |||
| 108 | } | |||
| 109 | } | |||
| 110 | ||||
| 111 | static constexpr char const* | |||
| 112 | charset_to_str(unsigned int cs) noexcept | |||
| 113 | { | |||
| 114 | auto alias = charset_alias_to_str(cs); | |||
| 115 | if (alias) | |||
| 116 | return alias; | |||
| 117 | ||||
| 118 | switch (cs) { | |||
| 119 | #define _BTE_CHARSET_PASTE(name) case BTE_CHARSET_##name: return #name; | |||
| 120 | #define _BTE_CHARSET(name) _BTE_CHARSET_PASTE(name) | |||
| 121 | #define _BTE_CHARSET_ALIAS_PASTE(name1,name2) | |||
| 122 | #define _BTE_CHARSET_ALIAS(name1,name2) | |||
| 123 | #include "parser-charset.hh" | |||
| 124 | #undef _BTE_CHARSET_PASTE | |||
| 125 | #undef _BTE_CHARSET | |||
| 126 | #undef _BTE_CHARSET_ALIAS_PASTE | |||
| 127 | #undef _BTE_CHARSET_ALIAS | |||
| 128 | default: | |||
| 129 | static char buf[32]; | |||
| 130 | snprintf(buf, sizeof(buf), "UNKOWN(%u)", cs); | |||
| 131 | return buf; | |||
| 132 | } | |||
| 133 | } | |||
| 134 | #endif | |||
| 135 | ||||
| 136 | class PrettyPrinter { | |||
| 137 | private: | |||
| 138 | std::string m_str; | |||
| 139 | bool m_plain; | |||
| 140 | bool m_codepoints; | |||
| 141 | ||||
| 142 | inline constexpr bool plain() const noexcept { return m_plain; } | |||
| 143 | ||||
| 144 | class Attribute { | |||
| 145 | public: | |||
| 146 | Attribute(PrettyPrinter* printer, | |||
| 147 | std::string const& intro, | |||
| 148 | std::string const& outro) noexcept | |||
| 149 | : m_printer{printer} | |||
| 150 | , m_outro{outro} { | |||
| 151 | if (!m_printer->plain()) | |||
| 152 | m_printer->m_str.append(intro); | |||
| 153 | } | |||
| 154 | ||||
| 155 | ~Attribute() noexcept | |||
| 156 | { | |||
| 157 | if (!m_printer->plain()) | |||
| 158 | m_printer->m_str.append(m_outro); | |||
| 159 | } | |||
| 160 | ||||
| 161 | private: | |||
| 162 | PrettyPrinter* m_printer; | |||
| 163 | std::string m_outro; | |||
| 164 | }; // class Attribute | |||
| 165 | ||||
| 166 | class ReverseAttr : private Attribute { | |||
| 167 | public: | |||
| 168 | ReverseAttr(PrettyPrinter* printer) | |||
| 169 | : Attribute(printer, "\e[7m"s, "\e[27m"s) | |||
| 170 | { } | |||
| 171 | }; | |||
| 172 | ||||
| 173 | class RedAttr : private Attribute { | |||
| 174 | public: | |||
| 175 | RedAttr(PrettyPrinter* printer) | |||
| 176 | : Attribute(printer, "\e[7;31m"s, "\e[27;39m"s) | |||
| 177 | { } | |||
| 178 | }; | |||
| 179 | ||||
| 180 | void | |||
| 181 | print_params(bte::parser::Sequence const& seq) noexcept | |||
| 182 | { | |||
| 183 | auto const size = seq.size(); | |||
| 184 | if (size > 0) | |||
| 185 | m_str.push_back(' '); | |||
| 186 | ||||
| 187 | for (unsigned int i = 0; i < size; i++) { | |||
| 188 | if (!seq.param_default(i)) | |||
| 189 | print_format("%d", seq.param(i)); | |||
| 190 | if (i + 1 < size) | |||
| 191 | m_str.push_back(seq.param_nonfinal(i) ? ':' : ';'); | |||
| 192 | } | |||
| 193 | } | |||
| 194 | ||||
| 195 | void | |||
| 196 | print_pintro(bte::parser::Sequence const& seq) noexcept | |||
| 197 | { | |||
| 198 | auto const type = seq.type(); | |||
| 199 | if (type != BTE_SEQ_CSI && | |||
| 200 | type != BTE_SEQ_DCS) | |||
| 201 | return; | |||
| 202 | ||||
| 203 | auto const p = seq.intermediates() & 0x7; | |||
| 204 | if (p == 0) | |||
| 205 | return; | |||
| 206 | ||||
| 207 | m_str.push_back(' '); | |||
| 208 | m_str.push_back(char(0x40 - p)); | |||
| 209 | } | |||
| 210 | ||||
| 211 | void | |||
| 212 | print_intermediates(bte::parser::Sequence const& seq) noexcept | |||
| 213 | { | |||
| 214 | auto const type = seq.type(); | |||
| 215 | auto intermediates = seq.intermediates(); | |||
| 216 | if (type == BTE_SEQ_CSI || | |||
| 217 | type == BTE_SEQ_DCS) | |||
| 218 | intermediates = intermediates >> 3; /* remove pintro */ | |||
| 219 | ||||
| 220 | while (intermediates != 0) { | |||
| 221 | unsigned int i = intermediates & 0x1f; | |||
| 222 | char c = 0x20 + i - 1; | |||
| 223 | ||||
| 224 | m_str.push_back(' '); | |||
| 225 | if (c == 0x20) | |||
| 226 | m_str.append("SP"s); | |||
| 227 | else | |||
| 228 | m_str.push_back(c); | |||
| 229 | ||||
| 230 | intermediates = intermediates >> 5; | |||
| 231 | } | |||
| 232 | } | |||
| 233 | ||||
| 234 | void | |||
| 235 | print_unichar(uint32_t c) noexcept | |||
| 236 | { | |||
| 237 | char buf[7]; | |||
| 238 | auto len = g_unichar_to_utf8(c, buf); | |||
| 239 | m_str.append(buf, len); | |||
| 240 | } | |||
| 241 | ||||
| 242 | G_GNUC_PRINTF(2, 3)__attribute__((__format__ (__printf__, 2, 3))) | |||
| 243 | void | |||
| 244 | print_format(char const* format, | |||
| 245 | ...) | |||
| 246 | { | |||
| 247 | char buf[256]; | |||
| 248 | va_list args; | |||
| 249 | va_start(args, format)__builtin_va_start(args, format); | |||
| 250 | auto len = g_vsnprintf(buf, sizeof(buf), format, args); | |||
| 251 | va_end(args)__builtin_va_end(args); | |||
| 252 | ||||
| 253 | m_str.append(buf, len); | |||
| 254 | } | |||
| 255 | ||||
| 256 | void | |||
| 257 | print_string(bte::parser::Sequence const& seq) noexcept | |||
| 258 | { | |||
| 259 | auto u8str = seq.string_param(); | |||
| 260 | ||||
| 261 | m_str.push_back('\"'); | |||
| 262 | m_str.append(u8str); | |||
| 263 | m_str.push_back('\"'); | |||
| 264 | ||||
| 265 | g_free(u8str); | |||
| 266 | } | |||
| 267 | ||||
| 268 | void | |||
| 269 | print_seq_and_params(bte::parser::Sequence const& seq) noexcept | |||
| 270 | { | |||
| 271 | ReverseAttr attr(this); | |||
| 272 | ||||
| 273 | if (seq.command() != BTE_CMD_NONE) { | |||
| 274 | m_str.push_back('{'); | |||
| 275 | m_str.append(cmd_to_str(seq.command())); | |||
| 276 | print_params(seq); | |||
| 277 | m_str.push_back('}'); | |||
| 278 | } else { | |||
| 279 | m_str.push_back('{'); | |||
| 280 | m_str.append(seq_to_str(seq.type())); | |||
| 281 | print_pintro(seq); | |||
| 282 | print_params(seq); | |||
| 283 | print_intermediates(seq); | |||
| 284 | m_str.push_back(' '); | |||
| 285 | m_str.push_back(seq.terminator()); | |||
| 286 | m_str.push_back('}'); | |||
| 287 | } | |||
| 288 | } | |||
| 289 | ||||
| 290 | void | |||
| 291 | print_seq(bte::parser::Sequence const& seq) noexcept | |||
| 292 | { | |||
| 293 | switch (seq.type()) { | |||
| 294 | case BTE_SEQ_NONE: { | |||
| 295 | RedAttr attr(this); | |||
| 296 | m_str.append("{NONE}"s); | |||
| 297 | break; | |||
| 298 | } | |||
| 299 | ||||
| 300 | case BTE_SEQ_IGNORE: { | |||
| 301 | RedAttr attr(this); | |||
| 302 | m_str.append("{IGNORE}"s); | |||
| 303 | break; | |||
| 304 | } | |||
| 305 | ||||
| 306 | case BTE_SEQ_GRAPHIC: { | |||
| 307 | auto const terminator = seq.terminator(); | |||
| 308 | bool const printable = g_unichar_isprint(terminator); | |||
| 309 | if (m_codepoints || !printable) { | |||
| 310 | if (printable) { | |||
| 311 | char ubuf[7]; | |||
| 312 | ubuf[g_unichar_to_utf8(terminator, ubuf)] = 0; | |||
| 313 | print_format("[%04X %s]", terminator, ubuf); | |||
| 314 | } else { | |||
| 315 | print_format("[%04X]", terminator); | |||
| 316 | } | |||
| 317 | } else { | |||
| 318 | print_unichar(terminator); | |||
| 319 | } | |||
| 320 | break; | |||
| 321 | } | |||
| 322 | ||||
| 323 | case BTE_SEQ_CONTROL: | |||
| 324 | case BTE_SEQ_ESCAPE: { | |||
| 325 | ReverseAttr attr(this); | |||
| 326 | print_format("{%s}", cmd_to_str(seq.command())); | |||
| 327 | break; | |||
| 328 | } | |||
| 329 | ||||
| 330 | case BTE_SEQ_CSI: | |||
| 331 | case BTE_SEQ_DCS: { | |||
| 332 | print_seq_and_params(seq); | |||
| 333 | break; | |||
| 334 | } | |||
| 335 | ||||
| 336 | case BTE_SEQ_OSC: { | |||
| 337 | ReverseAttr attr(this); | |||
| 338 | m_str.append("{OSC "s); | |||
| 339 | print_string(seq); | |||
| 340 | m_str.push_back('}'); | |||
| 341 | break; | |||
| 342 | } | |||
| 343 | ||||
| 344 | case BTE_SEQ_SCI: { | |||
| 345 | auto const terminator = seq.terminator(); | |||
| 346 | if (terminator <= 0x20) | |||
| 347 | print_format("{SCI %d/%d}", | |||
| 348 | terminator / 16, | |||
| 349 | terminator % 16); | |||
| 350 | else | |||
| 351 | print_format("{SCI %c}", terminator); | |||
| 352 | break; | |||
| 353 | } | |||
| 354 | ||||
| 355 | default: | |||
| 356 | assert(false)(static_cast <bool> (false) ? void (0) : __assert_fail ( "false", __builtin_FILE (), __builtin_LINE (), __extension__ __PRETTY_FUNCTION__ )); | |||
| 357 | } | |||
| 358 | } | |||
| 359 | ||||
| 360 | void | |||
| 361 | printout() noexcept | |||
| 362 | { | |||
| 363 | m_str.push_back('\n'); | |||
| 364 | #pragma GCC diagnostic push | |||
| 365 | #pragma GCC diagnostic ignored "-Wunused-result" | |||
| 366 | write(STDOUT_FILENO1, m_str.data(), m_str.size()); | |||
| 367 | #pragma GCC diagnostic pop | |||
| 368 | m_str.clear(); | |||
| 369 | } | |||
| 370 | ||||
| 371 | public: | |||
| 372 | ||||
| 373 | PrettyPrinter(bool plain, | |||
| 374 | bool codepoints) noexcept | |||
| 375 | : m_plain{plain} | |||
| 376 | , m_codepoints{codepoints} | |||
| 377 | { | |||
| 378 | } | |||
| 379 | ||||
| 380 | ~PrettyPrinter() noexcept | |||
| 381 | { | |||
| 382 | printout(); | |||
| 383 | } | |||
| 384 | ||||
| 385 | void operator()(bte::parser::Sequence const& seq) noexcept | |||
| 386 | { | |||
| 387 | print_seq(seq); | |||
| 388 | if (seq.command() == BTE_CMD_LF) | |||
| 389 | printout(); | |||
| 390 | } | |||
| 391 | ||||
| 392 | }; // class PrettyPrinter | |||
| 393 | ||||
| 394 | class Linter { | |||
| 395 | private: | |||
| 396 | G_GNUC_PRINTF(2, 3)__attribute__((__format__ (__printf__, 2, 3))) | |||
| 397 | void | |||
| 398 | warn(char const* format, | |||
| 399 | ...) const noexcept | |||
| 400 | { | |||
| 401 | va_list args; | |||
| 402 | va_start(args, format)__builtin_va_start(args, format); | |||
| 403 | char* str = g_strdup_vprintf(format, args); | |||
| 404 | va_end(args)__builtin_va_end(args); | |||
| 405 | g_printerr("WARNING: %s\n", str); | |||
| 406 | g_free(str); | |||
| 407 | } | |||
| 408 | ||||
| 409 | void | |||
| 410 | warn_deprecated(int cmd, | |||
| 411 | int replacement_cmd) const noexcept | |||
| 412 | { | |||
| 413 | warn("%s is deprecated; use %s instead", | |||
| 414 | cmd_to_str(cmd), | |||
| 415 | cmd_to_str(replacement_cmd)); | |||
| 416 | } | |||
| 417 | ||||
| 418 | void | |||
| 419 | check_sgr_number(int sgr) noexcept | |||
| 420 | { | |||
| 421 | switch (sgr) { | |||
| 422 | case -1: | |||
| 423 | #define _BTE_SGR(name, value) case value: | |||
| 424 | #define _BTE_NGR(...) | |||
| 425 | #include "parser-sgr.hh" | |||
| 426 | #undef _BTE_SGR | |||
| 427 | #undef _BTE_NGR | |||
| 428 | case BTE_SGR_SET_FORE_LEGACY_START+1 ... BTE_SGR_SET_FORE_LEGACY_END-1: | |||
| 429 | case BTE_SGR_SET_FORE_LEGACY_BRIGHT_START+1 ... BTE_SGR_SET_FORE_LEGACY_BRIGHT_END-1: | |||
| 430 | case BTE_SGR_SET_BACK_LEGACY_START+1 ... BTE_SGR_SET_BACK_LEGACY_END-1: | |||
| 431 | case BTE_SGR_SET_BACK_LEGACY_BRIGHT_START+1 ... BTE_SGR_SET_BACK_LEGACY_BRIGHT_END-1: | |||
| 432 | break; | |||
| 433 | ||||
| 434 | #define _BTE_SGR(...) | |||
| 435 | #define _BTE_NGR(name, value) case value: | |||
| 436 | #include "parser-sgr.hh" | |||
| 437 | #undef _BTE_SGR | |||
| 438 | #undef _BTE_NGR | |||
| 439 | case BTE_SGR_SET_FONT_FIRST+1 ... BTE_SGR_SET_FONT_LAST-1: | |||
| 440 | warn("SGR %d is unsupported", sgr); | |||
| 441 | break; | |||
| 442 | ||||
| 443 | default: | |||
| 444 | warn("SGR %d is unknown", sgr); | |||
| 445 | break; | |||
| 446 | ||||
| 447 | } | |||
| 448 | } | |||
| 449 | ||||
| 450 | void | |||
| 451 | check_sgr_color(bte::parser::Sequence const& seq, | |||
| 452 | unsigned int& idx) noexcept | |||
| 453 | { | |||
| 454 | auto const sgr = seq.param(idx); | |||
| 455 | ||||
| 456 | /* Simplified and adapted from Terminal::seq_parse_sgr_color() */ | |||
| 457 | if (seq.param_nonfinal(idx)) { | |||
| 458 | /* Colon version */ | |||
| 459 | auto const param = seq.param(++idx); | |||
| 460 | switch (param) { | |||
| 461 | case 2: { | |||
| 462 | auto const n = seq.next(idx) - idx; | |||
| 463 | if (n < 4) | |||
| 464 | warn("SGR %d:2 not enough parameters", sgr); | |||
| 465 | else if (n == 4) | |||
| 466 | warn("SGR %d:2:r:g:b is deprecated; use SGR %d:2::r:g:b instead", | |||
| 467 | sgr, sgr); | |||
| 468 | break; | |||
| 469 | } | |||
| 470 | case 5: { | |||
| 471 | auto const n = seq.next(idx) - idx; | |||
| 472 | if (n < 2) | |||
| 473 | warn("SGR %d:5 not enough parameters", sgr); | |||
| 474 | break; | |||
| 475 | } | |||
| 476 | case -1: | |||
| 477 | warn("SGR %d does not admit default parameters", sgr); | |||
| 478 | break; | |||
| 479 | case 0: | |||
| 480 | case 1: | |||
| 481 | case 3: | |||
| 482 | case 4: | |||
| 483 | warn("SGR %d:%d is unsupported", sgr, param); | |||
| 484 | break; | |||
| 485 | default: | |||
| 486 | warn("SGR %d:%d is unknown", sgr, param); | |||
| 487 | } | |||
| 488 | } else { | |||
| 489 | /* Semicolon version */ | |||
| 490 | idx = seq.next(idx); | |||
| 491 | auto const param = seq.param(idx); | |||
| 492 | switch (param) { | |||
| 493 | case 2: | |||
| 494 | /* Consume 3 more parameters */ | |||
| 495 | idx = seq.next(idx); | |||
| 496 | idx = seq.next(idx); | |||
| 497 | idx = seq.next(idx); | |||
| 498 | warn("SGR %d;%d;r;g;b is deprecated; use SGR %d:%d::r:g:b instead", | |||
| 499 | sgr, param, sgr, param); | |||
| 500 | break; | |||
| 501 | case 5: | |||
| 502 | /* Consume 1 more parameter */ | |||
| 503 | idx = seq.next(idx); | |||
| 504 | warn("SGR %d;%d;index is deprecated; use SGR %d:%d:index instead", | |||
| 505 | sgr, param, sgr, param); | |||
| 506 | break; | |||
| 507 | case -1: | |||
| 508 | warn("SGR %d does not admit default parameters", sgr); | |||
| 509 | break; | |||
| 510 | case 0: | |||
| 511 | case 1: | |||
| 512 | case 3: | |||
| 513 | case 4: | |||
| 514 | warn("SGR %d;%d;... is unsupported; use SGR %d:%d:... instead", | |||
| 515 | sgr, param, sgr, param); | |||
| 516 | break; | |||
| 517 | default: | |||
| 518 | warn("SGR %d;%d is unknown", sgr, param); | |||
| 519 | break; | |||
| 520 | } | |||
| 521 | } | |||
| 522 | } | |||
| 523 | ||||
| 524 | void | |||
| 525 | check_sgr_underline(bte::parser::Sequence const& seq, | |||
| 526 | unsigned int idx) noexcept | |||
| 527 | { | |||
| 528 | auto const sgr = seq.param(idx); | |||
| 529 | ||||
| 530 | int param = 1; | |||
| 531 | /* If we have a subparameter, get it */ | |||
| 532 | if (seq.param_nonfinal(idx)) | |||
| 533 | param = seq.param(idx + 1); | |||
| 534 | ||||
| 535 | switch (param) { | |||
| 536 | case -1: | |||
| 537 | case 0: | |||
| 538 | case 1: | |||
| 539 | case 2: | |||
| 540 | case 3: | |||
| 541 | break; | |||
| 542 | case 4: | |||
| 543 | case 5: | |||
| 544 | warn("SGR %d:%d is unsupported", sgr, param); | |||
| 545 | break; | |||
| 546 | default: | |||
| 547 | warn("SGR %d:%d is unknown", sgr, param); | |||
| 548 | break; | |||
| 549 | } | |||
| 550 | } | |||
| 551 | ||||
| 552 | void | |||
| 553 | check_sgr(bte::parser::Sequence const& seq) noexcept | |||
| 554 | { | |||
| 555 | for (unsigned int i = 0; i < seq.size(); i = seq.next(i)) { | |||
| 556 | auto const param = seq.param(i, 0); | |||
| 557 | ||||
| 558 | check_sgr_number(param); | |||
| 559 | ||||
| 560 | switch (param) { | |||
| 561 | case BTE_SGR_SET_UNDERLINE: | |||
| 562 | check_sgr_underline(seq, i); | |||
| 563 | break; | |||
| 564 | ||||
| 565 | case BTE_SGR_SET_FORE_SPEC: | |||
| 566 | case BTE_SGR_SET_BACK_SPEC: | |||
| 567 | case BTE_SGR_SET_DECO_SPEC: | |||
| 568 | check_sgr_color(seq, i); | |||
| 569 | break; | |||
| 570 | ||||
| 571 | default: | |||
| 572 | if (seq.param_nonfinal(i)) | |||
| 573 | warn("SGR %d does not admit subparameters", param); | |||
| 574 | break; | |||
| 575 | } | |||
| 576 | } | |||
| 577 | } | |||
| 578 | ||||
| 579 | public: | |||
| 580 | constexpr Linter() noexcept = default; | |||
| 581 | ~Linter() noexcept = default; | |||
| 582 | ||||
| 583 | void operator()(bte::parser::Sequence const& seq) noexcept | |||
| 584 | { | |||
| 585 | auto cmd = seq.command(); | |||
| 586 | switch (cmd) { | |||
| 587 | case BTE_CMD_OSC: | |||
| 588 | if (seq.terminator() == 7 /* BEL */) | |||
| 589 | warn("OSC terminated by BEL may be ignored; use ST (ESC \\) instead."); | |||
| 590 | break; | |||
| 591 | ||||
| 592 | case BTE_CMD_DECSLRM_OR_SCOSC: | |||
| 593 | cmd = BTE_CMD_SCOSC; | |||
| 594 | [[fallthrough]]; | |||
| 595 | case BTE_CMD_SCOSC: | |||
| 596 | warn_deprecated(cmd, BTE_CMD_DECSC); | |||
| 597 | break; | |||
| 598 | ||||
| 599 | case BTE_CMD_SCORC: | |||
| 600 | warn_deprecated(cmd, BTE_CMD_DECRC); | |||
| 601 | break; | |||
| 602 | ||||
| 603 | case BTE_CMD_SGR: | |||
| 604 | check_sgr(seq); | |||
| 605 | break; | |||
| 606 | ||||
| 607 | default: | |||
| 608 | if (cmd >= BTE_CMD_NOP_FIRST) | |||
| 609 | warn("%s is unimplemented", cmd_to_str(cmd)); | |||
| 610 | break; | |||
| 611 | } | |||
| 612 | } | |||
| 613 | ||||
| 614 | }; // class Linter | |||
| 615 | ||||
| 616 | class Sink { | |||
| 617 | public: | |||
| 618 | void operator()(bte::parser::Sequence const& seq) noexcept { } | |||
| 619 | ||||
| 620 | }; // class Sink | |||
| 621 | ||||
| 622 | class Processor { | |||
| 623 | private: | |||
| 624 | gsize m_seq_stats[BTE_SEQ_N]; | |||
| 625 | gsize m_cmd_stats[BTE_CMD_N]; | |||
| 626 | GArray* m_bench_times; | |||
| 627 | ||||
| 628 | template<class Functor> | |||
| 629 | void | |||
| 630 | process_file_utf8(int fd, | |||
| 631 | Functor& func) | |||
| 632 | { | |||
| 633 | bte::parser::Parser parser{}; | |||
| 634 | bte::parser::Sequence seq{parser}; | |||
| 635 | ||||
| 636 | gsize const buf_size = 16384; | |||
| 637 | guchar* buf = g_new0(guchar, buf_size)(guchar *) (__extension__ ({ gsize __n = (gsize) (buf_size); gsize __s = sizeof (guchar); gpointer __p; if (__s == 1) __p = g_malloc0 (__n); else if (__builtin_constant_p (__n) && (__s == 0 || __n <= (9223372036854775807L *2UL+1UL) / __s)) __p = g_malloc0 (__n * __s); else __p = g_malloc0_n (__n, __s); __p ; })); | |||
| 638 | ||||
| 639 | auto start_time = g_get_monotonic_time(); | |||
| 640 | ||||
| 641 | bte::base::UTF8Decoder decoder; | |||
| 642 | ||||
| 643 | gsize buf_start = 0; | |||
| 644 | for (;;) { | |||
| 645 | auto len = read(fd, buf + buf_start, buf_size - buf_start); | |||
| 646 | if (!len) | |||
| 647 | break; | |||
| 648 | if (len == -1) { | |||
| 649 | if (errno(*__errno_location ()) == EAGAIN11) | |||
| 650 | continue; | |||
| 651 | break; | |||
| 652 | } | |||
| 653 | ||||
| 654 | auto const bufend = buf + len; | |||
| 655 | for (auto sptr = buf; sptr < bufend; ++sptr) { | |||
| 656 | switch (decoder.decode(*sptr)) { | |||
| 657 | case bte::base::UTF8Decoder::REJECT_REWIND: | |||
| 658 | /* Rewind the stream. | |||
| 659 | * Note that this will never lead to a loop, since in the | |||
| 660 | * next round this byte *will* be consumed. | |||
| 661 | */ | |||
| 662 | --sptr; | |||
| 663 | [[fallthrough]]; | |||
| 664 | case bte::base::UTF8Decoder::REJECT: | |||
| 665 | decoder.reset(); | |||
| 666 | /* Fall through to insert the U+FFFD replacement character. */ | |||
| 667 | [[fallthrough]]; | |||
| 668 | case bte::base::UTF8Decoder::ACCEPT: { | |||
| 669 | auto ret = parser.feed(decoder.codepoint()); | |||
| 670 | if (G_UNLIKELY(ret < 0)(__builtin_expect (__extension__ ({ int _g_boolean_var_10 = 0 ; if (ret < 0) _g_boolean_var_10 = 1; _g_boolean_var_10; } ), 0))) { | |||
| 671 | g_printerr("Parser error!\n"); | |||
| 672 | goto out; | |||
| 673 | } | |||
| 674 | ||||
| 675 | m_seq_stats[ret]++; | |||
| 676 | if (ret != BTE_SEQ_NONE) { | |||
| 677 | m_cmd_stats[seq.command()]++; | |||
| 678 | func(seq); | |||
| 679 | } | |||
| 680 | break; | |||
| 681 | } | |||
| 682 | ||||
| 683 | default: | |||
| 684 | break; | |||
| 685 | } | |||
| 686 | } | |||
| 687 | } | |||
| 688 | ||||
| 689 | out: | |||
| 690 | ||||
| 691 | int64_t time_spent = g_get_monotonic_time() - start_time; | |||
| 692 | g_array_append_val(m_bench_times, time_spent)g_array_append_vals (m_bench_times, &(time_spent), 1); | |||
| 693 | ||||
| 694 | g_free(buf); | |||
| 695 | } | |||
| 696 | ||||
| 697 | template<class Functor> | |||
| 698 | bool | |||
| 699 | process_file(int fd, | |||
| 700 | int repeat, | |||
| 701 | Functor& func) | |||
| 702 | { | |||
| 703 | if (fd == STDIN_FILENO0 && repeat != 1) { | |||
| 704 | g_printerr("Cannot consume STDIN more than once\n"); | |||
| 705 | return false; | |||
| 706 | } | |||
| 707 | ||||
| 708 | for (auto i = 0; i < repeat; ++i) { | |||
| 709 | if (i > 0 && lseek(fd, 0, SEEK_SET0) != 0) { | |||
| 710 | auto errsv = bte::libc::ErrnoSaver{}; | |||
| 711 | g_printerr("Failed to seek: %s\n", g_strerror(errsv)); | |||
| 712 | return false; | |||
| 713 | } | |||
| 714 | ||||
| 715 | process_file_utf8(fd, func); | |||
| 716 | } | |||
| 717 | ||||
| 718 | return true; | |||
| 719 | } | |||
| 720 | ||||
| 721 | public: | |||
| 722 | ||||
| 723 | Processor() noexcept | |||
| 724 | { | |||
| 725 | memset(&m_seq_stats, 0, sizeof(m_seq_stats)); | |||
| 726 | memset(&m_cmd_stats, 0, sizeof(m_cmd_stats)); | |||
| 727 | m_bench_times = g_array_new(false, true, sizeof(int64_t)); | |||
| 728 | } | |||
| 729 | ||||
| 730 | ~Processor() noexcept | |||
| 731 | { | |||
| 732 | g_array_free(m_bench_times, true); | |||
| 733 | } | |||
| 734 | ||||
| 735 | template<class Functor> | |||
| 736 | bool | |||
| 737 | process_files(char const* const* filenames, | |||
| 738 | int repeat, | |||
| 739 | Functor& func) | |||
| 740 | { | |||
| 741 | bool r = true; | |||
| 742 | if (filenames != nullptr) { | |||
| 743 | for (auto i = 0; filenames[i] != nullptr; i++) { | |||
| 744 | char const* filename = filenames[i]; | |||
| 745 | ||||
| 746 | int fd = -1; | |||
| 747 | if (g_str_equal(filename, "-")(strcmp ((const char *) (filename), (const char *) ("-")) == 0 )) { | |||
| 748 | fd = STDIN_FILENO0; | |||
| 749 | } else { | |||
| 750 | fd = open(filename, O_RDONLY00); | |||
| 751 | if (fd == -1) { | |||
| 752 | auto errsv = bte::libc::ErrnoSaver{}; | |||
| 753 | g_printerr("Error opening file %s: %s\n", | |||
| 754 | filename, g_strerror(errsv)); | |||
| 755 | } | |||
| 756 | } | |||
| 757 | if (fd != -1) { | |||
| 758 | r = process_file(fd, repeat, func); | |||
| 759 | if (fd != STDIN_FILENO0) | |||
| 760 | close(fd); | |||
| 761 | if (!r) | |||
| 762 | break; | |||
| 763 | } | |||
| 764 | } | |||
| 765 | } else { | |||
| 766 | r = process_file(STDIN_FILENO0, repeat, func); | |||
| 767 | } | |||
| 768 | ||||
| 769 | return r; | |||
| 770 | } | |||
| 771 | ||||
| 772 | void print_statistics() const noexcept | |||
| 773 | { | |||
| 774 | for (unsigned int s = BTE_SEQ_NONE + 1; s < BTE_SEQ_N; s++) { | |||
| 775 | g_printerr("%\'16" G_GSIZE_FORMAT"lu" " %s\n", m_seq_stats[s], seq_to_str(s)); | |||
| 776 | } | |||
| 777 | ||||
| 778 | g_printerr("\n"); | |||
| 779 | for (unsigned int s = 0; s < BTE_CMD_N; s++) { | |||
| 780 | if (m_cmd_stats[s] > 0) { | |||
| 781 | g_printerr("%\'16" G_GSIZE_FORMAT"lu" " %s%s\n", | |||
| 782 | m_cmd_stats[s], | |||
| 783 | cmd_to_str(s), | |||
| 784 | s >= BTE_CMD_NOP_FIRST ? " [NOP]" : ""); | |||
| 785 | } | |||
| 786 | } | |||
| 787 | } | |||
| 788 | ||||
| 789 | void print_benchmark() const noexcept | |||
| 790 | { | |||
| 791 | g_array_sort(m_bench_times, | |||
| 792 | [](void const* p1, void const* p2) -> int { | |||
| 793 | int64_t const t1 = *(int64_t const*)p1; | |||
| 794 | int64_t const t2 = *(int64_t const*)p2; | |||
| 795 | return t1 == t2 ? 0 : (t1 < t2 ? -1 : 1); | |||
| 796 | }); | |||
| 797 | ||||
| 798 | int64_t total_time = 0; | |||
| 799 | for (unsigned int i = 0; i < m_bench_times->len; ++i) | |||
| 800 | total_time += g_array_index(m_bench_times, int64_t, i)(((int64_t*) (void *) (m_bench_times)->data) [(i)]); | |||
| 801 | ||||
| 802 | g_printerr("\nTimes: best %\'" G_GINT64_FORMAT"li" "µs " | |||
| 803 | "worst %\'" G_GINT64_FORMAT"li" "µs " | |||
| 804 | "average %\'" G_GINT64_FORMAT"li" "µs\n", | |||
| 805 | g_array_index(m_bench_times, int64_t, 0)(((int64_t*) (void *) (m_bench_times)->data) [(0)]), | |||
| 806 | g_array_index(m_bench_times, int64_t, m_bench_times->len - 1)(((int64_t*) (void *) (m_bench_times)->data) [(m_bench_times ->len - 1)]), | |||
| 807 | total_time / (int64_t)m_bench_times->len); | |||
| ||||
| 808 | for (unsigned int i = 0; i < m_bench_times->len; ++i) | |||
| 809 | g_printerr(" %\'" G_GINT64_FORMAT"li" "µs\n", | |||
| 810 | g_array_index(m_bench_times, int64_t, i)(((int64_t*) (void *) (m_bench_times)->data) [(i)])); | |||
| 811 | } | |||
| 812 | ||||
| 813 | }; // class Processor | |||
| 814 | ||||
| 815 | class Options { | |||
| 816 | private: | |||
| 817 | bool m_benchmark{false}; | |||
| 818 | bool m_codepoints{false}; | |||
| 819 | bool m_lint{false}; | |||
| 820 | bool m_plain{false}; | |||
| 821 | bool m_quiet{false}; | |||
| 822 | bool m_statistics{false}; | |||
| 823 | int m_repeat{1}; | |||
| 824 | char** m_filenames{nullptr}; | |||
| 825 | ||||
| 826 | template<typename T1, typename T2 = T1> | |||
| 827 | class OptionArg { | |||
| 828 | private: | |||
| 829 | T1* m_return_ptr; | |||
| 830 | T2 m_value; | |||
| 831 | public: | |||
| 832 | OptionArg(T1* ptr, T2 v) : m_return_ptr{ptr}, m_value{v} { } | |||
| 833 | ~OptionArg() { *m_return_ptr = m_value; } | |||
| 834 | ||||
| 835 | inline constexpr T2* ptr() noexcept { return &m_value; } | |||
| 836 | }; | |||
| 837 | ||||
| 838 | using BoolArg = OptionArg<bool, gboolean>; | |||
| 839 | using IntArg = OptionArg<int>; | |||
| 840 | using StrvArg = OptionArg<char**>; | |||
| 841 | ||||
| 842 | public: | |||
| 843 | ||||
| 844 | Options() noexcept = default; | |||
| 845 | Options(Options const&) = delete; | |||
| 846 | Options(Options&&) = delete; | |||
| 847 | ||||
| 848 | ~Options() { | |||
| 849 | if (m_filenames != nullptr) | |||
| 850 | g_strfreev(m_filenames); | |||
| 851 | } | |||
| 852 | ||||
| 853 | inline constexpr bool benchmark() const noexcept { return m_benchmark; } | |||
| 854 | inline constexpr bool codepoints() const noexcept { return m_codepoints; } | |||
| 855 | inline constexpr bool lint() const noexcept { return m_lint; } | |||
| 856 | inline constexpr bool plain() const noexcept { return m_plain; } | |||
| 857 | inline constexpr bool quiet() const noexcept { return m_quiet; } | |||
| 858 | inline constexpr bool statistics() const noexcept { return m_statistics; } | |||
| 859 | inline constexpr int repeat() const noexcept { return m_repeat; } | |||
| 860 | inline constexpr char const* const* filenames() const noexcept { return m_filenames; } | |||
| 861 | ||||
| 862 | bool parse(int argc, | |||
| 863 | char* argv[], | |||
| 864 | GError** error) noexcept | |||
| 865 | { | |||
| 866 | BoolArg benchmark{&m_benchmark, false}; | |||
| 867 | BoolArg codepoints{&m_codepoints, false}; | |||
| 868 | BoolArg lint{&m_lint, false}; | |||
| 869 | BoolArg plain{&m_plain, false}; | |||
| 870 | BoolArg quiet{&m_quiet, false}; | |||
| 871 | BoolArg statistics{&m_statistics, false}; | |||
| 872 | IntArg repeat{&m_repeat, 1}; | |||
| 873 | StrvArg filenames{&m_filenames, nullptr}; | |||
| 874 | GOptionEntry const entries[] = { | |||
| 875 | { .long_name = "benchmark", .short_name = 'b', .flags = 0, .arg = G_OPTION_ARG_NONE, .arg_data = benchmark.ptr(), | |||
| 876 | .description = "Measure time spent parsing each file", .arg_description = nullptr }, | |||
| 877 | ||||
| 878 | { .long_name = "codepoints", .short_name = 'u', .flags = 0, .arg = G_OPTION_ARG_NONE, .arg_data = codepoints.ptr(), | |||
| 879 | .description = "Output unicode code points by number", .arg_description = nullptr }, | |||
| 880 | ||||
| 881 | { .long_name = "lint", .short_name = 'l', .flags = 0, .arg = G_OPTION_ARG_NONE, .arg_data = lint.ptr(), | |||
| 882 | .description = "Check input", .arg_description = nullptr }, | |||
| 883 | ||||
| 884 | { .long_name = "plain", .short_name = 'p', .flags = 0, .arg = G_OPTION_ARG_NONE, .arg_data = plain.ptr(), | |||
| 885 | .description = "Output plain text without attributes", .arg_description = nullptr }, | |||
| 886 | ||||
| 887 | { .long_name = "quiet", .short_name = 'q', .flags = 0, .arg = G_OPTION_ARG_NONE, .arg_data = quiet.ptr(), | |||
| 888 | .description = "Suppress output except for statistics and benchmark", .arg_description = nullptr }, | |||
| 889 | ||||
| 890 | { .long_name = "repeat", .short_name = 'r', .flags = 0, .arg = G_OPTION_ARG_INT, .arg_data = repeat.ptr(), | |||
| 891 | .description = "Repeat each file COUNT times", .arg_description = "COUNT" }, | |||
| 892 | ||||
| 893 | { .long_name = "statistics", .short_name = 's', .flags = 0, .arg = G_OPTION_ARG_NONE, .arg_data = statistics.ptr(), | |||
| 894 | .description = "Output statistics", .arg_description = nullptr }, | |||
| 895 | ||||
| 896 | { .long_name = G_OPTION_REMAINING"", .short_name = 0, .flags = 0, .arg = G_OPTION_ARG_FILENAME_ARRAY, .arg_data = filenames.ptr(), | |||
| 897 | .description = nullptr, .arg_description = nullptr }, | |||
| 898 | }; | |||
| 899 | ||||
| 900 | auto context = g_option_context_new("[FILE…] — parser cat"); | |||
| 901 | g_option_context_set_help_enabled(context, true); | |||
| 902 | g_option_context_add_main_entries(context, entries, nullptr); | |||
| 903 | ||||
| 904 | bool rv = g_option_context_parse(context, &argc, &argv, error); | |||
| 905 | g_option_context_free(context); | |||
| 906 | return rv; | |||
| 907 | } | |||
| 908 | }; // class Options | |||
| 909 | ||||
| 910 | int | |||
| 911 | main(int argc, | |||
| 912 | char *argv[]) | |||
| 913 | { | |||
| 914 | setlocale(LC_ALL6, ""); | |||
| 915 | _bte_debug_init_bte_external_debug_init(); | |||
| 916 | ||||
| 917 | Options options{}; | |||
| 918 | auto error = bte::glib::Error{}; | |||
| 919 | if (!options.parse(argc, argv, error)) { | |||
| ||||
| 920 | g_printerr("Failed to parse arguments: %s\n", error.message()); | |||
| 921 | return EXIT_FAILURE1; | |||
| 922 | } | |||
| 923 | ||||
| 924 | bool rv; | |||
| 925 | Processor proc{}; | |||
| 926 | if (options.lint()) { | |||
| 927 | Linter linter{}; | |||
| 928 | rv = proc.process_files(options.filenames(), 1, linter); | |||
| 929 | } else if (options.quiet()) { | |||
| 930 | Sink sink{}; | |||
| 931 | rv = proc.process_files(options.filenames(), options.repeat(), sink); | |||
| 932 | } else { | |||
| 933 | PrettyPrinter pp{options.plain(), options.codepoints()}; | |||
| 934 | rv = proc.process_files(options.filenames(), options.repeat(), pp); | |||
| 935 | } | |||
| 936 | ||||
| 937 | if (options.statistics()) | |||
| 938 | proc.print_statistics(); | |||
| 939 | if (options.benchmark()) | |||
| 940 | proc.print_benchmark(); | |||
| 941 | ||||
| 942 | return rv ? EXIT_SUCCESS0 : EXIT_FAILURE1; | |||
| 943 | } |