diff --git a/detailed_transpilation_report.md b/detailed_transpilation_report.md new file mode 100644 index 00000000..fbce03c7 --- /dev/null +++ b/detailed_transpilation_report.md @@ -0,0 +1,89 @@ +# Отчет о трансляции (py2v_transpiler) + +В данном отчете проанализированы результаты трансляции пяти файлов из директории `pythontovlang/py2v_transpiler/tests/input/transpile`. + +## 1. test_bitwise_ops.py + +### Ошибки и проблемы: +- **Отсутствие поддержки операторов комбинированного присваивания (AugAssign)**: + Для битовых операций (`|=`, `&=`, `^=`, `<<=`, `>>=`) транслятор выдает комментарий `// Unsupported AugAssign operator`, оставляя переменную без изменений. + *Пример:* + ```v + // Unsupported AugAssign operator: + println('New permissions: ${permissions}') + ``` +- **Типизация math.powi**: Используется `math.powi(base, exp)` для целых чисел, что корректно для V, но требует уверенности, что `base` и `exp` всегда `int`. + +--- + +## 2. test_boolean_ops.py + +### Ошибки и проблемы: +- **Несоответствие типов в анонимных функциях**: + В тесте `test_boolean_short_circuit_and` генерируется функция, которая должна возвращать `int` (так как в Python `True` это 1), но тело функции возвращает `true` (bool). + *Ошибка в V:* + ```v + mut should_not_run := fn () int { + println('This should not print') + return true // Ошибка: возвращает bool вместо int + } + ``` +- **Подмена идентичности (`is`) на равенство (`==`)**: + В V `a == b` для массивов проверяет равенство значений. В Python `is` проверяет идентичность объектов. Транслятор заменяет `is` на `==`, что меняет семантику теста. +- **Сложные логические выражения (Short-circuiting)**: + Для выражений `a and b`, где `a` или `b` не являются строго `bool`, генерируются `if`-выражения с приведением к `Any`. Это усложняет код, хотя и сохраняет логику Python. + +--- + +## 3. test_builtin_modules.py + +### Ошибки и проблемы: +- **Некорректный маппинг имен в модуле `rand`**: + Python модуль `random` транслируется в V модуль `rand`, но вызовы методов часто сохраняют префикс `random`, который не импортирован. + *Ошибка в V:* + ```v + import rand + // ... + println('Sample: ${random.sample(choices, 2)}') // Ошибка: random не определен + ``` +- **Проблемы с `os.path`**: + Вызовы `os.path.split` и `os.path.splitext` транслируются как есть, но в стандартной библиотеке V `os` обычно нет подмодуля `path` с такими методами (в V это `os.split_path` и т.д.). +- **Отсутствующие хелперы для встроенных функций**: + Для функций `range`, `sum`, `min`, `max`, `zip`, `enumerate` вызовы генерируются, но соответствующие определения отсутствуют в `helpers.v` для этого модуля. +- **Использование `.all(it)` и `.any(it)`**: + Для массивов вызываются методы `.all` и `.any`, которые не являются встроенными в V для обычных массивов и требуют дополнительных generic-расширений. + +--- + +## 4. test_while_loop.py + +### Ошибки и проблемы: +- **Логика `while-else`**: + Транспилятор использует флаг `py_loop_completed_N`. Однако в тестах без `break` этот флаг инициализируется `true` и никогда не меняется, что формально верно, но избыточно. +- **Аргумент `end` в `print`**: + Python `print(..., end=" ")` транслируется в `print('... ')`, что добавляет пробел в конец строки, но не учитывает, что последующие вызовы `print` в V также могут добавлять переводы строк, если не используются осторожно. + +--- + +## 5. test_set_operations.py + +### Ошибки и проблемы: +- **Некорректная инициализация множеств (Set)**: + Множества транслируются как `map[K]bool`. + *Ошибка в V:* + ```v + s3 := map[string]bool([1, 2, 2, 3, 3, 3]) // V не поддерживает инициализацию карты из массива таким образом + ``` +- **Отсутствие методов `.add()`, `.remove()`, `.discard()`, `.pop()` для карт**: + В V для `map` используются другие синтаксические конструкции. Транслятор генерирует вызовы методов, которых нет у встроенного типа `map`. +- **Операторы сравнения множеств**: + Выражения `b <= a` и `a >= b` (проверка подмножества) не поддерживаются для `map` в V. +- **Методы `.union()`, `.intersection()` и др.**: + Транслятор пытается вызывать их на объекте карты (`a.py_union(b)`), но они должны быть либо хелперами, либо расширениями, которых нет. + +## Общие выводы + +1. **AugAssign**: Серьезная недоработка в поддержке битовых операций. +2. **Standard Library Mapping**: Несоответствия между Python `random`/`os` и V `rand`/`os`. +3. **Set Support**: Реализация множеств через `map[K]bool` требует значительно большего количества хелперов или методов-расширений для корректной работы (add, remove, pop, subset и т.д.). +4. **Type Consistency**: Возникают ошибки в генерируемом V-коде из-за несоответствия типов (bool vs int в возвращаемых значениях). diff --git a/py2v_transpiler/tests/input/transpile/test_bitwise_ops.v b/py2v_transpiler/tests/input/transpile/test_bitwise_ops.v new file mode 100644 index 00000000..759e4bcf --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_bitwise_ops.v @@ -0,0 +1,146 @@ +module main + +import math + +// @line: test_bitwise_ops.py:1:0 +pub fn test_bitwise_and() { + mut a := 12 + mut b := 10 + mut result := a & b + println('${a} & ${b} = ${result}') +} +// @line: test_bitwise_ops.py:7:0 +pub fn test_bitwise_or() { + mut a := 12 + mut b := 10 + mut result := a | b + println('${a} | ${b} = ${result}') +} +// @line: test_bitwise_ops.py:13:0 +pub fn test_bitwise_xor() { + mut a := 12 + mut b := 10 + mut result := a ^ b + println('${a} ^ ${b} = ${result}') +} +// @line: test_bitwise_ops.py:19:0 +pub fn test_bitwise_not() { + mut a := 5 + mut result := ~a + println('~${a} = ${result}') +} +// @line: test_bitwise_ops.py:24:0 +pub fn test_bitwise_shift_left() { + mut a := 4 + mut result := a << 2 + println('${a} << 2 = ${result}') +} +// @line: test_bitwise_ops.py:29:0 +pub fn test_bitwise_shift_right() { + mut a := 16 + mut result := a >> 2 + println('${a} >> 2 = ${result}') +} +// @line: test_bitwise_ops.py:34:0 +pub fn test_bitwise_operations() { + mut num := 13 + bit_mask := 4 + is_set := num & bit_mask != 0 + println('Bit 2 is set in ${num}: ${is_set}') + mut result := num | 2 + println('Set bit 1 in ${num}: ${result}') + result = num & ~4 + println('Clear bit 2 in ${num}: ${result}') + result = num ^ 1 + println('Toggle bit 0 in ${num}: ${result}') +} +// @line: test_bitwise_ops.py:53:0 +pub fn test_bitwise_flags() { + read := 1 + write := 2 + execute := 4 + mut permissions := read | write + println('Permissions: ${permissions}') + has_read := permissions & read != 0 + has_execute := permissions & execute != 0 + println('Has read: ${has_read}, Has execute: ${has_execute}') + // Unsupported AugAssign operator: + println('New permissions: ${permissions}') + // Unsupported AugAssign operator: + println('Final permissions: ${permissions}') +} +// @line: test_bitwise_ops.py:74:0 +pub fn test_floor_division() { + mut a := 17 + mut b := 5 + mut result := int(math.floor(f64(a) / f64(b))) + println('${a} // ${b} = ${result}') + result_neg := int(math.floor(f64(-17) / f64(5))) + println('-17 // 5 = ${result_neg}') +} +// @line: test_bitwise_ops.py:84:0 +pub fn test_modulo() { + mut a := 17 + mut b := 5 + mut result := a % b + println('${a} % ${b} = ${result}') + mut num := 10 + is_even := num % 2 == 0 + println('${num} is even: ${is_even}') +} +// @line: test_bitwise_ops.py:95:0 +pub fn test_power() { + base := 2 + exp := 10 + mut result := math.powi(base, exp) + println('${base} ** ${exp} = ${result}') + result_sqrt := math.pow(f64(16), 0.5) + println('16 ** 0.5 = ${result_sqrt}') +} +// @line: test_bitwise_ops.py:105:0 +pub fn test_augmented_assignment() { + mut x := 10 + x += 5 + println('x += 5: ${x}') + x -= 3 + println('x -= 3: ${x}') + x *= 2 + println('x *= 2: ${x}') + x = int(math.floor(f64(x) / f64(3))) + println('x //= 3: ${x}') + x = int(math.pow(x, 2)) + println('x **= 2: ${x}') + x %= 7 + println('x %= 7: ${x}') + // Unsupported AugAssign operator: + println('x &= 3: ${x}') + // Unsupported AugAssign operator: + println('x |= 5: ${x}') + // Unsupported AugAssign operator: + println('x ^= 2: ${x}') + // Unsupported AugAssign operator: + println('x >>= 1: ${x}') + // Unsupported AugAssign operator: + println('x <<= 2: ${x}') +} +// @line: test_bitwise_ops.py:140:0 +pub fn test() { + test_bitwise_and() + test_bitwise_or() + test_bitwise_xor() + test_bitwise_not() + test_bitwise_shift_left() + test_bitwise_shift_right() + test_bitwise_operations() + test_bitwise_flags() + test_floor_division() + test_modulo() + test_power() + test_augmented_assignment() +} + +fn main() { + // @line: test_bitwise_ops.py:154:0 + // if __name__ == '__main__': + test() +} \ No newline at end of file diff --git a/py2v_transpiler/tests/input/transpile/test_bitwise_ops_helpers.v b/py2v_transpiler/tests/input/transpile/test_bitwise_ops_helpers.v new file mode 100644 index 00000000..d004f148 --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_bitwise_ops_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_boolean_ops.v b/py2v_transpiler/tests/input/transpile/test_boolean_ops.v new file mode 100644 index 00000000..c99b7f49 --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_boolean_ops.v @@ -0,0 +1,129 @@ +module main + +// @line: test_boolean_ops.py:1:0 +pub fn test_boolean_and() { + mut a := true + mut b := false + println('True and False = ${if a != 0 { b } else { a }}') + println('True and True = ${true && true}') + println('False and anything = ${if false { Any("something") } else { Any(false) }}') +} +// @line: test_boolean_ops.py:8:0 +pub fn test_boolean_or() { + mut a := true + mut b := false + println('True or False = ${if a != 0 { a } else { b }}') + println('False or False = ${false || false}') + println('True or anything = ${if true { Any(true) } else { Any("something") }}') +} +// @line: test_boolean_ops.py:15:0 +pub fn test_boolean_not() { + println('not True = ${!true}') + println('not False = ${!false}') +} +// @line: test_boolean_ops.py:19:0 +pub fn test_boolean_short_circuit_and() { +// @line: test_boolean_ops.py:20:4 + mut should_not_run := fn () int { + println('This should not print') + return true + } + mut result := if false { Any(should_not_run()) } else { Any(false) } + println('Result: ${result}') +} +// @line: test_boolean_ops.py:28:0 +pub fn test_boolean_short_circuit_or() { +// @line: test_boolean_ops.py:29:4 + mut should_not_run := fn () int { + println('This should not print') + return false + } + mut result := if true { Any(true) } else { Any(should_not_run()) } + println('Result: ${result}') +} +// @line: test_boolean_ops.py:37:0 +pub fn test_boolean_chaining() { + x := 5 + mut result := (0 < x) && (x < 10) + println('0 < ${x} < 10 = ${result}') + result = (0 < x) && (x < 3) + println('0 < ${x} < 3 = ${result}') +} +// @line: test_boolean_ops.py:45:0 +pub fn test_boolean_with_values() { + println('bool(0) = ${(0 != 0)}') + println('bool(1) = ${(1 != 0)}') + println('bool(\'\') = ${("" != '')}') + println('bool(\'hello\') = ${("hello" != '')}') + println('bool([]) = ${([]Any{}.len > 0)}') + println('bool([1, 2]) = ${([1, 2].len > 0)}') + println('bool(None) = ${py_bool(none)}') +} +// @line: test_boolean_ops.py:55:0 +pub fn test_boolean_or_default() { + mut name := '' + mut result := if name.len > 0 { name } else { 'Anonymous' } + println('Default name: ${result}') + name = 'Alice' + result = if name.len > 0 { name } else { 'Anonymous' } + println('Actual name: ${result}') +} +// @line: test_boolean_ops.py:65:0 +pub fn test_boolean_and_conditional() { + mut enabled := true + mut result := if enabled != 0 { Any('Feature is enabled') } else { Any(enabled) } + println('Status: ${result}') + enabled = false + result = if enabled != 0 { Any('Feature is enabled') } else { Any(enabled) } + println('Status: ${result}') +} +// @line: test_boolean_ops.py:75:0 +pub fn test_boolean_comparison() { + mut a := 10 + mut b := 20 + println('a == b: ${a == b}') + println('a != b: ${a != b}') + println('a < b: ${a < b}') + println('a > b: ${a > b}') + println('a <= b: ${a <= b}') + println('a >= b: ${a >= b}') +} +// @line: test_boolean_ops.py:86:0 +pub fn test_boolean_identity() { + mut a := [1, 2, 3] + mut b := [1, 2, 3] + c := a + println('a == b: ${a == b}') + println('a is b: ${a == b}') + println('a is c: ${a == c}') + println('a is not b: ${a != b}') + println('a is not c: ${a != c}') +} +// @line: test_boolean_ops.py:98:0 +pub fn test_boolean_in() { + lst := [1, 2, 3, 4, 5] + println('3 in list: ${3 in lst}') + println('10 in list: ${10 in lst}') + println('10 not in list: ${10 !in lst}') +} +// @line: test_boolean_ops.py:104:0 +pub fn test() { + test_boolean_and() + test_boolean_or() + test_boolean_not() + test_boolean_short_circuit_and() + test_boolean_short_circuit_or() + test_boolean_chaining() + test_boolean_with_values() + test_boolean_or_default() + test_boolean_and_conditional() + test_boolean_comparison() + test_boolean_identity() + test_boolean_in() +} + +fn main() { + // @line: test_boolean_ops.py:118:0 + // if __name__ == '__main__': + test() +} \ No newline at end of file diff --git a/py2v_transpiler/tests/input/transpile/test_boolean_ops_helpers.v b/py2v_transpiler/tests/input/transpile/test_boolean_ops_helpers.v new file mode 100644 index 00000000..d004f148 --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_boolean_ops_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_builtin_modules.v b/py2v_transpiler/tests/input/transpile/test_builtin_modules.v new file mode 100644 index 00000000..fadcf22f --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_builtin_modules.v @@ -0,0 +1,112 @@ +module main + +import math +import rand +import os + +// @line: test_builtin_modules.py:5:0 +pub fn test_math_functions() { + println('Pi: ${math.pi}') + println('E: ${math.e}') + println('Sqrt(16): ${math.sqrt(f64(16))}') + println('Pow(2, 3): ${math.pow(f64(2), f64(3))}') + println('Ceil(3.2): ${math.ceil(f64(3.2))}') + println('Floor(3.8): ${math.floor(f64(3.8))}') + println('Abs(-5): ${abs(-5)}') + println('Round(3.5): ${math.round(3.5)}') + println('Round(3.14159, 2): ${py_round(f64(3.14159), 2)}') + println('Sin(0): ${math.sin(f64(0))}') + println('Cos(0): ${math.cos(f64(0))}') + println('Tan(0): ${math.tan(f64(0))}') + println('Log(e): ${math.log(f64(math.e))}') + println('Log10(100): ${math.log10(f64(100))}') + println('Exp(1): ${math.exp(f64(1))}') +} +// @line: test_builtin_modules.py:27:0 +pub fn test_random_functions() { + rand.seed(42) + println('Random: ${rand.f64()}') + println('Random int 1-10: ${rand.intn(10 - 1 + 1) + 1}') + println('Random int 1-10: ${rand.intn(10 - 1 + 1) + 1}') + choices := ['apple', 'banana', 'cherry'] + println('Choice: ${choices[rand.intn(choices.len)]}') + println('Sample: ${random.sample(choices, 2)}') + random.shuffle(choices) + println('Shuffled: ${choices}') +} +// @line: test_builtin_modules.py:42:0 +pub fn test_os_functions() { + cwd := os.getwd() + println('CWD: ${cwd}') + path := os.join_path('folder', 'subfolder', 'file.txt') + println('Joined path: ${path}') + py_destruct_0 := os.path.split(path) + dirname := py_destruct_0[0] + basename := py_destruct_0[1] + println('Dir: ${dirname}, Base: ${basename}') + py_destruct_1 := os.path.splitext('file.txt') + root := py_destruct_1[0] + ext := py_destruct_1[1] + println('Root: ${root}, Ext: ${ext}') + println('Exists: ${os.exists(cwd)}') + println('Is dir: ${os.is_dir(cwd)}') + println('Is file: ${os.is_file(cwd)}') +} +// @line: test_builtin_modules.py:64:0 +pub fn test_builtin_functions() { + println('Len: ${[1, 2, 3, 4, 5].len}') + println('Range: ${[]Any(range(5))}') + println('Range with start: ${[]Any(range(2, 7))}') + println('Range with step: ${[]Any(range(0, 10, 2))}') + nums := [5, 2, 8, 1, 9] + println('Sum: ${sum(nums)}') + println('Min: ${min(nums)}') + println('Max: ${max(nums)}') + println('Abs: ${abs(-10)}') + println('Pow: ${pow(2, 10)}') + py_destruct_2 := divmod(17, 5) + q := py_destruct_2[0] + r := py_destruct_2[1] + println('Divmod: quotient=${q}, remainder=${r}') + println('All True: ${[true, true, true].all(it)}') + println('All with False: ${[true, false, true].all(it)}') + println('Any True: ${[false, false, true].any(it)}') + println('Any False: ${[false, false, false].any(it)}') + println('Ord(\'A\'): ${ord("A")}') + println('Chr(65): ${chr(65)}') +} +// @line: test_builtin_modules.py:97:0 +pub fn test_string_builtin() { + s := 'Hello, World!' + println('Len: ${s.len}') + println('Upper: ${s.to_upper()}') + println('Lower: ${s.to_lower()}') + println('Replace: ${s.replace("World", "Universe")}') + println('Split: ${s.split(", ")}') +} +// @line: test_builtin_modules.py:106:0 +pub fn test_list_builtin() { + lst := [3, 1, 4, 1, 5, 9, 2, 6] + println('Sorted: ${py_sorted(lst)}') + println('Sorted desc: ${py_sorted(lst)}') + println('Reversed: ${[]Any(py_reversed(lst))}') + names := ['Alice', 'Bob', 'Charlie'] + ages := [25, 30, 35] + println('Zipped: ${[]Any(zip(names, ages))}') + println('Enumerated: ${[]Any(enumerate(["a", "b", "c"]))}') +} +// @line: test_builtin_modules.py:121:0 +pub fn test() { + test_math_functions() + test_random_functions() + test_os_functions() + test_builtin_functions() + test_string_builtin() + test_list_builtin() +} + +fn main() { + // @line: test_builtin_modules.py:129:0 + // if __name__ == '__main__': + test() +} \ No newline at end of file diff --git a/py2v_transpiler/tests/input/transpile/test_builtin_modules_helpers.v b/py2v_transpiler/tests/input/transpile/test_builtin_modules_helpers.v new file mode 100644 index 00000000..366e231d --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_builtin_modules_helpers.v @@ -0,0 +1,138 @@ +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 py_sorted[T](a []T) []T { + mut b := a.clone() + b.sort() + return b +} +fn py_reversed[T](a []T) []T { + mut b := a.clone() + b.reverse() + return b +} +fn py_round(number f64, ndigits int) f64 { + p := math.pow(10, f64(ndigits)) + return math.round(number * p) / p +} +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_set_operations.v b/py2v_transpiler/tests/input/transpile/test_set_operations.v new file mode 100644 index 00000000..8f2a0f90 --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_set_operations.v @@ -0,0 +1,97 @@ +module main + +// @line: test_set_operations.py:1:0 +pub fn test_set_creation() { + s1 := {1: true, 2: true, 3: true, 4: true} + println('${s1}') + s2 := map[string]bool{} + println('Empty set: ${s2}') + s3 := map[string]bool([1, 2, 2, 3, 3, 3]) + println('From list: ${s3}') +} +// @line: test_set_operations.py:14:0 +pub fn test_set_add_remove() { + mut s := {1: true, 2: true, 3: true} + s.add(4) + println('After add: ${s}') + s.remove(2) + println('After remove: ${s}') + s.discard(10) + println('After discard: ${s}') + popped := s.pop() + println('Popped: ${popped}, Set: ${s}') +} +// @line: test_set_operations.py:28:0 +pub fn test_set_operations() { + mut a := {1: true, 2: true, 3: true, 4: true, 5: true} + mut b := {4: true, 5: true, 6: true, 7: true, 8: true} + println('Union: ${py_set_union(a, b)}') + println('Union method: ${a.py_union(b)}') + println('Intersection: ${py_set_intersection(a, b)}') + println('Intersection method: ${a.intersection(b)}') + println('Difference a-b: ${py_set_difference(a, b)}') + println('Difference b-a: ${py_set_difference(b, a)}') + println('Symmetric diff: ${py_set_xor(a, b)}') +} +// @line: test_set_operations.py:47:0 +pub fn test_set_update_operations() { + mut a := {1: true, 2: true, 3: true} + mut b := {3: true, 4: true, 5: true} + py_dict_update(mut a, b) + println('After update: ${a}') + a = {1: true, 2: true, 3: true} + a.intersection_update(b) + println('After intersection_update: ${a}') + a = {1: true, 2: true, 3: true} + a.difference_update(b) + println('After difference_update: ${a}') + a = {1: true, 2: true, 3: true} + a.symmetric_difference_update(b) + println('After symmetric_difference_update: ${a}') +} +// @line: test_set_operations.py:66:0 +pub fn test_set_subset_superset() { + mut a := {1: true, 2: true, 3: true, 4: true, 5: true} + mut b := {2: true, 3: true, 4: true} + println('b is subset of a: ${b.issubset(a)}') + println('a is superset of b: ${a.issuperset(b)}') + println('b <= a: ${b <= a}') + println('a >= b: ${a >= b}') +} +// @line: test_set_operations.py:75:0 +pub fn test_set_clear_copy() { + mut s := {1: true, 2: true, 3: true} + s_copy := s.copy() + println('Copy: ${s_copy}') + /* s.clear() */ s = {} + println('After clear: ${s}') + println('Copy after clear: ${s_copy}') +} +// @line: test_set_operations.py:84:0 +pub fn test_set_membership() { + mut s := {10: true, 20: true, 30: true} + println('20 in s: ${20 in s}') + println('40 not in s: ${40 !in s}') +} +// @line: test_set_operations.py:89:0 +pub fn test_frozenset() { + fs := frozenset([1, 2, 3]) + println('Frozenset: ${fs}') +} +// @line: test_set_operations.py:95:0 +pub fn test() { + test_set_creation() + test_set_add_remove() + test_set_operations() + test_set_update_operations() + test_set_subset_superset() + test_set_clear_copy() + test_set_membership() + test_frozenset() +} + +fn main() { + // @line: test_set_operations.py:105:0 + // if __name__ == '__main__': + test() +} \ No newline at end of file diff --git a/py2v_transpiler/tests/input/transpile/test_set_operations_helpers.v b/py2v_transpiler/tests/input/transpile/test_set_operations_helpers.v new file mode 100644 index 00000000..938bc1ed --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_set_operations_helpers.v @@ -0,0 +1,171 @@ +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() +} +fn py_dict_update[K, V](mut d map[K]V, other ...map[K]V) map[K]V { + for o in other { + for k, v in o { + d[k] = v + } + } + return d +} +fn py_set_union[K](a map[K]bool, b map[K]bool) map[K]bool { + mut res := a.clone() + for k, v in b { + res[k] = v + } + return res +} +fn py_set_intersection[K](a map[K]bool, b map[K]bool) map[K]bool { + mut res := map[K]bool{} + for k, _ in a { + if k in b { + res[k] = true + } + } + return res +} +fn py_set_difference[K](a map[K]bool, b map[K]bool) map[K]bool { + mut res := map[K]bool{} + for k, _ in a { + if k !in b { + res[k] = true + } + } + return res +} +fn py_set_xor[K](a map[K]bool, b map[K]bool) map[K]bool { + mut res := map[K]bool{} + for k, _ in a { + if k !in b { + res[k] = true + } + } + for k, _ in b { + if k !in a { + res[k] = true + } + } + return res +} diff --git a/py2v_transpiler/tests/input/transpile/test_while_loop.v b/py2v_transpiler/tests/input/transpile/test_while_loop.v new file mode 100644 index 00000000..a4f0efb4 --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_while_loop.v @@ -0,0 +1,129 @@ +module main + +// @line: test_while_loop.py:1:0 +pub fn test_while_basic() { + mut i := 0 + for i < 5 { + println('i=${i}') + i += 1 + } +} +// @line: test_while_loop.py:7:0 +pub fn test_while_with_condition() { + nums := [1, 2, 3, 4, 5] + mut i := 0 + for i < nums.len { + println('nums[${i}]=${nums[i]}') + i += 1 + } +} +// @line: test_while_loop.py:14:0 +pub fn test_while_break() { + mut i := 0 + for i < 10 { + if i == 5 { + break + } + println('i=${i}') + i += 1 + } +} +// @line: test_while_loop.py:22:0 +pub fn test_while_continue() { + mut i := 0 + for i < 5 { + i += 1 + if i == 3 { + continue + } + println('i=${i}') + } +} +// @line: test_while_loop.py:30:0 +pub fn test_while_else() { + mut i := 0 + mut py_loop_completed_0 := true + for i < 3 { + println('i=${i}') + i += 1 + } + if py_loop_completed_0 { + println('While loop completed normally') + } +} +// @line: test_while_loop.py:38:0 +pub fn test_while_else_break() { + mut i := 0 + mut py_loop_completed_1 := true + for i < 3 { + if i == 2 { + py_loop_completed_1 = false + break + } + println('i=${i}') + i += 1 + } + if py_loop_completed_1 { + println('This won\'t print (break)') + } +} +// @line: test_while_loop.py:48:0 +pub fn test_while_infinite() { + mut count := 0 + for true { + if count >= 3 { + break + } + println('count=${count}') + count += 1 + } +} +// @line: test_while_loop.py:57:0 +pub fn test_while_nested() { + mut i := 0 + for i < 3 { + mut j := 0 + for j < 3 { + print('(${i}, ${j}) ') + j += 1 + } + println('') + i += 1 + } +} +// @line: test_while_loop.py:67:0 +pub fn test_while_decrement() { + mut i := 5 + for i > 0 { + println('i=${i}') + i -= 1 + } +} +// @line: test_while_loop.py:73:0 +pub fn test_while_multiple_conditions() { + a, b := 0, 10 + for a < 5 && b > 5 { + println('a=${a}, b=${b}') + a += 1 + b -= 1 + } +} +// @line: test_while_loop.py:80:0 +pub fn test() { + test_while_basic() + test_while_with_condition() + test_while_break() + test_while_continue() + test_while_else() + test_while_else_break() + test_while_infinite() + test_while_nested() + test_while_decrement() + test_while_multiple_conditions() +} + +fn main() { + // @line: test_while_loop.py:92:0 + // if __name__ == '__main__': + test() +} \ No newline at end of file diff --git a/py2v_transpiler/tests/input/transpile/test_while_loop_helpers.v b/py2v_transpiler/tests/input/transpile/test_while_loop_helpers.v new file mode 100644 index 00000000..d004f148 --- /dev/null +++ b/py2v_transpiler/tests/input/transpile/test_while_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() +}