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
@@ -0,0 +1,195 @@
1
+ /*
2
+ * xbintsc GUI engine — CSS selector matching against the DOM.
3
+ */
4
+
5
+ #include "css_internal.h"
6
+
7
+ #include <cstdlib>
8
+ #include <memory>
9
+
10
+ namespace xtgui {
11
+
12
+ namespace {
13
+
14
+ bool hasClass(const Node *node, const std::string &name) {
15
+ const std::string *value = node->attr("class");
16
+ if (value == nullptr) return false;
17
+ for (const std::string &token : xt_css_split_whitespace(*value)) {
18
+ if (token == name) return true;
19
+ }
20
+ return false;
21
+ }
22
+
23
+ bool matchAttribute(const Node *node, const AttrSelector &attr) {
24
+ const std::string *value = node->attr(attr.name);
25
+ if (value == nullptr) return false;
26
+ if (attr.op.empty()) return true;
27
+ if (attr.op == "=") return *value == attr.value;
28
+ if (attr.op == "~=") {
29
+ for (const std::string &token : xt_css_split_whitespace(*value)) {
30
+ if (token == attr.value) return true;
31
+ }
32
+ return false;
33
+ }
34
+ if (attr.op == "|=") {
35
+ return *value == attr.value || value->rfind(attr.value + "-", 0) == 0;
36
+ }
37
+ if (attr.op == "^=") return !attr.value.empty() && value->rfind(attr.value, 0) == 0;
38
+ if (attr.op == "$=") {
39
+ return !attr.value.empty() && value->size() >= attr.value.size() &&
40
+ value->compare(value->size() - attr.value.size(), attr.value.size(), attr.value) == 0;
41
+ }
42
+ if (attr.op == "*=") {
43
+ return !attr.value.empty() && value->find(attr.value) != std::string::npos;
44
+ }
45
+ return false;
46
+ }
47
+
48
+ /** Parse the `an+b` micro-syntax of `:nth-child`. */
49
+ bool parseNth(const std::string &expression, int *a, int *b) {
50
+ std::string text;
51
+ for (char c : xt_css_lower(xt_css_trim(expression))) {
52
+ if (!xt_is_css_space(c)) text.push_back(c);
53
+ }
54
+ if (text == "odd") { *a = 2; *b = 1; return true; }
55
+ if (text == "even") { *a = 2; *b = 0; return true; }
56
+ size_t nPos = text.find('n');
57
+ if (nPos == std::string::npos) {
58
+ *a = 0;
59
+ *b = atoi(text.c_str());
60
+ return !text.empty();
61
+ }
62
+ std::string aPart = text.substr(0, nPos);
63
+ std::string bPart = text.substr(nPos + 1);
64
+ if (aPart.empty() || aPart == "+") *a = 1;
65
+ else if (aPart == "-") *a = -1;
66
+ else *a = atoi(aPart.c_str());
67
+ if (bPart.empty()) *b = 0;
68
+ else *b = atoi(bPart.c_str());
69
+ return true;
70
+ }
71
+
72
+ bool matchesNth(int index, const std::string &expression) {
73
+ int a = 0;
74
+ int b = 0;
75
+ if (!parseNth(expression, &a, &b)) return false;
76
+ if (a == 0) return index == b;
77
+ int diff = index - b;
78
+ if (a > 0) return diff >= 0 && diff % a == 0;
79
+ return diff <= 0 && (-diff) % (-a) == 0;
80
+ }
81
+
82
+ bool isHoverTarget(const Node *node, const Node *hover) {
83
+ for (const Node *current = hover; current != nullptr; current = current->parent) {
84
+ if (current == node) return true;
85
+ }
86
+ return false;
87
+ }
88
+
89
+ bool matchPseudo(const Node *node, const std::string &pseudo, const MatchState &state) {
90
+ if (pseudo == "first-child") return node->elementIndex() == 0 && node->elementSiblingCount() > 0;
91
+ if (pseudo == "last-child") return node->elementIndex() == node->elementSiblingCount() - 1;
92
+ if (pseudo == "only-child") return node->elementSiblingCount() == 1;
93
+ if (pseudo == "root") return node->parent != nullptr && node->parent->isDocument();
94
+ if (pseudo == "hover") return isHoverTarget(node, state.hover);
95
+ if (pseudo == "focus") return node == state.focus;
96
+ if (pseudo == "empty") {
97
+ for (const std::unique_ptr<Node> &child : node->children) {
98
+ if (child->isElement()) return false;
99
+ if (child->isText() && !xt_css_trim(child->text).empty()) return false;
100
+ }
101
+ return true;
102
+ }
103
+ if (pseudo == "disabled") return node->attr("disabled") != nullptr;
104
+ if (pseudo == "checked") return node->attr("checked") != nullptr;
105
+ if (pseudo.rfind("nth-child(", 0) == 0) {
106
+ return matchesNth(node->elementIndex() + 1, pseudo.substr(10, pseudo.size() - 11));
107
+ }
108
+ if (pseudo.rfind("not(", 0) == 0) {
109
+ std::string inner = xt_css_trim(pseudo.substr(4, pseudo.size() - 5));
110
+ std::vector<ComplexSelector> parsed = xt_css_parse_selector_list(inner);
111
+ if (parsed.empty() || parsed[0].compounds.size() != 1) return false;
112
+ const CompoundSelector &compound = parsed[0].compounds[0];
113
+ for (const AttrSelector &attr : compound.attrs) {
114
+ if (!matchAttribute(node, attr)) return true;
115
+ }
116
+ if (!compound.id.empty()) {
117
+ const std::string *id = node->attr("id");
118
+ if (id == nullptr || *id != compound.id) return true;
119
+ }
120
+ if (!compound.tag.empty() && node->tag != compound.tag) return true;
121
+ for (const std::string &name : compound.classes) {
122
+ if (!hasClass(node, name)) return true;
123
+ }
124
+ for (const std::string &nested : compound.pseudoClasses) {
125
+ if (!matchPseudo(node, nested, state)) return true;
126
+ }
127
+ return false;
128
+ }
129
+ /* Truly unknown / unsupported pseudo-classes never match. */
130
+ return false;
131
+ }
132
+
133
+ bool matchCompound(const Node *node, const CompoundSelector &compound, const MatchState &state) {
134
+ if (!node->isElement()) return false;
135
+ if (!compound.tag.empty() && node->tag != compound.tag) return false;
136
+ if (!compound.id.empty()) {
137
+ const std::string *id = node->attr("id");
138
+ if (id == nullptr || *id != compound.id) return false;
139
+ }
140
+ for (const std::string &name : compound.classes) {
141
+ if (!hasClass(node, name)) return false;
142
+ }
143
+ for (const AttrSelector &attr : compound.attrs) {
144
+ if (!matchAttribute(node, attr)) return false;
145
+ }
146
+ for (const std::string &pseudo : compound.pseudoClasses) {
147
+ if (!matchPseudo(node, pseudo, state)) return false;
148
+ }
149
+ return true;
150
+ }
151
+
152
+ bool matchAt(const Node *node, const ComplexSelector &selector, size_t index,
153
+ const MatchState &state) {
154
+ if (!matchCompound(node, selector.compounds[index], state)) return false;
155
+ if (index == 0) return true;
156
+ Combinator combinator = selector.combinators[index - 1];
157
+ if (combinator == Combinator::Child) {
158
+ return node->parent != nullptr && node->parent->isElement() &&
159
+ matchAt(node->parent, selector, index - 1, state);
160
+ }
161
+ if (combinator == Combinator::Descendant) {
162
+ for (const Node *parent = node->parent; parent != nullptr; parent = parent->parent) {
163
+ if (parent->isElement() && matchAt(parent, selector, index - 1, state)) return true;
164
+ }
165
+ return false;
166
+ }
167
+ if (node->parent == nullptr) return false;
168
+ if (combinator == Combinator::Adjacent) {
169
+ const Node *previous = nullptr;
170
+ for (const std::unique_ptr<Node> &sibling : node->parent->children) {
171
+ if (sibling.get() == node) break;
172
+ if (sibling->isElement()) previous = sibling.get();
173
+ }
174
+ return previous != nullptr && matchAt(previous, selector, index - 1, state);
175
+ }
176
+ /* General sibling combinator (`~`): any previous element sibling. */
177
+ for (const std::unique_ptr<Node> &sibling : node->parent->children) {
178
+ if (sibling.get() == node) break;
179
+ if (sibling->isElement() && matchAt(sibling.get(), selector, index - 1, state)) return true;
180
+ }
181
+ return false;
182
+ }
183
+
184
+ } // namespace
185
+
186
+ bool xt_css_match(const Node *node, const ComplexSelector &selector) {
187
+ return xt_css_match(node, selector, MatchState());
188
+ }
189
+
190
+ bool xt_css_match(const Node *node, const ComplexSelector &selector, const MatchState &state) {
191
+ if (selector.compounds.empty()) return false;
192
+ return matchAt(node, selector, selector.compounds.size() - 1, state);
193
+ }
194
+
195
+ } // namespace xtgui
@@ -0,0 +1,207 @@
1
+ /*
2
+ * xbintsc GUI engine — CSS selector list parser.
3
+ */
4
+
5
+ #include "css_internal.h"
6
+
7
+ namespace xtgui {
8
+
9
+ namespace {
10
+
11
+ class SelectorParser {
12
+ public:
13
+ explicit SelectorParser(const std::string &text) : text_(text) {}
14
+
15
+ std::vector<ComplexSelector> parseList() {
16
+ std::vector<ComplexSelector> selectors;
17
+ size_t start = 0;
18
+ int depth = 0;
19
+ for (size_t i = 0; i <= text_.size(); i++) {
20
+ char c = (i < text_.size()) ? text_[i] : ',';
21
+ if (c == '(' || c == '[') depth++;
22
+ else if (c == ')' || c == ']') depth--;
23
+ else if (c == ',' && depth == 0) {
24
+ std::string part = text_.substr(start, i - start);
25
+ ComplexSelector selector;
26
+ if (parseComplex(part, &selector) && !selector.compounds.empty()) {
27
+ selectors.push_back(std::move(selector));
28
+ }
29
+ start = i + 1;
30
+ }
31
+ }
32
+ return selectors;
33
+ }
34
+
35
+ private:
36
+ const std::string &text_;
37
+
38
+ std::string readIdent(const std::string &text, size_t *index) {
39
+ size_t start = *index;
40
+ while (*index < text.size() && xt_css_is_ident_char(text[*index])) (*index)++;
41
+ return text.substr(start, *index - start);
42
+ }
43
+
44
+ /** Parse `div.foo#bar[a="b"]:pseudo` into one compound selector. */
45
+ bool parseCompound(const std::string &text, size_t *index, CompoundSelector *compound) {
46
+ bool any = false;
47
+ while (*index < text.size()) {
48
+ char c = text[*index];
49
+ if (xt_is_css_space(c) || c == '>' || c == '+' || c == '~' || c == ',') break;
50
+ if (c == '*') {
51
+ (*index)++;
52
+ any = true;
53
+ } else if (c == '.') {
54
+ (*index)++;
55
+ std::string name = readIdent(text, index);
56
+ if (name.empty()) return false;
57
+ compound->classes.push_back(name);
58
+ any = true;
59
+ } else if (c == '#') {
60
+ (*index)++;
61
+ compound->id = readIdent(text, index);
62
+ any = true;
63
+ } else if (c == '[') {
64
+ if (!parseAttribute(text, index, compound)) return false;
65
+ any = true;
66
+ } else if (c == ':') {
67
+ (*index)++;
68
+ bool pseudoElement = false;
69
+ if (*index < text.size() && text[*index] == ':') {
70
+ pseudoElement = true;
71
+ (*index)++;
72
+ }
73
+ std::string name = xt_css_lower(readIdent(text, index));
74
+ if (name.empty()) return false;
75
+ if (!pseudoElement && *index < text.size() && text[*index] == '(') {
76
+ int depth = 0;
77
+ size_t start = *index;
78
+ while (*index < text.size()) {
79
+ if (text[*index] == '(') depth++;
80
+ else if (text[*index] == ')') {
81
+ depth--;
82
+ if (depth == 0) {
83
+ (*index)++;
84
+ break;
85
+ }
86
+ }
87
+ (*index)++;
88
+ }
89
+ name += text.substr(start, *index - start);
90
+ }
91
+ compound->pseudoClasses.push_back(name);
92
+ any = true;
93
+ } else if (xt_css_is_ident_start(c)) {
94
+ compound->tag = xt_css_lower(readIdent(text, index));
95
+ any = true;
96
+ } else {
97
+ return false;
98
+ }
99
+ }
100
+ return any;
101
+ }
102
+
103
+ bool parseAttribute(const std::string &text, size_t *index, CompoundSelector *compound) {
104
+ (*index)++; // '['
105
+ while (*index < text.size() && xt_is_css_space(text[*index])) (*index)++;
106
+ AttrSelector attr;
107
+ attr.name = xt_css_lower(readIdent(text, index));
108
+ if (attr.name.empty()) return false;
109
+ while (*index < text.size() && xt_is_css_space(text[*index])) (*index)++;
110
+ if (*index < text.size() && text[*index] == ']') {
111
+ (*index)++;
112
+ compound->attrs.push_back(attr);
113
+ return true;
114
+ }
115
+ if (*index < text.size() && (text[*index] == '=' || text[*index] == '~' || text[*index] == '|' ||
116
+ text[*index] == '^' || text[*index] == '$' || text[*index] == '*')) {
117
+ std::string op(1, text[*index]);
118
+ (*index)++;
119
+ if (*index < text.size() && text[*index] == '=') {
120
+ op += '=';
121
+ (*index)++;
122
+ } else if (op != "=") {
123
+ /* Only `~= |= ^= $= *=` need a trailing `=`; a lone `=` is complete. */
124
+ return false;
125
+ }
126
+ while (*index < text.size() && xt_is_css_space(text[*index])) (*index)++;
127
+ std::string value;
128
+ if (*index < text.size() && (text[*index] == '"' || text[*index] == '\'')) {
129
+ char quote = text[(*index)++];
130
+ size_t start = *index;
131
+ while (*index < text.size() && text[*index] != quote) (*index)++;
132
+ value = text.substr(start, *index - start);
133
+ if (*index < text.size()) (*index)++;
134
+ } else {
135
+ value = readIdent(text, index);
136
+ }
137
+ attr.op = op;
138
+ attr.value = value;
139
+ }
140
+ while (*index < text.size() && xt_is_css_space(text[*index])) (*index)++;
141
+ if (*index >= text.size() || text[*index] != ']') return false;
142
+ (*index)++;
143
+ compound->attrs.push_back(attr);
144
+ return true;
145
+ }
146
+
147
+ bool parseComplex(const std::string &text, ComplexSelector *selector) {
148
+ size_t index = 0;
149
+ while (index < text.size() && xt_is_css_space(text[index])) index++;
150
+ while (index < text.size()) {
151
+ CompoundSelector compound;
152
+ if (!parseCompound(text, &index, &compound)) return false;
153
+ selector->compounds.push_back(std::move(compound));
154
+ bool hadSpace = false;
155
+ while (index < text.size() && xt_is_css_space(text[index])) {
156
+ hadSpace = true;
157
+ index++;
158
+ }
159
+ if (index >= text.size() || text[index] == ',') break;
160
+ char c = text[index];
161
+ if (c == '>' || c == '+' || c == '~') {
162
+ index++;
163
+ while (index < text.size() && xt_is_css_space(text[index])) index++;
164
+ selector->combinators.push_back(c == '>' ? Combinator::Child
165
+ : c == '+' ? Combinator::Adjacent
166
+ : Combinator::Sibling);
167
+ } else if (hadSpace) {
168
+ selector->combinators.push_back(Combinator::Descendant);
169
+ } else {
170
+ return false;
171
+ }
172
+ }
173
+ if (selector->combinators.size() + 1 != selector->compounds.size()) {
174
+ /* Ensure the invariant holds even if parsing stopped early. */
175
+ selector->combinators.resize(selector->compounds.size() > 0 ? selector->compounds.size() - 1 : 0);
176
+ }
177
+ selector->specificity = computeSpecificity(*selector);
178
+ return true;
179
+ }
180
+
181
+ static int computeCompoundSpecificity(const CompoundSelector &compound) {
182
+ /* `:not(x)` is approximated as a class; pseudo-elements would count as a
183
+ * type but are not modelled yet. Good enough for a UI stylesheet. */
184
+ return (int)(compound.classes.size() + compound.attrs.size() + compound.pseudoClasses.size());
185
+ }
186
+
187
+ static int computeSpecificity(const ComplexSelector &selector) {
188
+ int ids = 0;
189
+ int classes = 0;
190
+ int types = 0;
191
+ for (const CompoundSelector &compound : selector.compounds) {
192
+ if (!compound.id.empty()) ids++;
193
+ classes += computeCompoundSpecificity(compound);
194
+ if (!compound.tag.empty()) types++;
195
+ }
196
+ return (ids << 20) | (classes << 10) | types;
197
+ }
198
+ };
199
+
200
+ } // namespace
201
+
202
+ std::vector<ComplexSelector> xt_css_parse_selector_list(const std::string &text) {
203
+ SelectorParser parser(text);
204
+ return parser.parseList();
205
+ }
206
+
207
+ } // namespace xtgui
@@ -0,0 +1,129 @@
1
+ /*
2
+ * xbintsc GUI engine — CSS declaration and stylesheet parsing.
3
+ */
4
+
5
+ #include "css_internal.h"
6
+
7
+ namespace xtgui {
8
+
9
+ namespace {
10
+
11
+ // Strip C-style block comments before the sheet is tokenised.
12
+ std::string stripComments(const std::string &text) {
13
+ std::string out;
14
+ out.reserve(text.size());
15
+ for (size_t i = 0; i < text.size(); i++) {
16
+ if (text[i] == '/' && i + 1 < text.size() && text[i + 1] == '*') {
17
+ size_t end = text.find("*/", i + 2);
18
+ i = (end == std::string::npos) ? text.size() : end + 1;
19
+ continue;
20
+ }
21
+ out.push_back(text[i]);
22
+ }
23
+ return out;
24
+ }
25
+
26
+ /** Find the first `needle` at nesting depth zero (parens/brackets/quotes). */
27
+ size_t findTopLevel(const std::string &text, char needle, size_t from) {
28
+ int depth = 0;
29
+ char quote = 0;
30
+ for (size_t i = from; i < text.size(); i++) {
31
+ char c = text[i];
32
+ if (quote != 0) {
33
+ if (c == quote) quote = 0;
34
+ continue;
35
+ }
36
+ if (c == '"' || c == '\'') quote = c;
37
+ else if (c == '(' || c == '[') depth++;
38
+ else if (c == ')' || c == ']') depth--;
39
+ else if (c == needle && depth == 0) return i;
40
+ }
41
+ return std::string::npos;
42
+ }
43
+
44
+ std::string stripImportant(std::string value, bool *important) {
45
+ size_t pos = value.rfind('!');
46
+ if (pos == std::string::npos) return value;
47
+ std::string flag = xt_css_lower(xt_css_trim(value.substr(pos + 1)));
48
+ if (flag == "important") {
49
+ *important = true;
50
+ return xt_css_trim(value.substr(0, pos));
51
+ }
52
+ return value;
53
+ }
54
+
55
+ } // namespace
56
+
57
+ std::vector<Declaration> xt_css_parse_declarations(const std::string &text) {
58
+ std::vector<Declaration> declarations;
59
+ size_t start = 0;
60
+ while (start < text.size()) {
61
+ size_t end = findTopLevel(text, ';', start);
62
+ if (end == std::string::npos) end = text.size();
63
+ std::string part = text.substr(start, end - start);
64
+ start = end + 1;
65
+ size_t colon = findTopLevel(part, ':', 0);
66
+ if (colon == std::string::npos) continue;
67
+ Declaration declaration;
68
+ declaration.property = xt_css_lower(xt_css_trim(part.substr(0, colon)));
69
+ declaration.value = stripImportant(xt_css_trim(part.substr(colon + 1)), &declaration.important);
70
+ if (!declaration.property.empty() && !declaration.value.empty()) {
71
+ declarations.push_back(std::move(declaration));
72
+ }
73
+ }
74
+ return declarations;
75
+ }
76
+
77
+ StyleSheet xt_css_parse_stylesheet(const std::string &text) {
78
+ StyleSheet sheet;
79
+ std::string clean = stripComments(text);
80
+ size_t index = 0;
81
+ int order = 0;
82
+ while (index < clean.size()) {
83
+ while (index < clean.size() && xt_is_css_space(clean[index])) index++;
84
+ if (index >= clean.size()) break;
85
+ if (clean[index] == '@') {
86
+ size_t brace = clean.find('{', index);
87
+ size_t semi = clean.find(';', index);
88
+ if (brace != std::string::npos && (semi == std::string::npos || brace < semi)) {
89
+ int depth = 0;
90
+ size_t i = brace;
91
+ for (; i < clean.size(); i++) {
92
+ if (clean[i] == '{') depth++;
93
+ else if (clean[i] == '}') {
94
+ depth--;
95
+ if (depth == 0) break;
96
+ }
97
+ }
98
+ index = (i < clean.size()) ? i + 1 : clean.size();
99
+ } else {
100
+ index = (semi == std::string::npos) ? clean.size() : semi + 1;
101
+ }
102
+ continue;
103
+ }
104
+ size_t brace = clean.find('{', index);
105
+ if (brace == std::string::npos) break;
106
+ int depth = 0;
107
+ size_t close = std::string::npos;
108
+ for (size_t i = brace; i < clean.size(); i++) {
109
+ if (clean[i] == '{') depth++;
110
+ else if (clean[i] == '}') {
111
+ depth--;
112
+ if (depth == 0) {
113
+ close = i;
114
+ break;
115
+ }
116
+ }
117
+ }
118
+ if (close == std::string::npos) break;
119
+ Rule rule;
120
+ rule.selectors = xt_css_parse_selector_list(clean.substr(index, brace - index));
121
+ rule.declarations = xt_css_parse_declarations(clean.substr(brace + 1, close - brace - 1));
122
+ rule.order = order++;
123
+ if (!rule.selectors.empty() && !rule.declarations.empty()) sheet.rules.push_back(std::move(rule));
124
+ index = close + 1;
125
+ }
126
+ return sheet;
127
+ }
128
+
129
+ } // namespace xtgui