Skip to content

Commit 5715541

Browse files
committed
ssa: string interpolation
1 parent d10c662 commit 5715541

5 files changed

Lines changed: 281 additions & 58 deletions

File tree

‎cmd/tinyv/test.v‎

Lines changed: 31 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1647,45 +1647,36 @@ fn main() {
16471647
print_int(add(arr5[0], arr5[1])) // 7
16481648
print_int(mul(arr5[1], arr5[2])) // 20
16491649

1650-
print_str('=== All tests completed ===')
1651-
}
1650+
// ==================== 34. STRING INTERPOLATION ====================
1651+
print_str('--- 34. String Interpolation ---')
1652+
1653+
// 34.1 Basic integer interpolation
1654+
interp_x := 42
1655+
s1 := 'The answer is ${interp_x}'
1656+
print_str(s1) // The answer is 42
1657+
1658+
// 34.2 Multiple interpolations
1659+
interp_a := 10
1660+
interp_b := 20
1661+
s2 := '${interp_a} + ${interp_b} = ${interp_a + interp_b}'
1662+
print_str(s2) // 10 + 20 = 30
1663+
1664+
// 34.3 String at beginning and end
1665+
interp_val := 100
1666+
s3 := 'Value: ${interp_val}!'
1667+
print_str(s3) // Value: 100!
1668+
1669+
// 34.4 Just interpolation (no literal parts)
1670+
interp_num := 999
1671+
s4 := '${interp_num}'
1672+
print_str(s4) // 999
1673+
1674+
// 34.5 Multiple consecutive values
1675+
interp_v1 := 1
1676+
interp_v2 := 2
1677+
interp_v3 := 3
1678+
s5 := '${interp_v1}-${interp_v2}-${interp_v3}'
1679+
print_str(s5) // 1-2-3
16521680

1653-
/*
1654-
fn test_string_inter_literal() {
1655-
// Test basic integer interpolation
1656-
x := 42
1657-
s1 := 'The answer is ${x}'
1658-
print_str(s1)
1659-
1660-
// Test multiple interpolations
1661-
a := 10
1662-
b := 20
1663-
s2 := '${a} + ${b} = ${a + b}'
1664-
1665-
// Test with format specifiers
1666-
val := 255
1667-
s3 := 'hex: ${val:x}, dec: ${val:d}'
1668-
1669-
// Test float formatting
1670-
f := 3.14159
1671-
s4 := 'pi is approximately ${f:.2f}'
1672-
1673-
// Test with width specifier
1674-
n := 42
1675-
s5 := 'padded: ${n:5d}'
1676-
1677-
// Test string at beginning and end
1678-
name := 'world'
1679-
s6 := 'Hello, ${name}!'
1680-
1681-
// Test consecutive interpolations
1682-
x1 := 1
1683-
x2 := 2
1684-
x3 := 3
1685-
s7 := '${x1}${x2}${x3}'
1686-
1687-
// Test empty string parts
1688-
v := 100
1689-
s8 := '${v}'
1681+
print_str('=== All tests completed ===')
16901682
}
1691-
*/

‎vlib/v2/gen/arm64/arm64.v‎

