1use crate::ast::{ASTNode, Expr, Program, Property};
6use crate::executor::Executor;
7
8#[derive(Debug, Clone)]
10pub struct GeneratorOptions {
11 pub minify: bool,
13
14 pub indent: String,
16
17 pub trailing_newline: bool,
19}
20
21impl Default for GeneratorOptions {
22 fn default() -> Self {
23 Self {
24 minify: false,
25 indent: " ".to_string(),
26 trailing_newline: true,
27 }
28 }
29}
30
31pub struct Generator {
33 options: GeneratorOptions,
34 executor: Executor,
35}
36
37impl Generator {
38 pub fn new() -> Self {
40 Self {
41 options: GeneratorOptions::default(),
42 executor: Executor::new(),
43 }
44 }
45
46 pub fn with_options(options: GeneratorOptions) -> Self {
48 Self {
49 options,
50 executor: Executor::new(),
51 }
52 }
53
54 pub fn generate(&mut self, program: &Program) -> Result<String, String> {
56 self.executor.execute(program)?;
58
59 let mut css = String::new();
61
62 for node in &program.nodes {
63 if let ASTNode::CSSRule { selector, properties } = node {
64 let rule = self.generate_css_rule(selector, properties)?;
65 css.push_str(&rule);
66
67 if !self.options.minify {
68 css.push('\n');
69 }
70 }
71 }
72
73 if self.options.trailing_newline && !css.is_empty() && !css.ends_with('\n') {
75 css.push('\n');
76 }
77
78 Ok(css)
79 }
80
81 fn generate_css_rule(&mut self, selector: &str, properties: &[Property]) -> Result<String, String> {
83 let mut css = String::new();
84
85 css.push_str(selector);
87
88 if self.options.minify {
89 css.push('{');
90 } else {
91 css.push_str(" {\n");
92 }
93
94 for (_i, property) in properties.iter().enumerate() {
96 if !self.options.minify {
97 css.push_str(&self.options.indent);
98 }
99
100 css.push_str(&property.name);
101 css.push(':');
102
103 if !self.options.minify {
104 css.push(' ');
105 }
106
107 let value = self.evaluate_property_value(&property.value)?;
109 css.push_str(&value);
110 css.push(';');
111
112 if !self.options.minify {
113 css.push('\n');
114 }
115 }
116
117 css.push('}');
119
120 if !self.options.minify {
121 css.push('\n');
122 }
123
124 Ok(css)
125 }
126
127 fn evaluate_property_value(&mut self, expr: &Expr) -> Result<String, String> {
129 let value = self.executor.evaluate_expr(expr)?;
130 Ok(value.to_css_string())
131 }
132
133 pub fn generate_minified(&mut self, program: &Program) -> Result<String, String> {
135 let original_minify = self.options.minify;
136 self.options.minify = true;
137 let result = self.generate(program);
138 self.options.minify = original_minify;
139 result
140 }
141
142 pub fn generate_formatted(&mut self, program: &Program) -> Result<String, String> {
144 let original_minify = self.options.minify;
145 self.options.minify = false;
146 let result = self.generate(program);
147 self.options.minify = original_minify;
148 result
149 }
150
151 pub fn executor(&self) -> &Executor {
153 &self.executor
154 }
155
156 pub fn executor_mut(&mut self) -> &mut Executor {
158 &mut self.executor
159 }
160
161 pub fn options(&self) -> &GeneratorOptions {
163 &self.options
164 }
165
166 pub fn set_options(&mut self, options: GeneratorOptions) {
168 self.options = options;
169 }
170}
171
172impl Default for Generator {
173 fn default() -> Self {
174 Self::new()
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use crate::lexer::Lexer;
182 use crate::parser::Parser;
183
184 fn generate_css(source: &str) -> Result<String, String> {
185 let mut lexer = Lexer::new(source);
186 let tokens = lexer.tokenize()?;
187 let mut parser = Parser::new(tokens);
188 let program = parser.parse()?;
189 let mut generator = Generator::new();
190 generator.generate(&program)
191 }
192
193 #[test]
194 fn test_simple_css_rule() {
195 let source = ".button:\n padding: 16px\n color: #fff";
196 let css = generate_css(source).unwrap();
197 assert!(css.contains(".button"));
198 assert!(css.contains("padding: 16px;"));
199 assert!(css.contains("color: #fff;"));
200 }
201
202 #[test]
203 fn test_with_variable() {
204 let source = "@var primary: #3498db\n\n.button:\n background: primary";
205 let css = generate_css(source).unwrap();
206 assert!(css.contains("background: #3498db;"));
207 }
208
209 #[test]
210 fn test_with_function() {
211 let source = r#"
212@fn spacing(x):
213 return x * 16px
214
215.container:
216 padding: spacing(2)
217"#;
218 let css = generate_css(source).unwrap();
219 assert!(css.contains("padding: 32px;"));
220 }
221
222 #[test]
223 fn test_minified_output() {
224 let source = ".button:\n padding: 16px\n color: #fff";
225 let mut lexer = Lexer::new(source);
226 let tokens = lexer.tokenize().unwrap();
227 let mut parser = Parser::new(tokens);
228 let program = parser.parse().unwrap();
229
230 let mut generator = Generator::with_options(GeneratorOptions {
231 minify: true,
232 indent: " ".to_string(),
233 trailing_newline: false,
234 });
235
236 let css = generator.generate(&program).unwrap();
237 assert_eq!(css, ".button{padding:16px;color:#fff;}");
238 }
239
240 #[test]
241 fn test_formatted_output() {
242 let source = ".button:\n padding: 16px";
243 let css = generate_css(source).unwrap();
244 assert!(css.contains(" padding: 16px;"));
245 assert!(css.contains("{\n"));
246 assert!(css.contains("}\n"));
247 }
248
249 #[test]
250 fn test_multiple_rules() {
251 let source = r#"
252.button:
253 padding: 16px
254
255.container:
256 margin: 20px
257"#;
258 let css = generate_css(source).unwrap();
259 assert!(css.contains(".button"));
260 assert!(css.contains(".container"));
261 assert!(css.contains("padding: 16px;"));
262 assert!(css.contains("margin: 20px;"));
263 }
264
265 #[test]
266 fn test_complex_expressions() {
267 let source = r#"
268@var base: 16px
269@var multiplier: 2
270
271.container:
272 padding: base * multiplier
273 margin: base + 8px
274"#;
275 let css = generate_css(source).unwrap();
276 assert!(css.contains("padding: 32px;"));
277 assert!(css.contains("margin: 24px;"));
278 }
279
280 #[test]
281 fn test_string_values() {
282 let source = r#"
283@var font: "Arial"
284
285.text:
286 font-family: font
287"#;
288 let css = generate_css(source).unwrap();
289 assert!(css.contains("font-family: Arial;"));
290 }
291
292 #[test]
293 fn test_nested_function_calls() {
294 let source = r#"
295@fn double(x):
296 return x * 2
297
298@fn quadruple(x):
299 return double(double(x))
300
301.box:
302 width: quadruple(10px)
303"#;
304 let css = generate_css(source).unwrap();
305 assert!(css.contains("width: 40px;"));
306 }
307}
308