diff --git a/py2v_transpiler/tests/input/transpile/test_classes_inheritance.v b/py2v_transpiler/tests/input/transpile/test_classes_inheritance.v new file mode 100644 index 00000000..3f0529d9 --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_classes_inheritance.v @@ -0,0 +1,146 @@ +module main + +// @line: test_classes_inheritance.py:1:0 +pub struct Animal { + name string +} +// @line: test_classes_inheritance.py:11:0 +pub struct Dog { + Animal + breed string +} +// @line: test_classes_inheritance.py:22:0 +pub struct Cat { + Animal + lives int +} +// @line: test_classes_inheritance.py:46:0 +pub struct Vehicle { + brand string +} +// @line: test_classes_inheritance.py:52:0 +pub struct Car { + Vehicle + model string +} + +pub const Animal_new_animal__annotations__ = { 'name': 'string' } +pub const Animal_speak__annotations__ = { 'return': 'string' } +pub const Animal_info__annotations__ = { 'return': 'string' } +pub const Dog_new_dog__annotations__ = { 'name': 'string', 'breed': 'string' } +pub const Dog_speak__annotations__ = { 'return': 'string' } +pub const Dog_fetch__annotations__ = { 'return': 'string' } +pub const Cat_new_cat__annotations__ = { 'name': 'string' } +pub const Cat_speak__annotations__ = { 'return': 'string' } +pub const Cat_climb__annotations__ = { 'return': 'string' } +pub const Vehicle_new_vehicle__annotations__ = { 'brand': 'string' } +pub const Car_new_car__annotations__ = { 'brand': 'string', 'model': 'string' } +pub const Car_description__annotations__ = { 'return': 'string' } + +// @line: test_classes_inheritance.py:2:4 +pub fn new_animal(name string) Animal { + mut self := Animal{} + self.name = name + return self +} +// @line: test_classes_inheritance.py:5:4 +pub fn (self Animal) speak() string { + return 'Some sound' +} +// @line: test_classes_inheritance.py:8:4 +pub fn (self Animal) info() string { + return 'I am ${self.name}' +} +// @line: test_classes_inheritance.py:12:4 +pub fn new_dog(name string, breed string) Dog { + mut self := Dog{} + self.Animal = new_animal(name) + self.breed = breed + return self +} +// @line: test_classes_inheritance.py:16:4 +pub fn (self Dog) speak() string { + return 'Woof!' +} +// @line: test_classes_inheritance.py:19:4 +pub fn (self Dog) fetch() string { + return '${self.name} is fetching' +} +// @line: test_classes_inheritance.py:23:4 +pub fn new_cat(name string) Cat { + mut self := Cat{} + self.Animal = new_animal(name) + self.lives = 9 + return self +} +// @line: test_classes_inheritance.py:27:4 +pub fn (self Cat) speak() string { + return 'Meow!' +} +// @line: test_classes_inheritance.py:30:4 +pub fn (self Cat) climb() string { + return '${self.name} is climbing' +} +// @line: test_classes_inheritance.py:33:0 +pub fn test_basic_inheritance() { + mut dog := new_dog('Buddy', 'Golden') + println('${dog.name}') + println('${dog.breed}') + println('${dog.speak()}') + println('${dog.info()}') + println('${dog.fetch()}') +} +// @line: test_classes_inheritance.py:41:0 +pub fn test_polymorphism() { + animals := [new_dog('Rex', 'Shepherd'), new_cat('Whiskers')] + for animal in animals { + println('${animal.name} says: ${animal.speak()}') + } +} +// @line: test_classes_inheritance.py:49:4 +pub fn new_vehicle(brand string) Vehicle { + mut self := Vehicle{} + self.brand = brand + return self +} +// @line: test_classes_inheritance.py:53:4 +pub fn new_car(brand string, model string) Car { + mut self := Car{} + self.Vehicle = new_vehicle(brand) + self.model = model + return self +} +// @line: test_classes_inheritance.py:57:4 +pub fn (self Car) description() string { + return '${self.brand} ${self.model}' +} +// @line: test_classes_inheritance.py:60:0 +pub fn test_class_variables() { + car := new_car('Toyota', 'Camry') + println('Car: ${car.description()}') + println('Wheels: ${car.wheels}') + println('Class wheels: ${Vehicle.wheels}') +} +// @line: test_classes_inheritance.py:66:0 +pub fn test_isinstance_issubclass() { + mut dog := new_dog('Spot', 'Labrador') + println('dog is Animal: ${dog is Animal}') + println('dog is Dog: ${dog is dog}') + println('dog is Cat: ${dog is Cat}') + println('Dog is subclass of Animal: ${issubclass(dog, Animal)}') + println('Cat is subclass of Animal: ${issubclass(Cat, Animal)}') + println('Dog is subclass of Cat: ${issubclass(dog, Cat)}') +} +// @line: test_classes_inheritance.py:77:0 +pub fn test() { + test_basic_inheritance() + test_polymorphism() + test_class_variables() + test_isinstance_issubclass() +} + +fn main() { + // @line: test_classes_inheritance.py:83:0 + // if __name__ == '__main__': + test() +} \ No newline at end of file diff --git a/py2v_transpiler/tests/input/transpile/test_classes_inheritance_helpers.v b/py2v_transpiler/tests/input/transpile/test_classes_inheritance_helpers.v new file mode 100644 index 00000000..d004f148 --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_classes_inheritance_helpers.v @@ -0,0 +1,124 @@ +module main + +pub struct NoneType {} + +pub fn (n NoneType) str() string { + return 'None' +} + +pub struct Interpolation { +pub: + value Any + expression string + conversion string + format_spec string +} + +pub struct Template { +pub: + strings []string + interpolations []Interpolation +} + +pub fn (t Template) values() []Any { + mut res := []Any{cap: t.interpolations.len} + for i in t.interpolations { + res << i.value + } + return res +} + +pub fn (t1 Template) + (t2 Template) Template { + if t1.strings.len == 0 { return t2 } + if t2.strings.len == 0 { return t1 } + mut new_strings := t1.strings[..t1.strings.len - 1].clone() + new_strings << t1.strings.last() + t2.strings[0] + if t2.strings.len > 1 { + new_strings << t2.strings[1..] + } + mut new_interpolations := t1.interpolations.clone() + new_interpolations << t2.interpolations + return Template{ + strings: new_strings + interpolations: new_interpolations + } +} + +pub type Any = Interpolation | NoneType | Template | []Any | []u8 | bool | f64 | i64 | int | map[string]Any | string + +pub enum PyAnnotationFormat { value forwardref string } + +pub fn py_get_type_hints[T]() map[string]string { + mut hints := map[string]string{} + $for field in T.fields { + hints[field.name] = field.typ + } + return hints +} + +pub fn py_get_type_hints_generic(obj Any) map[string]string { + return map[string]string{} +} + +struct PyGeneratorInput { + val Any + is_exc bool + exc_msg string +} +struct PyGenerator[T] { +mut: + out chan T + in_ chan PyGeneratorInput + open bool = true +} + +fn (mut g PyGenerator[T]) next() ?T { + if !g.open { return none } + g.in_ <- PyGeneratorInput{val: 0} // Send dummy value + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) send(val Any) ?T { + if !g.open { panic('StopIteration') } + g.in_ <- PyGeneratorInput{val: val} + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) throw(msg string) ?T { + if !g.open { panic('StopIteration') } + g.in_ <- PyGeneratorInput{is_exc: true, exc_msg: msg} + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) close() { + g.open = false + g.in_.close() + // g.out will be closed by the generator function loop when it detects in_ closed or panic +} +fn py_yield[T](ch_out chan T, ch_in chan PyGeneratorInput, val T) Any { + ch_out <- val + inp := <-ch_in + if inp.is_exc { + panic(inp.exc_msg) + } + return inp.val +} +//##LLM@@ String formatting for bytes is stubbed and might be incorrect. Please implement proper bytes formatting or use V string interpolation. +fn py_bytes_format(fmt []u8, args Any) []u8 { + // Simplistic implementation for b'%s' % b'val' + // Converts bytes to string, formats, and converts back. + // This is not efficient or correct for non-ASCII bytes but works for simple cases. + fmt_str := fmt.bytestr() + // TODO: handle args properly. V's string interpolation/formatting expects distinct args. + // If args is []u8, treat as string. + arg_str := if args is []u8 { args.bytestr() } else { '${args}' } + + // Manual substitution of %s + // V does not have sprintf for runtime strings easily available in core without C interop. + // Simple replace for %s + res := fmt_str.replace('%s', arg_str) + return res.bytes() +} diff --git a/py2v_transpiler/tests/input/transpile/test_for_loop.v b/py2v_transpiler/tests/input/transpile/test_for_loop.v new file mode 100644 index 00000000..8845a2f5 --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_for_loop.v @@ -0,0 +1,155 @@ +module main + +// @line: test_for_loop.py:1:0 +pub fn test_for_basic() { + for i in 0..5 { + println('i=${i}') + } +} +// @line: test_for_loop.py:5:0 +pub fn test_for_list() { + for item in [1, 2, 3, 4, 5] { + println('item=${item}') + } +} +// @line: test_for_loop.py:9:0 +pub fn test_for_string() { + for char_u8 in 'hello' { + char := char_u8.ascii_str() + println('char=${char}') + } +} +// @line: test_for_loop.py:13:0 +pub fn test_for_dict() { + d := {'a': 1, 'b': 2, 'c': 3} + println('Keys:') + for key in d { + println('key=${key}') + } + println('Values:') + for value in d.values() { + println('value=${value}') + } + println('Items:') + for key, value in d { + println('${key}=${value}') + } +} +// @line: test_for_loop.py:28:0 +pub fn test_for_break() { + for i in 0..10 { + if i == 5 { + break + } + println('i=${i}') + } +} +// @line: test_for_loop.py:34:0 +pub fn test_for_continue() { + for i in 0..5 { + if i == 2 { + continue + } + println('i=${i}') + } +} +// @line: test_for_loop.py:40:0 +pub fn test_for_else() { + mut py_loop_completed_0 := true + for i in 0..3 { + println('i=${i}') + } + if py_loop_completed_0 { + println('For loop completed normally') + } +} +// @line: test_for_loop.py:46:0 +pub fn test_for_else_break() { + mut py_loop_completed_1 := true + for i in 0..3 { + if i == 2 { + py_loop_completed_1 = false + break + } + println('i=${i}') + } + if py_loop_completed_1 { + println('This won\'t print (break)') + } +} +// @line: test_for_loop.py:54:0 +pub fn test_for_nested() { + for i in 0..3 { + for j in 0..3 { + print('(${i}, ${j}) ') + } + println('') + } +} +// @line: test_for_loop.py:60:0 +pub fn test_for_enumerate() { + items := ['a', 'b', 'c', 'd'] + for index, item in items { + println('index=${index}, item=${item}') + } +} +// @line: test_for_loop.py:65:0 +pub fn test_for_zip() { + names := ['Alice', 'Bob', 'Charlie'] + ages := [25, 30, 35] + py_zip_it1_1 := names + py_zip_it2_1 := ages + for py_i_1, py_v1_1 in py_zip_it1_1 { + if py_i_1 >= py_zip_it2_1.len { break } + py_v2_1 := py_zip_it2_1[py_i_1] + name := py_v1_1 + age := py_v2_1 + println('${name} is ${age}') + } +} +// @line: test_for_loop.py:72:0 +pub fn test_for_unpacking() { + pairs := [[1, 2], [3, 4], [5, 6]] + for py_val_140707411655440 in pairs { + a := py_val_140707411655440[0] + b := py_val_140707411655440[1] + println('a=${a}, b=${b}') + } +} +// @line: test_for_loop.py:77:0 +pub fn test_for_else_found() { + target := 5 + mut py_loop_completed_2 := true + for i in 0..10 { + if i == target { + println('Found ${target}') + py_loop_completed_2 = false + break + } + } + if py_loop_completed_2 { + println('${target} not found') + } +} +// @line: test_for_loop.py:87:0 +pub fn test() { + test_for_basic() + test_for_list() + test_for_string() + test_for_dict() + test_for_break() + test_for_continue() + test_for_else() + test_for_else_break() + test_for_nested() + test_for_enumerate() + test_for_zip() + test_for_unpacking() + test_for_else_found() +} + +fn main() { + // @line: test_for_loop.py:102:0 + // if __name__ == '__main__': + test() +} \ No newline at end of file diff --git a/py2v_transpiler/tests/input/transpile/test_for_loop_helpers.v b/py2v_transpiler/tests/input/transpile/test_for_loop_helpers.v new file mode 100644 index 00000000..d004f148 --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_for_loop_helpers.v @@ -0,0 +1,124 @@ +module main + +pub struct NoneType {} + +pub fn (n NoneType) str() string { + return 'None' +} + +pub struct Interpolation { +pub: + value Any + expression string + conversion string + format_spec string +} + +pub struct Template { +pub: + strings []string + interpolations []Interpolation +} + +pub fn (t Template) values() []Any { + mut res := []Any{cap: t.interpolations.len} + for i in t.interpolations { + res << i.value + } + return res +} + +pub fn (t1 Template) + (t2 Template) Template { + if t1.strings.len == 0 { return t2 } + if t2.strings.len == 0 { return t1 } + mut new_strings := t1.strings[..t1.strings.len - 1].clone() + new_strings << t1.strings.last() + t2.strings[0] + if t2.strings.len > 1 { + new_strings << t2.strings[1..] + } + mut new_interpolations := t1.interpolations.clone() + new_interpolations << t2.interpolations + return Template{ + strings: new_strings + interpolations: new_interpolations + } +} + +pub type Any = Interpolation | NoneType | Template | []Any | []u8 | bool | f64 | i64 | int | map[string]Any | string + +pub enum PyAnnotationFormat { value forwardref string } + +pub fn py_get_type_hints[T]() map[string]string { + mut hints := map[string]string{} + $for field in T.fields { + hints[field.name] = field.typ + } + return hints +} + +pub fn py_get_type_hints_generic(obj Any) map[string]string { + return map[string]string{} +} + +struct PyGeneratorInput { + val Any + is_exc bool + exc_msg string +} +struct PyGenerator[T] { +mut: + out chan T + in_ chan PyGeneratorInput + open bool = true +} + +fn (mut g PyGenerator[T]) next() ?T { + if !g.open { return none } + g.in_ <- PyGeneratorInput{val: 0} // Send dummy value + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) send(val Any) ?T { + if !g.open { panic('StopIteration') } + g.in_ <- PyGeneratorInput{val: val} + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) throw(msg string) ?T { + if !g.open { panic('StopIteration') } + g.in_ <- PyGeneratorInput{is_exc: true, exc_msg: msg} + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) close() { + g.open = false + g.in_.close() + // g.out will be closed by the generator function loop when it detects in_ closed or panic +} +fn py_yield[T](ch_out chan T, ch_in chan PyGeneratorInput, val T) Any { + ch_out <- val + inp := <-ch_in + if inp.is_exc { + panic(inp.exc_msg) + } + return inp.val +} +//##LLM@@ String formatting for bytes is stubbed and might be incorrect. Please implement proper bytes formatting or use V string interpolation. +fn py_bytes_format(fmt []u8, args Any) []u8 { + // Simplistic implementation for b'%s' % b'val' + // Converts bytes to string, formats, and converts back. + // This is not efficient or correct for non-ASCII bytes but works for simple cases. + fmt_str := fmt.bytestr() + // TODO: handle args properly. V's string interpolation/formatting expects distinct args. + // If args is []u8, treat as string. + arg_str := if args is []u8 { args.bytestr() } else { '${args}' } + + // Manual substitution of %s + // V does not have sprintf for runtime strings easily available in core without C interop. + // Simple replace for %s + res := fmt_str.replace('%s', arg_str) + return res.bytes() +} diff --git a/py2v_transpiler/tests/input/transpile/test_function_defs.v b/py2v_transpiler/tests/input/transpile/test_function_defs.v new file mode 100644 index 00000000..f8a6ec74 --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_function_defs.v @@ -0,0 +1,170 @@ +module main + +// @line: test_function_defs.py:1:0 +pub fn test_function_basic() { +// @line: test_function_defs.py:2:4 + mut greet := fn () { + println('Hello!') + } + greet('Hello') +} +// @line: test_function_defs.py:7:0 +pub fn test_function_with_params() { +// @line: test_function_defs.py:8:4 + mut greet := fn (name string, age int) { + println('Name: ${name}, Age: ${age}') + } + greet('Alice', 30) +} +// @line: test_function_defs.py:13:0 +pub fn test_function_default_params() { +// @line: test_function_defs.py:14:4 + mut greet := fn (name string, greeting string) { + println('${greeting}, ${name}!') + } + greet('Alice', 'Hello') + greet('Bob', 'Hi') +} +// @line: test_function_defs.py:20:0 +pub fn test_function_keyword_args() { +// @line: test_function_defs.py:21:4 + mut describe := fn (name string, age int, city string) { + println('${name}, ${age}, ${city}') + } + describe('Alice', 30, 'NYC') + describe('Bob', 25, 'LA') +} +// @line: test_function_defs.py:27:0 +pub fn test_function_mixed_args() { +// @line: test_function_defs.py:28:4 + mut func := fn (a int, b int, c Any, d Any) { + println('a=${a}, b=${b}, c=${c}, d=${d}') + } + func(1, 2) + func(1, 2) +} +// @line: test_function_defs.py:34:0 +pub fn test_function_varargs() { +// @line: test_function_defs.py:35:4 + mut sum_all := fn (args ...Any) Any { + mut total := 0 + for arg in args { + total += arg + } + return total + } + println('${sum_all(1, 2, 3)}') + println('${sum_all(1, 2, 3, 4, 5)}') +} +// @line: test_function_defs.py:44:0 +pub fn test_function_kwargs() { +// @line: test_function_defs.py:45:4 + mut print_kwargs := fn (kwargs map[string]string) { + for key, value in kwargs { + println('${key}=${value}') + } + } + print_kwargs() +} +// @line: test_function_defs.py:51:0 +pub fn test_function_args_kwargs() { +//##LLM@@ Function `func` has both *args and **kwargs. V requires the variadic parameter (...args) to be the final parameter. Please reorder the parameters so that the variadic parameter is last, and update all calls to this function accordingly. +// @line: test_function_defs.py:52:4 + mut func := fn (a int, args ...Any, kwargs map[string]string) { + println('a=${a}') + println('args=${args}') + println('kwargs=${kwargs}') + } + func(1, 2, 3, 4) +} +// @line: test_function_defs.py:59:0 +pub fn test_function_return() { +// @line: test_function_defs.py:60:4 + mut add := fn (a int, b int) int { + return a + b + } + mut result := add(3, 4) + println('Result: ${result}') +} +// @line: test_function_defs.py:66:0 +pub fn test_function_multiple_returns() { +// @line: test_function_defs.py:67:4 + mut min_max := fn (nums Any) Any { + return [min(nums), max(nums)] + } + mut result := min_max([3, 1, 4, 1, 5, 9]) + println('Min and max: ${result}') + py_destruct_0 := min_max([3, 1, 4, 1, 5, 9]) + min_val := py_destruct_0[0] + max_val := py_destruct_0[1] + println('min=${min_val}, max=${max_val}') +} +// @line: test_function_defs.py:76:0 +pub fn test_function_no_return() { +// @line: test_function_defs.py:77:4 + mut no_return := fn () { + } + mut result := no_return() + println('Result: ${result}') +} +// @line: test_function_defs.py:83:0 +pub fn test_function_nested() { +// @line: test_function_defs.py:84:4 + mut outer := fn (x int) Any { +// @line: test_function_defs.py:85:8 + mut inner := fn [x] (y int) int { + return x + y + } + return inner + } + add_5 := outer(5) + println('${add_5(10)}') +} +// @line: test_function_defs.py:92:0 +pub fn test_function_recursive() { +// @line: test_function_defs.py:93:4 + mut factorial := fn (n int) int { + if n <= 1 { + return 1 + } + return n * factorial(n - 1) + } + println('5! = ${factorial(5)}') +} +// @line: test_function_defs.py:100:0 +pub fn test_function_recursive_fibonacci() { +// @line: test_function_defs.py:101:4 + mut fibonacci := fn (n int) int { + if n <= 1 { + return n + } + return fibonacci(n - 1) + fibonacci(n - 2) + } + for i in 0..10 { + print('fib(${i})=${fibonacci(i)} ') + } + println('') +} +// @line: test_function_defs.py:110:0 +pub fn test() { + test_function_basic() + test_function_with_params() + test_function_default_params() + test_function_keyword_args() + test_function_mixed_args() + test_function_varargs() + test_function_kwargs() + test_function_args_kwargs() + test_function_return() + test_function_multiple_returns() + test_function_no_return() + test_function_nested() + test_function_recursive() + test_function_recursive_fibonacci() +} + +fn main() { + // @line: test_function_defs.py:126:0 + // if __name__ == '__main__': + test() +} \ No newline at end of file diff --git a/py2v_transpiler/tests/input/transpile/test_function_defs_helpers.v b/py2v_transpiler/tests/input/transpile/test_function_defs_helpers.v new file mode 100644 index 00000000..d004f148 --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_function_defs_helpers.v @@ -0,0 +1,124 @@ +module main + +pub struct NoneType {} + +pub fn (n NoneType) str() string { + return 'None' +} + +pub struct Interpolation { +pub: + value Any + expression string + conversion string + format_spec string +} + +pub struct Template { +pub: + strings []string + interpolations []Interpolation +} + +pub fn (t Template) values() []Any { + mut res := []Any{cap: t.interpolations.len} + for i in t.interpolations { + res << i.value + } + return res +} + +pub fn (t1 Template) + (t2 Template) Template { + if t1.strings.len == 0 { return t2 } + if t2.strings.len == 0 { return t1 } + mut new_strings := t1.strings[..t1.strings.len - 1].clone() + new_strings << t1.strings.last() + t2.strings[0] + if t2.strings.len > 1 { + new_strings << t2.strings[1..] + } + mut new_interpolations := t1.interpolations.clone() + new_interpolations << t2.interpolations + return Template{ + strings: new_strings + interpolations: new_interpolations + } +} + +pub type Any = Interpolation | NoneType | Template | []Any | []u8 | bool | f64 | i64 | int | map[string]Any | string + +pub enum PyAnnotationFormat { value forwardref string } + +pub fn py_get_type_hints[T]() map[string]string { + mut hints := map[string]string{} + $for field in T.fields { + hints[field.name] = field.typ + } + return hints +} + +pub fn py_get_type_hints_generic(obj Any) map[string]string { + return map[string]string{} +} + +struct PyGeneratorInput { + val Any + is_exc bool + exc_msg string +} +struct PyGenerator[T] { +mut: + out chan T + in_ chan PyGeneratorInput + open bool = true +} + +fn (mut g PyGenerator[T]) next() ?T { + if !g.open { return none } + g.in_ <- PyGeneratorInput{val: 0} // Send dummy value + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) send(val Any) ?T { + if !g.open { panic('StopIteration') } + g.in_ <- PyGeneratorInput{val: val} + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) throw(msg string) ?T { + if !g.open { panic('StopIteration') } + g.in_ <- PyGeneratorInput{is_exc: true, exc_msg: msg} + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) close() { + g.open = false + g.in_.close() + // g.out will be closed by the generator function loop when it detects in_ closed or panic +} +fn py_yield[T](ch_out chan T, ch_in chan PyGeneratorInput, val T) Any { + ch_out <- val + inp := <-ch_in + if inp.is_exc { + panic(inp.exc_msg) + } + return inp.val +} +//##LLM@@ String formatting for bytes is stubbed and might be incorrect. Please implement proper bytes formatting or use V string interpolation. +fn py_bytes_format(fmt []u8, args Any) []u8 { + // Simplistic implementation for b'%s' % b'val' + // Converts bytes to string, formats, and converts back. + // This is not efficient or correct for non-ASCII bytes but works for simple cases. + fmt_str := fmt.bytestr() + // TODO: handle args properly. V's string interpolation/formatting expects distinct args. + // If args is []u8, treat as string. + arg_str := if args is []u8 { args.bytestr() } else { '${args}' } + + // Manual substitution of %s + // V does not have sprintf for runtime strings easily available in core without C interop. + // Simple replace for %s + res := fmt_str.replace('%s', arg_str) + return res.bytes() +} diff --git a/py2v_transpiler/tests/input/transpile/test_if_statement.v b/py2v_transpiler/tests/input/transpile/test_if_statement.v new file mode 100644 index 00000000..eb9f178a --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_if_statement.v @@ -0,0 +1,141 @@ +module main + +// @line: test_if_statement.py:1:0 +pub fn test_if_basic() { + mut x := 10 + if x > 5 { + println('${x} > 5') + } +} +// @line: test_if_statement.py:6:0 +pub fn test_if_else() { + mut x := 3 + if x > 5 { + println('${x} > 5') + } else { + println('${x} <= 5') + } +} +// @line: test_if_statement.py:13:0 +pub fn test_if_elif_else() { + mut x := 5 + if x > 10 { + println('${x} > 10') + } else if x > 5 { + println('${x} > 5') + } else if x == 5 { + println('${x} == 5') + } else { + println('${x} < 5') + } +} +// @line: test_if_statement.py:24:0 +pub fn test_if_nested() { + mut x := 10 + mut y := 20 + if x > 5 { + if y > 15 { + println('x > 5 and y > 15') + } + } +} +// @line: test_if_statement.py:31:0 +pub fn test_if_and_condition() { + mut x := 10 + mut y := 20 + if x > 5 && y > 15 { + println('Both conditions true') + } +} +// @line: test_if_statement.py:37:0 +pub fn test_if_or_condition() { + mut x := 3 + mut y := 10 + if x > 5 || y > 5 { + println('At least one condition true') + } +} +// @line: test_if_statement.py:43:0 +pub fn test_if_not() { + mut x := false + if !x { + println('x is False') + } +} +// @line: test_if_statement.py:48:0 +pub fn test_if_in() { + lst := [1, 2, 3, 4, 5] + if 3 in lst { + println('3 is in the list') + } +} +// @line: test_if_statement.py:53:0 +pub fn test_if_is() { + mut x := ?bool(none) + if x == none { + println('x is None') + } +} +// @line: test_if_statement.py:58:0 +pub fn test_if_ternary() { + mut x := 10 + result := if x > 0 { 'positive' } else { 'non-positive' } + println('x is ${result}') +} +// @line: test_if_statement.py:63:0 +pub fn test_if_multiple_elif() { + score := 85 + mut grade := ?int(none) + if score >= 90 { + grade = 'A' + } else if score >= 80 { + grade = 'B' + } else if score >= 70 { + grade = 'C' + } else if score >= 60 { + grade = 'D' + } else { + grade = 'F' + } + println('Grade: ${grade}') +} +// @line: test_if_statement.py:77:0 +pub fn test_if_truthy() { + value := 'hello' + if value.len > 0 { + println('Truthy value: ${value}') + } + empty := '' + if empty.len == 0 { + println('Empty string is falsy') + } +} +// @line: test_if_statement.py:86:0 +pub fn test_if_comparison_chain() { + mut x := 5 + if (0 < x) && (x < 10) { + println('${x} is between 0 and 10') + } +} +// @line: test_if_statement.py:91:0 +pub fn test() { + test_if_basic() + test_if_else() + test_if_elif_else() + test_if_nested() + test_if_and_condition() + test_if_or_condition() + test_if_not() + test_if_in() + test_if_is() + test_if_ternary() + test_if_multiple_elif() + test_if_truthy() + test_if_comparison_chain() +} + +fn main() { + // @line: test_if_statement.py:106:0 + // if __name__ == '__main__': + test() +} \ No newline at end of file diff --git a/py2v_transpiler/tests/input/transpile/test_if_statement_helpers.v b/py2v_transpiler/tests/input/transpile/test_if_statement_helpers.v new file mode 100644 index 00000000..d004f148 --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_if_statement_helpers.v @@ -0,0 +1,124 @@ +module main + +pub struct NoneType {} + +pub fn (n NoneType) str() string { + return 'None' +} + +pub struct Interpolation { +pub: + value Any + expression string + conversion string + format_spec string +} + +pub struct Template { +pub: + strings []string + interpolations []Interpolation +} + +pub fn (t Template) values() []Any { + mut res := []Any{cap: t.interpolations.len} + for i in t.interpolations { + res << i.value + } + return res +} + +pub fn (t1 Template) + (t2 Template) Template { + if t1.strings.len == 0 { return t2 } + if t2.strings.len == 0 { return t1 } + mut new_strings := t1.strings[..t1.strings.len - 1].clone() + new_strings << t1.strings.last() + t2.strings[0] + if t2.strings.len > 1 { + new_strings << t2.strings[1..] + } + mut new_interpolations := t1.interpolations.clone() + new_interpolations << t2.interpolations + return Template{ + strings: new_strings + interpolations: new_interpolations + } +} + +pub type Any = Interpolation | NoneType | Template | []Any | []u8 | bool | f64 | i64 | int | map[string]Any | string + +pub enum PyAnnotationFormat { value forwardref string } + +pub fn py_get_type_hints[T]() map[string]string { + mut hints := map[string]string{} + $for field in T.fields { + hints[field.name] = field.typ + } + return hints +} + +pub fn py_get_type_hints_generic(obj Any) map[string]string { + return map[string]string{} +} + +struct PyGeneratorInput { + val Any + is_exc bool + exc_msg string +} +struct PyGenerator[T] { +mut: + out chan T + in_ chan PyGeneratorInput + open bool = true +} + +fn (mut g PyGenerator[T]) next() ?T { + if !g.open { return none } + g.in_ <- PyGeneratorInput{val: 0} // Send dummy value + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) send(val Any) ?T { + if !g.open { panic('StopIteration') } + g.in_ <- PyGeneratorInput{val: val} + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) throw(msg string) ?T { + if !g.open { panic('StopIteration') } + g.in_ <- PyGeneratorInput{is_exc: true, exc_msg: msg} + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) close() { + g.open = false + g.in_.close() + // g.out will be closed by the generator function loop when it detects in_ closed or panic +} +fn py_yield[T](ch_out chan T, ch_in chan PyGeneratorInput, val T) Any { + ch_out <- val + inp := <-ch_in + if inp.is_exc { + panic(inp.exc_msg) + } + return inp.val +} +//##LLM@@ String formatting for bytes is stubbed and might be incorrect. Please implement proper bytes formatting or use V string interpolation. +fn py_bytes_format(fmt []u8, args Any) []u8 { + // Simplistic implementation for b'%s' % b'val' + // Converts bytes to string, formats, and converts back. + // This is not efficient or correct for non-ASCII bytes but works for simple cases. + fmt_str := fmt.bytestr() + // TODO: handle args properly. V's string interpolation/formatting expects distinct args. + // If args is []u8, treat as string. + arg_str := if args is []u8 { args.bytestr() } else { '${args}' } + + // Manual substitution of %s + // V does not have sprintf for runtime strings easily available in core without C interop. + // Simple replace for %s + res := fmt_str.replace('%s', arg_str) + return res.bytes() +} diff --git a/py2v_transpiler/tests/input/transpile/test_list_operations.v b/py2v_transpiler/tests/input/transpile/test_list_operations.v new file mode 100644 index 00000000..20095d23 --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_list_operations.v @@ -0,0 +1,133 @@ +module main + +// @line: test_list_operations.py:1:0 +pub fn test_list_append_extend() { + mut lst := []int{cap: 3} + lst << 1 + lst << 2 + lst << 3 + lst << 4 + println('${lst}') + lst << [5, 6, 7] + println('${lst}') +} +// @line: test_list_operations.py:9:0 +pub fn test_list_insert_remove() { + mut lst := []int{cap: 4} + lst << 1 + lst << 2 + lst << 4 + lst << 5 + lst.insert(2, 3) + println('${lst}') + lst.remove(3) + println('${lst}') +} +// @line: test_list_operations.py:17:0 +pub fn test_list_pop_clear() { + mut lst := []int{cap: 4} + lst << 10 + lst << 20 + lst << 30 + lst << 40 + popped := lst.pop() + println('Popped: ${popped}, List: ${lst}') + popped2 := lst.pop(1) + println('Popped at index: ${popped2}, List: ${lst}') + /* lst.clear() */ lst = {} + println('Cleared: ${lst}') +} +// @line: test_list_operations.py:28:0 +pub fn test_list_index_count() { + mut lst := []int{cap: 7} + lst << 1 + lst << 2 + lst << 3 + lst << 2 + lst << 4 + lst << 2 + lst << 5 + idx := lst.index(3) + println('Index of 3: ${idx}') + cnt := lst.count(2) + println('Count of 2: ${cnt}') +} +// @line: test_list_operations.py:36:0 +pub fn test_list_sort_reverse() { + mut lst := []int{cap: 5} + lst << 5 + lst << 2 + lst << 8 + lst << 1 + lst << 9 + lst.reverse() + println('Reversed: ${lst}') + lst.sort() + println('Sorted: ${lst}') + lst.sort(a > b) + println('Sorted desc: ${lst}') + words := ['banana', 'pie', 'Washington', 'book'] + words.sort() + println('Sorted by length: ${words}') +} +// @line: test_list_operations.py:52:0 +pub fn test_list_slicing_assignment() { + mut lst := []int{cap: 5} + lst << 1 + lst << 2 + lst << 3 + lst << 4 + lst << 5 + lst.delete_many(1, (3) - (1)) + lst.insert_many(1, [10, 20]) + println('${lst}') + lst.delete_many(0, (lst.len) - (0)) + lst.insert_many(0, [100, 200, 300]) + println('${lst}') +} +// @line: test_list_operations.py:60:0 +pub fn test_list_unpacking() { + a, b, c := 1, 2, 3 + println('a=${a}, b=${b}, c=${c}') + py_destruct_0 := [1, 2, 3, 4, 5] + first := py_destruct_0[0] + middle := py_destruct_0[1..py_destruct_0.len-1] + last := py_destruct_0[py_destruct_0.len-1] + println('First: ${first}, Middle: ${middle}, Last: ${last}') + py_destruct_1 := [10, 20, 30, 40] + start := py_destruct_1[0..py_destruct_1.len-1] + end := py_destruct_1[py_destruct_1.len-1] + println('Start: ${start}, End: ${end}') +} +// @line: test_list_operations.py:71:0 +pub fn test_list_methods_chain() { + mut lst := []int{cap: 8} + lst << 3 + lst << 1 + lst << 4 + lst << 1 + lst << 5 + lst << 9 + lst << 2 + lst << 6 + lst << 5 + lst.sort() + println('Sorted with append: ${lst}') +} +// @line: test_list_operations.py:78:0 +pub fn test() { + test_list_append_extend() + test_list_insert_remove() + test_list_pop_clear() + test_list_index_count() + test_list_sort_reverse() + test_list_slicing_assignment() + test_list_unpacking() + test_list_methods_chain() +} + +fn main() { + // @line: test_list_operations.py:88:0 + // if __name__ == '__main__': + test() +} \ No newline at end of file diff --git a/py2v_transpiler/tests/input/transpile/test_list_operations_helpers.v b/py2v_transpiler/tests/input/transpile/test_list_operations_helpers.v new file mode 100644 index 00000000..d004f148 --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_list_operations_helpers.v @@ -0,0 +1,124 @@ +module main + +pub struct NoneType {} + +pub fn (n NoneType) str() string { + return 'None' +} + +pub struct Interpolation { +pub: + value Any + expression string + conversion string + format_spec string +} + +pub struct Template { +pub: + strings []string + interpolations []Interpolation +} + +pub fn (t Template) values() []Any { + mut res := []Any{cap: t.interpolations.len} + for i in t.interpolations { + res << i.value + } + return res +} + +pub fn (t1 Template) + (t2 Template) Template { + if t1.strings.len == 0 { return t2 } + if t2.strings.len == 0 { return t1 } + mut new_strings := t1.strings[..t1.strings.len - 1].clone() + new_strings << t1.strings.last() + t2.strings[0] + if t2.strings.len > 1 { + new_strings << t2.strings[1..] + } + mut new_interpolations := t1.interpolations.clone() + new_interpolations << t2.interpolations + return Template{ + strings: new_strings + interpolations: new_interpolations + } +} + +pub type Any = Interpolation | NoneType | Template | []Any | []u8 | bool | f64 | i64 | int | map[string]Any | string + +pub enum PyAnnotationFormat { value forwardref string } + +pub fn py_get_type_hints[T]() map[string]string { + mut hints := map[string]string{} + $for field in T.fields { + hints[field.name] = field.typ + } + return hints +} + +pub fn py_get_type_hints_generic(obj Any) map[string]string { + return map[string]string{} +} + +struct PyGeneratorInput { + val Any + is_exc bool + exc_msg string +} +struct PyGenerator[T] { +mut: + out chan T + in_ chan PyGeneratorInput + open bool = true +} + +fn (mut g PyGenerator[T]) next() ?T { + if !g.open { return none } + g.in_ <- PyGeneratorInput{val: 0} // Send dummy value + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) send(val Any) ?T { + if !g.open { panic('StopIteration') } + g.in_ <- PyGeneratorInput{val: val} + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) throw(msg string) ?T { + if !g.open { panic('StopIteration') } + g.in_ <- PyGeneratorInput{is_exc: true, exc_msg: msg} + res := <-g.out + if res == none { g.open = false } + return res +} +fn (mut g PyGenerator[T]) close() { + g.open = false + g.in_.close() + // g.out will be closed by the generator function loop when it detects in_ closed or panic +} +fn py_yield[T](ch_out chan T, ch_in chan PyGeneratorInput, val T) Any { + ch_out <- val + inp := <-ch_in + if inp.is_exc { + panic(inp.exc_msg) + } + return inp.val +} +//##LLM@@ String formatting for bytes is stubbed and might be incorrect. Please implement proper bytes formatting or use V string interpolation. +fn py_bytes_format(fmt []u8, args Any) []u8 { + // Simplistic implementation for b'%s' % b'val' + // Converts bytes to string, formats, and converts back. + // This is not efficient or correct for non-ASCII bytes but works for simple cases. + fmt_str := fmt.bytestr() + // TODO: handle args properly. V's string interpolation/formatting expects distinct args. + // If args is []u8, treat as string. + arg_str := if args is []u8 { args.bytestr() } else { '${args}' } + + // Manual substitution of %s + // V does not have sprintf for runtime strings easily available in core without C interop. + // Simple replace for %s + res := fmt_str.replace('%s', arg_str) + return res.bytes() +} diff --git a/transpilation_analysis.md b/transpilation_analysis.md new file mode 100644 index 00000000..b34136ba --- /dev/null +++ b/transpilation_analysis.md @@ -0,0 +1,167 @@ +# Анализ трансляции Python в V (py2v) + +В данном документе представлен анализ результатов трансляции пяти Python-файлов с использованием инструмента `py2v`. + +## 1. Управляющие конструкции (`test_if_statement.py`) + +### Python +```python +def test_if_comparison_chain(): + x = 5 + if 0 < x < 10: + print(f"{x} is between 0 and 10") +``` + +### V +```v +pub fn test_if_comparison_chain() { + mut x := 5 + if (0 < x) && (x < 10) { + println('${x} is between 0 and 10') + } +} +``` + +**Наблюдения:** +- Цепочки сравнений (`0 < x < 10`) корректно разбиваются на конъюнкцию отдельных условий `(0 < x) && (x < 10)`. +- F-строки преобразуются в стандартную интерполяцию строк V `${x}`. +- Значения `None` проверяются через `== none`. +- Истинность строк (truthiness) преобразуется в проверку длины `.len > 0`. + +--- + +## 2. Циклы (`test_for_loop.py`) + +### Python +```python +def test_for_else(): + for i in range(3): + print(f"i={i}") + else: + print("For loop completed normally") +``` + +### V +```v +pub fn test_for_else() { + mut py_loop_completed_0 := true + for i in 0..3 { + println('i=${i}') + } + if py_loop_completed_0 { + println('For loop completed normally') + } +} +``` + +**Наблюдения:** +- Циклы `for-else` транслируются с использованием вспомогательного флага `py_loop_completed_N`, который сбрасывается при выполнении `break`. +- `range(n)` преобразуется в диапазон V `0..n`. +- Распаковка кортежей в циклах (`for a, b in pairs`) использует промежуточные переменные `py_destruct_N`. + +--- + +## 3. Определения функций (`test_function_defs.py`) + +### Python +```python +def test_function_nested(): + def outer(x: int): + def inner(y: int) -> int: + return x + y + return inner + + add_5 = outer(5) + print(add_5(10)) +``` + +### V +```v +pub fn test_function_nested() { + mut outer := fn (x int) Any { + mut inner := fn [x] (y int) int { + return x + y + } + return inner + } + add_5 := outer(5) + println('${add_5(10)}') +} +``` + +**Наблюдения:** +- Вложенные функции транслируются в анонимные функции V (замыкания) с явным захватом переменных `fn [x]`. +- Переменные аргументы (`*args`) отображаются в вариативные параметры V `...Any`. +- Значения по умолчанию для аргументов инжектируются в местах вызова (call sites). +- Функции, не возвращающие значения, неявно возвращают `NoneType`. + +--- + +## 4. Классы и наследование (`test_classes_inheritance.py`) + +### Python +```python +class Dog(Animal): + def __init__(self, name: str, breed: str): + super().__init__(name) + self.breed = breed +``` + +### V +```v +pub struct Dog { + Animal + breed string +} + +pub fn new_dog(name string, breed string) Dog { + mut self := Dog{} + self.Animal = new_animal(name) + self.breed = breed + return self +} +``` + +**Наблюдения:** +- Наследование реализуется через встраивание структур (struct embedding). +- Конструкторы `__init__` преобразуются в функции `new_имя_класса`. +- Методы становятся функциями верхнего уровня с получателем (receiver) `(self StructName)`. +- Реализована поддержка `isinstance` и `issubclass` (через оператор `is` и вспомогательные функции). + +--- + +## 5. Операции со списками (`test_list_operations.py`) + +### Python +```python +def test_list_append_extend(): + lst = [1, 2, 3] + lst.append(4) + lst.extend([5, 6, 7]) +``` + +### V +```v +pub fn test_list_append_extend() { + mut lst := []int{cap: 3} + lst << 1 + lst << 2 + lst << 3 + lst << 4 + lst << [5, 6, 7] +} +``` + +**Наблюдения:** +- Методы `append` и `extend` транслируются в оператор добавления V `<<`. +- Срезы (slices) в левой части присваивания (`lst[1:3] = [10, 20]`) преобразуются в вызовы `delete_many` и `insert_many`. +- Распаковка списков с использованием `*` (Extended unpacking) реализуется через создание срезов. + +--- + +## Общие выводы + +1. **Типизация:** Инструмент активно использует `mypy` для вывода типов. Если тип не может быть определен точно, используется тип-сумма `Any`. +2. **Хелперы:** Для каждого транслируемого файла создается файл `*_helpers.v`, содержащий общие определения (`NoneType`, `Any`, `Template` и др.). +3. **Комментарии LLM:** В сложных случаях (например, при конфликте порядка `*args` и `**kwargs` в V) транспилятор вставляет комментарии `//##LLM@@`, сигнализируя о необходимости ручной доработки или использования ИИ. +4. **Соответствие семантике:** Транспилятор старается максимально точно воспроизвести поведение Python, включая специфику циклов `for-else`, замыканий и динамической природы списков. diff --git a/transpilation_issues.md b/transpilation_issues.md new file mode 100644 index 00000000..dd312238 --- /dev/null +++ b/transpilation_issues.md @@ -0,0 +1,96 @@ +# Найденные ошибки и ограничения трансляции (py2v) + +В ходе анализа транслированного кода были выявлены следующие проблемы, которые приведут к ошибкам компиляции или неверному поведению программы в V. + +## 1. Потеря аргументов в вызовах функций (`test_function_defs.py`) + +### Ошибка А: Потеря именованных аргументов (`**kwargs`) +При трансляции функций с именованными аргументами, значения полностью теряются в месте вызова. + +**Python:** `print_kwargs(name="Alice", age=30, city="NYC")` +**V:** `print_kwargs() // АРГУМЕНТЫ ПОТЕРЯНЫ` + +### Ошибка Б: Ошибочная инжекция аргументов +В `test_function_basic` генерируется вызов с аргументом, хотя функция его не принимает. Это указывает на возможную утечку состояния в транспиляторе. + +**V:** +```v +mut greet := fn () { println('Hello!') } +greet('Hello') // Откуда взялся 'Hello'? Ошибка: слишком много аргументов. +``` + +## 2. Несоответствие сигнатур и типов (`test_function_defs.py`, `test_if_statement.py`) + +### Ошибка А: Присваивание void-результата +В V нельзя присвоить результат функции, которая ничего не возвращает (`void`). + +**V:** +```v +mut no_return := fn () { } +mut result := no_return() // Ошибка V: no_return does not return a value +``` + +### Ошибка Б: Неверный вывод типа (Type Mismatch) +В `test_if_multiple_elif` переменная `grade` определена как `?int`, но ей присваивается строка `'A'`, `'B'` и т.д. + +**V:** +```v +mut grade := ?int(none) +if score >= 90 { + grade = 'A' // Ошибка V: нельзя присвоить string переменной ?int +} +``` + +## 3. Проблемы с классами и наследованием (`test_classes_inheritance.py`) + +### Ошибка А: Отсутствие поддержки переменных класса +Python переменные класса (`wheels = 4`) не транслируются в поля структур V или константы. + +**V:** +```v +pub struct Vehicle { brand string } +println('Wheels: ${car.wheels}') // Ошибка V: wheels undefined +``` + +### Ошибка Б: Неверное использование оператора `is` +В проверке типа используется имя переменной вместо имени типа. + +**V:** +```v +println('dog is Dog: ${dog is dog}') // Ошибка V: 'dog' - это переменная, а не тип. Должно быть Dog. +``` + +## 4. Ошибки в операциях со списками (`test_list_operations.py`) + +### Ошибка А: `pop` с индексом +В V метод `pop()` не принимает индекс. Для удаления по индексу в V используется `delete()`. + +**V:** +```v +popped2 := lst.pop(1) // Ошибка V: слишком много аргументов +``` + +### Ошибка Б: Неверная очистка списка +Транспилятор заменяет `lst.clear()` на `lst = {}`. В V `{}` — это инициализация пустой карты (map), а не массива. Для массива должно быть `[]`. + +**V:** +```v +/* lst.clear() */ lst = {} // Ошибка V: нельзя присвоить map массиву []int +``` + +### Ошибка В: Отсутствующие хелперы +Транспилятор генерирует вызовы `delete_many` и `insert_many` для реализации срезов, но эти функции не определены в `_helpers.v`. + +## 5. Проблемы с вариативными аргументами (`test_function_defs.py`) + +### Ошибка +В V вариативный параметр (`...Any`) обязан быть последним в списке аргументов. Транспилятор ставит его перед `kwargs`. + +**V:** +```v +mut func := fn (a int, args ...Any, kwargs map[string]string) { ... } +// Ошибка V: variadic parameter must be the last one +``` + +--- +*Примечание: Выявленные проблемы требуют доработки логики `CallsMixin` и `TypeInference` в транспиляторе.*