Lines changed: 85 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub struct Gen {
1212
mut:
1313
macho &MachOObject
1414

15+
pub mut:
1516
stack_map map[int]int
1617
alloca_offsets map[int]int
1718
stack_size int
@@ -98,11 +99,17 @@ fn (mut g Gen) gen_func(func ssa.Function) {
9899
ptr_type := g.mod.type_store.types[val.typ]
99100
elem_type := g.mod.type_store.types[ptr_type.elem_type]
100101

101-
// Calculate size: arrays use elem_count * 8, others use fixed 64 bytes
102-
alloc_size := if elem_type.kind == .array_t {
103-
elem_type.len * 8 // array length * element size (assuming 64-bit)
104-
} else {
105-
64 // Default for non-array types
102+
// Calculate size based on element type
103+
mut alloc_size := 64 // Default for non-array types
104+
if elem_type.kind == .array_t {
105+
// Get the array element type to determine element size
106+
arr_elem_type := g.mod.type_store.types[elem_type.elem_type]
107+
elem_size := if arr_elem_type.width > 0 {
108+
(arr_elem_type.width + 7) / 8 // bits to bytes, rounded up
109+
} else {
110+
8 // default to 64-bit
111+
}
112+
alloc_size = elem_type.len * elem_size
106113
}
107114

108115
// Align to 16 bytes.
@@ -354,18 +361,83 @@ fn (mut g Gen) gen_instr(val_id int) {
354361
g.store_reg_to_val(8, val_id)
355362
}
356363
.call {
357-
// Load arguments in reverse order to avoid clobbering
358-
// If arg2 is in x0, loading arg1 to x0 first would clobber it
364+
fn_val := g.mod.values[instr.operands[0]]
365+
fn_name := fn_val.name
366+
367+
// Check if this is a variadic function (like sprintf)
368+
// On ARM64 macOS, variadic args must be passed on the stack
369+
is_variadic := fn_name in ['sprintf', 'printf', 'snprintf', 'fprintf', 'sscanf']
370+
num_fixed_args := if fn_name == 'sprintf' {
371+
2 // buffer, format
372+
} else if fn_name == 'printf' {
373+
1 // format
374+
} else if fn_name in ['snprintf', 'fprintf'] {
375+
3 // buffer/file, size, format
376+
} else if fn_name == 'sscanf' {
377+
2 // string, format
378+
} else {
379+
8 // default: all in registers
380+
}
381+
359382
num_args := instr.operands.len - 1
360-
for i := num_args; i >= 1; i-- {
361-
if i - 1 < 8 {
383+
384+
if is_variadic && num_args > num_fixed_args {
385+
// Variadic call: push variadic args to stack, fixed args to registers
386+
num_variadic := num_args - num_fixed_args
387+
388+
// Allocate stack space for variadic args (8 bytes each, 16-byte aligned)
389+
stack_space := ((num_variadic * 8) + 15) & ~0xF
390+
if stack_space > 0 {
391+
g.emit_sub_sp(stack_space)
392+
}
393+
394+
// Store variadic arguments to stack (in order)
395+
for i := 0; i < num_variadic; i++ {
396+
arg_idx := num_fixed_args + 1 + i // +1 because operands[0] is the function
397+
g.load_val_to_reg(8, instr.operands[arg_idx])
398+
// STR x8, [sp, #offset]
399+
offset := i * 8
400+
if offset == 0 {
401+
g.emit(0xF9000000 | (31 << 5) | 8) // STR x8, [sp]
402+
} else {
403+
imm12 := u32(offset / 8)
404+
g.emit(0xF9000000 | (imm12 << 10) | (31 << 5) | 8) // STR x8, [sp, #offset]
405+
}
406+
}
407+
408+
// Load fixed arguments to registers (in reverse order to avoid clobbering)
409+
for i := num_fixed_args; i >= 1; i-- {
362410
g.load_val_to_reg(i - 1, instr.operands[i])
363411
}
412+
413+
// Call function
414+
sym_idx := g.macho.add_undefined('_' + fn_name)
415+
g.macho.add_reloc(g.macho.text_data.len, sym_idx, arm64_reloc_branch26, true)
416+
g.emit(0x94000000)
417+
418+
// Restore stack
419+
if stack_space > 0 {
420+
// ADD sp, sp, #stack_space
421+
if stack_space <= 0xFFF {
422+
g.emit(0x910003FF | (u32(stack_space) << 10))
423+
} else {
424+
g.emit_mov_imm(10, u64(stack_space))
425+
g.emit(0x8B0A03FF) // ADD sp, sp, x10
426+
}
427+
}
428+
} else {
429+
// Non-variadic call: all args in registers
430+
// Load arguments in reverse order to avoid clobbering
431+
for i := num_args; i >= 1; i-- {
432+
if i - 1 < 8 {
433+
g.load_val_to_reg(i - 1, instr.operands[i])
434+
}
435+
}
436+
437+
sym_idx := g.macho.add_undefined('_' + fn_name)
438+
g.macho.add_reloc(g.macho.text_data.len, sym_idx, arm64_reloc_branch26, true)
439+
g.emit(0x94000000)
364440
}
365-
fn_val := g.mod.values[instr.operands[0]]
366-
sym_idx := g.macho.add_undefined('_' + fn_val.name)
367-
g.macho.add_reloc(g.macho.text_data.len, sym_idx, arm64_reloc_branch26, true)
368-
g.emit(0x94000000)
369441

370442
if g.mod.type_store.types[g.mod.values[val_id].typ].kind != .void_t {
371443
g.store_reg_to_val(0, val_id)

‎vlib/v2/gen/cleanc/cleanc.v‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ pub fn (mut g Gen) gen() string {
5555
g.sb.writeln('#include <stdlib.h>')
5656
g.sb.writeln('#include <stdbool.h>')
5757
g.sb.writeln('#include <stdint.h>')
58+
g.sb.writeln('#include <string.h>')
5859
g.sb.writeln('')
5960

6061
g.sb.writeln('typedef struct { char* str; int len; } string;')
@@ -190,6 +191,9 @@ fn (mut g Gen) infer_type(node ast.Expr) string {
190191
}
191192
return 'string'
192193
}
194+
ast.StringInterLiteral {
195+
return 'string'
196+
}
193197
ast.ArrayInitExpr {
194198
// For array literals, infer element type from first expr
195199
if node.exprs.len > 0 {
@@ -566,6 +570,36 @@ fn (mut g Gen) gen_expr(node ast.Expr) {
566570
g.sb.write_string('(string){"${val}", ${val.len}}')
567571
}
568572
}
573+
ast.StringInterLiteral {
574+
// String interpolation: 'prefix${a}middle${b}suffix'
575+
// Generate: ({ char buf[256]; sprintf(buf, "prefix%lldmiddle%lldsuffix", a, b); (string){buf, strlen(buf)}; })
576+
g.sb.write_string('({ char _buf[256]; sprintf(_buf, "')
577+
// Build format string
578+
for i, val in node.values {
579+
// Strip quotes from the first and last value parts
580+
mut clean_val := val
581+
if i == 0 {
582+
// First part: strip leading quote
583+
clean_val = clean_val.trim_left("'").trim_left('"')
584+
}
585+
if i == node.values.len - 1 {
586+
// Last part: strip trailing quote
587+
clean_val = clean_val.trim_right("'").trim_right('"')
588+
}
589+
g.sb.write_string(clean_val)
590+
if i < node.inters.len {
591+
inter := node.inters[i]
592+
g.sb.write_string(g.get_printf_format(inter))
593+
}
594+
}
595+
g.sb.write_string('"')
596+
// Add arguments
597+
for inter in node.inters {
598+
g.sb.write_string(', ')
599+
g.gen_expr(inter.expr)
600+
}
601+
g.sb.write_string('); (string){_buf, strlen(_buf)}; })')
602+
}
569603
ast.Ident {
570604
g.sb.write_string(node.name)
571605
}
@@ -1008,3 +1042,30 @@ fn (mut g Gen) gen_else_value(else_expr ast.Expr) {
10081042
g.gen_expr(else_expr)
10091043
}
10101044
}
1045+
1046+
// Convert V string interpolation format to C printf format specifier
1047+
fn (g Gen) get_printf_format(inter ast.StringInter) string {
1048+
base_fmt := match inter.format {
1049+
.unformatted { '%lld' } // Default: assume integer
1050+
.decimal { '%lld' }
1051+
.hex { '%llx' }
1052+
.octal { '%llo' }
1053+
.binary { '%lld' } // C doesn't have binary, use decimal
1054+
.float { '%f' }
1055+
.exponent { '%e' }
1056+
.exponent_short { '%g' }
1057+
.character { '%c' }
1058+
.string { '%s' }
1059+
.pointer_address { '%p' }
1060+
}
1061+
1062+
// Handle width and precision if specified
1063+
if inter.width > 0 && inter.precision > 0 {
1064+
return '%${inter.width}.${inter.precision}' + base_fmt[1..]
1065+
} else if inter.width > 0 {
1066+
return '%${inter.width}' + base_fmt[1..]
1067+
} else if inter.precision > 0 {
1068+
return '%.${inter.precision}' + base_fmt[1..]
1069+
}
1070+
return base_fmt
1071+
}

‎vlib/v2/gen/x64/x64.v‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,17 @@ fn (mut g Gen) gen_func(func ssa.Function) {
9393
ptr_type := g.mod.type_store.types[val.typ]
9494
elem_type := g.mod.type_store.types[ptr_type.elem_type]
9595

96-
// Calculate size: arrays use elem_count * 8, others use fixed 64 bytes
97-
alloc_size := if elem_type.kind == .array_t {
98-
elem_type.len * 8 // array length * element size (assuming 64-bit)
99-
} else {
100-
64 // Default for non-array types
96+
// Calculate size based on element type
97+
mut alloc_size := 64 // Default for non-array types
98+
if elem_type.kind == .array_t {
99+
// Get the array element type to determine element size
100+
arr_elem_type := g.mod.type_store.types[elem_type.elem_type]
101+
elem_size := if arr_elem_type.width > 0 {
102+
(arr_elem_type.width + 7) / 8 // bits to bytes, rounded up
103+
} else {
104+
8 // default to 64-bit
105+
}
106+
alloc_size = elem_type.len * elem_size
101107
}
102108

103109
// Align to 16 bytes

0 commit comments

Comments
 (0)