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/README.md +475 -0
- package/bin/stylpp.js +2 -0
- package/dist/stylpp.esm.js +726 -0
- package/dist/stylpp.esm.js.map +1 -0
- package/dist/stylpp.js +734 -0
- package/dist/stylpp.js.map +1 -0
- package/dist/stylpp.min.js +2 -0
- package/dist/stylpp.min.js.map +1 -0
- package/package.json +59 -0
- package/src/cli/index.js +400 -0
- package/src/compiler/animation.js +346 -0
- package/src/compiler/effects.js +344 -0
- package/src/compiler/generator.js +157 -0
- package/src/compiler/index.js +119 -0
- package/src/compiler/parser.js +210 -0
- package/src/compiler/physics.js +286 -0
- package/src/compiler/transformer.js +228 -0
- package/src/runtime/advanced.js +293 -0
- package/src/runtime/browser.js +239 -0
- package/stylpp-1.0.0.tgz +0 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* STYL++ CSS Generator
|
|
3
|
+
* Converts the transformed AST into valid CSS output
|
|
4
|
+
* Supports minification, source maps, and vendor prefixes
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
class StylppGenerator {
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.options = {
|
|
10
|
+
minify: false,
|
|
11
|
+
sourceMap: false,
|
|
12
|
+
vendorPrefix: true,
|
|
13
|
+
...options
|
|
14
|
+
};
|
|
15
|
+
this.css = '';
|
|
16
|
+
this.sourceMap = { version: 3, sources: [], mappings: '' };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Generate CSS from AST
|
|
21
|
+
* @param {Object} ast - Transformed AST
|
|
22
|
+
* @returns {string} CSS code
|
|
23
|
+
*/
|
|
24
|
+
generate(ast) {
|
|
25
|
+
this.css = '';
|
|
26
|
+
this.generateRules(ast.rules, 0);
|
|
27
|
+
|
|
28
|
+
if (this.options.minify) {
|
|
29
|
+
this.css = this.minifyCSS(this.css);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return this.css;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generate rules recursively
|
|
37
|
+
* @param {Array} rules - Rules array
|
|
38
|
+
* @param {number} indent - Indentation level
|
|
39
|
+
*/
|
|
40
|
+
generateRules(rules, indent = 0) {
|
|
41
|
+
const indentStr = this.options.minify ? '' : ' '.repeat(indent);
|
|
42
|
+
const newline = this.options.minify ? '' : '\n';
|
|
43
|
+
|
|
44
|
+
rules.forEach(rule => {
|
|
45
|
+
if (rule.type === 'rule') {
|
|
46
|
+
this.css += `${indentStr}${rule.selector} {${newline}`;
|
|
47
|
+
|
|
48
|
+
// Add declarations
|
|
49
|
+
if (rule.declarations && rule.declarations.length > 0) {
|
|
50
|
+
rule.declarations.forEach(decl => {
|
|
51
|
+
const property = this.normalizeProperty(decl.property);
|
|
52
|
+
const value = this.normalizeValue(decl.value);
|
|
53
|
+
this.css += `${indentStr} ${property}: ${value};${newline}`;
|
|
54
|
+
|
|
55
|
+
// Add vendor prefixes if needed
|
|
56
|
+
if (this.options.vendorPrefix) {
|
|
57
|
+
const prefixed = this.getVendorPrefixes(property, value);
|
|
58
|
+
prefixed.forEach(p => {
|
|
59
|
+
this.css += `${indentStr} ${p.property}: ${p.value};${newline}`;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Add nested rules
|
|
66
|
+
if (rule.rules && rule.rules.length > 0) {
|
|
67
|
+
this.generateRules(rule.rules, indent + 1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.css += `${indentStr}}${newline}`;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Normalize CSS property names (handle hyphenation)
|
|
77
|
+
* @param {string} property - Property name
|
|
78
|
+
* @returns {string} Normalized property
|
|
79
|
+
*/
|
|
80
|
+
normalizeProperty(property) {
|
|
81
|
+
// Convert camelCase to kebab-case
|
|
82
|
+
return property.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Normalize CSS values
|
|
87
|
+
* @param {string} value - Property value
|
|
88
|
+
* @returns {string} Normalized value
|
|
89
|
+
*/
|
|
90
|
+
normalizeValue(value) {
|
|
91
|
+
// Remove extra whitespace
|
|
92
|
+
value = value.replace(/\s+/g, ' ').trim();
|
|
93
|
+
|
|
94
|
+
// Ensure calc() is properly formatted
|
|
95
|
+
if (value.includes('calc(')) {
|
|
96
|
+
value = value.replace(/calc\(\s+/g, 'calc(').replace(/\s+\)/g, ')');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get vendor prefixes for a property
|
|
104
|
+
* @param {string} property - CSS property
|
|
105
|
+
* @param {string} value - CSS value
|
|
106
|
+
* @returns {Array} Vendor-prefixed versions
|
|
107
|
+
*/
|
|
108
|
+
getVendorPrefixes(property, value) {
|
|
109
|
+
const prefixes = [];
|
|
110
|
+
const prefixMap = {
|
|
111
|
+
'transform': ['-webkit-transform', '-moz-transform', '-o-transform'],
|
|
112
|
+
'transition': ['-webkit-transition', '-moz-transition', '-o-transition'],
|
|
113
|
+
'box-shadow': ['-webkit-box-shadow'],
|
|
114
|
+
'border-radius': ['-webkit-border-radius'],
|
|
115
|
+
'gradient': ['-webkit-linear-gradient', '-moz-linear-gradient'],
|
|
116
|
+
'appearance': ['-webkit-appearance', '-moz-appearance'],
|
|
117
|
+
'user-select': ['-webkit-user-select', '-moz-user-select', '-ms-user-select']
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
Object.keys(prefixMap).forEach(key => {
|
|
121
|
+
if (property.includes(key)) {
|
|
122
|
+
prefixMap[key].forEach(prefixed => {
|
|
123
|
+
prefixes.push({ property: prefixed, value: value });
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return prefixes;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Minify CSS
|
|
133
|
+
* @param {string} css - CSS code
|
|
134
|
+
* @returns {string} Minified CSS
|
|
135
|
+
*/
|
|
136
|
+
minifyCSS(css) {
|
|
137
|
+
return css
|
|
138
|
+
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove comments
|
|
139
|
+
.replace(/\s+{/g, '{') // Remove spaces before {
|
|
140
|
+
.replace(/\s+}/g, '}') // Remove spaces before }
|
|
141
|
+
.replace(/;\s+/g, ';') // Remove spaces after ;
|
|
142
|
+
.replace(/:\s+/g, ':') // Remove spaces after :
|
|
143
|
+
.replace(/,\s+/g, ',') // Remove spaces after ,
|
|
144
|
+
.replace(/\n/g, '') // Remove newlines
|
|
145
|
+
.trim();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Generate source map
|
|
150
|
+
* @returns {Object} Source map object
|
|
151
|
+
*/
|
|
152
|
+
generateSourceMap() {
|
|
153
|
+
return this.sourceMap;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
module.exports = StylppGenerator;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* STYL++ Main Compiler
|
|
3
|
+
* Orchestrates parsing, transformation, and code generation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const StylppParser = require('./parser');
|
|
7
|
+
const StylppTransformer = require('./transformer');
|
|
8
|
+
const StylppGenerator = require('./generator');
|
|
9
|
+
|
|
10
|
+
class StylppCompiler {
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.options = {
|
|
13
|
+
minify: false,
|
|
14
|
+
sourceMap: false,
|
|
15
|
+
vendorPrefix: true,
|
|
16
|
+
...options
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
this.parser = new StylppParser();
|
|
20
|
+
this.transformer = new StylppTransformer();
|
|
21
|
+
this.generator = new StylppGenerator(this.options);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Compile STYL++ code to CSS
|
|
26
|
+
* @param {string} code - STYL++ source code
|
|
27
|
+
* @param {string} filename - Source filename (for error reporting)
|
|
28
|
+
* @returns {Object} Compilation result
|
|
29
|
+
*/
|
|
30
|
+
compile(code, filename = 'input.stylpp') {
|
|
31
|
+
try {
|
|
32
|
+
// Phase 1: Parse
|
|
33
|
+
const ast = this.parser.parse(code);
|
|
34
|
+
|
|
35
|
+
if (this.parser.errors.length > 0) {
|
|
36
|
+
return {
|
|
37
|
+
success: false,
|
|
38
|
+
css: '',
|
|
39
|
+
ast: null,
|
|
40
|
+
errors: this.parser.errors,
|
|
41
|
+
filename
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Phase 2: Transform
|
|
46
|
+
const transformedAst = this.transformer.transform(ast);
|
|
47
|
+
|
|
48
|
+
if (this.transformer.errors.length > 0) {
|
|
49
|
+
return {
|
|
50
|
+
success: false,
|
|
51
|
+
css: '',
|
|
52
|
+
ast: ast,
|
|
53
|
+
transformedAst: null,
|
|
54
|
+
errors: this.transformer.errors,
|
|
55
|
+
filename
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Phase 3: Generate
|
|
60
|
+
const css = this.generator.generate(transformedAst);
|
|
61
|
+
|
|
62
|
+
// Optional: Generate source map
|
|
63
|
+
let sourceMap = null;
|
|
64
|
+
if (this.options.sourceMap) {
|
|
65
|
+
sourceMap = this.generator.generateSourceMap();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
success: true,
|
|
70
|
+
css: css,
|
|
71
|
+
ast: ast,
|
|
72
|
+
transformedAst: transformedAst,
|
|
73
|
+
sourceMap: sourceMap,
|
|
74
|
+
errors: [],
|
|
75
|
+
filename
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
} catch (error) {
|
|
79
|
+
return {
|
|
80
|
+
success: false,
|
|
81
|
+
css: '',
|
|
82
|
+
ast: null,
|
|
83
|
+
transformedAst: null,
|
|
84
|
+
errors: [{
|
|
85
|
+
type: 'CompileError',
|
|
86
|
+
message: error.message,
|
|
87
|
+
stack: error.stack,
|
|
88
|
+
filename
|
|
89
|
+
}],
|
|
90
|
+
filename
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Check if code is valid without compiling
|
|
97
|
+
* @param {string} code - STYL++ source code
|
|
98
|
+
* @returns {Object} Validation result
|
|
99
|
+
*/
|
|
100
|
+
validate(code) {
|
|
101
|
+
try {
|
|
102
|
+
const ast = this.parser.parse(code);
|
|
103
|
+
return {
|
|
104
|
+
valid: true,
|
|
105
|
+
errors: []
|
|
106
|
+
};
|
|
107
|
+
} catch (error) {
|
|
108
|
+
return {
|
|
109
|
+
valid: false,
|
|
110
|
+
errors: [{
|
|
111
|
+
message: error.message,
|
|
112
|
+
line: error.line
|
|
113
|
+
}]
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = StylppCompiler;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* STYL++ Parser
|
|
3
|
+
* Converts STYL++ source code into an Abstract Syntax Tree (AST)
|
|
4
|
+
* Handles semicolon-based indentation and property-value parsing
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
class StylppParser {
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.options = options;
|
|
10
|
+
this.errors = [];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Parse STYL++ code and return AST
|
|
15
|
+
* @param {string} code - STYL++ source code
|
|
16
|
+
* @returns {Object} AST
|
|
17
|
+
*/
|
|
18
|
+
parse(code) {
|
|
19
|
+
const lines = code.split('\n');
|
|
20
|
+
const ast = {
|
|
21
|
+
type: 'stylesheet',
|
|
22
|
+
rules: [],
|
|
23
|
+
variables: {}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
let currentIndent = 0;
|
|
27
|
+
const stack = [];
|
|
28
|
+
|
|
29
|
+
for (let lineNumber = 0; lineNumber < lines.length; lineNumber++) {
|
|
30
|
+
const line = lines[lineNumber];
|
|
31
|
+
const trimmed = line.trim();
|
|
32
|
+
|
|
33
|
+
// Skip empty lines
|
|
34
|
+
if (!trimmed) continue;
|
|
35
|
+
|
|
36
|
+
// Skip comment lines (start with ; at beginning)
|
|
37
|
+
if (trimmed.startsWith(';') && !trimmed.match(/^\s*;\s*\w+/)) continue;
|
|
38
|
+
|
|
39
|
+
// Count leading semicolons for indentation
|
|
40
|
+
const indentMatch = line.match(/^(;*)/);
|
|
41
|
+
const indentLevel = indentMatch ? indentMatch[0].length : 0;
|
|
42
|
+
const content = line.substring(indentLevel).trim();
|
|
43
|
+
|
|
44
|
+
// Skip empty content
|
|
45
|
+
if (!content) continue;
|
|
46
|
+
|
|
47
|
+
// Handle variables section
|
|
48
|
+
if (content === 'variables;' || content === 'variables') {
|
|
49
|
+
const variablesSection = this.parseVariablesSection(lines, lineNumber + 1);
|
|
50
|
+
ast.variables = variablesSection.variables;
|
|
51
|
+
lineNumber = variablesSection.endLine;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Handle property-value pairs
|
|
56
|
+
if (content.endsWith(';') && indentLevel > 0) {
|
|
57
|
+
const propValue = this.parseProperty(content);
|
|
58
|
+
if (propValue && stack.length > 0) {
|
|
59
|
+
const parent = stack[stack.length - 1];
|
|
60
|
+
if (!parent.declarations) parent.declarations = [];
|
|
61
|
+
parent.declarations.push(propValue);
|
|
62
|
+
}
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Handle rules/selectors
|
|
67
|
+
const selector = content.replace(/;$/, '');
|
|
68
|
+
const newRule = {
|
|
69
|
+
type: 'rule',
|
|
70
|
+
selector: selector,
|
|
71
|
+
declarations: [],
|
|
72
|
+
rules: []
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Manage indentation stack
|
|
76
|
+
if (indentLevel > currentIndent) {
|
|
77
|
+
// Nested rule
|
|
78
|
+
if (stack.length > 0) {
|
|
79
|
+
const parent = stack[stack.length - 1];
|
|
80
|
+
parent.rules.push(newRule);
|
|
81
|
+
}
|
|
82
|
+
stack.push(newRule);
|
|
83
|
+
} else if (indentLevel < currentIndent) {
|
|
84
|
+
// Pop stack until we reach the right level
|
|
85
|
+
const popCount = (currentIndent - indentLevel);
|
|
86
|
+
for (let i = 0; i < popCount; i++) {
|
|
87
|
+
stack.pop();
|
|
88
|
+
}
|
|
89
|
+
if (stack.length > 0) {
|
|
90
|
+
const parent = stack[stack.length - 1];
|
|
91
|
+
parent.rules.push(newRule);
|
|
92
|
+
} else {
|
|
93
|
+
ast.rules.push(newRule);
|
|
94
|
+
}
|
|
95
|
+
stack.push(newRule);
|
|
96
|
+
} else {
|
|
97
|
+
// Same level
|
|
98
|
+
if (stack.length > 0) {
|
|
99
|
+
stack.pop();
|
|
100
|
+
if (stack.length > 0) {
|
|
101
|
+
const parent = stack[stack.length - 1];
|
|
102
|
+
parent.rules.push(newRule);
|
|
103
|
+
} else {
|
|
104
|
+
ast.rules.push(newRule);
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
ast.rules.push(newRule);
|
|
108
|
+
}
|
|
109
|
+
stack.push(newRule);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
currentIndent = indentLevel;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return ast;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Parse variables section
|
|
120
|
+
* @param {Array} lines - All code lines
|
|
121
|
+
* @param {number} startLine - Starting line number
|
|
122
|
+
* @returns {Object} Variables and end line
|
|
123
|
+
*/
|
|
124
|
+
parseVariablesSection(lines, startLine) {
|
|
125
|
+
const variables = {};
|
|
126
|
+
let endLine = startLine;
|
|
127
|
+
|
|
128
|
+
for (let i = startLine; i < lines.length; i++) {
|
|
129
|
+
const line = lines[i];
|
|
130
|
+
const trimmed = line.trim();
|
|
131
|
+
|
|
132
|
+
if (!trimmed || trimmed.startsWith(';')) continue;
|
|
133
|
+
|
|
134
|
+
// Check if we've exited the variables section
|
|
135
|
+
const leadingSemicolons = line.match(/^(;*)/)[0].length;
|
|
136
|
+
if (leadingSemicolons === 0 && trimmed && !trimmed.match(/^\w+\s+/)) {
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const parts = trimmed.split(/\s+/);
|
|
141
|
+
if (parts.length >= 2) {
|
|
142
|
+
const name = parts[0];
|
|
143
|
+
const value = parts.slice(1).join(' ').replace(/;$/, '');
|
|
144
|
+
variables[name] = value;
|
|
145
|
+
endLine = i;
|
|
146
|
+
} else {
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return { variables, endLine };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Parse a property-value pair
|
|
156
|
+
* @param {string} content - Property-value string (e.g., "background white;")
|
|
157
|
+
* @returns {Object} Declaration object
|
|
158
|
+
*/
|
|
159
|
+
parseProperty(content) {
|
|
160
|
+
// Remove trailing semicolon
|
|
161
|
+
const cleaned = content.replace(/;$/, '').trim();
|
|
162
|
+
|
|
163
|
+
// Split by first space to separate property from value
|
|
164
|
+
const spaceIndex = cleaned.indexOf(' ');
|
|
165
|
+
if (spaceIndex === -1) return null;
|
|
166
|
+
|
|
167
|
+
const property = cleaned.substring(0, spaceIndex);
|
|
168
|
+
const value = cleaned.substring(spaceIndex + 1);
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
type: 'declaration',
|
|
172
|
+
property: property,
|
|
173
|
+
value: value
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Parse for loop syntax: for i in 1 to 5;
|
|
179
|
+
* @param {string} content - For loop declaration
|
|
180
|
+
* @returns {Object} Loop object or null
|
|
181
|
+
*/
|
|
182
|
+
parseForLoop(content) {
|
|
183
|
+
const match = content.match(/for\s+(\w+)\s+in\s+([\d\w]+)\s+to\s+([\d\w]+)/);
|
|
184
|
+
if (!match) return null;
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
type: 'for-loop',
|
|
188
|
+
variable: match[1],
|
|
189
|
+
from: parseInt(match[2]),
|
|
190
|
+
to: parseInt(match[3])
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Parse if/conditional syntax: if condition;
|
|
196
|
+
* @param {string} content - If statement
|
|
197
|
+
* @returns {Object} Conditional object or null
|
|
198
|
+
*/
|
|
199
|
+
parseConditional(content) {
|
|
200
|
+
const match = content.match(/@(\w+)/);
|
|
201
|
+
if (!match) return null;
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
type: 'conditional',
|
|
205
|
+
condition: match[1]
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
module.exports = StylppParser;
|