swl-core 1.2.0

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.
@@ -0,0 +1,478 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ // ── Dicionários ───────────────────────────────────────────────────────────────
5
+
6
+ const PROPS = {
7
+ fundo:"background-color", bg:"background-color", cor:"color", "text-cor":"color",
8
+ fonte:"font", tamanho:"font-size", peso:"font-weight", estilo:"font-style",
9
+ "altura-linha":"line-height", "espacamento-letras":"letter-spacing", familia:"font-family",
10
+ decoracao:"text-decoration", "transformacao-texto":"text-transform", "alinhamento-texto":"text-align",
11
+ largura:"width", w:"width", altura:"height", h:"height",
12
+ "max-largura":"max-width", "max-w":"max-width", "min-largura":"min-width", "min-w":"min-width",
13
+ "max-altura":"max-height", "max-h":"max-height", "min-altura":"min-height", "min-h":"min-height",
14
+ "proporcao-aspecto":"aspect-ratio", aspecto:"aspect-ratio",
15
+ margem:"margin", m:"margin", "margem-topo":"margin-top", mt:"margin-top",
16
+ "margem-direita":"margin-right", mr:"margin-right", "margem-inferior":"margin-bottom", mb:"margin-bottom",
17
+ "margem-esquerda":"margin-left", ml:"margin-left",
18
+ padding:"padding", p:"padding", "padding-topo":"padding-top", pt:"padding-top",
19
+ "padding-direita":"padding-right", pr:"padding-right", "padding-inferior":"padding-bottom", pb:"padding-bottom",
20
+ "padding-esquerda":"padding-left", pl:"padding-left",
21
+ gap:"gap", alinhamento:"text-align", "alinhamento-vertical":"vertical-align",
22
+ "alinhamento-item":"align-items", "alinhamento-conteudo":"align-content", "alinhamento-self":"align-self",
23
+ justificacao:"justify-content", exibicao:"display", display:"display",
24
+ direcao:"flex-direction", envolvimento:"flex-wrap",
25
+ "flex-crescimento":"flex-grow", "flex-encolhimento":"flex-shrink", "flex-base":"flex-basis",
26
+ "espacamento-item":"gap", flex:"flex",
27
+ colunas:"grid-template-columns", linhas:"grid-template-rows", lacuna:"gap",
28
+ "auto-coloca":"grid-auto-flow",
29
+ "coluna-inicial":"grid-column-start", "coluna-final":"grid-column-end",
30
+ "linha-inicial":"grid-row-start", "linha-final":"grid-row-end",
31
+ borda:"border", "borda-topo":"border-top", "borda-direita":"border-right",
32
+ "borda-inferior":"border-bottom", "borda-esquerda":"border-left",
33
+ "borda-raio":"border-radius", "borda-cor":"border-color",
34
+ "borda-estilo":"border-style", "borda-largura":"border-width",
35
+ posicao:"position", pos:"position",
36
+ topo:"top", top:"top", direita:"right", right:"right",
37
+ inferior:"bottom", bottom:"bottom", esquerda:"left", left:"left",
38
+ "indice-z":"z-index", z:"z-index",
39
+ opacidade:"opacity", sombra:"box-shadow", "sombra-texto":"text-shadow",
40
+ filtro:"filter", transformacao:"transform",
41
+ backtamanho:"background-size", backposicao:"background-position",
42
+ overflow:"overflow", "overflow-x":"overflow-x", "overflow-y":"overflow-y",
43
+ cursor:"cursor", "pointer-eventos":"pointer-events",
44
+ transicao:"transition", "transicao-funcao":"transition-timing-function",
45
+ "transicao-atraso":"transition-delay", animar:"animation",
46
+ outline:"outline", visibilidade:"visibility", clip:"clip-path",
47
+ "objeto-fit":"object-fit", "objeto-posicao":"object-position",
48
+ "list-estilo":"list-style", "white-space":"white-space",
49
+ "word-wrap":"word-wrap", "word-break":"word-break",
50
+ hifens:"hyphens", "quebra-texto":"text-overflow",
51
+ "mistura-modo":"mix-blend-mode", perspectiva:"perspective",
52
+ "transformacao-origem":"transform-origin", "backface-visibilidade":"backface-visibility",
53
+ "conteudo-caixa":"box-sizing", "box-sizing":"box-sizing",
54
+ "fundo-imagem":"background-image", "fundo-repetir":"background-repeat",
55
+ "fundo-anexar":"background-attachment", "fundo-origem":"background-origin",
56
+ "fundo-clip":"background-clip", resize:"resize",
57
+ "coluna-conta":"column-count", "coluna-gap":"column-gap",
58
+ "coluna-regra":"column-rule", "linha-clamp":"line-clamp",
59
+ "aparencia":"appearance", "user-select":"user-select",
60
+ "scroll-comportamento":"scroll-behavior", "scroll-margem":"scroll-margin",
61
+ "scroll-padding":"scroll-padding",
62
+ };
63
+
64
+ const COLORS = {
65
+ branco:"#FFFFFF", white:"#FFFFFF", preto:"#000000", black:"#000000",
66
+ cinza:"#808080", gray:"#808080", "cinza-claro":"#D3D3D3", "cinza-escuro":"#696969",
67
+ vermelho:"#FF0000", red:"#FF0000",
68
+ azul:"#0066FF", blue:"#0066FF", "azul-claro":"#87CEEB", "azul-escuro":"#00008B",
69
+ verde:"#008000", green:"#008000", "verde-claro":"#90EE90",
70
+ amarelo:"#FFFF00", yellow:"#FFFF00",
71
+ laranja:"#FFA500", orange:"#FFA500",
72
+ roxo:"#800080", purple:"#800080", violeta:"#EE82EE",
73
+ rosa:"#FFC0CB", pink:"#FFC0CB",
74
+ marrom:"#A52A2A", brown:"#A52A2A",
75
+ ouro:"#FFD700", gold:"#FFD700", prata:"#C0C0C0", silver:"#C0C0C0",
76
+ transparente:"transparent", transparent:"transparent",
77
+ ciano:"#00FFFF", cyan:"#00FFFF", magenta:"#FF00FF",
78
+ };
79
+
80
+ const VALUES = {
81
+ nenhuma:"none", nenhum:"none", oculta:"hidden", auto:"auto",
82
+ herdado:"inherit", inicial:"initial", conteudo:"content",
83
+ bloco:"block", inline:"inline", "inline-bloco":"inline-block",
84
+ flex:"flex", grid:"grid",
85
+ linha:"row", coluna:"column", "linha-reversa":"row-reverse", "coluna-reversa":"column-reverse",
86
+ envolver:"wrap", "envolver-reverso":"wrap-reverse",
87
+ "flexo-inicio":"flex-start", inicio:"flex-start", "flexo-fim":"flex-end", fim:"flex-end",
88
+ distribuido:"space-between", espacado:"space-around", "distribuido-uniformemente":"space-evenly",
89
+ esticado:"stretch", centro:"center", meio:"center",
90
+ justificado:"justify", solida:"solid", pontilhada:"dotted", tracejada:"dashed",
91
+ dupla:"double", redonda:"50%",
92
+ relativa:"relative", absoluta:"absolute", fixa:"fixed", pegajosa:"sticky", estatica:"static",
93
+ escondido:"hidden", visivel:"visible",
94
+ ponteiro:"pointer", mao:"pointer", hand:"pointer",
95
+ "nao-permitido":"not-allowed", texto:"text", movimento:"move", normal:"normal",
96
+ suave:"ease", "suave-entrada":"ease-in", "suave-saida":"ease-out", "suave-entrada-saida":"ease-in-out",
97
+ linear:"linear", infinito:"infinite",
98
+ denso:"dense", underline:"underline", overline:"overline", "line-through":"line-through",
99
+ maiuscula:"uppercase", minuscula:"lowercase", capitalizar:"capitalize",
100
+ nowrap:"nowrap", pre:"pre", "pre-wrap":"pre-wrap",
101
+ "break-word":"break-word", "break-all":"break-all",
102
+ cover:"cover", contain:"contain", fill:"fill", "scale-down":"scale-down",
103
+ borda:"border-box", conteudo:"content-box",
104
+ };
105
+
106
+ const PSEUDOS = {
107
+ pairado:"hover", ativo:"active", focado:"focus", visitado:"visited",
108
+ desabilitado:"disabled", marcado:"checked", indeterminado:"indeterminate",
109
+ "primeiro-filho":"first-child", "ultimo-filho":"last-child",
110
+ primeiro:"first-child", ultimo:"last-child",
111
+ "nth-filho":"nth-child", "somente-filho":"only-child",
112
+ "primeiro-tipo":"first-of-type", "ultimo-tipo":"last-of-type",
113
+ preenchido:"valid", invalido:"invalid",
114
+ "modo-escuro":"prefers-color-scheme: dark",
115
+ "modo-claro":"prefers-color-scheme: light",
116
+ "focado-visivel":"focus-visible", placeholder:"placeholder",
117
+ antes:"before", depois:"after", selecao:"selection",
118
+ "sem-filhos":"empty", raiz:"root",
119
+ };
120
+
121
+ // ── AST Nodes ─────────────────────────────────────────────────────────────────
122
+
123
+ class Node { constructor(type) { this.type = type; } }
124
+ class Program extends Node { constructor() { super("Program"); this.body = []; } }
125
+ class Variable extends Node { constructor(n, v) { super("Variable"); this.name = n; this.value = v; } }
126
+ class Import extends Node { constructor(p) { super("Import"); this.path = p; } }
127
+ class Property extends Node { constructor(n, v, l) { super("Property"); this.name = n; this.value = v; this.line = l; } }
128
+ class PseudoState extends Node { constructor(n) { super("PseudoState"); this.name = n; this.props = []; } }
129
+ class MediaQuery extends Node { constructor(c) { super("MediaQuery"); this.condition = c; this.rules = []; } }
130
+
131
+ class Rule extends Node {
132
+ constructor(sel) { super("Rule"); this.selector = sel; this.props = []; this.pseudos = []; this.children = []; }
133
+ }
134
+
135
+ class Component extends Node {
136
+ constructor(name, parent) { super("Component"); this.name = name; this.parent = parent; this.body = []; }
137
+ }
138
+
139
+ class Theme extends Node {
140
+ constructor(name) { super("Theme"); this.name = name; this.vars = []; }
141
+ }
142
+
143
+ class Animation extends Node {
144
+ constructor(name) { super("Animation"); this.name = name; this.frames = []; }
145
+ }
146
+
147
+ class Frame extends Node {
148
+ constructor(pos) { super("Frame"); this.position = pos; this.props = []; }
149
+ }
150
+
151
+ // ── Lexer ─────────────────────────────────────────────────────────────────────
152
+
153
+ class Lexer {
154
+ tokenize(code) {
155
+ const tokens = [];
156
+ let inBlock = false;
157
+ let ln = 0;
158
+
159
+ for (let raw of code.split("\n")) {
160
+ ln++;
161
+ let line = raw.trim();
162
+ if (line.includes("/*")) inBlock = true;
163
+ if (line.includes("*/")) { inBlock = false; continue; }
164
+ if (inBlock || !line || line.startsWith("//")) continue;
165
+ if (line.includes("//")) line = line.slice(0, line.indexOf("//")).trim();
166
+ if (!line) continue;
167
+
168
+ if (line.startsWith("@importar")) {
169
+ const m = line.match(/@importar\s+"([^"]+)"/);
170
+ if (m) tokens.push({ type:"IMPORT", value:m[1], line:ln });
171
+ continue;
172
+ }
173
+ if (line.startsWith("@tema ")) {
174
+ const m = line.match(/^@tema\s+([\w-]+)/);
175
+ if (m) tokens.push({ type:"THEME", value:m[1], line:ln });
176
+ continue;
177
+ }
178
+ if (line.startsWith("@componente ")) {
179
+ const m = line.match(/^@componente\s+([\w-]+)(?:\s+herda\s+([\w-]+))?/);
180
+ if (m) tokens.push({ type:"COMPONENT", value:m[1], parent:m[2]||null, line:ln });
181
+ continue;
182
+ }
183
+ if (line.startsWith("@animar ")) {
184
+ const m = line.match(/^@animar\s+([\w-]+)/);
185
+ if (m) tokens.push({ type:"ANIMATION", value:m[1], line:ln });
186
+ continue;
187
+ }
188
+ if (line.startsWith("@media ")) {
189
+ tokens.push({ type:"MEDIA", value:line.slice(7).trim(), line:ln });
190
+ continue;
191
+ }
192
+ if (line.startsWith("@") && line.includes(":")) {
193
+ const m = line.match(/^@([\w-]+)\s*:\s*(.+)$/);
194
+ if (m) tokens.push({ type:"VARIABLE", name:m[1], value:m[2].trim(), line:ln });
195
+ continue;
196
+ }
197
+ if (line === "(") { tokens.push({ type:"BLOCK_START", line:ln }); continue; }
198
+ if (line === ")") { tokens.push({ type:"BLOCK_END", line:ln }); continue; }
199
+
200
+ const ruleM = line.match(/^\((.+):$/);
201
+ if (ruleM) { tokens.push({ type:"RULE", selector:ruleM[1].trim(), line:ln }); continue; }
202
+
203
+ const pseudoM = line.match(/^&:([\w-]+)/);
204
+ if (pseudoM) { tokens.push({ type:"PSEUDO", value:pseudoM[1], line:ln }); continue; }
205
+
206
+ const frameM = line.match(/^(\d+%|de|para):$/);
207
+ if (frameM) { tokens.push({ type:"FRAME", value:frameM[1], line:ln }); continue; }
208
+
209
+ const propM = line.match(/^([\w-]+)\s+(.+)$/);
210
+ if (propM) { tokens.push({ type:"PROPERTY", name:propM[1], value:propM[2].trim(), line:ln }); continue; }
211
+ }
212
+ return tokens;
213
+ }
214
+ }
215
+
216
+ // ── Parser ────────────────────────────────────────────────────────────────────
217
+
218
+ class Parser {
219
+ parse(tokens) {
220
+ this.tokens = tokens;
221
+ this.pos = 0;
222
+ const prog = new Program();
223
+ while (this.pos < this.tokens.length) {
224
+ const t = this.tokens[this.pos];
225
+ switch (t.type) {
226
+ case "IMPORT": prog.body.push(new Import(t.value)); this.pos++; break;
227
+ case "VARIABLE": prog.body.push(new Variable(t.name, t.value)); this.pos++; break;
228
+ case "COMPONENT": prog.body.push(this.parseComponent()); break;
229
+ case "THEME": prog.body.push(this.parseTheme()); break;
230
+ case "ANIMATION": prog.body.push(this.parseAnimation()); break;
231
+ case "MEDIA": prog.body.push(this.parseMedia()); break;
232
+ case "RULE": prog.body.push(this.parseRule()); break;
233
+ default: this.pos++;
234
+ }
235
+ }
236
+ return prog;
237
+ }
238
+
239
+ cur() { return this.tokens[this.pos]; }
240
+
241
+ parseBlock(stopTypes = []) {
242
+ const nodes = [];
243
+ while (this.pos < this.tokens.length) {
244
+ const t = this.cur();
245
+ if (!t || stopTypes.includes(t.type) || t.type === "BLOCK_END") break;
246
+ if (t.type === "PROPERTY") { nodes.push(new Property(t.name, t.value, t.line)); this.pos++; continue; }
247
+ if (t.type === "PSEUDO") { nodes.push(this.parsePseudo()); continue; }
248
+ if (t.type === "MEDIA") { nodes.push(this.parseMedia()); continue; }
249
+ if (t.type === "RULE") { nodes.push(this.parseRule()); continue; }
250
+ if (t.type === "VARIABLE") { nodes.push(new Variable(t.name, t.value)); this.pos++; continue; }
251
+ this.pos++;
252
+ }
253
+ return nodes;
254
+ }
255
+
256
+ parseRule() {
257
+ const t = this.tokens[this.pos++];
258
+ const rule = new Rule(t.selector);
259
+ this.parseBlock(["RULE","COMPONENT","THEME","ANIMATION","MEDIA"]).forEach(n => {
260
+ if (n instanceof Property) rule.props.push(n);
261
+ else if (n instanceof PseudoState) rule.pseudos.push(n);
262
+ else rule.children.push(n);
263
+ });
264
+ return rule;
265
+ }
266
+
267
+ parsePseudo() {
268
+ const t = this.tokens[this.pos++];
269
+ const pseudo = new PseudoState(t.value);
270
+ this.parseBlock(["PSEUDO","RULE","MEDIA"]).forEach(n => {
271
+ if (n instanceof Property) pseudo.props.push(n);
272
+ });
273
+ return pseudo;
274
+ }
275
+
276
+ parseComponent() {
277
+ const t = this.tokens[this.pos++];
278
+ const comp = new Component(t.value, t.parent);
279
+ this.parseBlock(["COMPONENT","RULE","THEME","ANIMATION","MEDIA","IMPORT"]).forEach(n => comp.body.push(n));
280
+ return comp;
281
+ }
282
+
283
+ parseTheme() {
284
+ const t = this.tokens[this.pos++];
285
+ const theme = new Theme(t.name || t.value);
286
+ this.parseBlock(["THEME","RULE","COMPONENT","ANIMATION","MEDIA","IMPORT"]).forEach(n => {
287
+ if (n instanceof Variable) theme.vars.push(n);
288
+ });
289
+ return theme;
290
+ }
291
+
292
+ parseAnimation() {
293
+ const t = this.tokens[this.pos++];
294
+ const anim = new Animation(t.value);
295
+ let frame = null;
296
+ while (this.pos < this.tokens.length) {
297
+ const cur = this.cur();
298
+ if (!cur || ["ANIMATION","BLOCK_END","RULE","COMPONENT","THEME","MEDIA","IMPORT"].includes(cur.type)) break;
299
+ if (cur.type === "FRAME") {
300
+ frame = new Frame(cur.value);
301
+ anim.frames.push(frame);
302
+ this.pos++;
303
+ continue;
304
+ }
305
+ if (cur.type === "PROPERTY" && frame) {
306
+ frame.props.push(new Property(cur.name, cur.value, cur.line));
307
+ }
308
+ this.pos++;
309
+ }
310
+ return anim;
311
+ }
312
+
313
+ parseMedia() {
314
+ const t = this.tokens[this.pos++];
315
+ const media = new MediaQuery(t.value);
316
+ this.parseBlock(["MEDIA"]).forEach(n => {
317
+ if (n instanceof Rule) media.rules.push(n);
318
+ });
319
+ return media;
320
+ }
321
+ }
322
+
323
+ // ── Variable Resolver ─────────────────────────────────────────────────────────
324
+
325
+ class VarResolver {
326
+ resolve(ast) {
327
+ this.vars = {};
328
+ for (const n of ast.body) {
329
+ if (n.type === "Variable") this.vars[n.name] = n.value;
330
+ if (n.type === "Theme") n.vars.forEach(v => { this.vars[v.name] = v.value; });
331
+ }
332
+ this.walk(ast);
333
+ }
334
+
335
+ walk(node) {
336
+ if (!node) return;
337
+ const resolve = list => list && list.forEach(p => {
338
+ if (p.value) p.value = p.value.replace(/@([\w-]+)/g, (_, k) => this.vars[k] || `@${k}`);
339
+ });
340
+ switch (node.type) {
341
+ case "Program": node.body.forEach(c => this.walk(c)); break;
342
+ case "Rule": resolve(node.props); node.children.forEach(c => this.walk(c)); node.pseudos.forEach(p => this.walk(p)); break;
343
+ case "Component": resolve(node.body.filter(n => n.type === "Property")); node.body.forEach(n => this.walk(n)); break;
344
+ case "PseudoState":resolve(node.props); break;
345
+ case "MediaQuery": node.rules.forEach(r => this.walk(r)); break;
346
+ case "Animation": node.frames.forEach(f => resolve(f.props)); break;
347
+ }
348
+ }
349
+ }
350
+
351
+ // ── Generator ─────────────────────────────────────────────────────────────────
352
+
353
+ class Generator {
354
+ val(raw) {
355
+ if (!raw) return "";
356
+ const v = raw.trim();
357
+ if (COLORS[v]) return COLORS[v];
358
+ if (VALUES[v]) return VALUES[v];
359
+ if (v.includes(" ")) {
360
+ return v.split(/\s+/).map(p => COLORS[p] || VALUES[p] || p).join(" ");
361
+ }
362
+ return v;
363
+ }
364
+
365
+ prop(p) {
366
+ return ` ${PROPS[p.name] || p.name}: ${this.val(p.value)};\n`;
367
+ }
368
+
369
+ generate(ast) {
370
+ return ast.body.map(n => {
371
+ switch (n.type) {
372
+ case "Rule": return this.rule(n);
373
+ case "Component": return this.component(n);
374
+ case "Theme": return this.theme(n);
375
+ case "Animation": return this.animation(n);
376
+ case "MediaQuery": return this.media(n);
377
+ default: return "";
378
+ }
379
+ }).join("");
380
+ }
381
+
382
+ rule(node, parent = "") {
383
+ const sel = parent ? `${parent} ${node.selector}` : node.selector;
384
+ let out = "";
385
+ const block = node.props.map(p => this.prop(p)).join("");
386
+ if (block) out += `${sel} {\n${block}}\n\n`;
387
+ node.pseudos.forEach(ps => {
388
+ const name = PSEUDOS[ps.name] || ps.name;
389
+ const isMedia = name.includes(":");
390
+ if (isMedia) {
391
+ out += `@media (${name}) {\n ${sel} {\n`;
392
+ ps.props.forEach(p => { out += " " + this.prop(p); });
393
+ out += " }\n}\n\n";
394
+ } else {
395
+ out += `${sel}:${name} {\n`;
396
+ ps.props.forEach(p => { out += this.prop(p); });
397
+ out += "}\n\n";
398
+ }
399
+ });
400
+ node.children.forEach(c => { out += this.rule(c, sel); });
401
+ return out;
402
+ }
403
+
404
+ component(comp) {
405
+ let out = "";
406
+ const block = comp.body.filter(n => n instanceof Property).map(p => this.prop(p)).join("");
407
+ if (block) out += `.${comp.name} {\n${block}}\n\n`;
408
+ comp.body.filter(n => n instanceof Rule).forEach(r => { out += this.rule(r, `.${comp.name}`); });
409
+ return out;
410
+ }
411
+
412
+ theme(theme) {
413
+ let out = `:root[data-tema="${theme.name}"] {\n`;
414
+ theme.vars.forEach(v => { out += ` --${v.name}: ${this.val(v.value)};\n`; });
415
+ return out + "}\n\n";
416
+ }
417
+
418
+ animation(anim) {
419
+ let out = `@keyframes ${anim.name} {\n`;
420
+ anim.frames.forEach(f => {
421
+ const pos = f.position === "de" ? "from" : f.position === "para" ? "to" : f.position;
422
+ out += ` ${pos} {\n`;
423
+ f.props.forEach(p => { out += " " + this.prop(p); });
424
+ out += " }\n";
425
+ });
426
+ return out + "}\n\n";
427
+ }
428
+
429
+ media(mq) {
430
+ let out = `@media ${mq.condition} {\n`;
431
+ mq.rules.forEach(r => {
432
+ out += this.rule(r).split("\n").map(l => l ? " " + l : "").join("\n");
433
+ });
434
+ return out + "}\n\n";
435
+ }
436
+ }
437
+
438
+ // ── Compiler ──────────────────────────────────────────────────────────────────
439
+
440
+ class Compiler {
441
+ constructor() {
442
+ this.lexer = new Lexer();
443
+ this.parser = new Parser();
444
+ this.vars = new VarResolver();
445
+ this.gen = new Generator();
446
+ this.plugins = [];
447
+ }
448
+
449
+ use(plugin) {
450
+ if (plugin && typeof plugin.apply === "function") this.plugins.push(plugin);
451
+ }
452
+
453
+ compile(source, filename = "unknown.swls") {
454
+ try {
455
+ let src = source;
456
+ this.plugins.forEach(p => p.beforeParse && (src = p.beforeParse(src) || src));
457
+
458
+ const tokens = this.lexer.tokenize(src);
459
+ let ast = this.parser.parse(tokens);
460
+
461
+ this.plugins.forEach(p => p.afterParse && (ast = p.afterParse(ast) || ast));
462
+ this.vars.resolve(ast);
463
+
464
+ let css = this.gen.generate(ast);
465
+ this.plugins.forEach(p => p.afterGenerate && (css = p.afterGenerate(css) || css));
466
+
467
+ return { success: true, css, errors: [] };
468
+ } catch (err) {
469
+ return {
470
+ success: false,
471
+ css: "",
472
+ errors: [{ message: err.message, file: filename, line: err.line || null }],
473
+ };
474
+ }
475
+ }
476
+ }
477
+
478
+ module.exports = { Compiler, PROPS, COLORS, VALUES, PSEUDOS };
@@ -0,0 +1,96 @@
1
+ const http = require("http");
2
+ const WebSocket = require("ws");
3
+
4
+ class DevServer {
5
+ constructor(framework) {
6
+ this.fw = framework;
7
+ this.httpPort = 3000;
8
+ this.wsPort = 3030;
9
+ this.clients = new Set();
10
+ this.lastCSS = "";
11
+ }
12
+
13
+ start() {
14
+ this.ws = new WebSocket.Server({ port: this.wsPort });
15
+ this.ws.on("connection", (client) => {
16
+ this.clients.add(client);
17
+ client.send(JSON.stringify({ type: "connected" }));
18
+ client.on("close", () => this.clients.delete(client));
19
+ });
20
+
21
+ this.http = http.createServer((req, res) => {
22
+ if (req.url === "/swls.css") {
23
+ const css = this.fw.getCurrentStyle();
24
+ res.writeHead(200, { "Content-Type": "text/css; charset=utf-8" });
25
+ return res.end(css);
26
+ }
27
+ if (req.url === "/" || req.url === "/index.html") {
28
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
29
+ return res.end(this.html());
30
+ }
31
+ res.writeHead(404); res.end("Not found");
32
+ });
33
+
34
+ this.http.listen(this.httpPort, () => {
35
+ console.log(`📡 Dev Server: http://localhost:${this.httpPort}`);
36
+ console.log(`🔌 HMR WebSocket: ws://localhost:${this.wsPort}`);
37
+ });
38
+ }
39
+
40
+ broadcast(file, css) {
41
+ this.lastCSS = css;
42
+ if (!this.clients.size) return;
43
+ const msg = JSON.stringify({ type: "hmr-update", file, css, ts: Date.now() });
44
+ for (const c of this.clients) {
45
+ if (c.readyState === WebSocket.OPEN) c.send(msg);
46
+ }
47
+ }
48
+
49
+ html() {
50
+ return `<!DOCTYPE html>
51
+ <html lang="pt-BR">
52
+ <head>
53
+ <meta charset="UTF-8">
54
+ <title>SWLS Dev Server</title>
55
+ <link rel="stylesheet" href="/swls.css" id="swls-style">
56
+ <style>
57
+ body { font-family: system-ui, sans-serif; background: #13141c; color: #f4f4f7; padding: 2rem; margin: 0; }
58
+ .box { max-width: 760px; margin: 0 auto; background: #1b1d2a; padding: 2rem; border-radius: 10px; border: 1px solid #2e3248; }
59
+ h1 { color: #0066FF; margin: 0 0 1rem; }
60
+ .badge { font-size: .75rem; padding: 3px 8px; border-radius: 12px; background: #007A3D; font-weight: bold; }
61
+ .log { background: #0c0d14; padding: 1rem; border-radius: 6px; font-family: monospace; font-size: .85rem;
62
+ color: #a2a6b8; height: 140px; overflow-y: auto; border: 1px solid #1f2130; margin-top: 1.5rem; }
63
+ </style>
64
+ </head>
65
+ <body>
66
+ <div class="box">
67
+ <h1>SWLS Dev Server <span class="badge" id="st">Conectando...</span></h1>
68
+ <p>Monitorando arquivos <code>.swls</code>. Alterações são injetadas sem recarregar a página.</p>
69
+ <div class="log" id="log">[SWLS] Inicializando...<br></div>
70
+ </div>
71
+ <script>
72
+ const log = (m) => { const el = document.getElementById('log'); el.innerHTML += \`[\${new Date().toLocaleTimeString()}] \${m}<br>\`; el.scrollTop = el.scrollHeight; };
73
+ const st = document.getElementById('st');
74
+ const ws = new WebSocket('ws://localhost:${this.wsPort}');
75
+ ws.onopen = () => { st.textContent = 'HMR Ativo'; st.style.background = '#007A3D'; log('Conectado.'); };
76
+ ws.onclose = () => { st.textContent = 'Desconectado'; st.style.background = '#B30000'; };
77
+ ws.onmessage = ({ data }) => {
78
+ const d = JSON.parse(data);
79
+ if (d.type === 'hmr-update') {
80
+ log('⚡ Patch recebido.');
81
+ let tag = document.getElementById('swls-style');
82
+ if (!tag) { tag = document.createElement('style'); tag.id = 'swls-style'; document.head.appendChild(tag); }
83
+ if (tag.tagName === 'LINK') {
84
+ const s = document.createElement('style');
85
+ s.id = 'swls-style'; s.textContent = d.css;
86
+ tag.replaceWith(s);
87
+ } else { tag.textContent = d.css; }
88
+ }
89
+ };
90
+ </script>
91
+ </body>
92
+ </html>`;
93
+ }
94
+ }
95
+
96
+ module.exports = DevServer;
@@ -0,0 +1,83 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { Compiler } = require("./compiler");
4
+ const GraphEngine = require("./graph-engine");
5
+ const DevServer = require("./dev-server");
6
+
7
+ class SWLSFramework {
8
+ constructor(entry) {
9
+ this.entry = path.resolve(entry);
10
+ this.graph = new GraphEngine();
11
+ this.compiler = new Compiler();
12
+ this.server = new DevServer(this);
13
+ this.cache = new Map();
14
+ }
15
+
16
+ use(plugin) {
17
+ this.compiler.use(plugin);
18
+ return this;
19
+ }
20
+
21
+ build(outputPath = null) {
22
+ const t0 = Date.now();
23
+ const source = fs.readFileSync(this.entry, "utf-8");
24
+ this.graph.update(this.entry, source);
25
+
26
+ const result = this.compiler.compile(source, this.entry);
27
+ if (!result.success) {
28
+ result.errors.forEach(e => console.error(`❌ ${e.message}`));
29
+ process.exit(1);
30
+ }
31
+
32
+ const out = outputPath ? path.resolve(outputPath) : this.entry.replace(/\.swls$/, ".css");
33
+ fs.writeFileSync(out, result.css);
34
+ this.cache.set(this.entry, result.css);
35
+
36
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
37
+ console.log("✅ SWLS Core v1.2.0 — Build concluído");
38
+ console.log(` Entrada : ${path.basename(this.entry)}`);
39
+ console.log(` Saída : ${path.basename(out)}`);
40
+ console.log(` Tempo : ${Date.now() - t0}ms`);
41
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
42
+ return result.css;
43
+ }
44
+
45
+ dev() {
46
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
47
+ console.log("🔄 SWLS Core v1.2.0 — Modo Dev");
48
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
49
+ this.build();
50
+ this.server.start();
51
+ this.watch();
52
+ }
53
+
54
+ watch() {
55
+ console.log(`👀 Monitorando: ${path.basename(this.entry)}\n`);
56
+ fs.watch(this.entry, () => {
57
+ try {
58
+ const source = fs.readFileSync(this.entry, "utf-8");
59
+ this.graph.update(this.entry, source);
60
+ const affected = this.graph.getAffected(this.entry);
61
+ for (const file of affected) {
62
+ const src = fs.readFileSync(file, "utf-8");
63
+ const result = this.compiler.compile(src, file);
64
+ if (result.success) {
65
+ this.cache.set(file, result.css);
66
+ this.server.broadcast(file, result.css);
67
+ console.log(`⚡ HMR: ${path.basename(file)}`);
68
+ } else {
69
+ result.errors.forEach(e => console.error(`❌ ${path.basename(file)}: ${e.message}`));
70
+ }
71
+ }
72
+ } catch (err) {
73
+ console.error("⚠️ Erro no watch:", err.message);
74
+ }
75
+ });
76
+ }
77
+
78
+ getCurrentStyle() {
79
+ return this.cache.get(this.entry) || "";
80
+ }
81
+ }
82
+
83
+ module.exports = SWLSFramework;