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