xbintsc 0.3.25 → 0.3.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/README.md +39 -3
  2. package/dist/src/cli/main.js +124 -30
  3. package/dist/src/cli/main.js.map +1 -1
  4. package/dist/src/codegen/generator/calls/access.js +8 -1
  5. package/dist/src/codegen/generator/calls/access.js.map +1 -1
  6. package/dist/src/codegen/generator/module.js +10 -0
  7. package/dist/src/codegen/generator/module.js.map +1 -1
  8. package/dist/src/codegen/generator/state.js +24 -0
  9. package/dist/src/codegen/generator/state.js.map +1 -1
  10. package/dist/src/codegen/generator/tables.d.ts +6 -0
  11. package/dist/src/codegen/generator/tables.js +38 -0
  12. package/dist/src/codegen/generator/tables.js.map +1 -1
  13. package/dist/src/driver/bundler/commonjs.d.ts +52 -0
  14. package/dist/src/driver/bundler/commonjs.js +493 -0
  15. package/dist/src/driver/bundler/commonjs.js.map +1 -0
  16. package/dist/src/driver/bundler/graph.js +14 -0
  17. package/dist/src/driver/bundler/graph.js.map +1 -1
  18. package/dist/src/driver/bundler/merge.js +14 -0
  19. package/dist/src/driver/bundler/merge.js.map +1 -1
  20. package/dist/src/driver/bundler/types.d.ts +8 -0
  21. package/dist/src/driver/compiler.d.ts +8 -1
  22. package/dist/src/driver/compiler.js +73 -5
  23. package/dist/src/driver/compiler.js.map +1 -1
  24. package/dist/src/driver/config.d.ts +53 -0
  25. package/dist/src/driver/config.js +141 -0
  26. package/dist/src/driver/config.js.map +1 -0
  27. package/dist/src/driver/icon.d.ts +62 -0
  28. package/dist/src/driver/icon.js +151 -0
  29. package/dist/src/driver/icon.js.map +1 -0
  30. package/dist/src/driver/index.d.ts +4 -0
  31. package/dist/src/driver/index.js +4 -0
  32. package/dist/src/driver/index.js.map +1 -1
  33. package/dist/src/driver/mac-bundle.d.ts +41 -0
  34. package/dist/src/driver/mac-bundle.js +123 -0
  35. package/dist/src/driver/mac-bundle.js.map +1 -0
  36. package/dist/src/driver/win-icon.d.ts +27 -0
  37. package/dist/src/driver/win-icon.js +80 -0
  38. package/dist/src/driver/win-icon.js.map +1 -0
  39. package/dist/src/parser/statements.js +17 -4
  40. package/dist/src/parser/statements.js.map +1 -1
  41. package/dist/tests/cli/main.test.js +46 -1
  42. package/dist/tests/cli/main.test.js.map +1 -1
  43. package/dist/tests/driver/config.test.d.ts +1 -0
  44. package/dist/tests/driver/config.test.js +124 -0
  45. package/dist/tests/driver/config.test.js.map +1 -0
  46. package/dist/tests/driver/icon.test.d.ts +1 -0
  47. package/dist/tests/driver/icon.test.js +421 -0
  48. package/dist/tests/driver/icon.test.js.map +1 -0
  49. package/dist/tests/driver/modules.test.js +109 -0
  50. package/dist/tests/driver/modules.test.js.map +1 -1
  51. package/dist/tests/e2e/fetch.test.d.ts +12 -0
  52. package/dist/tests/e2e/fetch.test.js +272 -0
  53. package/dist/tests/e2e/fetch.test.js.map +1 -0
  54. package/dist/tests/e2e/gc.test.d.ts +1 -0
  55. package/dist/tests/e2e/gc.test.js +168 -0
  56. package/dist/tests/e2e/gc.test.js.map +1 -0
  57. package/dist/tests/e2e/gui-dom.test.d.ts +5 -0
  58. package/dist/tests/e2e/gui-dom.test.js +102 -0
  59. package/dist/tests/e2e/gui-dom.test.js.map +1 -0
  60. package/dist/tests/e2e/gui-events.test.d.ts +5 -0
  61. package/dist/tests/e2e/gui-events.test.js +60 -0
  62. package/dist/tests/e2e/gui-events.test.js.map +1 -0
  63. package/dist/tests/e2e/gui-helpers.d.ts +31 -0
  64. package/dist/tests/e2e/gui-helpers.js +74 -0
  65. package/dist/tests/e2e/gui-helpers.js.map +1 -0
  66. package/dist/tests/e2e/gui-layout.test.d.ts +4 -0
  67. package/dist/tests/e2e/gui-layout.test.js +88 -0
  68. package/dist/tests/e2e/gui-layout.test.js.map +1 -0
  69. package/dist/tests/e2e/gui-lifecycle.test.d.ts +5 -0
  70. package/dist/tests/e2e/gui-lifecycle.test.js +58 -0
  71. package/dist/tests/e2e/gui-lifecycle.test.js.map +1 -0
  72. package/dist/tests/e2e/gui-paint.test.d.ts +5 -0
  73. package/dist/tests/e2e/gui-paint.test.js +111 -0
  74. package/dist/tests/e2e/gui-paint.test.js.map +1 -0
  75. package/dist/tests/e2e/gui-script.test.d.ts +5 -0
  76. package/dist/tests/e2e/gui-script.test.js +78 -0
  77. package/dist/tests/e2e/gui-script.test.js.map +1 -0
  78. package/dist/tests/e2e/gui-style.test.d.ts +5 -0
  79. package/dist/tests/e2e/gui-style.test.js +175 -0
  80. package/dist/tests/e2e/gui-style.test.js.map +1 -0
  81. package/dist/tests/e2e/harness.d.ts +2 -0
  82. package/dist/tests/e2e/harness.js +1 -0
  83. package/dist/tests/e2e/harness.js.map +1 -1
  84. package/dist/tests/e2e/icon.test.d.ts +7 -0
  85. package/dist/tests/e2e/icon.test.js +80 -0
  86. package/dist/tests/e2e/icon.test.js.map +1 -0
  87. package/dist/tests/e2e/npm-modules.test.d.ts +4 -1
  88. package/dist/tests/e2e/npm-modules.test.js +47 -1
  89. package/dist/tests/e2e/npm-modules.test.js.map +1 -1
  90. package/dist/tests/e2e/runtime-coverage-typed-array.test.d.ts +9 -0
  91. package/dist/tests/e2e/runtime-coverage-typed-array.test.js +140 -0
  92. package/dist/tests/e2e/runtime-coverage-typed-array.test.js.map +1 -0
  93. package/dist/tests/parser/statements.test.js +11 -0
  94. package/dist/tests/parser/statements.test.js.map +1 -1
  95. package/package.json +3 -2
  96. package/runtime/ext_gui/css.cpp +4 -517
  97. package/runtime/ext_gui/css_internal.h +27 -0
  98. package/runtime/ext_gui/css_match.cpp +195 -0
  99. package/runtime/ext_gui/css_selector.cpp +207 -0
  100. package/runtime/ext_gui/css_sheet.cpp +129 -0
  101. package/runtime/ext_gui/dom.cpp +7 -403
  102. package/runtime/ext_gui/dom_api.cpp +63 -1146
  103. package/runtime/ext_gui/dom_api_document.cpp +96 -0
  104. package/runtime/ext_gui/dom_api_events.cpp +123 -0
  105. package/runtime/ext_gui/dom_api_internal.h +135 -0
  106. package/runtime/ext_gui/dom_api_node.cpp +329 -0
  107. package/runtime/ext_gui/dom_api_node_read.cpp +294 -0
  108. package/runtime/ext_gui/dom_api_proto.cpp +135 -0
  109. package/runtime/ext_gui/dom_api_style.cpp +192 -0
  110. package/runtime/ext_gui/dom_internal.h +39 -0
  111. package/runtime/ext_gui/dom_parse.cpp +348 -0
  112. package/runtime/ext_gui/dom_serialize.cpp +54 -0
  113. package/runtime/ext_gui/gui.cpp +56 -286
  114. package/runtime/ext_gui/gui_events.cpp +294 -0
  115. package/runtime/ext_gui/gui_internal.h +29 -0
  116. package/runtime/ext_gui/layout.cpp +6 -597
  117. package/runtime/ext_gui/layout_flex.cpp +200 -0
  118. package/runtime/ext_gui/layout_flow.cpp +191 -0
  119. package/runtime/ext_gui/layout_flow_internal.h +149 -0
  120. package/runtime/ext_gui/layout_inline.cpp +181 -0
  121. package/runtime/ext_gui/layout_internal.h +20 -0
  122. package/runtime/ext_gui/renderer.cpp +15 -679
  123. package/runtime/ext_gui/renderer_image.cpp +233 -0
  124. package/runtime/ext_gui/renderer_internal.h +112 -0
  125. package/runtime/ext_gui/renderer_shaders.h +108 -0
  126. package/runtime/ext_gui/renderer_text.cpp +342 -0
  127. package/runtime/ext_gui/style.cpp +12 -660
  128. package/runtime/ext_gui/style_internal.h +33 -0
  129. package/runtime/ext_gui/style_print.cpp +113 -0
  130. package/runtime/ext_gui/style_ua.cpp +98 -0
  131. package/runtime/ext_gui/style_values.cpp +468 -0
  132. package/runtime/ext_gui/window.cpp +1 -0
  133. package/runtime/ext_node/buffer/parts/prototype.inc +1 -0
  134. package/runtime/ext_node/dgram/dgram.c +1 -0
  135. package/runtime/ext_node/events/events.c +1 -0
  136. package/runtime/ext_node/fs/fs_ops.c +10 -25
  137. package/runtime/ext_node/fs/glob.c +13 -30
  138. package/runtime/ext_node/fs/promises.c +1 -0
  139. package/runtime/ext_node/http/parts/prototypes.inc +5 -0
  140. package/runtime/ext_node/net/parts/prototypes.inc +2 -0
  141. package/runtime/ext_node/process/process.c +9 -7
  142. package/runtime/ext_node/stream/stream.c +1 -0
  143. package/runtime/ext_node/util/util.c +6 -6
  144. package/runtime/rt.h +16 -0
  145. package/runtime/rt_internal.h +67 -2
  146. package/runtime/xt_alloc.c +442 -5
  147. package/runtime/xt_builtins.c +1 -0
  148. package/runtime/xt_containers/access.inc +32 -1
  149. package/runtime/xt_fetch/buffer.inc +64 -0
  150. package/runtime/xt_fetch/headers.inc +312 -0
  151. package/runtime/xt_fetch/request.inc +386 -0
  152. package/runtime/xt_fetch/response.inc +107 -0
  153. package/runtime/xt_fetch/socket.inc +72 -0
  154. package/runtime/xt_fetch/url.inc +108 -0
  155. package/runtime/xt_fetch.c +39 -0
  156. package/runtime/xt_generator.c +95 -1
  157. package/runtime/xt_loop.c +35 -1
  158. package/runtime/xt_promise.c +46 -0
  159. package/runtime/xt_stdlib2/error.inc +1 -0
  160. package/runtime/xt_stdlib2/regexp-match.inc +8 -7
  161. package/runtime/xt_stdlib2/set.inc +1 -1
  162. package/runtime/xt_symbol.c +2 -0
  163. package/runtime/xt_typed_array/construction.inc +142 -0
  164. package/runtime/xt_typed_array/elements.inc +92 -0
  165. package/runtime/xt_typed_array/methods.inc +329 -0
  166. package/runtime/xt_typed_array.c +72 -0
  167. package/runtime/xt_values/number-format.inc +26 -0
  168. package/scripts/check-file-length.ts +85 -0
  169. package/src/cli/main.ts +137 -29
  170. package/src/codegen/generator/calls/access.ts +9 -1
  171. package/src/codegen/generator/module.ts +10 -0
  172. package/src/codegen/generator/state.ts +24 -0
  173. package/src/codegen/generator/tables.ts +40 -0
  174. package/src/driver/bundler/commonjs.ts +553 -0
  175. package/src/driver/bundler/graph.ts +14 -0
  176. package/src/driver/bundler/merge.ts +15 -0
  177. package/src/driver/bundler/types.ts +8 -0
  178. package/src/driver/compiler.ts +83 -5
  179. package/src/driver/config.ts +173 -0
  180. package/src/driver/icon.ts +184 -0
  181. package/src/driver/index.ts +30 -0
  182. package/src/driver/mac-bundle.ts +156 -0
  183. package/src/driver/win-icon.ts +96 -0
  184. package/src/parser/statements.ts +17 -4
  185. package/dist/tests/e2e/gui.test.d.ts +0 -12
  186. package/dist/tests/e2e/gui.test.js +0 -702
  187. package/dist/tests/e2e/gui.test.js.map +0 -1
