Python transpiler: fanc py command#84
Open
trevadelman wants to merge 6 commits intofantom-lang:masterfrom
Open
Python transpiler: fanc py command#84trevadelman wants to merge 6 commits intofantom-lang:masterfrom
trevadelman wants to merge 6 commits intofantom-lang:masterfrom
Conversation
Add Python transpiler to the fanc tool as a TranspileCmd, following the same pattern as the existing Java transpiler command. Generates Python 3.12+ code from Fantom source via 'fanc py <pod>'. The transpiler maps Fantom constructs to idiomatic Python: snake_case naming, combined getter/setter fields, Func.make_closure() for closures, ObjUtil dispatch for cross-type operations, and lazy __init__.py module loaders to avoid circular imports. See design.md for the full Fantom-to-Python mapping. New files: src/fanc/fan/py/PythonCmd.fan - Entry point (fanc py <pod>) src/fanc/fan/py/PyTypePrinter.fan - Class/type generation src/fanc/fan/py/PyExprPrinter.fan - Expression generation src/fanc/fan/py/PyStmtPrinter.fan - Statement generation src/fanc/fan/py/PyPrinter.fan - Base printer src/fanc/fan/py/PyUtil.fan - Utilities and operator maps src/fanc/fan/py/design.md - Technical reference src/fanc/fan/py/quickstart.md - Build and test guide Modified files: src/build/fan/BuildPod.fan - Add pyDirs field (mirrors jsDirs) src/compiler/fan/CompilerInput.fan - Add pyFiles field (mirrors jsFiles) src/fanc/build.fan - Add py/ to srcDirs
The combined getter/setter pattern used _val_=None as the default, making it impossible to set a nullable field to null. Now uses a module-level _UNSET = object() sentinel so field(None) correctly enters the setter path. Also fixes return type hints on field accessors to always use Optional[T] since the setter path returns None. Addresses review feedback from Matthew.
…ables
The transpiler now detects Wrap$ synthetic classes (the Fantom compiler's
signal for closure-captured mutable variables) and emits Python's nonlocal
keyword instead of ObjUtil.cvar() wrappers. This produces cleaner, more
idiomatic Python output.
PyPrinter.fan - nonlocalVars map tracks wrapper-to-original-name mappings
PyStmtPrinter.fan - detectAndRecordNonlocal() skips Wrap$.make() lines,
prescanNonlocal() pre-scans method bodies, writeClosure()
emits nonlocal declarations
PyExprPrinter.fan - isWrapValAccess() intercepts Wrap$.val field access and
outputs plain variable names instead of wrapper._val
Result: 214 generated files now use nonlocal, zero ObjUtil.cvar in output.
Addresses review feedback from Matthew.
Move generated _UNSET sentinel from per-module 'object()' to an import from sys::ObjUtil, ensuring a single global identity for cross-pod field inheritance. The corresponding _UNSET definition in ObjUtil.py lands in PR 2 (sys runtime). Field accessor setter branches now return _val_ so both code paths match the return type. Type hints reflect Fantom's actual type: non-nullable Str -> 'str', nullable Str? -> 'Optional[str]'. Addresses review feedback from Brian (Issues 1.5, 1.6).
Collapse two loops over pod.typeDefs in genPod() into one pass.
Replace ~40 printLine calls in writeLazyLoader() with a Str raw
string template using {{POD}} and {{TYPES_DICT}} placeholders.
Addresses review feedback from Matthew (Issues 1.7, 1.8).
Address all 10 points from code review:
1. mapLiteral/listLiteral: Cast to MapType/ListType directly. Removed
try/catch control flow and dynamic dispatch (->). Use ns-level types
instead of pod.resolveType().
2. DRY sysPrefix: Extracted sysPrefix() helper, replacing 15 inline
occurrences of the curPod != sys check.
3. checkInCtor/enterCtor/exitCtor: Scoped to sys::Func parent type
check instead of bare name matching.
4. call() broken up: From ~120 lines to 15-line dispatcher delegating
to callSafe, callDynamic, callFunc, callPrivate, callNormal.
5. isObjUtilMethod: m.parent.isObj routes ALL Obj methods through
ObjUtil (no name checks). Matches ES compiler pattern.
6. isPrimitiveType: Err/Func confirmed NOT primitives (matches ES
compiler pmap). Num routed via ObjUtil.
7. primitiveMap: Single Str:Str hashmap replaces parallel if-chains
in isPrimitiveType and primitiveClassName.
8. usesMethodStyleSetters: Removed dead code.
9. TODO defaults: throw UnsupportedErr instead of outputting comments
or None for unimplemented paths.
10. CType methods: Replaced string comparisons (targetSig == sys::Str)
with CType methods (isStr, isObj, isFloat, isNum, isDecimal, isRange).
Additional DRY improvements:
- writeTypeRef(): Extracted pod-qualified type reference (4 duplicates)
- writeArgs(): Extracted comma-separated arg writing (6+ duplicates)
- compoundAssign(): Extracted from doShortcutBinaryOp
- objUtilOp(): Extracted from divOp/modOp
- incDec(): Merged identical increment/decrement methods
- Eliminated dead cmp() method (redundant with comparison())
PyTypePrinter: fieldInit now writes type defaults only, matching ES
compiler pattern. Actual initialization handled by instance$init.
Net reduction: ~325 lines removed (15% smaller).
Addresses review feedback from Brian.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add Python transpiler to the fanc tool -- generates Python 3.12+ code from Fantom source via
fanc py <pod>.Context
This is PR 1 of 6 for adding Python transpilation support to Fantom. Subsequent PRs add the hand-written runtime (sys, concurrent, util), test framework, and additional pod support (inet, crypto).
Changes
New files (8):
src/fanc/fan/py/PythonCmd.fan-- Entry point (fanc py <pod>)src/fanc/fan/py/PyTypePrinter.fan-- Class/type generationsrc/fanc/fan/py/PyExprPrinter.fan-- Expression generationsrc/fanc/fan/py/PyStmtPrinter.fan-- Statement generationsrc/fanc/fan/py/PyPrinter.fan-- Base printer with indentation and output managementsrc/fanc/fan/py/PyUtil.fan-- Utilities, operator maps, naming conventionssrc/fanc/fan/py/design.md-- Technical reference for the Fantom-to-Python mappingsrc/fanc/fan/py/quickstart.md-- Build and test guideModified files (3):
src/build/fan/BuildPod.fan-- AddpyDirsfield (mirrorsjsDirs),pod.native.pymeta,ci.pyFiles = pyDirssrc/compiler/fan/CompilerInput.fan-- AddpyFilesfield (mirrorsjsFiles)src/fanc/build.fan-- Addfan/py/tosrcDirsDependencies
Testing
The transpiler generates code but cannot run it yet -- the hand-written sys runtime (PR 2) and test framework (PR 4) are needed for execution.
Design Decisions
PythonCmdextendsTranspileCmd, same asJavaCmd. UsesPyTypePrinter/PyExprPrinter/PyStmtPrinterhierarchy mirroring the Java transpiler.camelCasemethods become Python'ssnake_case. A compile-time map handles the conversion.__init__.pyloaders: Avoids circular imports by deferring module loads until first access.Func.make_closure(): Closures are wrapped to support Fantom's captured variable semantics.ObjUtildispatch: Cross-type operations (compare, equals, hash) route through a utility module matching the JS transpiler's approach.pyDirsin BuildPod: MirrorsjsDirs-- pods with apy/directory contain hand-written Python natives that override transpiled output.See
design.mdfor the complete Fantom-to-Python mapping reference.