Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,485 @@
|
||||
// vtest build: macos
|
||||
module cleanc
|
||||
|
||||
import os
|
||||
import v2.parser
|
||||
import v2.pref as vpref
|
||||
import v2.token
|
||||
import v2.transformer
|
||||
import v2.types
|
||||
|
||||
fn generate_array_append_c_for_test(code string) string {
|
||||
return generate_array_append_c_for_test_files([code])
|
||||
}
|
||||
|
||||
fn generate_array_append_c_for_test_files(sources []string) string {
|
||||
tmp_dir := os.join_path(os.temp_dir(), 'v2_array_append_codegen_test_${os.getpid()}')
|
||||
os.rmdir_all(tmp_dir) or {}
|
||||
os.mkdir_all(tmp_dir) or { panic('failed to create temp dir') }
|
||||
defer {
|
||||
os.rmdir_all(tmp_dir) or {}
|
||||
}
|
||||
mut paths := []string{cap: sources.len}
|
||||
for i, code in sources {
|
||||
tmp_file := os.join_path(tmp_dir, 'file_${i}.v')
|
||||
os.write_file(tmp_file, code) or { panic('failed to write temp file') }
|
||||
paths << tmp_file
|
||||
}
|
||||
prefs := &vpref.Preferences{
|
||||
backend: .cleanc
|
||||
no_parallel: true
|
||||
}
|
||||
mut file_set := token.FileSet.new()
|
||||
mut par := parser.Parser.new(prefs)
|
||||
files := par.parse_files(paths, mut file_set)
|
||||
env := types.Environment.new()
|
||||
mut checker := types.Checker.new(prefs, file_set, env)
|
||||
checker.check_files(files)
|
||||
mut trans := transformer.Transformer.new_with_pref(env, prefs)
|
||||
mut gen := Gen.new_with_env_and_pref(trans.transform_files(files), env, prefs)
|
||||
return gen.gen()
|
||||
}
|
||||
|
||||
fn test_generate_c_uses_push_many_for_array_slice_append() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
fn main() {
|
||||
mut dst := []u8{}
|
||||
haystack := [u8(1), 2, 3]
|
||||
dst << haystack[1..3]
|
||||
}
|
||||
')
|
||||
assert csrc.contains('array__push_many(')
|
||||
assert !csrc.contains('&(u8[1]){array__slice(haystack, 1, 3)}')
|
||||
}
|
||||
|
||||
fn test_generate_c_uses_push_many_for_array_literal_append() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
fn main() {
|
||||
mut dst := []u8{}
|
||||
dst << [u8(`$`)]
|
||||
}
|
||||
')
|
||||
assert csrc.contains('array__push_many(')
|
||||
assert !csrc.contains('&(u8[1]){new_array_from_c_array(')
|
||||
}
|
||||
|
||||
fn test_generate_c_uses_push_many_for_local_array_append() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
fn source() []u8 {
|
||||
return [u8(1), 2]
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut dst := []u8{}
|
||||
src := source()
|
||||
dst << src
|
||||
}
|
||||
')
|
||||
assert csrc.contains('array__push_many(')
|
||||
assert !csrc.contains('&(u8[1]){src}')
|
||||
}
|
||||
|
||||
fn test_generate_c_push_many_to_mut_array_param_uses_param_pointer() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
fn extend(mut dst []usize, src []usize) {
|
||||
dst << src
|
||||
}
|
||||
')
|
||||
assert csrc.contains('array__push_many(')
|
||||
assert csrc.contains('array__push_many(((array*)(dst)),')
|
||||
assert !csrc.contains('array__push_many((array*)&dst,')
|
||||
assert !csrc.contains('array__push_many(((array*)(&dst)),')
|
||||
}
|
||||
|
||||
fn test_generate_c_lowered_push_many_to_mut_array_param_uses_param_pointer() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
struct Index {
|
||||
entries map[string][]usize
|
||||
}
|
||||
|
||||
fn (idx Index) matches_into(candidate string, mut matches []usize) {
|
||||
if hits := idx.entries[candidate] {
|
||||
matches << hits
|
||||
}
|
||||
}
|
||||
')
|
||||
assert csrc.contains('array__push_many(')
|
||||
assert csrc.contains('array__push_many(((array*)(matches)),')
|
||||
assert !csrc.contains('array__push_many((array*)&matches,')
|
||||
assert !csrc.contains('array__push_many(((array*)(&matches)),')
|
||||
}
|
||||
|
||||
fn test_generate_c_uses_push_many_for_fn_pointer_array_return_append() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
fn source(data []u8) []u8 {
|
||||
return data
|
||||
}
|
||||
|
||||
fn consume(hash_func fn ([]u8) []u8) {
|
||||
mut dst := []u8{}
|
||||
src := hash_func(dst)
|
||||
dst << src
|
||||
}
|
||||
|
||||
fn main() {
|
||||
consume(source)
|
||||
}
|
||||
')
|
||||
assert csrc.contains('array__push_many(')
|
||||
assert !csrc.contains('&(u8[1]){src}')
|
||||
}
|
||||
|
||||
fn test_generate_c_keeps_bitwise_or_inside_array_append_value() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
fn main() {
|
||||
mut bytes := []u8{}
|
||||
n1 := u8(1)
|
||||
n0 := u8(2)
|
||||
bytes << (n1 << 4) | n0
|
||||
}
|
||||
')
|
||||
assert csrc.contains('array__push(')
|
||||
assert csrc.contains('| n0')
|
||||
assert !csrc.contains('array__push_many(')
|
||||
assert !csrc.contains('_arr_push_many_tmp_')
|
||||
assert !csrc.contains('}) | n0')
|
||||
}
|
||||
|
||||
fn test_generate_c_wraps_mut_u8_array_param_bitwise_append_value() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
fn write_int(mut out []u8, value u64, high_bits u8) {
|
||||
out << high_bits | u8(value)
|
||||
}
|
||||
')
|
||||
assert csrc.contains('array__push(((array*)(out)), &(u8[1]){')
|
||||
assert !csrc.contains('array__push(((array*)(out)), (high_bits | ((u8)(value))))')
|
||||
}
|
||||
|
||||
fn test_generate_c_appends_for_mut_pointer_value_to_array() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
struct PullRequest {
|
||||
mut:
|
||||
repo_name string
|
||||
}
|
||||
|
||||
fn copy(mut prs []PullRequest) []PullRequest {
|
||||
mut out := []PullRequest{}
|
||||
for mut pr in prs {
|
||||
pr.repo_name = "x"
|
||||
out << pr
|
||||
}
|
||||
return out
|
||||
}
|
||||
')
|
||||
assert csrc.contains('array__push(((array*)(&out)), pr);')
|
||||
assert !csrc.contains('array__push(((array*)(&out)), &(PullRequest[1]){pr});')
|
||||
}
|
||||
|
||||
fn test_generate_c_indexes_local_array_that_shadows_function_name() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
fn bytes() []u8 {
|
||||
return []u8{}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut bytes := [u8(1)]
|
||||
bytes[0] &= u8(3)
|
||||
}
|
||||
')
|
||||
assert csrc.contains('((u8*)bytes.data)[((int)(0))] &= ((u8)(3));')
|
||||
assert !csrc.contains('bytes &= ((u8)(3));')
|
||||
}
|
||||
|
||||
fn test_generate_c_uses_typed_zero_for_array_tuple_if_declarations() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
fn pick(operand_a []u64, operand_b []u64) {
|
||||
mut a, mut b := if operand_a.len >= operand_b.len {
|
||||
operand_a, operand_b
|
||||
} else {
|
||||
operand_b, operand_a
|
||||
}
|
||||
_ = a
|
||||
_ = b
|
||||
}
|
||||
')
|
||||
assert csrc.contains('Array_u64 a = ((Array_u64){0});')
|
||||
assert csrc.contains('Array_u64 b = ((Array_u64){0});')
|
||||
assert !csrc.contains('Array_u64 a = 0;')
|
||||
assert !csrc.contains('Array_u64 b = 0;')
|
||||
}
|
||||
|
||||
fn test_generate_c_uses_default_init_for_empty_array_struct_fields() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
struct Pool {
|
||||
items []voidptr
|
||||
}
|
||||
|
||||
fn main() {
|
||||
pool := Pool{
|
||||
items: []
|
||||
}
|
||||
_ = pool.items.len
|
||||
}
|
||||
')
|
||||
assert csrc.contains('.items = __new_array_with_default_noscan(0, 0, sizeof(void*)')
|
||||
assert !csrc.contains('.items = new_array_from_c_array(0, 0, sizeof(void*)')
|
||||
}
|
||||
|
||||
fn test_generate_c_specializes_generic_pointer_array_push_literal() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
struct Foo {}
|
||||
|
||||
fn get_results_ref[T](items []voidptr) []&T {
|
||||
mut res := []&T{cap: items.len}
|
||||
for i in 0 .. items.len {
|
||||
res << unsafe { &T(items[i]) }
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
fn main() {
|
||||
items := []voidptr{}
|
||||
res := get_results_ref[Foo](items)
|
||||
_ = res.len
|
||||
}
|
||||
')
|
||||
assert csrc.contains('(Foo*[1]){((Foo*)')
|
||||
assert !csrc.contains('(T*[1])')
|
||||
assert !csrc.contains('stdatomic__T')
|
||||
}
|
||||
|
||||
fn test_generate_c_uses_captured_mut_array_pointer_for_closure_push() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
fn consume(cb fn (int) bool) {
|
||||
_ = cb(1)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut values := []int{}
|
||||
consume(fn [mut values] (value int) bool {
|
||||
values << value
|
||||
return true
|
||||
})
|
||||
}
|
||||
')
|
||||
assert csrc.contains('Array_int* values = _anon_fn_')
|
||||
assert csrc.contains('array__push(((array*)(values)),')
|
||||
assert !csrc.contains('array__push(((array*)(&values)),')
|
||||
}
|
||||
|
||||
fn test_generate_c_updates_captured_mut_scalar_for_closure_postfix() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
fn consume(cb fn () bool) {
|
||||
_ = cb()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut count := u64(0)
|
||||
consume(fn [mut count] () bool {
|
||||
count++
|
||||
return true
|
||||
})
|
||||
}
|
||||
')
|
||||
assert csrc.contains('u64* count = _anon_fn_')
|
||||
assert csrc.contains('(*count)++')
|
||||
assert !csrc.contains('count++;')
|
||||
}
|
||||
|
||||
fn test_generate_c_evaluates_generic_comptime_is_against_implements() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
interface Writer {
|
||||
write([]u8) !int
|
||||
}
|
||||
|
||||
struct Buffer implements Writer {}
|
||||
|
||||
fn (mut b Buffer) write(buf []u8) !int {
|
||||
_ = b
|
||||
return buf.len
|
||||
}
|
||||
|
||||
fn choose[T]() int {
|
||||
$if T is Writer {
|
||||
return 1
|
||||
} $else {
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
_ = choose[Buffer]()
|
||||
}
|
||||
')
|
||||
assert csrc.contains('return 1;')
|
||||
assert !csrc.contains('return 2;')
|
||||
}
|
||||
|
||||
fn test_generate_c_specializes_comptime_pointer_field_generic_call_by_field_type() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
fn struct_field_should_encode[T](val T) bool {
|
||||
_ = val
|
||||
return true
|
||||
}
|
||||
|
||||
fn encode_fields[T](val T) bool {
|
||||
mut ok := true
|
||||
$for field in T.fields {
|
||||
ok = struct_field_should_encode(val.$(field.name))
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
struct Node {
|
||||
next &Node = unsafe { nil }
|
||||
}
|
||||
|
||||
fn main() {
|
||||
_ = encode_fields(Node{})
|
||||
}
|
||||
')
|
||||
assert csrc.contains('struct_field_should_encode_T_Nodeptr(val.next)')
|
||||
assert !csrc.contains('struct_field_should_encode_T_Node((*val.next))')
|
||||
}
|
||||
|
||||
fn test_generate_c_specializes_comptime_generic_pointer_field_by_field_type() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
module json2
|
||||
|
||||
struct ValueInfo {}
|
||||
|
||||
struct Node[T] {
|
||||
value T
|
||||
next &Node[T] = unsafe { nil }
|
||||
}
|
||||
|
||||
struct Decoder {
|
||||
current_node &Node[ValueInfo] = unsafe { nil }
|
||||
}
|
||||
|
||||
struct EncoderFieldInfo {}
|
||||
|
||||
struct Encoder {}
|
||||
|
||||
fn (mut encoder Encoder) encode_value[T](val T) {
|
||||
$if T is $pointer {
|
||||
encoder.encode_value(*val)
|
||||
} $else $if T is $struct {
|
||||
_ = encoder.encode_fields[T](val)
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) cached_field_infos[T]() []EncoderFieldInfo {
|
||||
return []EncoderFieldInfo{}
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_struct_field_value[T](val T) {
|
||||
$if T.indirections == 1 {
|
||||
encoder.encode_value(*val)
|
||||
} $else {
|
||||
encoder.encode_value(val)
|
||||
}
|
||||
}
|
||||
|
||||
fn struct_field_should_encode[T](field_info EncoderFieldInfo, val T) bool {
|
||||
_ = field_info
|
||||
_ = val
|
||||
return true
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_fields[T](val T) bool {
|
||||
field_infos := encoder.cached_field_infos[T]()
|
||||
mut ok := true
|
||||
mut i := 0
|
||||
$for field in T.fields {
|
||||
field_info := field_infos[i]
|
||||
ok = struct_field_should_encode(field_info, val.$(field.name))
|
||||
encoder.encode_struct_field_value(val.$(field.name))
|
||||
i++
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
fn use_encoder() {
|
||||
mut encoder := Encoder{}
|
||||
_ = encoder.encode_fields(Decoder{})
|
||||
_ = encoder.encode_fields(Node[ValueInfo]{})
|
||||
}
|
||||
')
|
||||
assert csrc.contains('json2__struct_field_should_encode_T_json2_Nodeptr(field_info, val.current_node)')
|
||||
assert csrc.contains('json2__Encoder__encode_struct_field_value_T_json2_Nodeptr(encoder, val.current_node)')
|
||||
assert csrc.contains('json2__Encoder__cached_field_infos_T_json2_Decoder(encoder)')
|
||||
assert csrc.contains('Array_json2__EncoderFieldInfo json2__Encoder__cached_field_infos_T_json2_Node(json2__Encoder* encoder)')
|
||||
assert !csrc.contains('json2__struct_field_should_encode_T_json2_Node(field_info, (*val.current_node))')
|
||||
assert !csrc.contains('json2__Encoder__encode_struct_field_value_T_json2_Node(encoder, (*val.current_node))')
|
||||
}
|
||||
|
||||
fn test_generate_c_extracts_array_sum_variant_for_generic_array_param() {
|
||||
csrc := generate_array_append_c_for_test_files([
|
||||
'
|
||||
module orm
|
||||
|
||||
pub struct InfixType {
|
||||
right int
|
||||
}
|
||||
|
||||
pub type Primitive = InfixType | []InfixType | int
|
||||
|
||||
pub fn tenant_filter_array_primitive_type[T](value []T) int {
|
||||
return value.len
|
||||
}
|
||||
|
||||
pub fn tenant_filter_primitive_type(value Primitive) int {
|
||||
return match value {
|
||||
InfixType {
|
||||
2
|
||||
}
|
||||
[]InfixType {
|
||||
tenant_filter_array_primitive_type(value)
|
||||
}
|
||||
int {
|
||||
3
|
||||
}
|
||||
}
|
||||
}
|
||||
',
|
||||
'
|
||||
module main
|
||||
|
||||
import orm
|
||||
|
||||
fn main() {
|
||||
_ = orm.tenant_filter_primitive_type([]orm.InfixType{})
|
||||
}
|
||||
',
|
||||
])
|
||||
assert csrc.contains('orm__tenant_filter_array_primitive_type_T_orm_InfixType(((((Array_orm__InfixType*)(value._data._Array_orm__InfixType))')
|
||||
assert !csrc.contains('orm__tenant_filter_array_primitive_type_T_orm_InfixType(((((orm__InfixType*)(value._data._InfixType))')
|
||||
}
|
||||
|
||||
fn test_generate_c_lowers_global_interface_generic_method_call() {
|
||||
csrc := generate_array_append_c_for_test('
|
||||
interface Rng {
|
||||
intn(max int) !int
|
||||
}
|
||||
|
||||
__global default_rng &Rng
|
||||
|
||||
fn (mut rng Rng) element[T](array []T) !T {
|
||||
if array.len == 0 {
|
||||
return error("empty")
|
||||
}
|
||||
return array[rng.intn(array.len)!]
|
||||
}
|
||||
|
||||
fn element[T](array []T) !T {
|
||||
return default_rng.element[T](array)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
_ = element[u8]([u8(1)]) or { u8(0) }
|
||||
}
|
||||
')
|
||||
assert csrc.contains('Rng__element_T_u8(default_rng, _v_array)')
|
||||
assert !csrc.contains('u8 _val = (_v_array)')
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,324 @@
|
||||
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
module cleanc
|
||||
|
||||
import strings
|
||||
import v2.ast
|
||||
|
||||
fn (mut g Gen) gen_for_stmt(node ast.ForStmt) {
|
||||
if node.init is ast.ForInStmt {
|
||||
if g.gen_map_for_in_stmt(node, node.init) {
|
||||
return
|
||||
}
|
||||
if g.gen_array_for_in_stmt(node, node.init) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if g.gen_transformed_untyped_map_for_in_stmt(node) {
|
||||
return
|
||||
}
|
||||
g.write_indent()
|
||||
has_init := !is_empty_stmt(node.init)
|
||||
has_cond := !is_empty_expr(node.cond)
|
||||
has_post := !is_empty_stmt(node.post)
|
||||
|
||||
if has_init || has_post {
|
||||
// C-style for loop: for (init; cond; post)
|
||||
g.sb.write_string('for (')
|
||||
if has_init {
|
||||
g.gen_stmt_inline(node.init)
|
||||
}
|
||||
g.sb.write_string('; ')
|
||||
if has_cond {
|
||||
g.expr(node.cond)
|
||||
}
|
||||
g.sb.write_string('; ')
|
||||
if has_post {
|
||||
g.gen_stmt_inline(node.post)
|
||||
}
|
||||
g.sb.writeln(') {')
|
||||
} else if has_cond {
|
||||
// while-style: for cond {
|
||||
g.sb.write_string('while (')
|
||||
g.expr(node.cond)
|
||||
g.sb.writeln(') {')
|
||||
} else {
|
||||
// Infinite loop: for {
|
||||
g.sb.writeln('for (;;) {')
|
||||
}
|
||||
|
||||
g.indent++
|
||||
// Save runtime_local_types: variables declared in the loop body
|
||||
// (e.g. _filter_it in nested map/filter) can shadow outer variables.
|
||||
// Without save/restore, the inner type overwrites the outer in the flat map.
|
||||
saved_local_types := g.runtime_local_types.clone()
|
||||
saved_decl_types := g.runtime_decl_types.clone()
|
||||
g.gen_stmts(node.stmts)
|
||||
g.runtime_local_types = saved_local_types.clone()
|
||||
g.runtime_decl_types = saved_decl_types.clone()
|
||||
g.not_local_var_cache.clear()
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.writeln('}')
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_map_for_in_stmt(node ast.ForStmt, for_in ast.ForInStmt) bool {
|
||||
mut map_type := g.get_expr_type(for_in.expr).trim_space().trim_right('*')
|
||||
if (map_type == '' || map_type == 'int') && for_in.expr is ast.Ident {
|
||||
map_type = (g.get_local_var_c_type(for_in.expr.name) or { '' }).trim_space().trim_right('*')
|
||||
}
|
||||
if !map_type.starts_with('Map_') {
|
||||
return false
|
||||
}
|
||||
key_type, value_type := g.parse_map_kv_types(map_type['Map_'.len..])
|
||||
if key_type == '' || value_type == '' {
|
||||
return false
|
||||
}
|
||||
id := g.tmp_counter
|
||||
g.tmp_counter++
|
||||
map_tmp := '_map_iter_${id}'
|
||||
len_tmp := '_map_len_${id}'
|
||||
idx_tmp := '_map_idx_${id}'
|
||||
delta_tmp := '_map_delta_${id}'
|
||||
mut expr_sb := strings.new_builder(64)
|
||||
saved_sb := g.sb
|
||||
g.sb = expr_sb
|
||||
g.expr(for_in.expr)
|
||||
map_expr := g.sb.str()
|
||||
g.sb = saved_sb
|
||||
g.write_indent()
|
||||
g.sb.writeln('${map_type} ${map_tmp} = ${map_expr};')
|
||||
g.write_indent()
|
||||
g.sb.writeln('int ${len_tmp} = ${map_tmp}.key_values.len;')
|
||||
g.write_indent()
|
||||
g.sb.writeln('for (int ${idx_tmp} = 0; (${idx_tmp} < ${len_tmp}); ${idx_tmp} = (${idx_tmp} + 1)) {')
|
||||
g.indent++
|
||||
g.write_indent()
|
||||
g.sb.writeln('int ${delta_tmp} = (${map_tmp}.key_values.len - ${len_tmp});')
|
||||
g.write_indent()
|
||||
g.sb.writeln('${len_tmp} = ${map_tmp}.key_values.len;')
|
||||
g.write_indent()
|
||||
g.sb.writeln('if ((${delta_tmp} < 0)) {')
|
||||
g.indent++
|
||||
g.write_indent()
|
||||
g.sb.writeln('${idx_tmp} = -1;')
|
||||
g.write_indent()
|
||||
g.sb.writeln('continue;')
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.writeln('}')
|
||||
g.write_indent()
|
||||
g.sb.writeln('if (!DenseArray__has_index(&${map_tmp}.key_values, ${idx_tmp})) {')
|
||||
g.indent++
|
||||
g.write_indent()
|
||||
g.sb.writeln('continue;')
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.writeln('}')
|
||||
key_name := if for_in.key is ast.Ident { for_in.key.name } else { '' }
|
||||
value_name := if for_in.value is ast.Ident { for_in.value.name } else { '' }
|
||||
saved_local_types := g.runtime_local_types.clone()
|
||||
saved_decl_types := g.runtime_decl_types.clone()
|
||||
if key_name != '' && key_name != '_' {
|
||||
g.write_indent()
|
||||
g.sb.writeln('${key_type} ${key_name} = *((${key_type}*)DenseArray__key(&${map_tmp}.key_values, ${idx_tmp}));')
|
||||
if key_type == 'string' {
|
||||
g.write_indent()
|
||||
g.sb.writeln('${key_name} = string__clone(${key_name});')
|
||||
}
|
||||
g.remember_runtime_local_type(key_name, key_type)
|
||||
}
|
||||
if value_name != '' && value_name != '_' {
|
||||
g.write_indent()
|
||||
g.sb.writeln('${value_type} ${value_name} = *((${value_type}*)DenseArray__value(&${map_tmp}.key_values, ${idx_tmp}));')
|
||||
if value_type == 'string' {
|
||||
g.write_indent()
|
||||
g.sb.writeln('${value_name} = string__clone(${value_name});')
|
||||
}
|
||||
g.remember_runtime_local_type(value_name, value_type)
|
||||
}
|
||||
g.gen_stmts(node.stmts)
|
||||
g.runtime_local_types = saved_local_types.clone()
|
||||
g.runtime_decl_types = saved_decl_types.clone()
|
||||
g.not_local_var_cache.clear()
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.writeln('}')
|
||||
return true
|
||||
}
|
||||
|
||||
fn cleanc_for_in_ident_name(expr ast.Expr) (string, bool) {
|
||||
if expr is ast.Ident {
|
||||
return expr.name, false
|
||||
}
|
||||
if expr is ast.ModifierExpr {
|
||||
if expr.expr is ast.Ident {
|
||||
return expr.expr.name, expr.kind == .key_mut
|
||||
}
|
||||
}
|
||||
return '', false
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_array_for_in_stmt(node ast.ForStmt, for_in ast.ForInStmt) bool {
|
||||
mut array_type := g.get_expr_type(for_in.expr).trim_space().trim_right('*')
|
||||
if (array_type == '' || array_type == 'int') && for_in.expr is ast.Ident {
|
||||
array_type =
|
||||
(g.get_local_var_c_type(for_in.expr.name) or { '' }).trim_space().trim_right('*')
|
||||
}
|
||||
if array_type == '' {
|
||||
return false
|
||||
}
|
||||
mut array_decl_type := array_type
|
||||
if !c_type_is_array_value(array_decl_type) {
|
||||
alias_base := g.array_alias_base_type(array_decl_type)
|
||||
if alias_base != '' {
|
||||
array_decl_type = alias_base
|
||||
}
|
||||
}
|
||||
if !c_type_is_array_value(array_decl_type) {
|
||||
return false
|
||||
}
|
||||
elem_type := g.array_alias_elem_type_from_c_type(array_decl_type)
|
||||
if elem_type == '' {
|
||||
return false
|
||||
}
|
||||
id := g.tmp_counter
|
||||
g.tmp_counter++
|
||||
array_tmp := '_arr_iter_${id}'
|
||||
idx_tmp := '_arr_idx_${id}'
|
||||
mut expr_sb := strings.new_builder(64)
|
||||
saved_sb := g.sb
|
||||
g.sb = expr_sb
|
||||
g.expr(for_in.expr)
|
||||
array_expr := g.sb.str()
|
||||
g.sb = saved_sb
|
||||
g.write_indent()
|
||||
g.sb.writeln('${array_decl_type} ${array_tmp} = ${array_expr};')
|
||||
g.write_indent()
|
||||
g.sb.writeln('for (int ${idx_tmp} = 0; (${idx_tmp} < ${array_tmp}.len); ${idx_tmp} = (${idx_tmp} + 1)) {')
|
||||
g.indent++
|
||||
key_name, _ := cleanc_for_in_ident_name(for_in.key)
|
||||
value_name, value_is_mut := cleanc_for_in_ident_name(for_in.value)
|
||||
saved_local_types := g.runtime_local_types.clone()
|
||||
saved_decl_types := g.runtime_decl_types.clone()
|
||||
if key_name != '' && key_name != '_' {
|
||||
g.write_indent()
|
||||
g.sb.writeln('int ${key_name} = ${idx_tmp};')
|
||||
g.remember_runtime_local_type(key_name, 'int')
|
||||
}
|
||||
if value_name != '' && value_name != '_' {
|
||||
g.write_indent()
|
||||
if value_is_mut {
|
||||
g.sb.writeln('${elem_type}* ${value_name} = &(((${elem_type}*)${array_tmp}.data)[${idx_tmp}]);')
|
||||
g.remember_runtime_local_type(value_name, '${elem_type}*')
|
||||
} else {
|
||||
g.sb.writeln('${elem_type} ${value_name} = ((${elem_type}*)${array_tmp}.data)[${idx_tmp}];')
|
||||
if elem_type == 'string' {
|
||||
g.write_indent()
|
||||
g.sb.writeln('${value_name} = string__clone(${value_name});')
|
||||
}
|
||||
g.remember_runtime_local_type(value_name, elem_type)
|
||||
}
|
||||
}
|
||||
g.gen_stmts(node.stmts)
|
||||
g.runtime_local_types = saved_local_types.clone()
|
||||
g.runtime_decl_types = saved_decl_types.clone()
|
||||
g.not_local_var_cache.clear()
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.writeln('}')
|
||||
return true
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_transformed_untyped_map_for_in_stmt(node ast.ForStmt) bool {
|
||||
if node.init !is ast.AssignStmt || node.stmts.len == 0 {
|
||||
return false
|
||||
}
|
||||
init := node.init as ast.AssignStmt
|
||||
if init.lhs.len == 0 || init.lhs[0] !is ast.Ident {
|
||||
return false
|
||||
}
|
||||
key_name := (init.lhs[0] as ast.Ident).name
|
||||
first_stmt := node.stmts[0]
|
||||
if first_stmt !is ast.AssignStmt {
|
||||
return false
|
||||
}
|
||||
value_assign := first_stmt as ast.AssignStmt
|
||||
if value_assign.lhs.len == 0 || value_assign.rhs.len == 0 || value_assign.lhs[0] !is ast.Ident
|
||||
|| value_assign.rhs[0] !is ast.IndexExpr {
|
||||
return false
|
||||
}
|
||||
value_name := (value_assign.lhs[0] as ast.Ident).name
|
||||
index_expr := value_assign.rhs[0] as ast.IndexExpr
|
||||
mut map_type := g.get_expr_type(index_expr.lhs).trim_space().trim_right('*')
|
||||
if (map_type == '' || map_type == 'int') && index_expr.lhs is ast.Ident {
|
||||
map_type =
|
||||
(g.get_local_var_c_type(index_expr.lhs.name) or { '' }).trim_space().trim_right('*')
|
||||
}
|
||||
if !map_type.starts_with('Map_') {
|
||||
return false
|
||||
}
|
||||
map_node := ast.ForStmt{
|
||||
stmts: node.stmts[1..]
|
||||
}
|
||||
return g.gen_map_for_in_stmt(map_node, ast.ForInStmt{
|
||||
key: ast.Expr(ast.Ident{
|
||||
name: key_name
|
||||
})
|
||||
value: ast.Expr(ast.Ident{
|
||||
name: value_name
|
||||
})
|
||||
expr: index_expr.lhs
|
||||
})
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_stmt_inline(node ast.Stmt) {
|
||||
if !stmt_has_valid_data(node) {
|
||||
return
|
||||
}
|
||||
match node {
|
||||
ast.AssignStmt {
|
||||
lhs := node.lhs[0]
|
||||
rhs := node.rhs[0]
|
||||
if node.op == .decl_assign {
|
||||
mut name := ''
|
||||
if lhs is ast.Ident {
|
||||
name = lhs.name
|
||||
}
|
||||
typ := g.get_expr_type(rhs)
|
||||
g.sb.write_string('${typ} ${name} = ')
|
||||
g.expr(rhs)
|
||||
// Register the for-loop init variable so assignments inside
|
||||
// the loop body don't re-declare it.
|
||||
if name != '' && typ != '' {
|
||||
g.remember_runtime_local_type(name, typ)
|
||||
}
|
||||
} else {
|
||||
g.expr(lhs)
|
||||
op_str := match node.op {
|
||||
.assign { '=' }
|
||||
.plus_assign { '+=' }
|
||||
.minus_assign { '-=' }
|
||||
.mul_assign { '*=' }
|
||||
.div_assign { '/=' }
|
||||
.mod_assign { '%=' }
|
||||
.and_assign { '&=' }
|
||||
.or_assign { '|=' }
|
||||
.xor_assign { '^=' }
|
||||
.left_shift_assign { '<<=' }
|
||||
.right_shift_assign { '>>=' }
|
||||
else { '=' }
|
||||
}
|
||||
|
||||
g.sb.write_string(' ${op_str} ')
|
||||
g.expr(rhs)
|
||||
}
|
||||
}
|
||||
ast.ExprStmt {
|
||||
g.expr(node.expr)
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
module cleanc
|
||||
|
||||
fn test_generic_struct_scan_job_count_caps_runtime_jobs() {
|
||||
assert generic_struct_scan_job_count(128, 1000) == max_generic_struct_scan_jobs
|
||||
assert generic_struct_scan_job_count(64, 15) == 3
|
||||
assert generic_struct_scan_job_count(2, 1000) == 2
|
||||
assert generic_struct_scan_job_count(4, 0) == 0
|
||||
assert generic_struct_scan_job_count(0, 4) == 0
|
||||
}
|
||||
@@ -0,0 +1,613 @@
|
||||
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
module cleanc
|
||||
|
||||
import v2.ast
|
||||
|
||||
fn const_bool_value(expr ast.Expr) ?bool {
|
||||
match expr {
|
||||
ast.BasicLiteral {
|
||||
if expr.kind == .key_true {
|
||||
return true
|
||||
}
|
||||
if expr.kind == .key_false {
|
||||
return false
|
||||
}
|
||||
if expr.value == 'true' {
|
||||
return true
|
||||
}
|
||||
if expr.value == 'false' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
ast.Ident {
|
||||
if expr.name == 'true' {
|
||||
return true
|
||||
}
|
||||
if expr.name == 'false' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
ast.Keyword {
|
||||
if expr.tok == .key_true {
|
||||
return true
|
||||
}
|
||||
if expr.tok == .key_false {
|
||||
return false
|
||||
}
|
||||
}
|
||||
ast.ParenExpr {
|
||||
return const_bool_value(expr.expr)
|
||||
}
|
||||
ast.ModifierExpr {
|
||||
return const_bool_value(expr.expr)
|
||||
}
|
||||
ast.CastExpr {
|
||||
return const_bool_value(expr.expr)
|
||||
}
|
||||
ast.ComptimeExpr {
|
||||
return const_bool_value(expr.expr)
|
||||
}
|
||||
ast.UnsafeExpr {
|
||||
if expr.stmts.len == 1 && expr.stmts[0] is ast.ExprStmt {
|
||||
stmt := expr.stmts[0] as ast.ExprStmt
|
||||
return const_bool_value(stmt.expr)
|
||||
}
|
||||
}
|
||||
ast.PrefixExpr {
|
||||
if expr.op == .not {
|
||||
if v := const_bool_value(expr.expr) {
|
||||
return !v
|
||||
}
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
return none
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_if_expr_assign_value(name string, value_type string, expr ast.Expr) {
|
||||
g.write_indent()
|
||||
if value_type.starts_with('Array_fixed_') && !value_type.ends_with('*') {
|
||||
if expr is ast.CallExpr {
|
||||
if call_ret := g.get_call_return_type(expr.lhs, expr.args) {
|
||||
if call_ret == value_type {
|
||||
wrapper_type := g.c_fn_return_type_from_v(value_type)
|
||||
g.sb.write_string('{ ${wrapper_type} _tmp = ')
|
||||
g.expr(expr)
|
||||
g.sb.writeln('; memcpy(${name}, _tmp.ret_arr, sizeof(${value_type})); }')
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
g.sb.write_string('memcpy(${name}, ')
|
||||
if expr is ast.ArrayInitExpr {
|
||||
g.sb.write_string('((${value_type})')
|
||||
g.expr(expr)
|
||||
g.sb.write_string(')')
|
||||
} else {
|
||||
g.expr(expr)
|
||||
}
|
||||
g.sb.writeln(', sizeof(${value_type}));')
|
||||
return
|
||||
}
|
||||
g.sb.write_string('${name} = ')
|
||||
if g.get_sum_type_variants_for(value_type).len > 0 {
|
||||
g.gen_type_cast_expr(value_type, expr)
|
||||
} else {
|
||||
g.expr(expr)
|
||||
}
|
||||
g.sb.writeln(';')
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_decl_if_expr_branch(name string, value_type string, stmts []ast.Stmt) {
|
||||
last_idx := last_non_empty_stmt_index(stmts)
|
||||
if last_idx < 0 {
|
||||
return
|
||||
}
|
||||
for i, stmt in stmts {
|
||||
if stmt is ast.EmptyStmt {
|
||||
continue
|
||||
}
|
||||
if i == last_idx && stmt is ast.ExprStmt {
|
||||
if stmt.expr is ast.IfExpr {
|
||||
nested_if := stmt.expr as ast.IfExpr
|
||||
if !g.if_expr_can_be_ternary(&nested_if) && nested_if.else_expr !is ast.EmptyExpr {
|
||||
g.gen_decl_if_expr(name, value_type, &nested_if)
|
||||
continue
|
||||
}
|
||||
if nested_if.else_expr is ast.EmptyExpr {
|
||||
if payload_expr := g.unsafe_expr_or_payload_value(stmts) {
|
||||
g.gen_stmt(stmt)
|
||||
g.gen_if_expr_assign_value(name, value_type, payload_expr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
// When the last expression is a void call (e.g. array__push from <<),
|
||||
// emit it as a standalone statement without assigning to the temp var.
|
||||
if g.expr_is_void_call(stmt.expr) {
|
||||
g.write_indent()
|
||||
g.expr(stmt.expr)
|
||||
g.sb.writeln(';')
|
||||
} else {
|
||||
g.gen_if_expr_assign_value(name, value_type, stmt.expr)
|
||||
}
|
||||
} else {
|
||||
g.gen_stmt(stmt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_scoped_decl_if_expr_branch(name string, value_type string, stmts []ast.Stmt) {
|
||||
saved_runtime_local_types := g.runtime_local_types.clone()
|
||||
saved_runtime_decl_types := g.runtime_decl_types.clone()
|
||||
saved_not_local_var_cache := g.not_local_var_cache.clone()
|
||||
g.gen_decl_if_expr_branch(name, value_type, stmts)
|
||||
g.runtime_local_types = saved_runtime_local_types.clone()
|
||||
g.runtime_decl_types = saved_runtime_decl_types.clone()
|
||||
g.not_local_var_cache = saved_not_local_var_cache.clone()
|
||||
}
|
||||
|
||||
// expr_is_void_call checks if the expression is a function call that returns void.
|
||||
// Used to prevent assigning void results to temp variables in if-expression branches.
|
||||
fn (mut g Gen) expr_is_void_call(expr ast.Expr) bool {
|
||||
if expr is ast.CallExpr {
|
||||
if ret := g.get_call_return_type(expr.lhs, expr.args) {
|
||||
return ret == 'void'
|
||||
}
|
||||
// Check resolved name
|
||||
c_name := g.resolve_call_name(expr.lhs, expr.args.len)
|
||||
if c_name != '' {
|
||||
if ret := g.fn_return_types[c_name] {
|
||||
return ret == 'void'
|
||||
}
|
||||
}
|
||||
// panic/v_panic always returns void (noreturn)
|
||||
if expr.lhs is ast.Ident {
|
||||
if expr.lhs.name in ['panic', 'v_panic'] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_decl_if_expr(name string, value_type string, if_expr &ast.IfExpr) {
|
||||
if if_expr.cond is ast.EmptyExpr {
|
||||
g.gen_scoped_decl_if_expr_branch(name, value_type, if_expr.stmts)
|
||||
return
|
||||
}
|
||||
g.write_indent()
|
||||
g.sb.write_string('if (')
|
||||
g.expr(if_expr.cond)
|
||||
g.sb.writeln(') {')
|
||||
g.indent++
|
||||
g.gen_scoped_decl_if_expr_branch(name, value_type, if_expr.stmts)
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.write_string('}')
|
||||
if if_expr.else_expr is ast.IfExpr {
|
||||
else_if := if_expr.else_expr as ast.IfExpr
|
||||
if else_if.cond is ast.EmptyExpr {
|
||||
g.sb.writeln(' else {')
|
||||
g.indent++
|
||||
g.gen_scoped_decl_if_expr_branch(name, value_type, else_if.stmts)
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.writeln('}')
|
||||
} else {
|
||||
g.sb.writeln(' else {')
|
||||
g.indent++
|
||||
g.gen_decl_if_expr(name, value_type, &else_if)
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.writeln('}')
|
||||
}
|
||||
} else if if_expr.else_expr !is ast.EmptyExpr {
|
||||
g.sb.writeln(' else {')
|
||||
g.indent++
|
||||
g.gen_if_expr_assign_value(name, value_type, if_expr.else_expr)
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.writeln('}')
|
||||
} else {
|
||||
g.sb.writeln('')
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_return_if_branch(stmts []ast.Stmt) {
|
||||
if stmts.len == 0 {
|
||||
return
|
||||
}
|
||||
for i, stmt in stmts {
|
||||
if i == stmts.len - 1 && stmt is ast.ExprStmt {
|
||||
if stmt.expr is ast.IfExpr {
|
||||
nested_if := stmt.expr as ast.IfExpr
|
||||
if !g.if_expr_can_be_ternary(&nested_if) && nested_if.else_expr !is ast.EmptyExpr {
|
||||
g.gen_return_if_expr(nested_if, true)
|
||||
continue
|
||||
}
|
||||
}
|
||||
mut ret_exprs := []ast.Expr{cap: 1}
|
||||
ret_exprs << stmt.expr
|
||||
ret_stmt := ast.ReturnStmt{
|
||||
exprs: ret_exprs
|
||||
}
|
||||
prev_suppress := g.suppress_return_drop_emit
|
||||
g.suppress_return_drop_emit = true
|
||||
g.gen_stmt(ret_stmt)
|
||||
g.suppress_return_drop_emit = prev_suppress
|
||||
} else {
|
||||
g.gen_stmt(stmt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_return_if_expr(if_expr ast.IfExpr, emit_indent bool) {
|
||||
if if_expr.cond is ast.EmptyExpr {
|
||||
g.gen_return_if_branch(if_expr.stmts)
|
||||
return
|
||||
}
|
||||
if emit_indent {
|
||||
g.write_indent()
|
||||
}
|
||||
g.sb.write_string('if (')
|
||||
g.expr(if_expr.cond)
|
||||
g.sb.writeln(') {')
|
||||
g.indent++
|
||||
g.gen_return_if_branch(if_expr.stmts)
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.write_string('}')
|
||||
if if_expr.else_expr is ast.IfExpr {
|
||||
else_if := if_expr.else_expr as ast.IfExpr
|
||||
if else_if.cond is ast.EmptyExpr {
|
||||
g.sb.writeln(' else {')
|
||||
g.indent++
|
||||
g.gen_return_if_branch(else_if.stmts)
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.writeln('}')
|
||||
} else {
|
||||
g.sb.writeln(' else {')
|
||||
g.indent++
|
||||
g.gen_return_if_expr(else_if, true)
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.writeln('}')
|
||||
}
|
||||
} else if if_expr.else_expr !is ast.EmptyExpr {
|
||||
g.sb.writeln(' else {')
|
||||
g.indent++
|
||||
mut else_exprs := []ast.Expr{cap: 1}
|
||||
else_exprs << if_expr.else_expr
|
||||
else_stmt := ast.ReturnStmt{
|
||||
exprs: else_exprs
|
||||
}
|
||||
prev_suppress := g.suppress_return_drop_emit
|
||||
g.suppress_return_drop_emit = true
|
||||
g.gen_stmt(else_stmt)
|
||||
g.suppress_return_drop_emit = prev_suppress
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.writeln('}')
|
||||
} else {
|
||||
g.sb.writeln('')
|
||||
}
|
||||
}
|
||||
|
||||
fn (g &Gen) if_expr_can_be_ternary(node &ast.IfExpr) bool {
|
||||
if node.cond !is ast.EmptyExpr {
|
||||
if node.stmts.len != 1 || node.stmts[0] !is ast.ExprStmt {
|
||||
return false
|
||||
}
|
||||
if node.else_expr is ast.EmptyExpr {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if node.cond is ast.EmptyExpr {
|
||||
return node.stmts.len == 1 && node.stmts[0] is ast.ExprStmt
|
||||
}
|
||||
if node.else_expr is ast.IfExpr {
|
||||
else_if := node.else_expr as ast.IfExpr
|
||||
return g.if_expr_can_be_ternary(&else_if)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fn (g &Gen) extract_if_expr(expr ast.Expr) ?ast.IfExpr {
|
||||
match expr {
|
||||
ast.IfExpr {
|
||||
return expr
|
||||
}
|
||||
ast.ParenExpr {
|
||||
return g.extract_if_expr(expr.expr)
|
||||
}
|
||||
ast.ModifierExpr {
|
||||
return g.extract_if_expr(expr.expr)
|
||||
}
|
||||
ast.UnsafeExpr {
|
||||
if expr.stmts.len == 1 {
|
||||
stmt0 := expr.stmts[0]
|
||||
if stmt0 is ast.ExprStmt {
|
||||
expr_stmt := stmt0 as ast.ExprStmt
|
||||
return g.extract_if_expr(expr_stmt.expr)
|
||||
}
|
||||
}
|
||||
return none
|
||||
}
|
||||
else {
|
||||
return none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn last_non_empty_stmt_index(stmts []ast.Stmt) int {
|
||||
mut idx := stmts.len - 1
|
||||
for idx >= 0 && stmts[idx] is ast.EmptyStmt {
|
||||
idx--
|
||||
}
|
||||
return idx
|
||||
}
|
||||
|
||||
fn (mut g Gen) branch_result_type(stmts []ast.Stmt) string {
|
||||
last_idx := last_non_empty_stmt_index(stmts)
|
||||
if last_idx < 0 {
|
||||
return ''
|
||||
}
|
||||
last := stmts[last_idx]
|
||||
match last {
|
||||
ast.BlockStmt {
|
||||
return g.branch_result_type(last.stmts)
|
||||
}
|
||||
ast.ExprStmt {
|
||||
if nested := g.extract_if_expr(last.expr) {
|
||||
return g.get_if_expr_type(&nested)
|
||||
}
|
||||
mut typ := g.get_expr_type(last.expr)
|
||||
if (typ == '' || typ == 'int' || typ == 'void*' || typ == 'voidptr')
|
||||
&& last.expr is ast.Ident {
|
||||
if local_typ := g.branch_declared_ident_type(stmts, last_idx, last.expr.name) {
|
||||
typ = local_typ
|
||||
}
|
||||
}
|
||||
if typ == '' || typ == 'int' || typ == 'void*' || typ == 'voidptr' {
|
||||
if raw := g.get_raw_type(last.expr) {
|
||||
raw_type := g.types_type_to_c(raw)
|
||||
if raw_type != '' && raw_type != 'void*' && raw_type != 'voidptr' {
|
||||
typ = raw_type
|
||||
}
|
||||
}
|
||||
}
|
||||
return typ
|
||||
}
|
||||
else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) branch_declared_ident_type(stmts []ast.Stmt, before_idx int, name string) ?string {
|
||||
if name == '' {
|
||||
return none
|
||||
}
|
||||
mut i := before_idx - 1
|
||||
for i >= 0 {
|
||||
stmt := stmts[i]
|
||||
if stmt is ast.AssignStmt {
|
||||
if stmt.op == .decl_assign && stmt.lhs.len == stmt.rhs.len {
|
||||
for j, lhs in stmt.lhs {
|
||||
if lhs is ast.Ident && lhs.name == name {
|
||||
mut typ := g.get_expr_type(stmt.rhs[j])
|
||||
if typ == '' || typ == 'int' || typ == 'void*' || typ == 'voidptr' {
|
||||
if raw := g.get_raw_type(stmt.rhs[j]) {
|
||||
raw_type := g.types_type_to_c(raw)
|
||||
if raw_type != '' && raw_type != 'void*' && raw_type != 'voidptr' {
|
||||
typ = raw_type
|
||||
}
|
||||
}
|
||||
}
|
||||
if typ != '' && typ != 'int' && typ != 'void*' && typ != 'voidptr' {
|
||||
return typ
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
i--
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_if_expr_stmt(node &ast.IfExpr) {
|
||||
// Skip empty conditions (pure else blocks shouldn't appear at top level)
|
||||
if node.cond is ast.EmptyExpr {
|
||||
return
|
||||
}
|
||||
if cond_value := const_bool_value(node.cond) {
|
||||
if cond_value {
|
||||
g.gen_scoped_stmts(node.stmts)
|
||||
return
|
||||
}
|
||||
if node.else_expr is ast.IfExpr {
|
||||
else_if := node.else_expr as ast.IfExpr
|
||||
if else_if.cond is ast.EmptyExpr {
|
||||
g.gen_scoped_stmts(else_if.stmts)
|
||||
} else {
|
||||
g.gen_if_expr_stmt(&else_if)
|
||||
}
|
||||
} else if node.else_expr !is ast.EmptyExpr {
|
||||
g.gen_scoped_expr_stmts(node.else_expr)
|
||||
}
|
||||
return
|
||||
}
|
||||
g.sb.write_string('if (')
|
||||
g.expr(node.cond)
|
||||
g.sb.writeln(') {')
|
||||
g.indent++
|
||||
g.gen_scoped_stmts(node.stmts)
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.write_string('}')
|
||||
// Handle else / else-if
|
||||
if node.else_expr !is ast.EmptyExpr {
|
||||
if node.else_expr is ast.IfExpr {
|
||||
else_if := node.else_expr as ast.IfExpr
|
||||
if else_if.cond is ast.EmptyExpr {
|
||||
g.sb.writeln(' else {')
|
||||
g.indent++
|
||||
g.gen_scoped_stmts(else_if.stmts)
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.write_string('}')
|
||||
} else {
|
||||
g.sb.write_string(' else ')
|
||||
g.gen_if_expr_stmt(&else_if)
|
||||
}
|
||||
} else {
|
||||
g.sb.writeln(' else {')
|
||||
g.indent++
|
||||
g.gen_scoped_expr_stmts(node.else_expr)
|
||||
g.indent--
|
||||
g.write_indent()
|
||||
g.sb.write_string('}')
|
||||
}
|
||||
}
|
||||
g.sb.writeln('')
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_if_expr_ternary(node &ast.IfExpr) {
|
||||
if node.cond is ast.EmptyExpr {
|
||||
if node.stmts.len == 1 && node.stmts[0] is ast.ExprStmt {
|
||||
stmt := node.stmts[0] as ast.ExprStmt
|
||||
g.expr(stmt.expr)
|
||||
} else {
|
||||
g.sb.write_string('0')
|
||||
}
|
||||
return
|
||||
}
|
||||
g.sb.write_string('(')
|
||||
g.expr(node.cond)
|
||||
g.sb.write_string(' ? ')
|
||||
if node.stmts.len == 1 && node.stmts[0] is ast.ExprStmt {
|
||||
stmt := node.stmts[0] as ast.ExprStmt
|
||||
if nested := g.extract_if_expr(stmt.expr) {
|
||||
g.gen_if_expr_value(&nested)
|
||||
} else {
|
||||
g.expr(stmt.expr)
|
||||
}
|
||||
} else {
|
||||
g.sb.write_string('0')
|
||||
}
|
||||
g.sb.write_string(' : ')
|
||||
if node.else_expr is ast.IfExpr {
|
||||
else_if := node.else_expr as ast.IfExpr
|
||||
g.gen_if_expr_value(&else_if)
|
||||
} else if node.else_expr is ast.EmptyExpr {
|
||||
g.sb.write_string('0')
|
||||
} else {
|
||||
if nested := g.extract_if_expr(node.else_expr) {
|
||||
g.gen_if_expr_value(&nested)
|
||||
} else {
|
||||
g.expr(node.else_expr)
|
||||
}
|
||||
}
|
||||
g.sb.write_string(')')
|
||||
}
|
||||
|
||||
fn (mut g Gen) gen_if_expr_value(node &ast.IfExpr) {
|
||||
mut value_type := g.get_if_expr_type(node)
|
||||
if value_type == '' || value_type == 'void' {
|
||||
g.sb.write_string('({ ')
|
||||
g.gen_if_expr_stmt(node)
|
||||
g.sb.write_string('; 0; })')
|
||||
return
|
||||
}
|
||||
if value_type == 'int_literal' {
|
||||
value_type = 'int'
|
||||
}
|
||||
tmp_name := '_if_expr_t${g.tmp_counter}'
|
||||
g.tmp_counter++
|
||||
g.sb.write_string('({ ${value_type} ${tmp_name} = ${zero_value_for_type(value_type)}; ')
|
||||
g.gen_decl_if_expr(tmp_name, value_type, node)
|
||||
if value_type.starts_with('Array_fixed_') && !value_type.ends_with('*') {
|
||||
_, fixed_len := parse_fixed_array_elem_type(value_type)
|
||||
if fixed_len > 0 {
|
||||
g.sb.write_string(' (${value_type}){')
|
||||
for i in 0 .. fixed_len {
|
||||
if i > 0 {
|
||||
g.sb.write_string(', ')
|
||||
}
|
||||
g.sb.write_string('${tmp_name}[${i}]')
|
||||
}
|
||||
g.sb.write_string('}; })')
|
||||
return
|
||||
}
|
||||
}
|
||||
g.sb.write_string(' ${tmp_name}; })')
|
||||
}
|
||||
|
||||
fn (mut g Gen) get_if_expr_type(node &ast.IfExpr) string {
|
||||
mut env_type := ''
|
||||
if t := g.get_expr_type_from_env(ast.Expr(*node)) {
|
||||
if t != '' {
|
||||
env_type = t
|
||||
}
|
||||
}
|
||||
mut branch_type := g.branch_result_type(node.stmts)
|
||||
if branch_type == 'int_literal' {
|
||||
branch_type = 'int'
|
||||
}
|
||||
mut else_type := ''
|
||||
if node.else_expr is ast.IfExpr {
|
||||
else_if := node.else_expr as ast.IfExpr
|
||||
else_type = g.get_if_expr_type(&else_if)
|
||||
} else if node.else_expr !is ast.EmptyExpr {
|
||||
if nested := g.extract_if_expr(node.else_expr) {
|
||||
else_type = g.get_if_expr_type(&nested)
|
||||
} else {
|
||||
else_type = g.get_expr_type(node.else_expr)
|
||||
if else_type == '' || else_type == 'int' || else_type == 'void*'
|
||||
|| else_type == 'voidptr' {
|
||||
if raw := g.get_raw_type(node.else_expr) {
|
||||
raw_type := g.types_type_to_c(raw)
|
||||
if raw_type != '' && raw_type != 'void*' && raw_type != 'voidptr' {
|
||||
else_type = raw_type
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if else_type == 'int_literal' {
|
||||
else_type = 'int'
|
||||
}
|
||||
if branch_type != '' && else_type != '' && branch_type == else_type {
|
||||
if branch_type != 'int' || env_type == '' || env_type == 'int' || env_type == 'void*'
|
||||
|| env_type == 'voidptr' {
|
||||
return branch_type
|
||||
}
|
||||
}
|
||||
if branch_type != '' && branch_type != 'int' {
|
||||
if env_type == '' || env_type == 'int' || branch_type.starts_with('${env_type}_T_') {
|
||||
return branch_type
|
||||
}
|
||||
}
|
||||
if env_type != '' {
|
||||
if g.cur_fn_ret_type.starts_with('${env_type}_T_') {
|
||||
return g.cur_fn_ret_type
|
||||
}
|
||||
return env_type
|
||||
}
|
||||
if else_type != '' {
|
||||
return else_type
|
||||
}
|
||||
return 'int'
|
||||
}
|
||||
|
||||
// expr_produces_pointer checks if an expression produces a pointer value in C.
|
||||
// More comprehensive than expr_is_pointer - handles casts, pointer arithmetic, etc.
|
||||
// Used in gen_call_arg to avoid wrapping pointer expressions in compound literals.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2020-2024 Joe Conigliaro. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
module cleanc
|
||||
|
||||
import os
|
||||
|
||||
fn (g &Gen) print_cgen_mem(stage string) {
|
||||
if os.getenv('V2_MEM') == '' {
|
||||
return
|
||||
}
|
||||
$if macos {
|
||||
eprintln(' [mem] cleanc/${stage}: live ${darwin_cleanc_live_mb()} MB')
|
||||
} $else {
|
||||
eprintln(' [mem] cleanc/${stage}')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2020-2024 Joe Conigliaro. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
module cleanc
|
||||
|
||||
#include <malloc/malloc.h>
|
||||
|
||||
struct C.malloc_statistics_t {
|
||||
blocks_in_use u32
|
||||
size_in_use usize
|
||||
max_size_in_use usize
|
||||
size_allocated usize
|
||||
}
|
||||
|
||||
fn C.malloc_default_zone() voidptr
|
||||
fn C.malloc_zone_statistics(zone voidptr, stats &C.malloc_statistics_t)
|
||||
|
||||
fn darwin_cleanc_live_mb() u64 {
|
||||
mut st := C.malloc_statistics_t{}
|
||||
C.malloc_zone_statistics(C.malloc_default_zone(), &st)
|
||||
return u64(st.size_in_use) / (1024 * 1024)
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
module cleanc
|
||||
|
||||
import os
|
||||
import v2.parser
|
||||
import v2.pref as vpref
|
||||
import v2.token
|
||||
import v2.transformer
|
||||
import v2.types
|
||||
|
||||
fn module_storage_csrc_for_test_sources(sources map[string]string) string {
|
||||
tmp_dir := os.join_path(os.temp_dir(), 'v2_module_storage_codegen_${os.getpid()}')
|
||||
os.rmdir_all(tmp_dir) or {}
|
||||
os.mkdir_all(tmp_dir) or { panic('failed to create temp dir') }
|
||||
defer {
|
||||
os.rmdir_all(tmp_dir) or {}
|
||||
}
|
||||
mut paths := []string{}
|
||||
for rel_path, code in sources {
|
||||
tmp_file := os.join_path(tmp_dir, rel_path)
|
||||
os.mkdir_all(os.dir(tmp_file)) or { panic('failed to create temp source dir') }
|
||||
os.write_file(tmp_file, code) or { panic('failed to write temp source') }
|
||||
paths << tmp_file
|
||||
}
|
||||
prefs := &vpref.Preferences{
|
||||
backend: .cleanc
|
||||
no_parallel: true
|
||||
}
|
||||
mut file_set := token.FileSet.new()
|
||||
mut par := parser.Parser.new(prefs)
|
||||
files := par.parse_files(paths, mut file_set)
|
||||
env := types.Environment.new()
|
||||
mut checker := types.Checker.new(prefs, file_set, env)
|
||||
checker.check_files(files)
|
||||
mut trans := transformer.Transformer.new_with_pref(env, prefs)
|
||||
trans.set_file_set(file_set)
|
||||
transformed_files := trans.transform_files(files)
|
||||
mut gen := Gen.new_with_env_and_pref(transformed_files, env, prefs)
|
||||
return gen.gen()
|
||||
}
|
||||
|
||||
fn test_generate_c_uses_module_prefix_for_qualified_module_storage() {
|
||||
csrc := module_storage_csrc_for_test_sources({
|
||||
'report/report.v': 'module report
|
||||
|
||||
pub __global mut errors = 0
|
||||
'
|
||||
'main.v': 'module main
|
||||
|
||||
import report
|
||||
|
||||
fn main() {
|
||||
report.errors += 1
|
||||
}
|
||||
'
|
||||
})
|
||||
assert csrc.contains('int report__errors'), csrc
|
||||
assert csrc.contains('report__errors'), csrc
|
||||
assert !csrc.contains('\nint errors'), csrc
|
||||
}
|
||||
|
||||
fn test_generate_c_resolves_module_storage_import_alias() {
|
||||
csrc := module_storage_csrc_for_test_sources({
|
||||
'report/report.v': 'module report
|
||||
|
||||
pub __global mut errors = 0
|
||||
'
|
||||
'main.v': 'module main
|
||||
|
||||
import report as r
|
||||
|
||||
fn main() {
|
||||
r.errors += 1
|
||||
}
|
||||
'
|
||||
})
|
||||
assert csrc.contains('report__errors'), csrc
|
||||
assert !csrc.contains('r__errors'), csrc
|
||||
}
|
||||
|
||||
fn test_generate_c_keeps_same_short_module_storage_names_distinct() {
|
||||
csrc := module_storage_csrc_for_test_sources({
|
||||
'a/a.v': 'module a
|
||||
|
||||
pub __global state = 1
|
||||
'
|
||||
'b/b.v': 'module b
|
||||
|
||||
pub __global state = 2
|
||||
'
|
||||
'main.v': 'module main
|
||||
|
||||
import a
|
||||
import b
|
||||
|
||||
fn main() {
|
||||
_ = a.state + b.state
|
||||
}
|
||||
'
|
||||
})
|
||||
assert csrc.contains('int a__state = 1;'), csrc
|
||||
assert csrc.contains('int b__state = 2;'), csrc
|
||||
assert !csrc.contains('\nint state ='), csrc
|
||||
}
|
||||
|
||||
fn test_generate_c_prefixes_module_storage_names_containing_dunders() {
|
||||
csrc := module_storage_csrc_for_test_sources({
|
||||
'a/a.v': 'module a
|
||||
|
||||
pub __global mut state__x = 1
|
||||
|
||||
pub fn bump() int {
|
||||
state__x += 1
|
||||
return state__x
|
||||
}
|
||||
'
|
||||
'b/b.v': 'module b
|
||||
|
||||
pub __global mut state__x = 2
|
||||
|
||||
pub fn bump() int {
|
||||
state__x += 1
|
||||
return state__x
|
||||
}
|
||||
'
|
||||
'main.v': 'module main
|
||||
|
||||
import a
|
||||
import b
|
||||
|
||||
fn main() {
|
||||
_ = a.bump() + b.bump()
|
||||
_ = a.state__x + b.state__x
|
||||
}
|
||||
'
|
||||
})
|
||||
assert csrc.contains('int a__state__x = 1;'), csrc
|
||||
assert csrc.contains('int b__state__x = 2;'), csrc
|
||||
assert csrc.contains('a__state__x += 1;'), csrc
|
||||
assert csrc.contains('return a__state__x;'), csrc
|
||||
assert csrc.contains('b__state__x += 1;'), csrc
|
||||
assert csrc.contains('return b__state__x;'), csrc
|
||||
assert !csrc.contains('\nint state__x ='), csrc
|
||||
assert !csrc.contains('\n\tstate__x += 1;'), csrc
|
||||
assert !csrc.contains('\n\treturn state__x;'), csrc
|
||||
}
|
||||
|
||||
fn test_generate_c_keeps_c_global_declaration_unmangled() {
|
||||
csrc := module_storage_csrc_for_test_sources({
|
||||
'main.v': 'module main
|
||||
|
||||
@[c_extern]
|
||||
__global C.errno int
|
||||
|
||||
@[c_extern]
|
||||
__global C.stdin voidptr
|
||||
|
||||
@[c_extern]
|
||||
__global C.stdout voidptr
|
||||
|
||||
@[c_extern]
|
||||
__global C.stderr voidptr
|
||||
|
||||
fn main() {
|
||||
_ = C.errno
|
||||
_ = C.stdin
|
||||
_ = C.stdout
|
||||
_ = C.stderr
|
||||
}
|
||||
'
|
||||
})
|
||||
assert !csrc.contains('main__C.errno'), csrc
|
||||
assert !csrc.contains('main__errno'), csrc
|
||||
assert !csrc.contains('main__stdin'), csrc
|
||||
assert !csrc.contains('main__stdout'), csrc
|
||||
assert !csrc.contains('main__stderr'), csrc
|
||||
assert csrc.contains('errno'), csrc
|
||||
assert csrc.contains('stdin'), csrc
|
||||
assert csrc.contains('stdout'), csrc
|
||||
assert csrc.contains('stderr'), csrc
|
||||
}
|
||||
|
||||
fn test_generate_c_keeps_c_extern_global_attributes_unmangled() {
|
||||
csrc := module_storage_csrc_for_test_sources({
|
||||
'main.v': 'module main
|
||||
|
||||
@[c_extern; export: "raw_counter"; weak; hidden; markused]
|
||||
__global raw_counter int
|
||||
|
||||
fn main() {
|
||||
_ = raw_counter
|
||||
}
|
||||
'
|
||||
})
|
||||
assert csrc.contains('extern int raw_counter;'), csrc
|
||||
assert csrc.contains('raw_counter'), csrc
|
||||
assert !csrc.contains('main__raw_counter'), csrc
|
||||
}
|
||||
|
||||
fn test_generate_c_keeps_translated_c_v_extern_global_unmangled() {
|
||||
csrc := module_storage_csrc_for_test_sources({
|
||||
'cwrap/runtime.c.v': '@[translated]
|
||||
module cwrap
|
||||
|
||||
@[c_extern; hidden; markused]
|
||||
__global raw_status int
|
||||
|
||||
pub fn read_status() int {
|
||||
return raw_status
|
||||
}
|
||||
'
|
||||
'main.v': 'module main
|
||||
|
||||
import cwrap
|
||||
|
||||
fn main() {
|
||||
_ = cwrap.read_status()
|
||||
_ = cwrap.raw_status
|
||||
}
|
||||
'
|
||||
})
|
||||
assert csrc.contains('extern int raw_status;'), csrc
|
||||
assert csrc.contains('return raw_status;'), csrc
|
||||
assert csrc.count('raw_status') >= 3, csrc
|
||||
assert !csrc.contains('cwrap__raw_status'), csrc
|
||||
}
|
||||
|
||||
fn test_generate_c_keeps_qualified_c_extern_global_alias_unmangled() {
|
||||
csrc := module_storage_csrc_for_test_sources({
|
||||
'cwrap/runtime.c.v': '@[translated]
|
||||
module cwrap
|
||||
|
||||
@[c_extern; hidden; markused]
|
||||
__global raw_status int
|
||||
'
|
||||
'main.v': 'module main
|
||||
|
||||
import cwrap as cw
|
||||
|
||||
fn main() {
|
||||
_ = cw.raw_status
|
||||
}
|
||||
'
|
||||
})
|
||||
assert csrc.contains('extern int raw_status;'), csrc
|
||||
assert csrc.count('raw_status') >= 2, csrc
|
||||
assert !csrc.contains('cwrap__raw_status'), csrc
|
||||
assert !csrc.contains('cw__raw_status'), csrc
|
||||
}
|
||||
|
||||
fn test_generate_c_keeps_plain_module_c_extern_global_unmangled() {
|
||||
csrc := module_storage_csrc_for_test_sources({
|
||||
'ext/ext.v': 'module ext
|
||||
|
||||
@[c_extern]
|
||||
pub __global raw_status int
|
||||
|
||||
pub fn read_status() int {
|
||||
return raw_status
|
||||
}
|
||||
'
|
||||
'main.v': 'module main
|
||||
|
||||
import ext
|
||||
|
||||
fn main() {
|
||||
_ = ext.raw_status
|
||||
_ = ext.read_status()
|
||||
}
|
||||
'
|
||||
})
|
||||
assert csrc.contains('extern int raw_status;'), csrc
|
||||
assert csrc.contains('return raw_status;'), csrc
|
||||
assert csrc.contains('(void)(raw_status);'), csrc
|
||||
assert !csrc.contains('ext__raw_status'), csrc
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
module cleanc
|
||||
|
||||
import v2.ast
|
||||
|
||||
fn (mut g Gen) try_gen_orm_create_call(c_name string, call_args []ast.Expr) bool {
|
||||
if call_args.len != 3 || !c_name.ends_with('__create') {
|
||||
return false
|
||||
}
|
||||
if !orm_table_can_emit(call_args[1]) || !orm_table_fields_array_can_emit(call_args[2]) {
|
||||
return false
|
||||
}
|
||||
g.sb.write_string('${c_name}(')
|
||||
g.gen_call_arg(c_name, 0, call_args[0])
|
||||
g.sb.write_string(', ')
|
||||
g.write_orm_table_c_expr(call_args[1])
|
||||
g.sb.write_string(', ')
|
||||
g.write_orm_table_fields_array_c_expr(call_args[2])
|
||||
g.sb.write_string(')')
|
||||
if c_name != '' {
|
||||
g.mark_called_fn_name(c_name)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fn (mut g Gen) try_gen_orm_create_call_expr(node ast.CallExpr) bool {
|
||||
if !orm_create_call_can_emit(node) {
|
||||
return false
|
||||
}
|
||||
lhs := node.lhs as ast.SelectorExpr
|
||||
name := g.orm_create_call_name(node) or { return false }
|
||||
call_args := [
|
||||
lhs.lhs,
|
||||
node.args[0],
|
||||
node.args[1],
|
||||
]
|
||||
return g.try_gen_orm_create_call(name, call_args)
|
||||
}
|
||||
|
||||
fn orm_create_call_can_emit(node ast.CallExpr) bool {
|
||||
if node.args.len != 2 || node.lhs !is ast.SelectorExpr {
|
||||
return false
|
||||
}
|
||||
lhs := node.lhs as ast.SelectorExpr
|
||||
return lhs.rhs.name == 'create' && orm_table_can_emit(node.args[0])
|
||||
&& orm_table_fields_array_can_emit(node.args[1])
|
||||
}
|
||||
|
||||
fn (mut g Gen) orm_create_call_name(node ast.CallExpr) ?string {
|
||||
if node.lhs !is ast.SelectorExpr {
|
||||
return none
|
||||
}
|
||||
lhs := node.lhs as ast.SelectorExpr
|
||||
method_name := sanitize_fn_ident(lhs.rhs.name)
|
||||
mut name := g.resolve_call_name(node.lhs, node.args.len)
|
||||
if name != '' {
|
||||
return name
|
||||
}
|
||||
mut base_type := g.method_receiver_base_type(lhs.lhs)
|
||||
if base_type == '' {
|
||||
base_type = g.get_expr_type(lhs.lhs).trim_space().trim_right('*')
|
||||
}
|
||||
if base_type == '' {
|
||||
base_type = g.receiver_local_base_type(lhs.lhs)
|
||||
}
|
||||
if name == '' && base_type != '' {
|
||||
if embedded := g.resolve_method_on_embedded_receiver(base_type, method_name) {
|
||||
name = embedded.method_c_name
|
||||
}
|
||||
}
|
||||
if name == '' && base_type != '' {
|
||||
name = '${base_type}__${method_name}'
|
||||
}
|
||||
if name == '' {
|
||||
return none
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
fn (mut g Gen) orm_create_call_result_type(node ast.CallExpr) ?string {
|
||||
if !orm_create_call_can_emit(node) {
|
||||
return none
|
||||
}
|
||||
name := g.orm_create_call_name(node) or { return none }
|
||||
if ret := g.fn_return_types[name] {
|
||||
return ret
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
fn orm_table_can_emit(expr ast.Expr) bool {
|
||||
init := orm_init_expr(expr) or { return false }
|
||||
name_expr := orm_init_field_value(init, 'name') or { return false }
|
||||
attrs_expr := orm_init_field_value(init, 'attrs') or { return false }
|
||||
return orm_string_value(name_expr) != none && orm_vattribute_array_can_emit(attrs_expr)
|
||||
}
|
||||
|
||||
fn orm_table_fields_array_can_emit(expr ast.Expr) bool {
|
||||
arr := orm_array_init_expr(expr) or { return false }
|
||||
for field_expr in arr.exprs {
|
||||
if !orm_table_field_can_emit(field_expr) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fn orm_table_field_can_emit(expr ast.Expr) bool {
|
||||
init := orm_init_expr(expr) or { return false }
|
||||
name_expr := orm_init_field_value(init, 'name') or { return false }
|
||||
typ_expr := orm_init_field_value(init, 'typ') or { return false }
|
||||
nullable_expr := orm_init_field_value(init, 'nullable') or { return false }
|
||||
default_val_expr := orm_init_field_value(init, 'default_val') or { return false }
|
||||
attrs_expr := orm_init_field_value(init, 'attrs') or { return false }
|
||||
is_arr_expr := orm_init_field_value(init, 'is_arr') or { return false }
|
||||
return orm_string_value(name_expr) != none && orm_number_c_expr(typ_expr) != none
|
||||
&& orm_bool_c_expr(nullable_expr) != none && orm_string_value(default_val_expr) != none
|
||||
&& orm_vattribute_array_can_emit(attrs_expr) && orm_bool_c_expr(is_arr_expr) != none
|
||||
}
|
||||
|
||||
fn orm_vattribute_array_can_emit(expr ast.Expr) bool {
|
||||
arr := orm_array_init_expr(expr) or { return false }
|
||||
for attr_expr in arr.exprs {
|
||||
if !orm_vattribute_can_emit(attr_expr) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fn orm_vattribute_can_emit(expr ast.Expr) bool {
|
||||
init := orm_init_expr(expr) or { return false }
|
||||
name_expr := orm_init_field_value(init, 'name') or { return false }
|
||||
has_arg_expr := orm_init_field_value(init, 'has_arg') or { return false }
|
||||
arg_expr := orm_init_field_value(init, 'arg') or { return false }
|
||||
kind_expr := orm_init_field_value(init, 'kind') or { return false }
|
||||
return orm_string_value(name_expr) != none && orm_bool_c_expr(has_arg_expr) != none
|
||||
&& orm_string_value(arg_expr) != none && orm_ident_c_expr(kind_expr) != none
|
||||
}
|
||||
|
||||
fn (mut g Gen) write_orm_table_c_expr(expr ast.Expr) {
|
||||
init := orm_init_expr(expr) or { return }
|
||||
name_expr := orm_init_field_value(init, 'name') or { return }
|
||||
attrs_expr := orm_init_field_value(init, 'attrs') or { return }
|
||||
g.sb.write_string('((orm__Table){.name = ')
|
||||
g.write_orm_v_string_c_expr(name_expr)
|
||||
g.sb.write_string(',.attrs = ')
|
||||
g.write_orm_vattribute_array_c_expr(attrs_expr)
|
||||
g.sb.write_string('})')
|
||||
}
|
||||
|
||||
fn (mut g Gen) write_orm_table_fields_array_c_expr(expr ast.Expr) {
|
||||
arr := orm_array_init_expr(expr) or { return }
|
||||
g.sb.write_string('new_array_from_c_array(${arr.exprs.len}, ${arr.exprs.len}, sizeof(orm__TableField), ')
|
||||
if arr.exprs.len == 0 {
|
||||
g.sb.write_string('&(array){0})')
|
||||
return
|
||||
}
|
||||
g.sb.write_string('&(orm__TableField[${arr.exprs.len}]){')
|
||||
for i, field_expr in arr.exprs {
|
||||
if i > 0 {
|
||||
g.sb.write_string(', ')
|
||||
}
|
||||
g.write_orm_table_field_c_expr(field_expr)
|
||||
}
|
||||
g.sb.write_string('})')
|
||||
}
|
||||
|
||||
fn (mut g Gen) write_orm_table_field_c_expr(expr ast.Expr) {
|
||||
init := orm_init_expr(expr) or { return }
|
||||
name_expr := orm_init_field_value(init, 'name') or { return }
|
||||
typ_expr := orm_init_field_value(init, 'typ') or { return }
|
||||
nullable_expr := orm_init_field_value(init, 'nullable') or { return }
|
||||
default_val_expr := orm_init_field_value(init, 'default_val') or { return }
|
||||
attrs_expr := orm_init_field_value(init, 'attrs') or { return }
|
||||
is_arr_expr := orm_init_field_value(init, 'is_arr') or { return }
|
||||
g.sb.write_string('((orm__TableField){.name = ')
|
||||
g.write_orm_v_string_c_expr(name_expr)
|
||||
g.sb.write_string(',.typ = ')
|
||||
g.sb.write_string(orm_number_c_expr(typ_expr) or { return })
|
||||
g.sb.write_string(',.nullable = ')
|
||||
g.sb.write_string(orm_bool_c_expr(nullable_expr) or { return })
|
||||
g.sb.write_string(',.default_val = ')
|
||||
g.write_orm_v_string_c_expr(default_val_expr)
|
||||
g.sb.write_string(',.attrs = ')
|
||||
g.write_orm_vattribute_array_c_expr(attrs_expr)
|
||||
g.sb.write_string(',.is_arr = ')
|
||||
g.sb.write_string(orm_bool_c_expr(is_arr_expr) or { return })
|
||||
g.sb.write_string('})')
|
||||
}
|
||||
|
||||
fn (mut g Gen) write_orm_vattribute_array_c_expr(expr ast.Expr) {
|
||||
arr := orm_array_init_expr(expr) or { return }
|
||||
if arr.exprs.len == 0 {
|
||||
g.sb.write_string('new_array_from_c_array(0, 0, sizeof(VAttribute), &(array){0})')
|
||||
return
|
||||
}
|
||||
g.sb.write_string('new_array_from_c_array(${arr.exprs.len}, ${arr.exprs.len}, sizeof(VAttribute), &(VAttribute[${arr.exprs.len}]){')
|
||||
for i, attr_expr in arr.exprs {
|
||||
if i > 0 {
|
||||
g.sb.write_string(', ')
|
||||
}
|
||||
g.write_orm_vattribute_c_expr(attr_expr)
|
||||
}
|
||||
g.sb.write_string('})')
|
||||
}
|
||||
|
||||
fn (mut g Gen) write_orm_vattribute_c_expr(expr ast.Expr) {
|
||||
init := orm_init_expr(expr) or { return }
|
||||
name_expr := orm_init_field_value(init, 'name') or { return }
|
||||
has_arg_expr := orm_init_field_value(init, 'has_arg') or { return }
|
||||
arg_expr := orm_init_field_value(init, 'arg') or { return }
|
||||
kind_expr := orm_init_field_value(init, 'kind') or { return }
|
||||
g.sb.write_string('((VAttribute){.name = ')
|
||||
g.write_orm_v_string_c_expr(name_expr)
|
||||
g.sb.write_string(',.has_arg = ')
|
||||
g.sb.write_string(orm_bool_c_expr(has_arg_expr) or { return })
|
||||
g.sb.write_string(',.arg = ')
|
||||
g.write_orm_v_string_c_expr(arg_expr)
|
||||
g.sb.write_string(',.kind = ')
|
||||
g.sb.write_string(orm_ident_c_expr(kind_expr) or { return })
|
||||
g.sb.write_string('})')
|
||||
}
|
||||
|
||||
fn orm_init_expr(expr ast.Expr) ?ast.InitExpr {
|
||||
match expr {
|
||||
ast.InitExpr {
|
||||
return expr
|
||||
}
|
||||
ast.ParenExpr {
|
||||
return orm_init_expr(expr.expr)
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
return none
|
||||
}
|
||||
|
||||
fn orm_array_init_expr(expr ast.Expr) ?ast.ArrayInitExpr {
|
||||
match expr {
|
||||
ast.ArrayInitExpr {
|
||||
return expr
|
||||
}
|
||||
ast.CallExpr {
|
||||
name := orm_call_name(expr.lhs)
|
||||
if name in ['builtin__new_array_from_c_array_noscan', 'builtin__new_array_from_c_array', 'new_array_from_c_array']
|
||||
&& expr.args.len >= 4 {
|
||||
return orm_array_init_expr(expr.args[3])
|
||||
}
|
||||
}
|
||||
ast.PrefixExpr {
|
||||
if expr.op == .amp {
|
||||
return orm_array_init_expr(expr.expr)
|
||||
}
|
||||
}
|
||||
ast.ParenExpr {
|
||||
return orm_array_init_expr(expr.expr)
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
return none
|
||||
}
|
||||
|
||||
fn orm_call_name(lhs ast.Expr) string {
|
||||
match lhs {
|
||||
ast.Ident {
|
||||
return lhs.name
|
||||
}
|
||||
ast.SelectorExpr {
|
||||
if lhs.lhs is ast.Ident {
|
||||
left := lhs.lhs as ast.Ident
|
||||
return '${left.name}__${lhs.rhs.name}'
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
fn orm_init_field_value(init ast.InitExpr, name string) ?ast.Expr {
|
||||
for field in init.fields {
|
||||
if field.name == name {
|
||||
return field.value
|
||||
}
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
fn (mut g Gen) write_orm_v_string_c_expr(expr ast.Expr) {
|
||||
raw := orm_string_value(expr) or { return }
|
||||
g.sb.write_string(c_static_v_string_expr_from_c_literal(c_string_literal_content_to_c(raw)))
|
||||
}
|
||||
|
||||
fn orm_string_value(expr ast.Expr) ?string {
|
||||
match expr {
|
||||
ast.StringLiteral {
|
||||
mut val := strip_literal_quotes(expr.value)
|
||||
if expr.kind == .raw {
|
||||
val = val.replace('\\', '\\\\')
|
||||
} else {
|
||||
val = process_line_continuations(val)
|
||||
}
|
||||
return val
|
||||
}
|
||||
ast.BasicLiteral {
|
||||
if expr.kind == .string {
|
||||
return process_line_continuations(strip_literal_quotes(expr.value))
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
return none
|
||||
}
|
||||
|
||||
fn orm_bool_c_expr(expr ast.Expr) ?string {
|
||||
match expr {
|
||||
ast.BasicLiteral {
|
||||
if expr.kind == .key_true || expr.value == 'true' {
|
||||
return 'true'
|
||||
}
|
||||
if expr.kind == .key_false || expr.value == 'false' {
|
||||
return 'false'
|
||||
}
|
||||
}
|
||||
ast.Ident {
|
||||
if expr.name == 'true' || expr.name == 'false' {
|
||||
return expr.name
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
return none
|
||||
}
|
||||
|
||||
fn orm_number_c_expr(expr ast.Expr) ?string {
|
||||
match expr {
|
||||
ast.BasicLiteral {
|
||||
return sanitize_c_number_literal(expr.value)
|
||||
}
|
||||
ast.PrefixExpr {
|
||||
if expr.op == .minus && expr.expr is ast.BasicLiteral {
|
||||
inner := expr.expr as ast.BasicLiteral
|
||||
return '-' + sanitize_c_number_literal(inner.value)
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
return none
|
||||
}
|
||||
|
||||
fn orm_ident_c_expr(expr ast.Expr) ?string {
|
||||
match expr {
|
||||
ast.Ident {
|
||||
return expr.name
|
||||
}
|
||||
ast.SelectorExpr {
|
||||
if expr.lhs is ast.Ident {
|
||||
left := expr.lhs as ast.Ident
|
||||
return '${left.name}__${expr.rhs.name}'
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
return none
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// vtest build: !linux && !windows
|
||||
module cleanc
|
||||
|
||||
import os
|
||||
import v2.markused
|
||||
import v2.parser
|
||||
import v2.pref as vpref
|
||||
import v2.token
|
||||
import v2.transformer
|
||||
import v2.types
|
||||
|
||||
fn generate_parallel_worker_c_with_cached_init_for_test(code string, cached_init_calls []string) string {
|
||||
tmp_file := os.join_path(os.temp_dir(), 'v2_parallel_cache_init_test_${os.getpid()}.v')
|
||||
os.write_file(tmp_file, code) or { panic('failed to write temp file') }
|
||||
defer {
|
||||
os.rm(tmp_file) or {}
|
||||
}
|
||||
prefs := &vpref.Preferences{
|
||||
backend: .cleanc
|
||||
no_parallel: false
|
||||
}
|
||||
mut file_set := token.FileSet.new()
|
||||
mut par := parser.Parser.new(prefs)
|
||||
files := par.parse_files([tmp_file], mut file_set)
|
||||
env := types.Environment.new()
|
||||
mut checker := types.Checker.new(prefs, file_set, env)
|
||||
checker.check_files(files)
|
||||
mut trans := transformer.Transformer.new_with_pref(env, prefs)
|
||||
gen_files := trans.transform_files(files)
|
||||
used := markused.mark_used(gen_files, env)
|
||||
mut gen := Gen.new_with_env_and_pref(gen_files, env, prefs)
|
||||
gen.set_used_fn_keys(used)
|
||||
gen.set_cached_init_calls(cached_init_calls)
|
||||
gen.gen_passes_1_to_4()
|
||||
emit_indices := gen.gen_pass5_pre()
|
||||
mut worker := gen.new_pass5_worker(emit_indices, 0)
|
||||
worker.gen_pass5_files(emit_indices)
|
||||
gen.merge_pass5_worker(worker)
|
||||
gen.gen_pass5_post()
|
||||
return gen.gen_finalize()
|
||||
}
|
||||
|
||||
fn test_parallel_worker_preserves_cached_init_calls_in_main() {
|
||||
csrc := generate_parallel_worker_c_with_cached_init_for_test('
|
||||
fn main() {
|
||||
}
|
||||
', [
|
||||
'__v2_cached_init_imports',
|
||||
])
|
||||
assert csrc.contains('__v2_cached_init_imports();')
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
module cleanc
|
||||
|
||||
fn test_pass5_worker_clones_global_type_lookup() {
|
||||
mut g := Gen.new([])
|
||||
g.global_var_types['main__settings'] = 'main__Settings'
|
||||
mut worker := g.new_pass5_worker([], 0)
|
||||
assert worker.global_var_types['main__settings'] == 'main__Settings'
|
||||
g.global_var_types['main__settings'] = 'main__OtherSettings'
|
||||
assert worker.global_var_types['main__settings'] == 'main__Settings'
|
||||
}
|
||||
|
||||
fn test_specialized_receiver_method_index_tracks_ambiguity() {
|
||||
mut g := Gen.new([])
|
||||
g.fn_return_types['Box_T_i32__get'] = 'int'
|
||||
g.remember_specialized_fn_base('Box_T_i32__get')
|
||||
got := g.resolve_specialized_receiver_method('Box', 'get') or { '' }
|
||||
assert got == 'Box_T_i32__get'
|
||||
|
||||
g.fn_return_types['Box_T_string__get'] = 'string'
|
||||
g.remember_specialized_fn_base('Box_T_string__get')
|
||||
if _ := g.resolve_specialized_receiver_method('Box', 'get') {
|
||||
assert false
|
||||
} else {
|
||||
assert true
|
||||
}
|
||||
}
|
||||
|
||||
// When a file is split across pass-5 workers, a non-owning worker blocks the
|
||||
// file's fns but must still emit its own assigned slice. The owner-scoped bypass
|
||||
// allows exactly the fns owned by the slice's file (explicit_slice_file) and
|
||||
// nothing transitively reached from another file.
|
||||
fn test_explicit_slice_emit_allows_is_scoped_to_owning_file() {
|
||||
mut g := Gen.new([])
|
||||
g.fn_owner_file['fn_foo'] = 3 // foo is owned by file 3
|
||||
g.fn_owner_file['fn_bar'] = 5 // bar is owned by a different file
|
||||
g.blocked_fn_keys['fn_foo'] = true
|
||||
g.blocked_fn_keys['fn_bar'] = true
|
||||
|
||||
// Not in an explicit slice: nothing is unblocked.
|
||||
assert !g.explicit_slice_emit_allows('fn_foo')
|
||||
assert !g.explicit_slice_emit_allows('fn_bar')
|
||||
|
||||
// Emitting file 3's slice: only file 3's fns are unblocked.
|
||||
g.explicit_slice_active = true
|
||||
g.explicit_slice_file = 3
|
||||
assert g.explicit_slice_emit_allows('fn_foo')
|
||||
assert !g.explicit_slice_emit_allows('fn_bar') // owned by file 5 -> stays blocked
|
||||
assert !g.explicit_slice_emit_allows('fn_unknown') // no recorded owner -> blocked
|
||||
|
||||
// Switching to file 5's slice flips which fn is allowed.
|
||||
g.explicit_slice_file = 5
|
||||
assert !g.explicit_slice_emit_allows('fn_foo')
|
||||
assert g.explicit_slice_emit_allows('fn_bar')
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,148 @@
|
||||
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
// Structure of Arrays (SoA) code generation.
|
||||
//
|
||||
// When a struct is annotated with @[soa], the compiler generates a companion
|
||||
// SoA container struct that stores each field in a separate contiguous array.
|
||||
// This layout provides significantly better cache performance for batch
|
||||
// operations that touch only a subset of fields (common in game math, ECS,
|
||||
// particle systems, physics simulations, etc.).
|
||||
//
|
||||
// Example:
|
||||
// @[soa]
|
||||
// struct Particle {
|
||||
// x f32
|
||||
// y f32
|
||||
// vx f32
|
||||
// vy f32
|
||||
// }
|
||||
//
|
||||
// Generates a companion type `Particle_SOA` with:
|
||||
// struct Particle_SOA {
|
||||
// int len;
|
||||
// int cap;
|
||||
// f32* x;
|
||||
// f32* y;
|
||||
// f32* vx;
|
||||
// f32* vy;
|
||||
// };
|
||||
//
|
||||
// And helper functions:
|
||||
// Particle_SOA Particle_SOA_new(int len, int cap);
|
||||
// void Particle_SOA_push(Particle_SOA* soa, Particle val);
|
||||
// Particle Particle_SOA_get(Particle_SOA soa, int i);
|
||||
// void Particle_SOA_set(Particle_SOA* soa, int i, Particle val);
|
||||
// void Particle_SOA_free(Particle_SOA* soa);
|
||||
module cleanc
|
||||
|
||||
import v2.types
|
||||
|
||||
// gen_soa_companion generates the SoA container struct and helper functions
|
||||
// for a struct annotated with @[soa].
|
||||
fn (mut g Gen) gen_soa_companion(name string, s types.Struct) {
|
||||
soa_name := '${name}_SOA'
|
||||
|
||||
// --- SoA container struct ---
|
||||
g.sb.writeln('// SoA (Structure of Arrays) companion for ${name}')
|
||||
g.sb.writeln('typedef struct {')
|
||||
g.sb.writeln('\tint len;')
|
||||
g.sb.writeln('\tint cap;')
|
||||
for field in s.fields {
|
||||
c_type := g.types_type_to_c(field.typ)
|
||||
fname := escape_c_keyword(field.name)
|
||||
g.sb.writeln('\t${c_type}* ${fname};')
|
||||
}
|
||||
g.sb.writeln('} ${soa_name};')
|
||||
g.sb.writeln('')
|
||||
|
||||
// --- new: allocate SoA container ---
|
||||
g.sb.writeln('static inline ${soa_name} ${soa_name}_new(int len, int cap) {')
|
||||
g.sb.writeln('\tif (cap < len) cap = len;')
|
||||
g.sb.writeln('\t${soa_name} soa;')
|
||||
g.sb.writeln('\tsoa.len = len;')
|
||||
g.sb.writeln('\tsoa.cap = cap;')
|
||||
for field in s.fields {
|
||||
c_type := g.types_type_to_c(field.typ)
|
||||
fname := escape_c_keyword(field.name)
|
||||
if g.has_freestanding_hook_capability('alloc') {
|
||||
g.sb.writeln('\tsoa.${fname} = (${c_type}*)v_platform_malloc(cap * sizeof(${c_type}));')
|
||||
g.sb.writeln('\tmemset(soa.${fname}, 0, cap * sizeof(${c_type}));')
|
||||
} else if g.is_freestanding_target() {
|
||||
g.sb.writeln('\tsoa.${fname} = (${c_type}*)${g.c_heap_malloc_call('cap * sizeof(${c_type})')};')
|
||||
g.sb.writeln('\tmemset(soa.${fname}, 0, cap * sizeof(${c_type}));')
|
||||
} else {
|
||||
g.sb.writeln('\tsoa.${fname} = (${c_type}*)calloc(cap, sizeof(${c_type}));')
|
||||
}
|
||||
}
|
||||
g.sb.writeln('\treturn soa;')
|
||||
g.sb.writeln('}')
|
||||
g.sb.writeln('')
|
||||
|
||||
// --- get: retrieve element at index as original struct ---
|
||||
g.sb.writeln('static inline ${name} ${soa_name}_get(${soa_name} soa, int i) {')
|
||||
g.sb.writeln('\treturn (${name}){')
|
||||
for i, field in s.fields {
|
||||
comma := if i < s.fields.len - 1 { ',' } else { '' }
|
||||
fname := escape_c_keyword(field.name)
|
||||
g.sb.writeln('\t\t.${fname} = soa.${fname}[i]${comma}')
|
||||
}
|
||||
g.sb.writeln('\t};')
|
||||
g.sb.writeln('}')
|
||||
g.sb.writeln('')
|
||||
|
||||
// --- set: set element at index from original struct ---
|
||||
g.sb.writeln('static inline void ${soa_name}_set(${soa_name}* soa, int i, ${name} val) {')
|
||||
for field in s.fields {
|
||||
fname := escape_c_keyword(field.name)
|
||||
g.sb.writeln('\tsoa->${fname}[i] = val.${fname};')
|
||||
}
|
||||
g.sb.writeln('}')
|
||||
g.sb.writeln('')
|
||||
|
||||
// --- push: append element, growing capacity if needed ---
|
||||
g.sb.writeln('static inline void ${soa_name}_push(${soa_name}* soa, ${name} val) {')
|
||||
g.sb.writeln('\tif (soa->len >= soa->cap) {')
|
||||
g.sb.writeln('\t\tint new_cap = soa->cap < 8 ? 8 : soa->cap * 2;')
|
||||
for field in s.fields {
|
||||
c_type := g.types_type_to_c(field.typ)
|
||||
fname := escape_c_keyword(field.name)
|
||||
g.sb.writeln('\t\tsoa->${fname} = (${c_type}*)${g.c_heap_realloc_call('soa->${fname}',
|
||||
'new_cap * sizeof(${c_type})')};')
|
||||
}
|
||||
g.sb.writeln('\t\tsoa->cap = new_cap;')
|
||||
g.sb.writeln('\t}')
|
||||
for field in s.fields {
|
||||
fname := escape_c_keyword(field.name)
|
||||
g.sb.writeln('\tsoa->${fname}[soa->len] = val.${fname};')
|
||||
}
|
||||
g.sb.writeln('\tsoa->len++;')
|
||||
g.sb.writeln('}')
|
||||
g.sb.writeln('')
|
||||
|
||||
// --- pop: remove and return last element ---
|
||||
g.sb.writeln('static inline ${name} ${soa_name}_pop(${soa_name}* soa) {')
|
||||
g.sb.writeln('\tif (soa->len == 0) return (${name}){0};')
|
||||
g.sb.writeln('\tsoa->len--;')
|
||||
g.sb.writeln('\treturn (${name}){')
|
||||
for i, field in s.fields {
|
||||
comma := if i < s.fields.len - 1 { ',' } else { '' }
|
||||
fname := escape_c_keyword(field.name)
|
||||
g.sb.writeln('\t\t.${fname} = soa->${fname}[soa->len]${comma}')
|
||||
}
|
||||
g.sb.writeln('\t};')
|
||||
g.sb.writeln('}')
|
||||
g.sb.writeln('')
|
||||
|
||||
// --- free: deallocate all field arrays ---
|
||||
g.sb.writeln('static inline void ${soa_name}_free(${soa_name}* soa) {')
|
||||
for field in s.fields {
|
||||
fname := escape_c_keyword(field.name)
|
||||
g.sb.writeln('\t${g.c_heap_free_call('soa->${fname}')};')
|
||||
}
|
||||
g.sb.writeln('\tsoa->len = 0;')
|
||||
g.sb.writeln('\tsoa->cap = 0;')
|
||||
g.sb.writeln('}')
|
||||
g.sb.writeln('')
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,248 @@
|
||||
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
module cleanc
|
||||
|
||||
import v2.ast
|
||||
import strings
|
||||
|
||||
fn (mut g Gen) gen_string_inter_literal(node ast.StringInterLiteral) {
|
||||
if g.is_freestanding_target() {
|
||||
g.sb.write_string(g.c_freestanding_missing_format_string_expr())
|
||||
return
|
||||
}
|
||||
// Two-pass snprintf: first into a 256-byte stack buffer to measure,
|
||||
// then use it directly or heap-allocate only when it doesn't fit.
|
||||
// This avoids the unconditional heap allocation of asprintf.
|
||||
// Wrapped in GCC compound expression ({ ... })
|
||||
mut fmt_str := strings.new_builder(64)
|
||||
for i, raw_val in node.values {
|
||||
mut val := raw_val
|
||||
// Strip only the outer string delimiters. Using trim_* here can over-strip
|
||||
// escaped quotes in edge chunks like: 'A "${x}"', leaving a trailing `\`.
|
||||
if i == 0 && val.len > 0 && val[0] in [`'`, `"`] {
|
||||
val = val[1..]
|
||||
}
|
||||
if i == node.values.len - 1 && val.len > 0 && val[val.len - 1] in [`'`, `"`] {
|
||||
val = val[..val.len - 1]
|
||||
}
|
||||
escaped := val.replace('%', '%%').replace('\t', '\\t')
|
||||
fmt_str.write_string(escaped)
|
||||
if i < node.inters.len {
|
||||
inter := node.inters[i]
|
||||
fmt_str.write_string(g.get_sprintf_format(inter))
|
||||
}
|
||||
}
|
||||
fmt_lit := c_string_literal_content_to_c(fmt_str.str())
|
||||
|
||||
// Build the argument list string once (used in both snprintf calls).
|
||||
// write_sprintf_arg writes to g.sb, so we temporarily swap it out,
|
||||
// capture the result, and restore it.
|
||||
mut args_sb := strings.new_builder(64)
|
||||
for inter in node.inters {
|
||||
args_sb.write_string(', ')
|
||||
saved := g.sb
|
||||
g.sb = strings.new_builder(64)
|
||||
g.write_sprintf_arg(inter)
|
||||
arg_str := g.sb.str()
|
||||
g.sb = saved
|
||||
args_sb.write_string(arg_str)
|
||||
}
|
||||
args_str := args_sb.str()
|
||||
|
||||
// Emit: try stack buffer first, fall back to malloc only if needed.
|
||||
g.sb.write_string('({ char _sib[256]; int _sil = snprintf(_sib, sizeof(_sib), ${fmt_lit}${args_str}); ')
|
||||
g.sb.write_string('char* _sip; if (_sil < (int)sizeof(_sib)) { _sip = memdup(_sib, _sil + 1); } ')
|
||||
g.sb.write_string('else { _sip = (char*)malloc(_sil + 1); snprintf(_sip, _sil + 1, ${fmt_lit}${args_str}); } ')
|
||||
g.sb.write_string('${c_v_string_expr_from_ptr_len('_sip', '_sil', false)}; })')
|
||||
}
|
||||
|
||||
fn string_inter_needs_selector_type_fallback(expr_type string) bool {
|
||||
return expr_type == '' || expr_type == 'int' || expr_type == 'int_literal'
|
||||
|| expr_type == 'void' || expr_type == 'void*' || expr_type == 'voidptr'
|
||||
}
|
||||
|
||||
fn (mut g Gen) string_inter_expr_type(expr ast.Expr) string {
|
||||
expr_type := g.get_expr_type(expr)
|
||||
if expr is ast.SelectorExpr && string_inter_needs_selector_type_fallback(expr_type) {
|
||||
declared_type := g.selector_declared_field_type(expr)
|
||||
if !string_inter_needs_selector_type_fallback(declared_type) {
|
||||
return declared_type
|
||||
}
|
||||
field_type := g.selector_field_type(expr)
|
||||
if !string_inter_needs_selector_type_fallback(field_type) {
|
||||
return field_type
|
||||
}
|
||||
}
|
||||
return expr_type
|
||||
}
|
||||
|
||||
fn (mut g Gen) write_sprintf_arg(inter ast.StringInter) {
|
||||
expr_type := g.string_inter_expr_type(inter.expr)
|
||||
expr_src := g.expr_to_string(inter.expr)
|
||||
fmt := g.get_sprintf_format(inter)
|
||||
// Keep vararg C types aligned with the emitted format string.
|
||||
// If formatter expects a non-string argument, pass expression as-is.
|
||||
if !fmt.ends_with('s') {
|
||||
if expr_src == '' {
|
||||
g.sb.write_string('0')
|
||||
} else {
|
||||
g.sb.write_string(expr_src)
|
||||
}
|
||||
return
|
||||
}
|
||||
str_fn := g.get_str_fn_for_type(expr_type) or { '' }
|
||||
// Float types: use V's str() for default formatting ('0.0' not '0.000000')
|
||||
if expr_type in ['f64', 'f32', 'float_literal'] && inter.format == .unformatted {
|
||||
str_name := if expr_type == 'f32' { 'f32__str' } else { 'f64__str' }
|
||||
g.sb.write_string('${str_name}(')
|
||||
if expr_src == '' {
|
||||
g.sb.write_string('0')
|
||||
} else {
|
||||
g.sb.write_string(expr_src)
|
||||
}
|
||||
g.sb.write_string(').str')
|
||||
return
|
||||
}
|
||||
if expr_type == 'string' {
|
||||
if expr_src == '' {
|
||||
g.sb.write_string('""')
|
||||
return
|
||||
}
|
||||
g.sb.write_string(expr_src)
|
||||
g.sb.write_string('.str')
|
||||
} else if expr_type == 'bool' {
|
||||
g.sb.write_string('(')
|
||||
if expr_src == '' {
|
||||
g.sb.write_string('false')
|
||||
} else {
|
||||
g.sb.write_string(expr_src)
|
||||
}
|
||||
g.sb.write_string(' ? "true" : "false")')
|
||||
} else if str_fn != '' {
|
||||
g.sb.write_string('${str_fn}(')
|
||||
if expr_src == '' {
|
||||
g.sb.write_string('0')
|
||||
} else {
|
||||
g.sb.write_string(expr_src)
|
||||
}
|
||||
g.sb.write_string(').str')
|
||||
} else {
|
||||
if expr_src == '' {
|
||||
g.sb.write_string('0')
|
||||
} else {
|
||||
g.sb.write_string(expr_src)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) get_sprintf_format(inter ast.StringInter) string {
|
||||
mut fmt := '%'
|
||||
mut width := inter.width
|
||||
mut precision := inter.precision
|
||||
// Extract width/precision from format_expr if not set explicitly
|
||||
if width == 0 && precision == 0 && inter.format_expr !is ast.EmptyExpr {
|
||||
if inter.format_expr is ast.BasicLiteral {
|
||||
val := inter.format_expr.value
|
||||
if val.contains('.') {
|
||||
// ".3" or "0.3" → precision
|
||||
parts := val.split('.')
|
||||
if parts.len == 2 {
|
||||
if parts[0].len > 0 && parts[0] != '0' {
|
||||
width = parts[0].int()
|
||||
}
|
||||
precision = parts[1].int()
|
||||
}
|
||||
} else {
|
||||
// Plain number → width (e.g., "03" for zero-padded)
|
||||
if val.starts_with('0') && val.len > 1 {
|
||||
fmt += '0'
|
||||
}
|
||||
width = val.int()
|
||||
}
|
||||
} else if inter.format_expr is ast.PrefixExpr {
|
||||
// Negative alignment: -10 → left-align with width 10
|
||||
if inter.format_expr.op == .minus && inter.format_expr.expr is ast.BasicLiteral {
|
||||
fmt += '-'
|
||||
width = inter.format_expr.expr.value.int()
|
||||
}
|
||||
}
|
||||
}
|
||||
// Width
|
||||
if width > 0 {
|
||||
fmt += '${width}'
|
||||
}
|
||||
// Precision
|
||||
if precision > 0 {
|
||||
fmt += '.${precision}'
|
||||
}
|
||||
// Format specifier
|
||||
expr_type := g.string_inter_expr_type(inter.expr)
|
||||
if inter.format != .unformatted {
|
||||
match inter.format {
|
||||
.decimal { fmt += 'd' }
|
||||
.float { fmt += 'f' }
|
||||
.hex { fmt += 'x' }
|
||||
.octal { fmt += 'o' }
|
||||
.character { fmt += 'c' }
|
||||
.exponent { fmt += 'e' }
|
||||
.exponent_short { fmt += 'g' }
|
||||
.binary { fmt += 'd' } // binary not supported in printf, fallback to decimal
|
||||
.pointer_address { fmt += 'p' }
|
||||
.string { fmt += 's' }
|
||||
.unformatted { fmt += 'd' }
|
||||
}
|
||||
|
||||
return fmt
|
||||
}
|
||||
if inter.resolved_fmt != '' {
|
||||
if expr_type in ['string', 'bool', 'f32', 'f64', 'float_literal'] {
|
||||
return '%s'
|
||||
}
|
||||
if _ := g.get_str_fn_for_type(expr_type) {
|
||||
return '%s'
|
||||
}
|
||||
return inter.resolved_fmt
|
||||
}
|
||||
// Infer from expression type
|
||||
match expr_type {
|
||||
'string' {
|
||||
return '%s'
|
||||
}
|
||||
'int', 'i8', 'i16', 'i32' {
|
||||
return '%d'
|
||||
}
|
||||
'i64' {
|
||||
return '%lld'
|
||||
}
|
||||
'u8', 'u16', 'u32' {
|
||||
return '%u'
|
||||
}
|
||||
'u64' {
|
||||
return '%llu'
|
||||
}
|
||||
'f32', 'f64', 'float_literal' {
|
||||
// Use %s with V's str() function for default formatting,
|
||||
// since C's %f produces '0.000000' instead of V's '0.0'.
|
||||
return '%s'
|
||||
}
|
||||
'bool' {
|
||||
return '%s'
|
||||
}
|
||||
'rune' {
|
||||
return '%c'
|
||||
}
|
||||
'char' {
|
||||
return '%c'
|
||||
}
|
||||
else {
|
||||
if _ := g.get_str_fn_for_type(expr_type) {
|
||||
return '%s'
|
||||
}
|
||||
return '%d'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get_str_fn_for_type returns the name of the str() function for a type, if one exists.
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,7 @@
|
||||
module main
|
||||
|
||||
fn main() {
|
||||
arr := []f32{len: 4, init: -1e9}
|
||||
println(int(arr[0]))
|
||||
println(int(arr[3]))
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
module main
|
||||
|
||||
struct Foo {
|
||||
a int
|
||||
b int
|
||||
}
|
||||
|
||||
fn main() {
|
||||
x := Foo{
|
||||
a: 1
|
||||
b: 2
|
||||
}
|
||||
|
||||
// AssocExpr lowering: Foo{...x b: 3}
|
||||
y := Foo{
|
||||
...x
|
||||
b: 3
|
||||
}
|
||||
println(y.a)
|
||||
println(y.b)
|
||||
|
||||
// &AssocExpr lowering: &Foo{...x a: 9}
|
||||
p := &Foo{
|
||||
...x
|
||||
a: 9
|
||||
}
|
||||
println(p.a)
|
||||
println(p.b)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fn main() {
|
||||
a := u32(0x1234)
|
||||
b := u32(0x56)
|
||||
c := u32(0x78)
|
||||
packed := (a >> 3) | (b << 13) | (c << 29)
|
||||
println(packed)
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
struct QuaternionKey {
|
||||
values [3]u16
|
||||
}
|
||||
|
||||
fn unpack(key QuaternionKey) u32 {
|
||||
packed := u32(key.values[0]) >> 3 | u32(key.values[1]) << 13 | u32(key.values[2]) << 29
|
||||
return packed
|
||||
}
|
||||
|
||||
fn main() {
|
||||
key := QuaternionKey{
|
||||
values: [u16(0x1234), u16(0x56), u16(0x79)]!
|
||||
}
|
||||
println(unpack(key))
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
module main
|
||||
|
||||
@[flag]
|
||||
enum Perms {
|
||||
read
|
||||
write
|
||||
execute
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// .has() and .all()
|
||||
mut p := Perms.read | Perms.write
|
||||
println(p.has(.read))
|
||||
println(p.has(.execute))
|
||||
println(p.all(.read | .write))
|
||||
println(p.all(.read | .execute))
|
||||
|
||||
// .set()
|
||||
p.set(.execute)
|
||||
println(p.has(.execute))
|
||||
println(p.all(.read | .write | .execute))
|
||||
|
||||
// .clear()
|
||||
p.clear(.write)
|
||||
println(p.has(.write))
|
||||
println(p.has(.read))
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
struct Point {
|
||||
x int
|
||||
}
|
||||
|
||||
type Primitive = []int | []string | []Point | bool | int | string | Point
|
||||
|
||||
fn wrap_first[T](values []T) Primitive {
|
||||
if values.len > 0 {
|
||||
first := values[0]
|
||||
return Primitive(first)
|
||||
}
|
||||
return Primitive(false)
|
||||
}
|
||||
|
||||
fn check(value Primitive) {
|
||||
match value {
|
||||
[]int {
|
||||
int_value := wrap_first(value)
|
||||
if int_value is int {
|
||||
println('int:${int_value}')
|
||||
} else {
|
||||
println('int:bad')
|
||||
}
|
||||
}
|
||||
[]string {
|
||||
string_value := wrap_first(value)
|
||||
if string_value is string {
|
||||
println('string:${string_value}')
|
||||
} else {
|
||||
println('string:bad')
|
||||
}
|
||||
}
|
||||
[]Point {
|
||||
point_value := wrap_first(value)
|
||||
if point_value is Point {
|
||||
println('point:${point_value.x}')
|
||||
} else {
|
||||
println('point:bad')
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
ints := []int{len: 1, init: 7}
|
||||
check(Primitive(ints))
|
||||
|
||||
strings := []string{len: 1, init: 'abc'}
|
||||
check(Primitive(strings))
|
||||
|
||||
points := []Point{len: 1, init: Point{
|
||||
x: 3
|
||||
}}
|
||||
check(Primitive(points))
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
module main
|
||||
|
||||
interface Backend {}
|
||||
|
||||
struct MetalBackend {}
|
||||
|
||||
struct NullBackend {}
|
||||
|
||||
fn make_backend() Backend {
|
||||
return MetalBackend{}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut backend := make_backend()
|
||||
if mut backend is MetalBackend {
|
||||
println('metal')
|
||||
} else {
|
||||
println('not-metal')
|
||||
}
|
||||
if mut backend is NullBackend {
|
||||
println('null')
|
||||
} else {
|
||||
println('not-null')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
module main
|
||||
|
||||
interface Backend {
|
||||
foo()
|
||||
}
|
||||
|
||||
struct MetalBackend {}
|
||||
|
||||
fn (m MetalBackend) foo() {}
|
||||
|
||||
fn (m MetalBackend) bar() {
|
||||
println('bar')
|
||||
}
|
||||
|
||||
fn make_backend() Backend {
|
||||
return MetalBackend{}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut backend := make_backend()
|
||||
if mut backend is MetalBackend {
|
||||
backend.bar()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
module main
|
||||
|
||||
fn make_empty() map[string]int {
|
||||
return {}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Empty shorthand map literal `{}` should lower to a correct runtime map constructor.
|
||||
mut m := make_empty()
|
||||
key1 := 'abc'
|
||||
key2 := key1.clone() // Ensure key equality is based on string content, not pointer identity.
|
||||
m[key1] = 123
|
||||
println(m[key2])
|
||||
println(m['missing'])
|
||||
|
||||
// Typed empty map init should also lower to a runtime map constructor.
|
||||
mut m2 := map[string]int{}
|
||||
m2['x'] = 7
|
||||
println(m2['x'])
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
module main
|
||||
|
||||
enum State {
|
||||
idle
|
||||
run
|
||||
}
|
||||
|
||||
fn make_explicit() map[State]int {
|
||||
return {
|
||||
.idle: 10
|
||||
.run: 20
|
||||
}
|
||||
}
|
||||
|
||||
fn make_from_context() map[State]int {
|
||||
return {
|
||||
.idle: 30
|
||||
.run: 40
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
a := make_explicit()
|
||||
b := make_from_context()
|
||||
println(a[State.idle])
|
||||
println(a[State.run])
|
||||
println(b[State.idle])
|
||||
println(b[State.run])
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
module main
|
||||
|
||||
fn mk() map[string]int {
|
||||
return {
|
||||
'a': 10
|
||||
}
|
||||
}
|
||||
|
||||
fn mk2() map[string]string {
|
||||
return {
|
||||
'x': 'y'
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Map index reads should lower in the transformer even when the map expression is not addressable.
|
||||
println(mk()['a'])
|
||||
println(mk()['missing'])
|
||||
|
||||
// Missing string key should return an empty string (not nil).
|
||||
s := mk2()['missing']
|
||||
println('>${s}<')
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// Test runner for cleanc backend regression tests
|
||||
// Run with: ./vnew run vlib/v2_toberemoved/gen/cleanc/tests/run_tests.v
|
||||
// Or: cd vlib/v2_toberemoved/gen/cleanc/tests && v run run_tests.v
|
||||
|
||||
module main
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
fn main() {
|
||||
t0 := time.now()
|
||||
|
||||
vroot := os.dir(@VEXE)
|
||||
v2_source := os.join_path(vroot, 'cmd', 'v2', 'v2.v')
|
||||
v2_binary := os.join_path(vroot, 'cmd', 'v2', 'v2')
|
||||
tests_dir := os.join_path(vroot, 'vlib', 'v2', 'gen', 'cleanc', 'tests')
|
||||
|
||||
// Build v2 compiler (with v1).
|
||||
println('[*] Building v2...')
|
||||
build_res := os.execute('${@VEXE} -gc none -cc cc ${v2_source} -o ${v2_binary}')
|
||||
if build_res.exit_code != 0 {
|
||||
eprintln('Error: Failed to build v2')
|
||||
eprintln(build_res.output)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
// Find all test files (*.v except run_tests.v).
|
||||
test_files := os.ls(tests_dir) or {
|
||||
eprintln('Error: Cannot list tests directory')
|
||||
exit(1)
|
||||
}
|
||||
|
||||
mut passed := 0
|
||||
mut failed := 0
|
||||
mut test_count := 0
|
||||
|
||||
for file in test_files {
|
||||
if !file.ends_with('.v') || file == 'run_tests.v' {
|
||||
continue
|
||||
}
|
||||
test_count++
|
||||
test_path := os.join_path(tests_dir, file)
|
||||
test_name := file.replace('.v', '')
|
||||
|
||||
println('\n[*] Testing: ${file}')
|
||||
|
||||
// Run v2 with cleanc backend.
|
||||
v2_output := os.join_path(tests_dir, 'test_${test_name}')
|
||||
v2_cmd := '${v2_binary} -gc none -backend cleanc ${test_path} -o ${v2_output}'
|
||||
v2_res := os.execute(v2_cmd)
|
||||
if v2_res.exit_code != 0 {
|
||||
eprintln(' [FAIL] v2 compilation failed')
|
||||
eprintln(v2_res.output)
|
||||
failed++
|
||||
continue
|
||||
}
|
||||
|
||||
// Run the generated binary.
|
||||
gen_res := os.execute(v2_output)
|
||||
if gen_res.exit_code != 0 {
|
||||
eprintln(' [FAIL] Generated binary exited with code ${gen_res.exit_code}')
|
||||
eprintln(gen_res.output)
|
||||
failed++
|
||||
continue
|
||||
}
|
||||
|
||||
// Run reference compiler (v1).
|
||||
ref_res := os.execute('${@VEXE} -gc none -n -w -enable-globals run ${test_path}')
|
||||
if ref_res.exit_code != 0 {
|
||||
eprintln(' [FAIL] Reference compilation failed')
|
||||
eprintln(ref_res.output)
|
||||
failed++
|
||||
continue
|
||||
}
|
||||
|
||||
// Compare outputs.
|
||||
expected := ref_res.output.trim_space().replace('\r\n', '\n')
|
||||
actual := gen_res.output.trim_space().replace('\r\n', '\n')
|
||||
|
||||
if expected == actual {
|
||||
println(' [PASS] Output matches reference')
|
||||
passed++
|
||||
} else {
|
||||
println(' [FAIL] Output mismatch')
|
||||
println(' Expected:\n${expected}')
|
||||
println(' Got:\n${actual}')
|
||||
failed++
|
||||
}
|
||||
|
||||
// Clean up.
|
||||
os.rm(v2_output) or {}
|
||||
os.rm('${v2_output}.c') or {}
|
||||
}
|
||||
|
||||
println('\n========================================')
|
||||
println('Results: ${passed}/${test_count} tests passed')
|
||||
if failed > 0 {
|
||||
println('${failed} tests FAILED')
|
||||
exit(1)
|
||||
}
|
||||
println('Total time: ${time.since(t0)}')
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
module main
|
||||
|
||||
@[soa]
|
||||
struct Vec2 {
|
||||
x f32
|
||||
y f32
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Test that the SOA struct definition is generated
|
||||
// The companion type Vec2_SOA should be available
|
||||
println('soa_struct test: ok')
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user