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 ColorSpec, ContentFormatSpec, FormatAlignment, FormatColor, InterpolationFormatSpec,
12 InterpolationPart, NamedContentColor, parse_content_interpolation_with_mode,
13 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 let count = self.program.add_constant(Constant::Number(1.0));
271 self.emit(Instruction::new(
272 OpCode::PushConst,
273 Some(Operand::Const(count)),
274 ));
275 self.emit(Instruction::new(
276 OpCode::BuiltinCall,
277 Some(Operand::Builtin(BuiltinFunction::FormatValueWithMeta)),
278 ));
279
280 self.emit_content_builtin(BuiltinFunction::MakeContentText, 1)?;
282
283 if let Some(InterpolationFormatSpec::ContentStyle(ref spec)) = format_spec {
285 self.emit_content_style_args(spec)?;
286 self.emit_content_builtin(BuiltinFunction::ApplyContentStyle, 7)?;
287 }
288 }
289 }
290 }
291
292 if part_count > 1 {
294 let count_const = self.program.add_constant(Constant::Int(part_count as i64));
295 self.emit(Instruction::new(
296 OpCode::PushConst,
297 Some(Operand::Const(count_const)),
298 ));
299 self.emit_content_builtin(BuiltinFunction::MakeContentFragment, part_count + 1)?;
300 }
301
302 Ok(())
303 }
304
305 fn emit_content_builtin(&mut self, builtin: BuiltinFunction, arg_count: usize) -> Result<()> {
307 let count = self
308 .program
309 .add_constant(Constant::Number(arg_count as f64));
310 self.emit(Instruction::new(
311 OpCode::PushConst,
312 Some(Operand::Const(count)),
313 ));
314 self.emit(Instruction::new(
315 OpCode::BuiltinCall,
316 Some(Operand::Builtin(builtin)),
317 ));
318 Ok(())
319 }
320
321 fn emit_content_style_args(&mut self, spec: &ContentFormatSpec) -> Result<()> {
326 let fg_val = Self::encode_color_spec(&spec.fg);
328 let fg = self.program.add_constant(Constant::Int(fg_val));
329 self.emit(Instruction::new(
330 OpCode::PushConst,
331 Some(Operand::Const(fg)),
332 ));
333
334 let bg_val = Self::encode_color_spec(&spec.bg);
336 let bg = self.program.add_constant(Constant::Int(bg_val));
337 self.emit(Instruction::new(
338 OpCode::PushConst,
339 Some(Operand::Const(bg)),
340 ));
341
342 let bold = self.program.add_constant(Constant::Bool(spec.bold));
344 self.emit(Instruction::new(
345 OpCode::PushConst,
346 Some(Operand::Const(bold)),
347 ));
348
349 let italic = self.program.add_constant(Constant::Bool(spec.italic));
351 self.emit(Instruction::new(
352 OpCode::PushConst,
353 Some(Operand::Const(italic)),
354 ));
355
356 let underline = self.program.add_constant(Constant::Bool(spec.underline));
358 self.emit(Instruction::new(
359 OpCode::PushConst,
360 Some(Operand::Const(underline)),
361 ));
362
363 let dim = self.program.add_constant(Constant::Bool(spec.dim));
365 self.emit(Instruction::new(
366 OpCode::PushConst,
367 Some(Operand::Const(dim)),
368 ));
369
370 Ok(())
371 }
372
373 fn encode_color_spec(color: &Option<ColorSpec>) -> i64 {
376 match color {
377 None => -1,
378 Some(ColorSpec::Named(named)) => match named {
379 NamedContentColor::Red => 0,
380 NamedContentColor::Green => 1,
381 NamedContentColor::Blue => 2,
382 NamedContentColor::Yellow => 3,
383 NamedContentColor::Magenta => 4,
384 NamedContentColor::Cyan => 5,
385 NamedContentColor::White => 6,
386 NamedContentColor::Default => 7,
387 },
388 Some(ColorSpec::Rgb(r, g, b)) => {
389 256 + (*r as i64) * 65536 + (*g as i64) * 256 + (*b as i64)
390 }
391 }
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398 use shape_ast::interpolation::parse_interpolation_with_mode;
399
400 fn parse_braces(s: &str) -> shape_ast::error::Result<Vec<InterpolationPart>> {
401 parse_interpolation_with_mode(s, InterpolationMode::Braces)
402 }
403
404 #[test]
405 fn test_no_interpolation() {
406 let parts = parse_braces("Hello World").unwrap();
407 assert_eq!(parts.len(), 1);
408 assert!(matches!(&parts[0], InterpolationPart::Literal(s) if s == "Hello World"));
409 }
410
411 #[test]
412 fn test_simple_interpolation() {
413 let parts = parse_braces("value: {x}").unwrap();
414 assert_eq!(parts.len(), 2);
415 assert!(matches!(&parts[0], InterpolationPart::Literal(s) if s == "value: "));
416 assert!(matches!(
417 &parts[1],
418 InterpolationPart::Expression {
419 expr,
420 format_spec: None
421 } if expr == "x"
422 ));
423 }
424
425 #[test]
426 fn test_expression_interpolation() {
427 let parts = parse_braces("sum: {x + y}").unwrap();
428 assert_eq!(parts.len(), 2);
429 assert!(matches!(&parts[0], InterpolationPart::Literal(s) if s == "sum: "));
430 assert!(matches!(
431 &parts[1],
432 InterpolationPart::Expression {
433 expr,
434 format_spec: None
435 } if expr == "x + y"
436 ));
437 }
438
439 #[test]
440 fn test_multiple_interpolations() {
441 let parts = parse_braces("a={a}, b={b}").unwrap();
442 assert_eq!(parts.len(), 4);
443 assert!(matches!(&parts[0], InterpolationPart::Literal(s) if s == "a="));
444 assert!(matches!(
445 &parts[1],
446 InterpolationPart::Expression {
447 expr,
448 format_spec: None
449 } if expr == "a"
450 ));
451 assert!(matches!(&parts[2], InterpolationPart::Literal(s) if s == ", b="));
452 assert!(matches!(
453 &parts[3],
454 InterpolationPart::Expression {
455 expr,
456 format_spec: None
457 } if expr == "b"
458 ));
459 }
460
461 #[test]
462 fn test_escaped_braces() {
463 let parts = parse_braces("Use {{x}} for literal").unwrap();
464 assert_eq!(parts.len(), 1);
465 assert!(matches!(&parts[0], InterpolationPart::Literal(s) if s == "Use {x} for literal"));
466 }
467
468 #[test]
469 fn test_as_type_in_interpolation() {
470 let parts = parse_braces("{x as Percent}").unwrap();
471 assert_eq!(parts.len(), 1);
472 assert!(matches!(
473 &parts[0],
474 InterpolationPart::Expression {
475 expr,
476 format_spec: None
477 } if expr == "x as Percent"
478 ));
479 }
480
481 #[test]
482 fn test_nested_braces_in_object() {
483 let parts = parse_braces("obj: {x.method({a: 1})}").unwrap();
484 assert_eq!(parts.len(), 2);
485 assert!(matches!(
486 &parts[1],
487 InterpolationPart::Expression {
488 expr,
489 format_spec: None
490 } if expr == "x.method({a: 1})"
491 ));
492 }
493
494 #[test]
495 fn test_interpolation_with_format_spec() {
496 let parts = parse_braces("px={price:fixed(2)}").unwrap();
497 assert_eq!(parts.len(), 2);
498 assert!(matches!(&parts[0], InterpolationPart::Literal(s) if s == "px="));
499 assert!(matches!(
500 &parts[1],
501 InterpolationPart::Expression {
502 expr,
503 format_spec: Some(spec)
504 } if expr == "price"
505 && *spec == InterpolationFormatSpec::Fixed { precision: 2 }
506 ));
507 }
508
509 #[test]
510 fn test_interpolation_does_not_split_double_colon() {
511 let parts = parse_braces("{Type::Variant}").unwrap();
512 assert_eq!(parts.len(), 1);
513 assert!(matches!(
514 &parts[0],
515 InterpolationPart::Expression {
516 expr,
517 format_spec: None
518 } if expr == "Type::Variant"
519 ));
520 }
521
522 #[test]
523 fn test_missing_format_spec_error() {
524 let result = parse_braces("value: {x:}");
525 assert!(result.is_err());
526 }
527
528 #[test]
529 fn test_unmatched_close_brace_error() {
530 let result = parse_braces("value: }");
531 assert!(result.is_err());
532 }
533
534 #[test]
535 fn test_has_interpolation() {
536 assert!(has_interpolation_with_mode(
537 "value: {x}",
538 InterpolationMode::Braces
539 ));
540 assert!(has_interpolation_with_mode(
541 "{x + y}",
542 InterpolationMode::Braces
543 ));
544 assert!(!has_interpolation_with_mode(
545 "Hello World",
546 InterpolationMode::Braces
547 ));
548 assert!(!has_interpolation_with_mode(
549 "Use {{x}} for literal",
550 InterpolationMode::Braces
551 )); }
553
554 #[test]
555 fn test_empty_interpolation_error() {
556 let result = parse_braces("value: {}");
557 assert!(result.is_err());
558 }
559
560 #[test]
561 fn test_dollar_mode_interpolation() {
562 let parts =
563 parse_interpolation_with_mode("{\"name\": ${user.name}}", InterpolationMode::Dollar)
564 .unwrap();
565 assert_eq!(parts.len(), 3);
566 assert!(matches!(
567 &parts[0],
568 InterpolationPart::Literal(s) if s == "{\"name\": "
569 ));
570 assert!(matches!(
571 &parts[1],
572 InterpolationPart::Expression {
573 expr,
574 format_spec: None
575 } if expr == "user.name"
576 ));
577 assert!(matches!(&parts[2], InterpolationPart::Literal(s) if s == "}"));
578 }
579}