@@ -1,10 +1,11 @@
1
1
  /*
2
- * xbintsc GUI engine — CSS parser, selector matcher and value parser.
2
+ * xbintsc GUI engine — CSS string helpers and value parsing (numbers, lengths,
3
+ * colors). Selector parsing lives in `css_selector.cpp`, at-rule/declaration
4
+ * parsing in `css_sheet.cpp` and matching in `css_match.cpp`.
3
5
  */
4
6
 
5
- #include "css.h"
7
+ #include "css_internal.h"
6
8
 
7
- #include <cctype>
8
9
  #include <cmath>
9
10
  #include <cstdlib>
10
11
  #include <cstring>
@@ -44,28 +45,6 @@ std::vector<std::string> xt_css_split_whitespace(const std::string &text) {
44
45
  return parts;
45
46
  }
46
47
 
47
- static bool isIdentStart(char c) {
48
- return std::isalpha((unsigned char)c) || c == '_' || c == '-' || (unsigned char)c >= 0x80;
49
- }
50
-
51
- static bool isIdentChar(char c) {
52
- return std::isalnum((unsigned char)c) || c == '_' || c == '-' || (unsigned char)c >= 0x80;
53
- }
54
-
55
- static std::string stripComments(const std::string &text) {
56
- std::string out;
57
- out.reserve(text.size());
58
- for (size_t i = 0; i < text.size(); i++) {
59
- if (text[i] == '/' && i + 1 < text.size() && text[i + 1] == '*') {
60
- size_t end = text.find("*/", i + 2);
61
- i = (end == std::string::npos) ? text.size() : end + 1;
62
- continue;
63
- }
64
- out.push_back(text[i]);
65
- }
66
- return out;
67
- }
68
-
69
48
  /* -- numbers / lengths / colors ------------------------------------------- */
