1use crate::bytecode::{BuiltinFunction, Constant, Instruction, OpCode, Operand};
7use crate::compiler::BytecodeCompiler;
8use shape_ast::ast::InterpolationMode;
9use shape_ast::error::{Result, ShapeError};
10use shape_ast::interpolation::{
11 ChartTypeSpec, ColorSpec, ContentFormatSpec, FormatAlignment, FormatColor,
12 InterpolationFormatSpec, InterpolationPart, NamedContentColor,
13 parse_content_interpolation_with_mode, parse_interpolation_with_mode,
14};
15pub use shape_ast::interpolation::{has_interpolation, has_interpolation_with_mode};
16
17const FORMAT_SPEC_FIXED: i64 = 1;
18const FORMAT_SPEC_TABLE: i64 = 2;
19
20impl BytecodeCompiler {
21 fn emit_interpolation_format_call(
22 &mut self,
23 format_spec: Option<&InterpolationFormatSpec>,
24 ) -> Result<()> {
25 match format_spec {
26 None => {
27 let count = self.program.add_constant(Constant::Number(1.0));
29 self.emit(Instruction::new(
30 OpCode::PushConst,
31 Some(Operand::Const(count)),
32 ));
33 self.emit(Instruction::new(
34 OpCode::BuiltinCall,
35 Some(Operand::Builtin(BuiltinFunction::FormatValueWithMeta)),
36 ));
37 }
38 Some(InterpolationFormatSpec::Fixed { precision }) => {
39 let tag = self.program.add_constant(Constant::Int(FORMAT_SPEC_FIXED));
41 self.emit(Instruction::new(
42 OpCode::PushConst,
43 Some(Operand::Const(tag)),
44 ));
45 let precision = self.program.add_constant(Constant::Int(*precision as i64));
46 self.emit(Instruction::new(
47 OpCode::PushConst,
48 Some(Operand::Const(precision)),
49 ));
50 let count = self.program.add_constant(Constant::Number(3.0));
51 self.emit(Instruction::new(
52 OpCode::PushConst,
53 Some(Operand::Const(count)),
54 ));
55 self.emit(Instruction::new(
56 OpCode::BuiltinCall,
57 Some(Operand::Builtin(BuiltinFunction::FormatValueWithSpec)),
58 ));
59 }
60 Some(InterpolationFormatSpec::Table(spec)) => {
61 let tag = self.program.add_constant(Constant::Int(FORMAT_SPEC_TABLE));
63 self.emit(Instruction::new(
64 OpCode::PushConst,
65 Some(Operand::Const(tag)),
66 ));
67
68 let max_rows = self
69 .program
70 .add_constant(Constant::Int(spec.max_rows.map(|v| v as i64).unwrap_or(-1)));
71 self.emit(Instruction::new(
72 OpCode::PushConst,
73 Some(Operand::Const(max_rows)),
74 ));
75
76 let align = self.program.add_constant(Constant::Int(
77 spec.align
78 .map(|v| match v {
79 FormatAlignment::Left => 0,
80 FormatAlignment::Center => 1,
81 FormatAlignment::Right => 2,
82 })
83 .unwrap_or(-1),
84 ));
85 self.emit(Instruction::new(
86 OpCode::PushConst,
87 Some(Operand::Const(align)),
88 ));
89
90 let precision = self.program.add_constant(Constant::Int(
91 spec.precision.map(|v| v as i64).unwrap_or(-1),
92 ));
93 self.emit(Instruction::new(
94 OpCode::PushConst,
95 Some(Operand::Const(precision)),
96 ));
97
98 let color = self.program.add_constant(Constant::Int(
99 spec.color
100 .map(|v| match v {
101 FormatColor::Default => 0,
102 FormatColor::Red => 1,
103 FormatColor::Green => 2,
104 FormatColor::Yellow => 3,
105 FormatColor::Blue => 4,
106 FormatColor::Magenta => 5,
107 FormatColor::Cyan => 6,
108 FormatColor::White => 7,
109 })
110 .unwrap_or(-1),
111 ));
112 self.emit(Instruction::new(
113 OpCode::PushConst,
114 Some(Operand::Const(color)),
115 ));
116
117 let border = self.program.add_constant(Constant::Bool(spec.border));
118 self.emit(Instruction::new(
119 OpCode::PushConst,
120 Some(Operand::Const(border)),
121 ));
122
123 let count = self.program.add_constant(Constant::Number(7.0));
124 self.emit(Instruction::new(
125 OpCode::PushConst,
126 Some(Operand::Const(count)),
127 ));
128 self.emit(Instruction::new(
129 OpCode::BuiltinCall,
130 Some(Operand::Builtin(BuiltinFunction::FormatValueWithSpec)),
131 ));
132 }
133 Some(InterpolationFormatSpec::ContentStyle(_)) => {
134 let count = self.program.add_constant(Constant::Number(1.0));
138 self.emit(Instruction::new(
139 OpCode::PushConst,
140 Some(Operand::Const(count)),
141 ));
142 self.emit(Instruction::new(
143 OpCode::BuiltinCall,
144 Some(Operand::Builtin(BuiltinFunction::FormatValueWithMeta)),
145 ));
146 }
147 }
148
149 Ok(())
150 }
151
152 pub(in crate::compiler) fn compile_interpolated_string_expression(
160 &mut self,
161 s: &str,
162 mode: InterpolationMode,
163 ) -> Result<()> {
164 let parts = parse_interpolation_with_mode(s, mode)?;
165
166 if parts.is_empty() {
167 let const_idx = self.program.add_constant(Constant::String(String::new()));
169 self.emit(Instruction::new(
170 OpCode::PushConst,
171 Some(Operand::Const(const_idx)),
172 ));
173 return Ok(());
174 }
175
176 let mut first = true;
177
178 for part in parts {
179 match part {
180 InterpolationPart::Literal(text) => {
181 let const_idx = self.program.add_constant(Constant::String(text));
182 self.emit(Instruction::new(
183 OpCode::PushConst,
184 Some(Operand::Const(const_idx)),
185 ));
186 }
187 InterpolationPart::Expression { expr, format_spec } => {
188 let expr = shape_ast::parser::parse_expression_str(&expr).map_err(|e| {
190 ShapeError::RuntimeError {
191 message: format!(
192 "Failed to parse expression '{}' in interpolation: {}",
193 expr, e
194 ),
195 location: None,
196 }
197 })?;
198
199 self.compile_expr(&expr)?;
201
202 self.emit_interpolation_format_call(format_spec.as_ref())?;
204 }
205 }
206
207 if !first {
209 self.emit(Instruction::simple(OpCode::Add));
210 }
211 first = false;
212 }
213
214 Ok(())
215 }
216
217 pub(in crate::compiler) fn compile_content_string_expression(
226 &mut self,
227 s: &str,
228 mode: InterpolationMode,
229 ) -> Result<()> {
230 let parts = parse_content_interpolation_with_mode(s, mode)?;
231
232 if parts.is_empty() {
233 let const_idx = self.program.add_constant(Constant::String(String::new()));
235 self.emit(Instruction::new(
236 OpCode::PushConst,
237 Some(Operand::Const(const_idx)),
238 ));
239 self.emit_content_builtin(BuiltinFunction::MakeContentText, 1)?;
240 return Ok(());
241 }
242
243 let part_count = parts.len();
244
245 for part in parts {
246 match part {
247 InterpolationPart::Literal(text) => {
248 let const_idx = self.program.add_constant(Constant::String(text));
249 self.emit(Instruction::new(
250 OpCode::PushConst,
251 Some(Operand::Const(const_idx)),
252 ));
253 self.emit_content_builtin(BuiltinFunction::MakeContentText, 1)?;
255 }
256 InterpolationPart::Expression { expr, format_spec } => {
257 let expr = shape_ast::parser::parse_expression_str(&expr).map_err(|e| {
259 ShapeError::RuntimeError {
260 message: format!(
261 "Failed to parse expression '{}' in content string: {}",
262 expr, e
263 ),
264 location: None,
265 }
266 })?;
267 self.compile_expr(&expr)?;
268
269 if let Some(InterpolationFormatSpec::ContentStyle(ref spec)) = format_spec {
272 if spec.chart_type.is_some() {
273 self.emit_content_chart_from_value_args(spec)?;
274 continue;
275 }
276 }
277
278 let count = self.program.add_constant(Constant::Number(1.0));
280 self.emit(Instruction::new(
281 OpCode::PushConst,
282 Some(Operand::Const(count)),
283 ));
284 self.emit(Instruction::new(
285 OpCode::BuiltinCall,
286 Some(Operand::Builtin(BuiltinFunction::FormatValueWithMeta)),
287 ));
288
289 self.emit_content_builtin(BuiltinFunction::MakeContentText, 1)?;
291
292 if let Some(InterpolationFormatSpec::ContentStyle(ref spec)) = format_spec {
294 self.emit_content_style_args(spec)?;
295 self.emit_content_builtin(BuiltinFunction::ApplyContentStyle, 7)?;
296 }
297 }
298 }
299 }
300
301 if part_count > 1 {
303 let count_const = self.program.add_constant(Constant::Int(part_count as i64));
304 self.emit(Instruction::new(
305 OpCode::PushConst,
306 Some(Operand::Const(count_const)),
307 ));
308 self.emit_content_builtin(BuiltinFunction::MakeContentFragment, part_count + 1)?;
309 }
310
311 Ok(())
312 }
313
314 fn emit_content_builtin(&mut self, builtin: BuiltinFunction, arg_count: usize) -> Result<()> {
316 let count = self
317 .program
318 .add_constant(Constant::Number(arg_count as f64));
319 self.emit(Instruction::new(
320 OpCode::PushConst,
321 Some(Operand::Const(count)),
322 ));
323 self.emit(Instruction::new(
324 OpCode::BuiltinCall,
325 Some(Operand::Builtin(builtin)),
326 ));
327 Ok(())
328 }
329
330 fn emit_content_style_args(&mut self, spec: &ContentFormatSpec) -> Result<()> {
335 let fg_val = Self::encode_color_spec(&spec.fg);
337 let fg = self.program.add_constant(Constant::Int(fg_val));
338 self.emit(Instruction::new(
339 OpCode::PushConst,
340 Some(Operand::Const(fg)),
341 ));
342
343 let bg_val = Self::encode_color_spec(&spec.bg);
345 let bg = self.program.add_constant(Constant::Int(bg_val));
346 self.emit(Instruction::new(
347 OpCode::PushConst,
348 Some(Operand::Const(bg)),
349 ));
350
351 let bold = self.program.add_constant(Constant::Bool(spec.bold));
353 self.emit(Instruction::new(
354 OpCode::PushConst,
355 Some(Operand::Const(bold)),
356 ));
357
358 let italic = self.program.add_constant(Constant::Bool(spec.italic));
360 self.emit(Instruction::new(
361 OpCode::PushConst,
362 Some(Operand::Const(italic)),
363 ));
364
365 let underline = self.program.add_constant(Constant::Bool(spec.underline));
367 self.emit(Instruction::new(
368 OpCode::PushConst,
369 Some(Operand::Const(underline)),
370 ));
371
372 let dim = self.program.add_constant(Constant::Bool(spec.dim));
374 self.emit(Instruction::new(
375 OpCode::PushConst,
376 Some(Operand::Const(dim)),
377 ));
378
379 Ok(())
380 }
381
382 fn emit_content_chart_from_value_args(&mut self, spec: &ContentFormatSpec) -> Result<()> {
387 let chart_type_str = match spec.chart_type {
389 Some(ChartTypeSpec::Line) => "line",
390 Some(ChartTypeSpec::Bar) => "bar",
391 Some(ChartTypeSpec::Scatter) => "scatter",
392 Some(ChartTypeSpec::Area) => "area",
393 Some(ChartTypeSpec::Histogram) => "histogram",
394 None => "line",
395 };
396 let ct = self
397 .program
398 .add_constant(Constant::String(chart_type_str.to_string()));
399 self.emit(Instruction::new(
400 OpCode::PushConst,
401 Some(Operand::Const(ct)),
402 ));
403
404 let x_col = spec.x_column.as_deref().unwrap_or("");
406 let xc = self
407 .program
408 .add_constant(Constant::String(x_col.to_string()));
409 self.emit(Instruction::new(
410 OpCode::PushConst,
411 Some(Operand::Const(xc)),
412 ));
413
414 let y_count = self
416 .program
417 .add_constant(Constant::Int(spec.y_columns.len() as i64));
418 self.emit(Instruction::new(
419 OpCode::PushConst,
420 Some(Operand::Const(y_count)),
421 ));
422
423 for y_col in &spec.y_columns {
425 let yc = self.program.add_constant(Constant::String(y_col.clone()));
426 self.emit(Instruction::new(
427 OpCode::PushConst,
428 Some(Operand::Const(yc)),
429 ));
430 }
431
432 let total_args = 4 + spec.y_columns.len();
434 self.emit_content_builtin(BuiltinFunction::MakeContentChartFromValue, total_args)?;
435
436 Ok(())
437 }
438
439 fn encode_color_spec(color: &Option<ColorSpec>) -> i64 {
442 match color {
443 None => -1,
444 Some(ColorSpec::Named(named)) => match named {
445 NamedContentColor::Red => 0,
446 NamedContentColor::Green => 1,
447 NamedContentColor::Blue => 2,
448 NamedContentColor::Yellow => 3,
449 NamedContentColor::Magenta => 4,
450 NamedContentColor::Cyan => 5,
451 NamedContentColor::White => 6,
452 NamedContentColor::Default => 7,
453 },
454 Some(ColorSpec::Rgb(r, g, b)) => {
455 256 + (*r as i64) * 65536 + (*g as i64) * 256 + (*b as i64)
456 }
457 }
458 }
459}
460
461#[cfg(test)]
462mod tests {
463 use super::*;
464 use shape_ast::interpolation::parse_interpolation_with_mode;
465
466 fn parse_braces(s: &str) -> shape_ast::error::Result<Vec<InterpolationPart>> {
467 parse_interpolation_with_mode(s, InterpolationMode::Braces)
468 }
469
470 #[test]
471 fn test_no_interpolation() {
472 let parts = parse_braces("Hello World").unwrap();
473 assert_eq!(parts.len(), 1);
474 assert!(matches!(&parts[0], InterpolationPart::Literal(s) if s == "Hello World"));
475 }
476
477 #[test]
478 fn test_simple_interpolation() {
479 let parts = parse_braces("value: {x}").unwrap();
480 assert_eq!(parts.len(), 2);
481 assert!(matches!(&parts[0], InterpolationPart::Literal(s) if s == "value: "));
482 assert!(matches!(
483 &parts[1],
484 InterpolationPart::Expression {
485 expr,
486 format_spec: None
487 } if expr == "x"
488 ));
489 }
490
491 #[test]
492 fn test_expression_interpolation() {
493 let parts = parse_braces("sum: {x + y}").unwrap();
494 assert_eq!(parts.len(), 2);
495 assert!(matches!(&parts[0], InterpolationPart::Literal(s) if s == "sum: "));
496 assert!(matches!(
497 &parts[1],
498 InterpolationPart::Expression {
499 expr,
500 format_spec: None
501 } if expr == "x + y"
502 ));
503 }
504
505 #[test]
506 fn test_multiple_interpolations() {
507 let parts = parse_braces("a={a}, b={b}").unwrap();
508 assert_eq!(parts.len(), 4);
509 assert!(matches!(&parts[0], InterpolationPart::Literal(s) if s == "a="));
510 assert!(matches!(
511 &parts[1],
512 InterpolationPart::Expression {
513 expr,
514 format_spec: None
515 } if expr == "a"
516 ));
517 assert!(matches!(&parts[2], InterpolationPart::Literal(s) if s == ", b="));
518 assert!(matches!(
519 &parts[3],
520 InterpolationPart::Expression {
521 expr,
522 format_spec: None
523 } if expr == "b"
524 ));
525 }
526
527 #[test]
528 fn test_escaped_braces() {
529 let parts = parse_braces("Use {{x}} for literal").unwrap();
530 assert_eq!(parts.len(), 1);
531 assert!(matches!(&parts[0], InterpolationPart::Literal(s) if s == "Use {x} for literal"));
532 }
533
534 #[test]
535 fn test_as_type_in_interpolation() {
536 let parts = parse_braces("{x as Percent}").unwrap();
537 assert_eq!(parts.len(), 1);
538 assert!(matches!(
539 &parts[0],
540 InterpolationPart::Expression {
541 expr,
542 format_spec: None
543 } if expr == "x as Percent"
544 ));
545 }
546
547 #[test]
548 fn test_nested_braces_in_object() {
549 let parts = parse_braces("obj: {x.method({a: 1})}").unwrap();
550 assert_eq!(parts.len(), 2);
551 assert!(matches!(
552 &parts[1],
553 InterpolationPart::Expression {
554 expr,
555 format_spec: None
556 } if expr == "x.method({a: 1})"
557 ));
558 }
559
560 #[test]
561 fn test_interpolation_with_format_spec() {
562 let parts = parse_braces("px={price:fixed(2)}").unwrap();
563 assert_eq!(parts.len(), 2);
564 assert!(matches!(&parts[0], InterpolationPart::Literal(s) if s == "px="));
565 assert!(matches!(
566 &parts[1],
567 InterpolationPart::Expression {
568 expr,
569 format_spec: Some(spec)
570 } if expr == "price"
571 && *spec == InterpolationFormatSpec::Fixed { precision: 2 }
572 ));
573 }
574
575 #[test]
576 fn test_interpolation_does_not_split_double_colon() {
577 let parts = parse_braces("{Type::Variant}").unwrap();
578 assert_eq!(parts.len(), 1);
579 assert!(matches!(
580 &parts[0],
581 InterpolationPart::Expression {
582 expr,
583 format_spec: None
584 } if expr == "Type::Variant"
585 ));
586 }
587
588 #[test]
589 fn test_missing_format_spec_error() {
590 let result = parse_braces("value: {x:}");
591 assert!(result.is_err());
592 }
593
594 #[test]
595 fn test_unmatched_close_brace_error() {
596 let result = parse_braces("value: }");
597 assert!(result.is_err());
598 }
599
600 #[test]
601 fn test_has_interpolation() {
602 assert!(has_interpolation_with_mode(
603 "value: {x}",
604 InterpolationMode::Braces
605 ));
606 assert!(has_interpolation_with_mode(
607 "{x + y}",
608 InterpolationMode::Braces
609 ));
610 assert!(!has_interpolation_with_mode(
611 "Hello World",
612 InterpolationMode::Braces
613 ));
614 assert!(!has_interpolation_with_mode(
615 "Use {{x}} for literal",
616 InterpolationMode::Braces
617 )); }
619
620 #[test]
621 fn test_empty_interpolation_error() {
622 let result = parse_braces("value: {}");
623 assert!(result.is_err());
624 }
625
626 #[test]
627 fn test_dollar_mode_interpolation() {
628 let parts =
629 parse_interpolation_with_mode("{\"name\": ${user.name}}", InterpolationMode::Dollar)
630 .unwrap();
631 assert_eq!(parts.len(), 3);
632 assert!(matches!(
633 &parts[0],
634 InterpolationPart::Literal(s) if s == "{\"name\": "
635 ));
636 assert!(matches!(
637 &parts[1],
638 InterpolationPart::Expression {
639 expr,
640 format_spec: None
641 } if expr == "user.name"
642 ));
643 assert!(matches!(&parts[2], InterpolationPart::Literal(s) if s == "}"));
644 }
645}