Skip to content

Commit ff598c8

Browse files
Copilotenghitalo
andcommitted
Implement << operator for array append operations
Co-authored-by: enghitalo <63821277+enghitalo@users.noreply.github.com>
1 parent 4f64789 commit ff598c8

4 files changed

Lines changed: 226 additions & 0 deletions

File tree

vlib/v/gen/wasm/gen.v

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,14 @@ pub fn (mut g Gen) handle_ptr_arithmetic(typ ast.Type) {
412412
}
413413

414414
pub fn (mut g Gen) infix_expr(node ast.InfixExpr, expected ast.Type) {
415+
// Special handling for array << operator
416+
if node.op == .left_shift && g.table.type_kind(node.left_type) == .array {
417+
// Array append operation: arr << elem
418+
lhs := g.get_var_or_make_from_expr(node.left, node.left_type)
419+
g.array_push(lhs, node.right)
420+
return
421+
}
422+
415423
if node.op in [.logical_or, .and] {
416424
temp := g.func.new_local_named(.i32_t, '__tmp<bool>')
417425
{
@@ -1167,6 +1175,13 @@ pub fn (mut g Gen) expr_stmt(node ast.Stmt, expected ast.Type) {
11671175
rop := token.assign_op_to_infix_op(node.op)
11681176
lhs := g.get_var_or_make_from_expr(left, typ)
11691177

1178+
// Special handling for array << operator
1179+
if node.op == .left_shift_assign && g.table.type_kind(lhs.typ) == .array {
1180+
// Array append operation: arr << elem
1181+
g.array_push(lhs, right)
1182+
return
1183+
}
1184+
11701185
if !g.is_pure_type(lhs.typ) {
11711186
// main.struct.+
11721187
name := '${g.table.get_type_name(lhs.typ)}.${rop}'

vlib/v/gen/wasm/mem.v

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,201 @@ pub fn (mut g Gen) ensure_cap(arr_var Var, required int) {
494494
g.store(ast.int_type, 12)
495495
}
496496

497+
// array_push implements the array << operator (append single element)
498+
// arr_var: Var pointing to the array struct
499+
// elem_expr: Expression for the element to append
500+
pub fn (mut g Gen) array_push(arr_var Var, elem_expr ast.Expr) {
501+
// Get array element type
502+
elem_type := g.table.value_type(arr_var.typ)
503+
504+
// Load current len and check if len >= max_int
505+
g.get(arr_var)
506+
g.load(ast.int_type, 8) // array.len
507+
current_len := g.func.new_local_named(.i32_t, '__tmp<len>')
508+
g.func.local_tee(current_len)
509+
510+
g.literalint(2147483647, ast.int_type) // max_int
511+
g.func.ge(.i32_t, true) // len >= max_int
512+
513+
blk_len_check := g.func.c_if([], [])
514+
{
515+
// len >= max_int, panic
516+
g.expr(ast.StringLiteral{ val: 'array.push: len exceeds max_int' }, ast.string_type)
517+
g.func.call('panic')
518+
}
519+
g.func.c_end(blk_len_check)
520+
521+
// Load current cap
522+
g.get(arr_var)
523+
g.load(ast.int_type, 12) // array.cap
524+
current_cap := g.func.new_local_named(.i32_t, '__tmp<cap>')
525+
g.func.local_set(current_cap)
526+
527+
// Check if we need to grow: len >= cap
528+
g.func.local_get(current_len)
529+
g.func.local_get(current_cap)
530+
g.func.ge(.i32_t, true) // len >= cap
531+
532+
blk_grow := g.func.c_if([], [])
533+
{
534+
// Need to grow, call ensure_cap(len + 1)
535+
// Calculate required capacity
536+
g.func.local_get(current_len)
537+
g.literalint(1, ast.int_type)
538+
g.func.add(.i32_t)
539+
required := g.func.new_local_named(.i32_t, '__tmp<required>')
540+
g.func.local_tee(required)
541+
542+
// Call ensure_cap with the required capacity as int
543+
// We need to pass it as a literal to ensure_cap
544+
// Since ensure_cap expects a compile-time int, we'll inline the logic here
545+
546+
// Get element_size first
547+
g.get(arr_var)
548+
elem_size := g.func.new_local_named(.i32_t, '__tmp<elem_size>')
549+
g.load(ast.int_type, 20) // array.element_size
550+
g.func.local_set(elem_size)
551+
552+
// Check nogrow flag
553+
g.get(arr_var)
554+
g.load(ast.int_type, 16) // array.flags
555+
flags := g.func.new_local_named(.i32_t, '__tmp<flags>')
556+
g.func.local_tee(flags)
557+
558+
g.literalint(4, ast.int_type) // nogrow = bit 2 = 4
559+
g.func.b_and(.i32_t)
560+
561+
blk_nogrow := g.func.c_if([], [])
562+
{
563+
g.expr(ast.StringLiteral{ val: 'array.push: array with nogrow flag cannot grow' }, ast.string_type)
564+
g.func.call('panic')
565+
}
566+
g.func.c_end(blk_nogrow)
567+
568+
// Calculate new capacity (doubling from current)
569+
g.func.local_get(current_cap)
570+
new_cap := g.func.new_local_named(.i32_t, '__tmp<new_cap>')
571+
572+
// If cap <= 0, use 2, else double
573+
g.func.eqz(.i32_t)
574+
blk_cap_init := g.func.c_if([], [.i32_t])
575+
{
576+
g.literalint(2, ast.int_type)
577+
}
578+
g.func.c_else(blk_cap_init)
579+
{
580+
g.func.local_get(current_cap)
581+
g.literalint(2, ast.int_type)
582+
g.func.mul(.i32_t)
583+
}
584+
g.func.c_end(blk_cap_init)
585+
g.func.local_set(new_cap)
586+
587+
// Ensure new_cap >= required
588+
loop_lbl := g.func.c_loop([], [])
589+
{
590+
g.func.local_get(new_cap)
591+
g.func.local_get(required)
592+
g.func.lt(.i32_t, true)
593+
594+
blk_double := g.func.c_if([], [])
595+
{
596+
g.func.local_get(new_cap)
597+
g.literalint(2, ast.int_type)
598+
g.func.mul(.i32_t)
599+
g.func.local_set(new_cap)
600+
g.func.c_br(loop_lbl)
601+
}
602+
g.func.c_end(blk_double)
603+
}
604+
g.func.c_end(loop_lbl)
605+
606+
// Allocate new memory
607+
g.func.local_get(new_cap)
608+
g.func.local_get(elem_size)
609+
g.func.mul(.i32_t)
610+
g.func.call('malloc')
611+
new_data := g.func.new_local_named(.i32_t, '__tmp<new_data>')
612+
g.func.local_set(new_data)
613+
614+
// Copy existing data
615+
g.get(arr_var)
616+
old_data := g.func.new_local_named(.i32_t, '__tmp<old_data>')
617+
g.load(ast.voidptr_type, 0)
618+
g.func.local_tee(old_data)
619+
620+
g.func.eqz(.i32_t)
621+
blk_copy := g.func.c_if([], [])
622+
{
623+
// old_data is nil
624+
}
625+
g.func.c_else(blk_copy)
626+
{
627+
// vmemcpy(new_data, old_data, len * element_size)
628+
g.func.local_get(new_data)
629+
g.func.local_get(old_data)
630+
g.func.local_get(current_len)
631+
g.func.local_get(elem_size)
632+
g.func.mul(.i32_t)
633+
g.func.call('vmemcpy')
634+
g.func.drop()
635+
636+
// Free old data if noslices
637+
g.func.local_get(flags)
638+
g.literalint(1, ast.int_type)
639+
g.func.b_and(.i32_t)
640+
641+
blk_free := g.func.c_if([], [])
642+
{
643+
g.func.local_get(old_data)
644+
g.func.call('free')
645+
}
646+
g.func.c_end(blk_free)
647+
}
648+
g.func.c_end(blk_copy)
649+
650+
// Update array struct
651+
g.get(arr_var)
652+
g.func.local_get(new_data)
653+
g.store(ast.voidptr_type, 0)
654+
655+
g.get(arr_var)
656+
g.literalint(0, ast.int_type)
657+
g.store(ast.int_type, 4) // offset = 0
658+
659+
g.get(arr_var)
660+
g.func.local_get(new_cap)
661+
g.store(ast.int_type, 12) // cap = new_cap
662+
}
663+
g.func.c_end(blk_grow)
664+
665+
// Get element_size (offset 20)
666+
g.get(arr_var)
667+
final_elem_size := g.func.new_local_named(.i32_t, '__tmp<final_elem_size>')
668+
g.load(ast.int_type, 20)
669+
g.func.local_set(final_elem_size)
670+
671+
// Calculate address for new element: data + (len * element_size)
672+
g.get(arr_var)
673+
g.load(ast.voidptr_type, 0) // array.data
674+
675+
g.func.local_get(current_len)
676+
g.func.local_get(final_elem_size)
677+
g.func.mul(.i32_t)
678+
g.func.add(.i32_t)
679+
680+
// Evaluate and store the new element
681+
g.expr(elem_expr, elem_type)
682+
g.store(elem_type, 0)
683+
684+
// Increment len (offset 8)
685+
g.get(arr_var)
686+
g.func.local_get(current_len)
687+
g.literalint(1, ast.int_type)
688+
g.func.add(.i32_t)
689+
g.store(ast.int_type, 8)
690+
}
691+
497692
pub fn log2(size int) int {
498693
return match size {
499694
1 { 0 }
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Test array append with << operator
2+
fn main() {
3+
mut arr := []int{}
4+
arr << 10
5+
arr << 20
6+
arr << 30
7+
8+
println('Array after appends:')
9+
println(arr[0])
10+
println(arr[1])
11+
println(arr[2])
12+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Array after appends:
2+
10
3+
20
4+
30

0 commit comments

Comments
 (0)