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
426 .program
427 .add_constant(Constant::String(y_col.clone()));
428 self.emit(Instruction::new(
429 OpCode::PushConst,
430 Some(Operand::Const(yc)),
431 ));
432 }
433
434 let total_args = 4 + spec.y_columns.len();
436 self.emit_content_builtin(BuiltinFunction::MakeContentChartFromValue, total_args)?;
437
438 Ok(())
439 }
440
441 fn encode_color_spec(color: &Option<ColorSpec>) -> i64 {
444 match color {
445 None => -1,
446 Some(ColorSpec::Named(named)) => match named {
447 NamedContentColor::Red => 0,
448 NamedContentColor::Green => 1,
449 NamedContentColor::Blue => 2,
450 NamedContentColor::Yellow => 3,
451 NamedContentColor::Magenta => 4,
452 NamedContentColor::Cyan => 5,
453 NamedContentColor::White => 6,
454 NamedContentColor::Default => 7,
455 },
456 Some(ColorSpec::Rgb(r, g, b)) => {
457 256 + (*r as i64) * 65536 + (*g as i64) * 256 + (*b as i64)
458 }
459 }
460 }
461}
462
463#[cfg(test)]
464mod tests {
465 use super::*;
466 use shape_ast::interpolation::parse_interpolation_with_mode;
467
468 fn parse_braces(s: &str) -> shape_ast::error::Result<Vec<InterpolationPart>> {
469 parse_interpolation_with_mode(s, InterpolationMode::Braces)
470 }
471
472 #[test]
473 fn test_no_interpolation() {
474 let parts = parse_braces("Hello World").unwrap();
475 assert_eq!(parts.len(), 1);
476 assert!(matches!(&parts[0], InterpolationPart::Literal(s) if s == "Hello World"));
477 }
478
479 #[test]
480 fn test_simple_interpolation() {
481 let parts = parse_braces("value: {x}").unwrap();
482 assert_eq!(parts.len(), 2);
483 assert!(matches!(&parts[0], InterpolationPart::Literal(s) if s == "value: "));
484 assert!(matches!(
485 &parts[1],
486 InterpolationPart::Expression {
487 expr,
488 format_spec: None
489 } if expr == "x"
490 ));
491 }
492
493 #[test]
494 fn test_expression_interpolation() {
495 let parts = parse_braces("sum: {x + y}").unwrap();
496 assert_eq!(parts.len(), 2);
497 assert!(matches!(&parts[0], InterpolationPart::Literal(s) if s == "sum: "));
498 assert!(matches!(
499 &parts[1],
500 InterpolationPart::Expression {
501 expr,
502 format_spec: None
503 } if expr == "x + y"
504 ));
505 }
506
507 #[test]
508 fn test_multiple_interpolations() {
509 let parts = parse_braces("a={a}, b={b}").unwrap();
510 assert_eq!(parts.len(), 4);
511 assert!(matches!(&parts[0], InterpolationPart::Literal(s) if s == "a="));
512 assert!(matches!(
513 &parts[1],
514 InterpolationPart::Expression {
515 expr,
516 format_spec: None
517 } if expr == "a"
518 ));
519 assert!(matches!(&parts[2], InterpolationPart::Literal(s) if s == ", b="));
520 assert!(matches!(
521 &parts[3],
522 InterpolationPart::Expression {
523 expr,
524 format_spec: None
525 } if expr == "b"
526 ));
527 }
528
529 #[test]
530 fn test_escaped_braces() {
531 let parts = parse_braces("Use {{x}} for literal").unwrap();
532 assert_eq!(parts.len(), 1);
533 assert!(matches!(&parts[0], InterpolationPart::Literal(s) if s == "Use {x} for literal"));
534 }
535
536 #[test]
537 fn test_as_type_in_interpolation() {
538 let parts = parse_braces("{x as Percent}").unwrap();
539 assert_eq!(parts.len(), 1);
540 assert!(matches!(
541 &parts[0],
542 InterpolationPart::Expression {
543 expr,
544 format_spec: None
545 } if expr == "x as Percent"
546 ));
547 }
548
549 #[test]
550 fn test_nested_braces_in_object() {
551 let parts = parse_braces("obj: {x.method({a: 1})}").unwrap();
552 assert_eq!(parts.len(), 2);
553 assert!(matches!(
554 &parts[1],
555 InterpolationPart::Expression {
556 expr,
557 format_spec: None
558 } if expr == "x.method({a: 1})"
559 ));
560 }
561
562 #[test]
563 fn test_interpolation_with_format_spec() {
564 let parts = parse_braces("px={price:fixed(2)}").unwrap();
565 assert_eq!(parts.len(), 2);
566 assert!(matches!(&parts[0], InterpolationPart::Literal(s) if s == "px="));
567 assert!(matches!(
568 &parts[1],
569 InterpolationPart::Expression {
570 expr,
571 format_spec: Some(spec)
572 } if expr == "price"
573 && *spec == InterpolationFormatSpec::Fixed { precision: 2 }
574 ));
575 }
576
577 #[test]
578 fn test_interpolation_does_not_split_double_colon() {
579 let parts = parse_braces("{Type::Variant}").unwrap();
580 assert_eq!(parts.len(), 1);
581 assert!(matches!(
582 &parts[0],
583 InterpolationPart::Expression {
584 expr,
585 format_spec: None
586 } if expr == "Type::Variant"
587 ));
588 }
589
590 #[test]
591 fn test_missing_format_spec_error() {
592 let result = parse_braces("value: {x:}");
593 assert!(result.is_err());
594 }
595
596 #[test]
597 fn test_unmatched_close_brace_error() {
598 let result = parse_braces("value: }");
599 assert!(result.is_err());
600 }
601
602 #[test]
603 fn test_has_interpolation() {
604 assert!(has_interpolation_with_mode(
605 "value: {x}",
606 InterpolationMode::Braces
607 ));
608 assert!(has_interpolation_with_mode(
609 "{x + y}",
610 InterpolationMode::Braces
611 ));
612 assert!(!has_interpolation_with_mode(
613 "Hello World",
614 InterpolationMode::Braces
615 ));
616 assert!(!has_interpolation_with_mode(
617 "Use {{x}} for literal",
618 InterpolationMode::Braces
619 )); }
621
622 #[test]
623 fn test_empty_interpolation_error() {
624 let result = parse_braces("value: {}");
625 assert!(result.is_err());
626 }
627
628 #[test]
629 fn test_dollar_mode_interpolation() {
630 let parts =
631 parse_interpolation_with_mode("{\"name\": ${user.name}}", InterpolationMode::Dollar)
632 .unwrap();
633 assert_eq!(parts.len(), 3);
634 assert!(matches!(
635 &parts[0],
636 InterpolationPart::Literal(s) if s == "{\"name\": "
637 ));
638 assert!(matches!(
639 &parts[1],
640 InterpolationPart::Expression {
641 expr,
642 format_spec: None
643 } if expr == "user.name"
644 ));
645 assert!(matches!(&parts[2], InterpolationPart::Literal(s) if s == "}"));
646 }
647}