stylpp 1.0.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,726 @@
1
+ function getDefaultExportFromCjs (x) {
2
+ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
3
+ }
4
+
5
+ /**
6
+ * STYL++ Parser
7
+ * Converts STYL++ source code into an Abstract Syntax Tree (AST)
8
+ * Handles semicolon-based indentation and property-value parsing
9
+ */
10
+
11
+ let StylppParser$1 = class StylppParser {
12
+ constructor(options = {}) {
13
+ this.options = options;
14
+ this.errors = [];
15
+ }
16
+
17
+ /**
18
+ * Parse STYL++ code and return AST
19
+ * @param {string} code - STYL++ source code
20
+ * @returns {Object} AST
21
+ */
22
+ parse(code) {
23
+ const lines = code.split('\n');
24
+ const ast = {
25
+ type: 'stylesheet',
26
+ rules: [],
27
+ variables: {}
28
+ };
29
+
30
+ let currentIndent = 0;
31
+ const stack = [];
32
+
33
+ for (let lineNumber = 0; lineNumber < lines.length; lineNumber++) {
34
+ const line = lines[lineNumber];
35
+ const trimmed = line.trim();
36
+
37
+ // Skip empty lines
38
+ if (!trimmed) continue;
39
+
40
+ // Skip comment lines (start with ; at beginning)
41
+ if (trimmed.startsWith(';') && !trimmed.match(/^\s*;\s*\w+/)) continue;
42
+
43
+ // Count leading semicolons for indentation
44
+ const indentMatch = line.match(/^(;*)/);
45
+ const indentLevel = indentMatch ? indentMatch[0].length : 0;
46
+ const content = line.substring(indentLevel).trim();
47
+
48
+ // Skip empty content
49
+ if (!content) continue;
50
+
51
+ // Handle variables section
52
+ if (content === 'variables;' || content === 'variables') {
53
+ const variablesSection = this.parseVariablesSection(lines, lineNumber + 1);
54
+ ast.variables = variablesSection.variables;
55
+ lineNumber = variablesSection.endLine;
56
+ continue;
57
+ }
58
+
59
+ // Handle property-value pairs
60
+ if (content.endsWith(';') && indentLevel > 0) {
61
+ const propValue = this.parseProperty(content);
62
+ if (propValue && stack.length > 0) {
63
+ const parent = stack[stack.length - 1];
64
+ if (!parent.declarations) parent.declarations = [];
65
+ parent.declarations.push(propValue);
66
+ }
67
+ continue;
68
+ }
69
+
70
+ // Handle rules/selectors
71
+ const selector = content.replace(/;$/, '');
72
+ const newRule = {
73
+ type: 'rule',
74
+ selector: selector,
75
+ declarations: [],
76
+ rules: []
77
+ };
78
+
79
+ // Manage indentation stack
80
+ if (indentLevel > currentIndent) {
81
+ // Nested rule
82
+ if (stack.length > 0) {
83
+ const parent = stack[stack.length - 1];
84
+ parent.rules.push(newRule);
85
+ }
86
+ stack.push(newRule);
87
+ } else if (indentLevel < currentIndent) {
88
+ // Pop stack until we reach the right level
89
+ const popCount = (currentIndent - indentLevel);
90
+ for (let i = 0; i < popCount; i++) {
91
+ stack.pop();
92
+ }
93
+ if (stack.length > 0) {
94
+ const parent = stack[stack.length - 1];
95
+ parent.rules.push(newRule);
96
+ } else {
97
+ ast.rules.push(newRule);
98
+ }
99
+ stack.push(newRule);
100
+ } else {
101
+ // Same level
102
+ if (stack.length > 0) {
103
+ stack.pop();
104
+ if (stack.length > 0) {
105
+ const parent = stack[stack.length - 1];
106
+ parent.rules.push(newRule);
107
+ } else {
108
+ ast.rules.push(newRule);
109
+ }
110
+ } else {
111
+ ast.rules.push(newRule);
112
+ }
113
+ stack.push(newRule);
114
+ }
115
+
116
+ currentIndent = indentLevel;
117
+ }
118
+
119
+ return ast;
120
+ }
121
+
122
+ /**
123
+ * Parse variables section
124
+ * @param {Array} lines - All code lines
125
+ * @param {number} startLine - Starting line number
126
+ * @returns {Object} Variables and end line
127
+ */
128
+ parseVariablesSection(lines, startLine) {
129
+ const variables = {};
130
+ let endLine = startLine;
131
+
132
+ for (let i = startLine; i < lines.length; i++) {
133
+ const line = lines[i];
134
+ const trimmed = line.trim();
135
+
136
+ if (!trimmed || trimmed.startsWith(';')) continue;
137
+
138
+ // Check if we've exited the variables section
139
+ const leadingSemicolons = line.match(/^(;*)/)[0].length;
140
+ if (leadingSemicolons === 0 && trimmed && !trimmed.match(/^\w+\s+/)) {
141
+ break;
142
+ }
143
+
144
+ const parts = trimmed.split(/\s+/);
145
+ if (parts.length >= 2) {
146
+ const name = parts[0];
147
+ const value = parts.slice(1).join(' ').replace(/;$/, '');
148
+ variables[name] = value;
149
+ endLine = i;
150
+ } else {
151
+ break;
152
+ }
153
+ }
154
+
155
+ return { variables, endLine };
156
+ }
157
+
158
+ /**
159
+ * Parse a property-value pair
160
+ * @param {string} content - Property-value string (e.g., "background white;")
161
+ * @returns {Object} Declaration object
162
+ */
163
+ parseProperty(content) {
164
+ // Remove trailing semicolon
165
+ const cleaned = content.replace(/;$/, '').trim();
166
+
167
+ // Split by first space to separate property from value
168
+ const spaceIndex = cleaned.indexOf(' ');
169
+ if (spaceIndex === -1) return null;
170
+
171
+ const property = cleaned.substring(0, spaceIndex);
172
+ const value = cleaned.substring(spaceIndex + 1);
173
+
174
+ return {
175
+ type: 'declaration',
176
+ property: property,
177
+ value: value
178
+ };
179
+ }
180
+
181
+ /**
182
+ * Parse for loop syntax: for i in 1 to 5;
183
+ * @param {string} content - For loop declaration
184
+ * @returns {Object} Loop object or null
185
+ */
186
+ parseForLoop(content) {
187
+ const match = content.match(/for\s+(\w+)\s+in\s+([\d\w]+)\s+to\s+([\d\w]+)/);
188
+ if (!match) return null;
189
+
190
+ return {
191
+ type: 'for-loop',
192
+ variable: match[1],
193
+ from: parseInt(match[2]),
194
+ to: parseInt(match[3])
195
+ };
196
+ }
197
+
198
+ /**
199
+ * Parse if/conditional syntax: if condition;
200
+ * @param {string} content - If statement
201
+ * @returns {Object} Conditional object or null
202
+ */
203
+ parseConditional(content) {
204
+ const match = content.match(/@(\w+)/);
205
+ if (!match) return null;
206
+
207
+ return {
208
+ type: 'conditional',
209
+ condition: match[1]
210
+ };
211
+ }
212
+ };
213
+
214
+ var parser = StylppParser$1;
215
+
216
+ /**
217
+ * STYL++ Transformer
218
+ * Transforms the AST to handle:
219
+ * - Variable resolution
220
+ * - Math operations
221
+ * - Loop expansion
222
+ * - Conditional processing
223
+ * - Function expansion
224
+ */
225
+
226
+ let StylppTransformer$1 = class StylppTransformer {
227
+ constructor(options = {}) {
228
+ this.options = options;
229
+ this.variables = {};
230
+ this.errors = [];
231
+ }
232
+
233
+ /**
234
+ * Transform AST
235
+ * @param {Object} ast - Original AST
236
+ * @returns {Object} Transformed AST
237
+ */
238
+ transform(ast) {
239
+ // First pass: collect all variables
240
+ this.variables = { ...ast.variables };
241
+
242
+ // Second pass: transform rules
243
+ const transformedRules = ast.rules.map(rule => this.transformRule(rule));
244
+
245
+ return {
246
+ type: 'stylesheet',
247
+ rules: transformedRules,
248
+ variables: this.variables
249
+ };
250
+ }
251
+
252
+ /**
253
+ * Transform a single rule
254
+ * @param {Object} rule - Rule object
255
+ * @returns {Object} Transformed rule
256
+ */
257
+ transformRule(rule) {
258
+ const transformed = {
259
+ type: rule.type,
260
+ selector: rule.selector,
261
+ declarations: [],
262
+ rules: []
263
+ };
264
+
265
+ // Transform declarations
266
+ if (rule.declarations) {
267
+ rule.declarations.forEach(decl => {
268
+ const transformedDecl = this.transformDeclaration(decl);
269
+ if (Array.isArray(transformedDecl)) {
270
+ transformed.declarations.push(...transformedDecl);
271
+ } else {
272
+ transformed.declarations.push(transformedDecl);
273
+ }
274
+ });
275
+ }
276
+
277
+ // Transform nested rules
278
+ if (rule.rules) {
279
+ rule.rules.forEach(nestedRule => {
280
+ const transformed = this.transformRule(nestedRule);
281
+ if (Array.isArray(transformed)) {
282
+ transformed.forEach(r => rule.rules.push(r));
283
+ } else {
284
+ rule.rules.push(transformed);
285
+ }
286
+ });
287
+ transformed.rules = rule.rules;
288
+ }
289
+
290
+ return transformed;
291
+ }
292
+
293
+ /**
294
+ * Transform a declaration (property-value pair)
295
+ * @param {Object} decl - Declaration object
296
+ * @returns {Object|Array} Transformed declaration(s)
297
+ */
298
+ transformDeclaration(decl) {
299
+ let value = decl.value;
300
+
301
+ // Resolve variables
302
+ value = this.resolveVariables(value);
303
+
304
+ // Handle math operations
305
+ if (this.hasMathOperation(value)) {
306
+ value = this.processMathOperation(value);
307
+ }
308
+
309
+ // Handle responsive values
310
+ if (this.isResponsiveValue(value)) {
311
+ value = this.processResponsiveValue(value);
312
+ }
313
+
314
+ // Handle color operations
315
+ if (this.isColorOperation(value)) {
316
+ value = this.processColorOperation(value);
317
+ }
318
+
319
+ return {
320
+ type: 'declaration',
321
+ property: decl.property,
322
+ value: value
323
+ };
324
+ }
325
+
326
+ /**
327
+ * Resolve variables in value (e.g., var(primary) -> #007bff)
328
+ * @param {string} value - Value string
329
+ * @returns {string} Resolved value
330
+ */
331
+ resolveVariables(value) {
332
+ return value.replace(/var\((\w+)\)/g, (match, varName) => {
333
+ return this.variables[varName] || match;
334
+ });
335
+ }
336
+
337
+ /**
338
+ * Check if value contains math operations
339
+ * @param {string} value - Value string
340
+ * @returns {boolean}
341
+ */
342
+ hasMathOperation(value) {
343
+ return /[\+\-\*\/\^]/.test(value) && /\d/.test(value);
344
+ }
345
+
346
+ /**
347
+ * Process math operations: 100% - 40px -> calc(100% - 40px)
348
+ * @param {string} value - Value with math
349
+ * @returns {string} CSS calc expression
350
+ */
351
+ processMathOperation(value) {
352
+ // Already has calc? Return as-is
353
+ if (value.includes('calc(')) return value;
354
+
355
+ // Check for mathematical expressions
356
+ const mathPattern = /^[\d\w\s\+\-\*\/\%\(\)\.]+$/;
357
+ if (mathPattern.test(value.replace(/px|em|rem|%|vh|vw|ex|ch|deg|s|ms/g, ''))) {
358
+ return `calc(${value})`;
359
+ }
360
+
361
+ return value;
362
+ }
363
+
364
+ /**
365
+ * Check if value is a responsive function
366
+ * @param {string} value - Value string
367
+ * @returns {boolean}
368
+ */
369
+ isResponsiveValue(value) {
370
+ return /responsive\(|fluid\(/.test(value);
371
+ }
372
+
373
+ /**
374
+ * Process responsive value: responsive(300px, 800px) -> clamp(...)
375
+ * @param {string} value - Value string
376
+ * @returns {string} Clamp expression
377
+ */
378
+ processResponsiveValue(value) {
379
+ // responsive(300px, 800px) -> clamp(300px, 50vw + 100px, 800px)
380
+ const match = value.match(/responsive\(([^,]+),\s*([^)]+)\)/);
381
+ if (match) {
382
+ const min = match[1].trim();
383
+ const max = match[2].trim();
384
+ const mid = `${(parseInt(min) + parseInt(max)) / 4}px`;
385
+ return `clamp(${min}, 50vw + ${mid}, ${max})`;
386
+ }
387
+
388
+ // fluid(300px, 800px)
389
+ const fluidMatch = value.match(/fluid\(([^,]+),\s*([^)]+)\)/);
390
+ if (fluidMatch) {
391
+ const min = fluidMatch[1].trim();
392
+ const max = fluidMatch[2].trim();
393
+ return `clamp(${min}, 5vw, ${max})`;
394
+ }
395
+
396
+ return value;
397
+ }
398
+
399
+ /**
400
+ * Check if value uses color operations
401
+ * @param {string} value - Value string
402
+ * @returns {boolean}
403
+ */
404
+ isColorOperation(value) {
405
+ return /darken\(|lighten\(|mix\(/.test(value);
406
+ }
407
+
408
+ /**
409
+ * Process color operations
410
+ * @param {string} value - Value string
411
+ * @returns {string} Processed color
412
+ */
413
+ processColorOperation(value) {
414
+ // darken(#007bff, 20%) - simplified implementation
415
+ const darkenMatch = value.match(/darken\(([^,]+),\s*(\d+)%\)/);
416
+ if (darkenMatch) {
417
+ const color = darkenMatch[1].trim();
418
+ parseInt(darkenMatch[2]);
419
+ // Simple approximation - in real implementation would use color library
420
+ return `${color}`;
421
+ }
422
+
423
+ return value;
424
+ }
425
+
426
+ /**
427
+ * Expand for loops
428
+ * @param {Object} rule - Rule with for loop
429
+ * @param {number} iterations - Number of iterations
430
+ * @returns {Array} Expanded rules
431
+ */
432
+ expandForLoop(rule, iterations) {
433
+ const expanded = [];
434
+ for (let i = 0; i < iterations; i++) {
435
+ const expandedRule = { ...rule };
436
+ expandedRule.selector = rule.selector.replace(/{i}/g, i);
437
+ expanded.push(expandedRule);
438
+ }
439
+ return expanded;
440
+ }
441
+ };
442
+
443
+ var transformer = StylppTransformer$1;
444
+
445
+ /**
446
+ * STYL++ CSS Generator
447
+ * Converts the transformed AST into valid CSS output
448
+ * Supports minification, source maps, and vendor prefixes
449
+ */
450
+
451
+ let StylppGenerator$1 = class StylppGenerator {
452
+ constructor(options = {}) {
453
+ this.options = {
454
+ minify: false,
455
+ sourceMap: false,
456
+ vendorPrefix: true,
457
+ ...options
458
+ };
459
+ this.css = '';
460
+ this.sourceMap = { version: 3, sources: [], mappings: '' };
461
+ }
462
+
463
+ /**
464
+ * Generate CSS from AST
465
+ * @param {Object} ast - Transformed AST
466
+ * @returns {string} CSS code
467
+ */
468
+ generate(ast) {
469
+ this.css = '';
470
+ this.generateRules(ast.rules, 0);
471
+
472
+ if (this.options.minify) {
473
+ this.css = this.minifyCSS(this.css);
474
+ }
475
+
476
+ return this.css;
477
+ }
478
+
479
+ /**
480
+ * Generate rules recursively
481
+ * @param {Array} rules - Rules array
482
+ * @param {number} indent - Indentation level
483
+ */
484
+ generateRules(rules, indent = 0) {
485
+ const indentStr = this.options.minify ? '' : ' '.repeat(indent);
486
+ const newline = this.options.minify ? '' : '\n';
487
+
488
+ rules.forEach(rule => {
489
+ if (rule.type === 'rule') {
490
+ this.css += `${indentStr}${rule.selector} {${newline}`;
491
+
492
+ // Add declarations
493
+ if (rule.declarations && rule.declarations.length > 0) {
494
+ rule.declarations.forEach(decl => {
495
+ const property = this.normalizeProperty(decl.property);
496
+ const value = this.normalizeValue(decl.value);
497
+ this.css += `${indentStr} ${property}: ${value};${newline}`;
498
+
499
+ // Add vendor prefixes if needed
500
+ if (this.options.vendorPrefix) {
501
+ const prefixed = this.getVendorPrefixes(property, value);
502
+ prefixed.forEach(p => {
503
+ this.css += `${indentStr} ${p.property}: ${p.value};${newline}`;
504
+ });
505
+ }
506
+ });
507
+ }
508
+
509
+ // Add nested rules
510
+ if (rule.rules && rule.rules.length > 0) {
511
+ this.generateRules(rule.rules, indent + 1);
512
+ }
513
+
514
+ this.css += `${indentStr}}${newline}`;
515
+ }
516
+ });
517
+ }
518
+
519
+ /**
520
+ * Normalize CSS property names (handle hyphenation)
521
+ * @param {string} property - Property name
522
+ * @returns {string} Normalized property
523
+ */
524
+ normalizeProperty(property) {
525
+ // Convert camelCase to kebab-case
526
+ return property.replace(/([A-Z])/g, '-$1').toLowerCase();
527
+ }
528
+
529
+ /**
530
+ * Normalize CSS values
531
+ * @param {string} value - Property value
532
+ * @returns {string} Normalized value
533
+ */
534
+ normalizeValue(value) {
535
+ // Remove extra whitespace
536
+ value = value.replace(/\s+/g, ' ').trim();
537
+
538
+ // Ensure calc() is properly formatted
539
+ if (value.includes('calc(')) {
540
+ value = value.replace(/calc\(\s+/g, 'calc(').replace(/\s+\)/g, ')');
541
+ }
542
+
543
+ return value;
544
+ }
545
+
546
+ /**
547
+ * Get vendor prefixes for a property
548
+ * @param {string} property - CSS property
549
+ * @param {string} value - CSS value
550
+ * @returns {Array} Vendor-prefixed versions
551
+ */
552
+ getVendorPrefixes(property, value) {
553
+ const prefixes = [];
554
+ const prefixMap = {
555
+ 'transform': ['-webkit-transform', '-moz-transform', '-o-transform'],
556
+ 'transition': ['-webkit-transition', '-moz-transition', '-o-transition'],
557
+ 'box-shadow': ['-webkit-box-shadow'],
558
+ 'border-radius': ['-webkit-border-radius'],
559
+ 'gradient': ['-webkit-linear-gradient', '-moz-linear-gradient'],
560
+ 'appearance': ['-webkit-appearance', '-moz-appearance'],
561
+ 'user-select': ['-webkit-user-select', '-moz-user-select', '-ms-user-select']
562
+ };
563
+
564
+ Object.keys(prefixMap).forEach(key => {
565
+ if (property.includes(key)) {
566
+ prefixMap[key].forEach(prefixed => {
567
+ prefixes.push({ property: prefixed, value: value });
568
+ });
569
+ }
570
+ });
571
+
572
+ return prefixes;
573
+ }
574
+
575
+ /**
576
+ * Minify CSS
577
+ * @param {string} css - CSS code
578
+ * @returns {string} Minified CSS
579
+ */
580
+ minifyCSS(css) {
581
+ return css
582
+ .replace(/\/\*[\s\S]*?\*\//g, '') // Remove comments
583
+ .replace(/\s+{/g, '{') // Remove spaces before {
584
+ .replace(/\s+}/g, '}') // Remove spaces before }
585
+ .replace(/;\s+/g, ';') // Remove spaces after ;
586
+ .replace(/:\s+/g, ':') // Remove spaces after :
587
+ .replace(/,\s+/g, ',') // Remove spaces after ,
588
+ .replace(/\n/g, '') // Remove newlines
589
+ .trim();
590
+ }
591
+
592
+ /**
593
+ * Generate source map
594
+ * @returns {Object} Source map object
595
+ */
596
+ generateSourceMap() {
597
+ return this.sourceMap;
598
+ }
599
+ };
600
+
601
+ var generator = StylppGenerator$1;
602
+
603
+ /**
604
+ * STYL++ Main Compiler
605
+ * Orchestrates parsing, transformation, and code generation
606
+ */
607
+
608
+ const StylppParser = parser;
609
+ const StylppTransformer = transformer;
610
+ const StylppGenerator = generator;
611
+
612
+ class StylppCompiler {
613
+ constructor(options = {}) {
614
+ this.options = {
615
+ minify: false,
616
+ sourceMap: false,
617
+ vendorPrefix: true,
618
+ ...options
619
+ };
620
+
621
+ this.parser = new StylppParser();
622
+ this.transformer = new StylppTransformer();
623
+ this.generator = new StylppGenerator(this.options);
624
+ }
625
+
626
+ /**
627
+ * Compile STYL++ code to CSS
628
+ * @param {string} code - STYL++ source code
629
+ * @param {string} filename - Source filename (for error reporting)
630
+ * @returns {Object} Compilation result
631
+ */
632
+ compile(code, filename = 'input.stylpp') {
633
+ try {
634
+ // Phase 1: Parse
635
+ const ast = this.parser.parse(code);
636
+
637
+ if (this.parser.errors.length > 0) {
638
+ return {
639
+ success: false,
640
+ css: '',
641
+ ast: null,
642
+ errors: this.parser.errors,
643
+ filename
644
+ };
645
+ }
646
+
647
+ // Phase 2: Transform
648
+ const transformedAst = this.transformer.transform(ast);
649
+
650
+ if (this.transformer.errors.length > 0) {
651
+ return {
652
+ success: false,
653
+ css: '',
654
+ ast: ast,
655
+ transformedAst: null,
656
+ errors: this.transformer.errors,
657
+ filename
658
+ };
659
+ }
660
+
661
+ // Phase 3: Generate
662
+ const css = this.generator.generate(transformedAst);
663
+
664
+ // Optional: Generate source map
665
+ let sourceMap = null;
666
+ if (this.options.sourceMap) {
667
+ sourceMap = this.generator.generateSourceMap();
668
+ }
669
+
670
+ return {
671
+ success: true,
672
+ css: css,
673
+ ast: ast,
674
+ transformedAst: transformedAst,
675
+ sourceMap: sourceMap,
676
+ errors: [],
677
+ filename
678
+ };
679
+
680
+ } catch (error) {
681
+ return {
682
+ success: false,
683
+ css: '',
684
+ ast: null,
685
+ transformedAst: null,
686
+ errors: [{
687
+ type: 'CompileError',
688
+ message: error.message,
689
+ stack: error.stack,
690
+ filename
691
+ }],
692
+ filename
693
+ };
694
+ }
695
+ }
696
+
697
+ /**
698
+ * Check if code is valid without compiling
699
+ * @param {string} code - STYL++ source code
700
+ * @returns {Object} Validation result
701
+ */
702
+ validate(code) {
703
+ try {
704
+ const ast = this.parser.parse(code);
705
+ return {
706
+ valid: true,
707
+ errors: []
708
+ };
709
+ } catch (error) {
710
+ return {
711
+ valid: false,
712
+ errors: [{
713
+ message: error.message,
714
+ line: error.line
715
+ }]
716
+ };
717
+ }
718
+ }
719
+ }
720
+
721
+ var compiler = StylppCompiler;
722
+
723
+ var index = /*@__PURE__*/getDefaultExportFromCjs(compiler);
724
+
725
+ export { index as default };
726
+ //# sourceMappingURL=stylpp.esm.js.map