70
49
 
71
50
  bool xt_css_parse_number(const std::string &value, float *out) {
@@ -248,496 +227,4 @@ Color xt_css_parse_color(const std::string &value) {
248
227
  return color;
249
228
  }
250
229
 
251
- /* -- selector parsing ----------------------------------------------------- */
252
-
253
- namespace {
254
-
255
- class SelectorParser {
256
- public:
257
- explicit SelectorParser(const std::string &text) : text_(text) {}
258
-
259
- std::vector<ComplexSelector> parseList() {
260
- std::vector<ComplexSelector> selectors;
261
- size_t start = 0;
262
- int depth = 0;
263
- for (size_t i = 0; i <= text_.size(); i++) {
264
- char c = (i < text_.size()) ? text_[i] : ',';
265
- if (c == '(' || c == '[') depth++;
266
- else if (c == ')' || c == ']') depth--;
267
- else if (c == ',' && depth == 0) {
268
- std::string part = text_.substr(start, i - start);
269
- ComplexSelector selector;
270
- if (parseComplex(part, &selector) && !selector.compounds.empty()) {
271
- selectors.push_back(std::move(selector));
272
- }
273
- start = i + 1;
274
- }
275
- }
276
- return selectors;
277
- }
278
-
279
- private:
280
- const std::string &text_;
281
-
282
- std::string readIdent(const std::string &text, size_t *index) {
283
- size_t start = *index;
284
- while (*index < text.size() && isIdentChar(text[*index])) (*index)++;
285
- return text.substr(start, *index - start);
286
- }
287
-
288
- /** Parse `div.foo#bar[a="b"]:pseudo` into one compound selector. */
289
- bool parseCompound(const std::string &text, size_t *index, CompoundSelector *compound) {
290
- bool any = false;
291
- while (*index < text.size()) {
292
- char c = text[*index];
293
- if (xt_is_css_space(c) || c == '>' || c == '+' || c == '~' || c == ',') break;
294
- if (c == '*') {
295
- (*index)++;
296
- any = true;
297
- } else if (c == '.') {
298
- (*index)++;
299
- std::string name = readIdent(text, index);
300
- if (name.empty()) return false;
301
- compound->classes.push_back(name);
302
- any = true;
303
- } else if (c == '#') {
304
- (*index)++;
305
- compound->id = readIdent(text, index);
306
- any = true;
307
- } else if (c == '[') {
308
- if (!parseAttribute(text, index, compound)) return false;
309
- any = true;
310
- } else if (c == ':') {
311
- (*index)++;
312
- bool pseudoElement = false;
313
- if (*index < text.size() && text[*index] == ':') {
314
- pseudoElement = true;
315
- (*index)++;
316
- }
317
- std::string name = xt_css_lower(readIdent(text, index));
318
- if (name.empty()) return false;
319
- if (!pseudoElement && *index < text.size() && text[*index] == '(') {
320
- int depth = 0;
321
- size_t start = *index;
322
- while (*index < text.size()) {
323
- if (text[*index] == '(') depth++;
324
- else if (text[*index] == ')') {
325
- depth--;
326
- if (depth == 0) {
327
- (*index)++;
328
- break;
329
- }
330
- }
331
- (*index)++;
332
- }
333
- name += text.substr(start, *index - start);
334
- }
335
- compound->pseudoClasses.push_back(name);
336
- any = true;
337
- } else if (isIdentStart(c)) {
338
- compound->tag = xt_css_lower(readIdent(text, index));
339
- any = true;
340
- } else {
341
- return false;
342
- }
343
- }
344
- return any;
345
- }
346
-
347
- bool parseAttribute(const std::string &text, size_t *index, CompoundSelector *compound) {
348
- (*index)++; // '['
349
- while (*index < text.size() && xt_is_css_space(text[*index])) (*index)++;
350
- AttrSelector attr;
351
- attr.name = xt_css_lower(readIdent(text, index));
352
- if (attr.name.empty()) return false;
353
- while (*index < text.size() && xt_is_css_space(text[*index])) (*index)++;
354
- if (*index < text.size() && text[*index] == ']') {
355
- (*index)++;
356
- compound->attrs.push_back(attr);
357
- return true;
358
- }
359
- if (*index < text.size() && (text[*index] == '=' || text[*index] == '~' || text[*index] == '|' ||
360
- text[*index] == '^' || text[*index] == '$' || text[*index] == '*')) {
361
- std::string op(1, text[*index]);
362
- (*index)++;
363
- if (*index < text.size() && text[*index] == '=') {
364
- op += '=';
365
- (*index)++;
366
- } else if (op != "=") {
367
- /* Only `~= |= ^= $= *=` need a trailing `=`; a lone `=` is complete. */
368
- return false;
369
- }
370
- while (*index < text.size() && xt_is_css_space(text[*index])) (*index)++;
371
- std::string value;
372
- if (*index < text.size() && (text[*index] == '"' || text[*index] == '\'')) {
373
- char quote = text[(*index)++];
374
- size_t start = *index;
375
- while (*index < text.size() && text[*index] != quote) (*index)++;
376
- value = text.substr(start, *index - start);
377
- if (*index < text.size()) (*index)++;
378
- } else {
379
- value = readIdent(text, index);
380
- }
381
- attr.op = op;
382
- attr.value = value;
383
- }
384
- while (*index < text.size() && xt_is_css_space(text[*index])) (*index)++;
385
- if (*index >= text.size() || text[*index] != ']') return false;
386
- (*index)++;
387
- compound->attrs.push_back(attr);
388
- return true;
389
- }
390
-
391
- bool parseComplex(const std::string &text, ComplexSelector *selector) {
392
- size_t index = 0;
393
- while (index < text.size() && xt_is_css_space(text[index])) index++;
394
- while (index < text.size()) {
395
- CompoundSelector compound;
396
- if (!parseCompound(text, &index, &compound)) return false;
397
- selector->compounds.push_back(std::move(compound));
398
- bool hadSpace = false;
399
- while (index < text.size() && xt_is_css_space(text[index])) {
400
- hadSpace = true;
401
- index++;
402
- }
403
- if (index >= text.size() || text[index] == ',') break;
404
- char c = text[index];
405
- if (c == '>' || c == '+' || c == '~') {
406
- index++;
407
- while (index < text.size() && xt_is_css_space(text[index])) index++;
408
- selector->combinators.push_back(c == '>' ? Combinator::Child
409
- : c == '+' ? Combinator::Adjacent
410
- : Combinator::Sibling);
411
- } else if (hadSpace) {
412
- selector->combinators.push_back(Combinator::Descendant);
413
- } else {
414
- return false;
415
- }
416
- }
417
- if (selector->combinators.size() + 1 != selector->compounds.size()) {
418
- /* Ensure the invariant holds even if parsing stopped early. */
419
- selector->combinators.resize(selector->compounds.size() > 0 ? selector->compounds.size() - 1 : 0);
420
- }
421
- selector->specificity = computeSpecificity(*selector);
422
- return true;
423
- }
424
-
425
- static int computeCompoundSpecificity(const CompoundSelector &compound) {
426
- /* `:not(x)` is approximated as a class; pseudo-elements would count as a
427
- * type but are not modelled yet. Good enough for a UI stylesheet. */
428
- return (int)(compound.classes.size() + compound.attrs.size() + compound.pseudoClasses.size());
429
- }
430
-
431
- static int computeSpecificity(const ComplexSelector &selector) {
432
- int ids = 0;
433
- int classes = 0;
434
- int types = 0;
435
- for (const CompoundSelector &compound : selector.compounds) {
436
- if (!compound.id.empty()) ids++;
437
- classes += computeCompoundSpecificity(compound);
438
- if (!compound.tag.empty()) types++;
439
- }
440
- return (ids << 20) | (classes << 10) | types;
441
- }
442
- };
443
-
444
- } // namespace
445
-
446
- std::vector<ComplexSelector> xt_css_parse_selector_list(const std::string &text) {
447
- SelectorParser parser(text);
448
- return parser.parseList();
449
- }
450
-
451
- /* -- declaration / stylesheet parsing ------------------------------------- */
452
-
453
- namespace {
454
-
455
- /** Find the first `needle` at nesting depth zero (parens/brackets/quotes). */
456
- size_t findTopLevel(const std::string &text, char needle, size_t from) {
457
- int depth = 0;
458
- char quote = 0;
459
- for (size_t i = from; i < text.size(); i++) {
460
- char c = text[i];
461
- if (quote != 0) {
462
- if (c == quote) quote = 0;
463
- continue;
464
- }
465
- if (c == '"' || c == '\'') quote = c;
466
- else if (c == '(' || c == '[') depth++;
467
- else if (c == ')' || c == ']') depth--;
468
- else if (c == needle && depth == 0) return i;
469
- }
470
- return std::string::npos;
471
- }
472
-
473
- std::string stripImportant(std::string value, bool *important) {
474
- size_t pos = value.rfind('!');
475
- if (pos == std::string::npos) return value;
476
- std::string flag = xt_css_lower(xt_css_trim(value.substr(pos + 1)));
477
- if (flag == "important") {
478
- *important = true;
479
- return xt_css_trim(value.substr(0, pos));
480
- }
481
- return value;
482
- }
483
-
484
- } // namespace
485
-
486
- std::vector<Declaration> xt_css_parse_declarations(const std::string &text) {
487
- std::vector<Declaration> declarations;
488
- size_t start = 0;
489
- while (start < text.size()) {
490
- size_t end = findTopLevel(text, ';', start);
491
- if (end == std::string::npos) end = text.size();
492
- std::string part = text.substr(start, end - start);
493
- start = end + 1;
494
- size_t colon = findTopLevel(part, ':', 0);
495
- if (colon == std::string::npos) continue;
496
- Declaration declaration;
497
- declaration.property = xt_css_lower(xt_css_trim(part.substr(0, colon)));
498
- declaration.value = stripImportant(xt_css_trim(part.substr(colon + 1)), &declaration.important);
499
- if (!declaration.property.empty() && !declaration.value.empty()) {
500
- declarations.push_back(std::move(declaration));
501
- }
502
- }
503
- return declarations;
504
- }
505
-
506
- StyleSheet xt_css_parse_stylesheet(const std::string &text) {
507
- StyleSheet sheet;
508
- std::string clean = stripComments(text);
509
- size_t index = 0;
510
- int order = 0;
511
- while (index < clean.size()) {
512
- while (index < clean.size() && xt_is_css_space(clean[index])) index++;
513
- if (index >= clean.size()) break;
514
- if (clean[index] == '@') {
515
- size_t brace = clean.find('{', index);
516
- size_t semi = clean.find(';', index);
517
- if (brace != std::string::npos && (semi == std::string::npos || brace < semi)) {
518
- int depth = 0;
519
- size_t i = brace;
520
- for (; i < clean.size(); i++) {
521
- if (clean[i] == '{') depth++;
522
- else if (clean[i] == '}') {
523
- depth--;
524
- if (depth == 0) break;
525
- }
526
- }
527
- index = (i < clean.size()) ? i + 1 : clean.size();
528
- } else {
529
- index = (semi == std::string::npos) ? clean.size() : semi + 1;
530
- }
531
- continue;
532
- }
533
- size_t brace = clean.find('{', index);
534
- if (brace == std::string::npos) break;
535
- int depth = 0;
536
- size_t close = std::string::npos;
537
- for (size_t i = brace; i < clean.size(); i++) {
538
- if (clean[i] == '{') depth++;
539
- else if (clean[i] == '}') {
540
- depth--;
541
- if (depth == 0) {
542
- close = i;
543
- break;
544
- }
545
- }
546
- }
547
- if (close == std::string::npos) break;
548
- Rule rule;
549
- rule.selectors = xt_css_parse_selector_list(clean.substr(index, brace - index));
550
- rule.declarations = xt_css_parse_declarations(clean.substr(brace + 1, close - brace - 1));
551
- rule.order = order++;
552
- if (!rule.selectors.empty() && !rule.declarations.empty()) sheet.rules.push_back(std::move(rule));
553
- index = close + 1;
554
- }
555
- return sheet;
556
- }
557
-
558
- /* -- matching ------------------------------------------------------------- */
559
-
560
- namespace {
561
-
562
- bool hasClass(const Node *node, const std::string &name) {
563
- const std::string *value = node->attr("class");
564
- if (value == nullptr) return false;
565
- for (const std::string &token : xt_css_split_whitespace(*value)) {
566
- if (token == name) return true;
567
- }
568
- return false;
569
- }
570
-
571
- bool matchAttribute(const Node *node, const AttrSelector &attr) {
572
- const std::string *value = node->attr(attr.name);
573
- if (value == nullptr) return false;
574
- if (attr.op.empty()) return true;
575
- if (attr.op == "=") return *value == attr.value;
576
- if (attr.op == "~=") {
577
- for (const std::string &token : xt_css_split_whitespace(*value)) {
578
- if (token == attr.value) return true;
579
- }
580
- return false;
581
- }
582
- if (attr.op == "|=") {
583
- return *value == attr.value || value->rfind(attr.value + "-", 0) == 0;
584
- }
585
- if (attr.op == "^=") return !attr.value.empty() && value->rfind(attr.value, 0) == 0;
586
- if (attr.op == "$=") {
587
- return !attr.value.empty() && value->size() >= attr.value.size() &&
588
- value->compare(value->size() - attr.value.size(), attr.value.size(), attr.value) == 0;
589
- }
590
- if (attr.op == "*=") {
591
- return !attr.value.empty() && value->find(attr.value) != std::string::npos;
592
- }
593
- return false;
594
- }
595
-
596
- /** Parse the `an+b` micro-syntax of `:nth-child`. */
597
- bool parseNth(const std::string &expression, int *a, int *b) {
598
- std::string text;
599
- for (char c : xt_css_lower(xt_css_trim(expression))) {
600
- if (!xt_is_css_space(c)) text.push_back(c);
601
- }
602
- if (text == "odd") { *a = 2; *b = 1; return true; }
603
- if (text == "even") { *a = 2; *b = 0; return true; }
604
- size_t nPos = text.find('n');
605
- if (nPos == std::string::npos) {
606
- *a = 0;
607
- *b = atoi(text.c_str());
608
- return !text.empty();
609
- }
610
- std::string aPart = text.substr(0, nPos);
611
- std::string bPart = text.substr(nPos + 1);
612
- if (aPart.empty() || aPart == "+") *a = 1;
613
- else if (aPart == "-") *a = -1;
614
- else *a = atoi(aPart.c_str());
615
- if (bPart.empty()) *b = 0;
616
- else *b = atoi(bPart.c_str());
617
- return true;
618
- }
619
-
620
- bool matchesNth(int index, const std::string &expression) {
621
- int a = 0;
622
- int b = 0;
623
- if (!parseNth(expression, &a, &b)) return false;
624
- if (a == 0) return index == b;
625
- int diff = index - b;
626
- if (a > 0) return diff >= 0 && diff % a == 0;
627
- return diff <= 0 && (-diff) % (-a) == 0;
628
- }
629
-
630
- bool isHoverTarget(const Node *node, const Node *hover) {
631
- for (const Node *current = hover; current != nullptr; current = current->parent) {
632
- if (current == node) return true;
633
- }
634
- return false;
635
- }
636
-
637
- bool matchPseudo(const Node *node, const std::string &pseudo, const MatchState &state) {
638
- if (pseudo == "first-child") return node->elementIndex() == 0 && node->elementSiblingCount() > 0;
639
- if (pseudo == "last-child") return node->elementIndex() == node->elementSiblingCount() - 1;
640
- if (pseudo == "only-child") return node->elementSiblingCount() == 1;
641
- if (pseudo == "root") return node->parent != nullptr && node->parent->isDocument();
642
- if (pseudo == "hover") return isHoverTarget(node, state.hover);
643
- if (pseudo == "focus") return node == state.focus;
644
- if (pseudo == "empty") {
645
- for (const std::unique_ptr<Node> &child : node->children) {
646
- if (child->isElement()) return false;
647
- if (child->isText() && !xt_css_trim(child->text).empty()) return false;
648
- }
649
- return true;
650
- }
651
- if (pseudo == "disabled") return node->attr("disabled") != nullptr;
652
- if (pseudo == "checked") return node->attr("checked") != nullptr;
653
- if (pseudo.rfind("nth-child(", 0) == 0) {
654
- return matchesNth(node->elementIndex() + 1, pseudo.substr(10, pseudo.size() - 11));
655
- }
656
- if (pseudo.rfind("not(", 0) == 0) {
657
- std::string inner = xt_css_trim(pseudo.substr(4, pseudo.size() - 5));
658
- std::vector<ComplexSelector> parsed = xt_css_parse_selector_list(inner);
659
- if (parsed.empty() || parsed[0].compounds.size() != 1) return false;
660
- const CompoundSelector &compound = parsed[0].compounds[0];
661
- for (const AttrSelector &attr : compound.attrs) {
662
- if (!matchAttribute(node, attr)) return true;
663
- }
664
- if (!compound.id.empty()) {
665
- const std::string *id = node->attr("id");
666
- if (id == nullptr || *id != compound.id) return true;
667
- }
668
- if (!compound.tag.empty() && node->tag != compound.tag) return true;
669
- for (const std::string &name : compound.classes) {
670
- if (!hasClass(node, name)) return true;
671
- }
672
- for (const std::string &nested : compound.pseudoClasses) {
673
- if (!matchPseudo(node, nested, state)) return true;
674
- }
675
- return false;
676
- }
677
- /* Truly unknown / unsupported pseudo-classes never match. */
678
- return false;
679
- }
680
-
681
- bool matchCompound(const Node *node, const CompoundSelector &compound, const MatchState &state) {
682
- if (!node->isElement()) return false;
683
- if (!compound.tag.empty() && node->tag != compound.tag) return false;
684
- if (!compound.id.empty()) {
685
- const std::string *id = node->attr("id");
686
- if (id == nullptr || *id != compound.id) return false;
687
- }
688
- for (const std::string &name : compound.classes) {
689
- if (!hasClass(node, name)) return false;
690
- }
691
- for (const AttrSelector &attr : compound.attrs) {
692
- if (!matchAttribute(node, attr)) return false;
693
- }
694
- for (const std::string &pseudo : compound.pseudoClasses) {
695
- if (!matchPseudo(node, pseudo, state)) return false;
696
- }
697
- return true;
698
- }
699
-
700
- bool matchAt(const Node *node, const ComplexSelector &selector, size_t index,
701
- const MatchState &state) {
702
- if (!matchCompound(node, selector.compounds[index], state)) return false;
703
- if (index == 0) return true;
704
- Combinator combinator = selector.combinators[index - 1];
705
- if (combinator == Combinator::Child) {
706
- return node->parent != nullptr && node->parent->isElement() &&
707
- matchAt(node->parent, selector, index - 1, state);
708
- }
709
- if (combinator == Combinator::Descendant) {
710
- for (const Node *parent = node->parent; parent != nullptr; parent = parent->parent) {
711
- if (parent->isElement() && matchAt(parent, selector, index - 1, state)) return true;
712
- }
713
- return false;
714
- }
715
- if (node->parent == nullptr) return false;
716
- if (combinator == Combinator::Adjacent) {
717
- const Node *previous = nullptr;
718
- for (const std::unique_ptr<Node> &sibling : node->parent->children) {
719
- if (sibling.get() == node) break;
720
- if (sibling->isElement()) previous = sibling.get();
721
- }
722
- return previous != nullptr && matchAt(previous, selector, index - 1, state);
723
- }
724
- /* General sibling combinator (`~`): any previous element sibling. */
725
- for (const std::unique_ptr<Node> &sibling : node->parent->children) {
726
- if (sibling.get() == node) break;
727
- if (sibling->isElement() && matchAt(sibling.get(), selector, index - 1, state)) return true;
728
- }
729
- return false;
730
- }
731
-
732
- } // namespace
733
-
734
- bool xt_css_match(const Node *node, const ComplexSelector &selector) {
735
- return xt_css_match(node, selector, MatchState());
736
- }
737
-
738
- bool xt_css_match(const Node *node, const ComplexSelector &selector, const MatchState &state) {
739
- if (selector.compounds.empty()) return false;
740
- return matchAt(node, selector, selector.compounds.size() - 1, state);
741
- }
742
-
743
230
  } // namespace xtgui
@@ -0,0 +1,27 @@
1
+ /*
2
+ * xbintsc GUI engine — shared helpers for the CSS translation units
3
+ * (`css.cpp`, `css_selector.cpp`, `css_sheet.cpp`, `css_match.cpp`).
4
+ */
5
+ #ifndef XT_GUI_CSS_INTERNAL_H
6
+ #define XT_GUI_CSS_INTERNAL_H
7
+
8
+ #include <cctype>
9
+
10
+ #include "css.h"
11
+
12
+ namespace xtgui {
13
+
14
+ /** CSS whitespace test, shared by the trim/split helpers and the parsers. */
15
+ bool xt_is_css_space(char c);
16
+
17
+ inline bool xt_css_is_ident_start(char c) {
18
+ return std::isalpha((unsigned char)c) || c == '_' || c == '-' || (unsigned char)c >= 0x80;
19
+ }
20
+
21
+ inline bool xt_css_is_ident_char(char c) {
22
+ return std::isalnum((unsigned char)c) || c == '_' || c == '-' || (unsigned char)c >= 0x80;
23
+ }
24
+
25
+ } // namespace xtgui
26
+
27
+ #endif /* XT_GUI_CSS_INTERNAL_H */