namespace pxt . py { export function decompileToPython ( program , filename ) { let result = emptyResult ( ) ; try { let output = tsToPy ( program , filename ) ; let outFilename = filename . replace ( /(\.py)?\.\w*$/i , '' ) + '.py' ; result . outfiles [ outFilename ] = output ; } catch ( e ) { if ( e . pyDiagnostic ) result . diagnostics = [ e . pyDiagnostic ] ; else pxt . reportException ( e ) ; result . success = false ; } return result ; } function emptyResult ( ) { return { blocksInfo : undefined , outfiles : { } , diagnostics : [ ] , success : true , times : { } } ; } function throwError ( node , code , messageText ) { const diag = { fileName : node . getSourceFile ( ) . fileName , start : node . getStart ( ) , length : node . getEnd ( ) - node . getStart ( ) , line : undefined , column : undefined , code , category : pxtc . DiagnosticCategory . Error , messageText , } ; const err = new Error ( messageText ) ; ( err as any ) . pyDiagnostic = diag ; throw err ; } const SUPPORT_LAMBDAS = false ; const SUPPORT_CLASSES = false ; export const INDENT = "    " ; export function indent ( lvl ) { return s => ( s || "" ) . split ( '\n' ) . map ( line => `${ INDENT . repeat ( lvl ) }${ line }` ) . join ( '\n' ) ; } export const indent1 = indent ( 1 ) ; function tsToPy ( prog , filename ) { const tc = prog . getTypeChecker ( ) ; const lhost = new ts . pxtc . LSHost ( prog ) ; const file = prog . getSourceFile ( filename ) ; const commentMap = pxtc . decompiler . buildCommentMap ( file ) ; const reservedWords = pxt . U . toSet ( getReservedNmes ( ) , s => s ) ; const [ renameMap , globalNames ] = ts . pxtc . decompiler . buildRenameMap ( prog , file , { takenNames : reservedWords , declarations : "all" } ) ; const allSymbols = pxtc . getApiInfo ( prog ) ; const symbols = pxt . U . mapMap ( allSymbols . byQName , ( k , v ) => v . fileName == filename ? undefined : v ) ; const scopeLookup = computeScopeVariableLookup ( file ) ; return emitFile ( file ) ; function getReservedNmes ( ) { const reservedNames = [ 'ArithmeticError' , 'AssertionError' , 'AttributeError' , 'BaseException' , 'BlockingIOError' , 'BrokenPipeError' , 'BufferError' , 'BytesWarning' , 'ChildProcessError' , 'ConnectionAbortedError' , 'ConnectionError' , 'ConnectionRefusedError' , 'ConnectionResetError' , 'DeprecationWarning' , 'EOFError' , 'Ellipsis' , 'EnvironmentError' , 'Exception' , 'False' , 'FileExistsError' , 'FileNotFoundError' , 'FloatingPointError' , 'FutureWarning' , 'GeneratorExit' , 'IOError' , 'ImportError' , 'ImportWarning' , 'IndentationError' , 'IndexError' , 'InterruptedError' , 'IsADirectoryError' , 'KeyError' , 'KeyboardInterrupt' , 'LookupError' , 'MemoryError' , 'NameError' , 'None' , 'NotADirectoryError' , 'NotImplemented' , 'NotImplementedError' , 'OSError' , 'OverflowError' , 'PendingDeprecationWarning' , 'PermissionError' , 'ProcessLookupError' , 'RecursionError' , 'ReferenceError' , 'ResourceWarning' , 'RuntimeError' , 'RuntimeWarning' , 'StopAsyncIteration' , 'StopIteration' , 'SyntaxError' , 'SyntaxWarning' , 'SystemError' , 'SystemExit' , 'TabError' , 'TimeoutError' , 'True' , 'TypeError' , 'UnboundLocalError' , 'UnicodeDecodeError' , 'UnicodeEncodeError' , 'UnicodeError' , 'UnicodeTranslateError' , 'UnicodeWarning' , 'UserWarning' , 'ValueError' , 'Warning' , 'ZeroDivisionError' , '_' , '__build_class__' , '__debug__' , '__doc__' , '__import__' , '__loader__' , '__name__' , '__package__' , '__spec__' , 'abs' , 'all' , 'any' , 'ascii' , 'bin' , 'bool' , 'bytearray' , 'bytes' , 'callable' , 'chr' , 'classmethod' , 'compile' , 'complex' , 'copyright' , 'credits' , 'delattr' , 'dict' , 'dir' , 'divmod' , 'enumerate' , 'eval' , 'exec' , 'exit' , 'filter' , 'float' , 'format' , 'frozenset' , 'getattr' , 'globals' , 'hasattr' , 'hash' , 'help' , 'hex' , 'id' , 'input' , 'int' , 'isinstance' , 'issubclass' , 'iter' , 'len' , 'license' , 'list' , 'locals' , 'map' , 'max' , 'memoryview' , 'min' , 'next' , 'object' , 'oct' , 'open' , 'ord' , 'pow' , 'print' , 'property' , 'quit' , 'range' , 'repr' , 'reversed' , 'round' , 'set' , 'setattr' , 'slice' , 'sorted' , 'staticmethod' , 'str' , 'sum' , 'super' , 'tuple' , 'type' , 'vars' , 'zip' , ... Object . keys ( pxt . py . keywords ) ] ; return reservedNames ; } function tryGetSymbol ( exp ) { if ( ! exp . getSourceFile ( ) ) return null ; let tsExp = exp . getText ( ) ; return symbols [ tsExp ] || null ; } function tryGetPyName ( exp ) { if ( ! exp . getSourceFile ( ) ) return null ; let tsExp = exp . getText ( ) ; const tsSym = tc . getSymbolAtLocation ( exp ) ; if ( tsSym ) { tsExp = tc . getFullyQualifiedName ( tsSym ) ; } let sym = symbols [ tsExp ] ; if ( sym && sym . attributes . alias ) { return sym . attributes . alias ; } if ( sym && sym . pyQName ) { if ( sym . isInstance ) { if ( ts . isPropertyAccessExpression ( exp ) ) { return null ; } const nameRegExp = new RegExp ( `(?:^|\.)${ sym . namespace }\.(.+)` ) ; const match = nameRegExp . exec ( sym . pyQName ) ; if ( match ) return match [ 1 ] ; } return sym . pyQName ; } else if ( tsExp in pxtc . ts2PyFunNameMap ) { return pxtc . ts2PyFunNameMap [ tsExp ] . n ; } return null ; } function getName ( name ) { let pyName = tryGetPyName ( name ) ; if ( pyName ) return pyName ; if ( ! ts . isIdentifier ( name ) ) { pxt . tickEvent ( "depython.todo.advancedname" ) ; return throwError ( name , 3001 , "Unsupported advanced name format: " + name . getText ( ) ) ; } let outName = name . text ; let hasSrc = name . getSourceFile ( ) ; if ( hasSrc ) { const rename = renameMap . getRenameForPosition ( name . getStart ( ) ) ; if ( rename ) { outName = rename . name ; } } return outName ; } function getNewGlobalName ( nameHint ) { if ( typeof nameHint !== "string" ) nameHint = getName ( nameHint ) ; if ( globalNames [ nameHint ] ) { return pxtc . decompiler . getNewName ( nameHint , globalNames ) ; } else { globalNames [ nameHint ] = true ; return nameHint ; } } function hasTypeFlag ( t , fs ) { return ( t . flags & fs ) !== 0 ; } function isType ( s , fs ) { let type = tc . getTypeAtLocation ( s ) ; return hasTypeFlag ( type , fs ) ; } function isStringType ( s ) { return isType ( s , ts . TypeFlags . StringLike ) ; } function isNumberType ( s ) { return isType ( s , ts . TypeFlags . NumberLike ) ; } function emitFile ( file ) { let outLns = file . getChildren ( ) . map ( emitNode ) . reduce ( ( p , c ) => p . concat ( c ) , [ ] ) . reduce ( ( p , c ) => { if ( ! c && ! p [ p . length - 1 ] ) { return p ; } else { return [ ... p , c ] ; } } , [ ] ) ; commentMap . filter ( c => ! c . owner ) . forEach ( comment => outLns . push ( ... emitComment ( comment ) ) ) ; return outLns . join ( "\n" ) ; } function emitNode ( s ) { switch ( s . kind ) { case ts . SyntaxKind . SyntaxList : return ( s as ts . SyntaxList ) . _children . map ( emitNode ) . reduce ( ( p , c ) => p . concat ( c ) , [ ] ) ; case ts . SyntaxKind . EndOfFileToken : case ts . SyntaxKind . OpenBraceToken : case ts . SyntaxKind . CloseBraceToken : return [ ] ; default : return emitStmtWithNewlines ( s as ts . Statement ) ; } } function emitComment ( comment ) { let out = [ ] ; if ( comment . kind === pxtc . decompiler . CommentKind . SingleLine ) { out . push ( "# " + comment . text ) ; } else { out . push ( `"""` ) ; for ( const line of comment . lines ) { out . push ( line ) ; } out . push ( `"""` ) ; } return out ; } function emitStmtWithNewlines ( s ) { const out = emitStmt ( s ) ; const comments = pxtc . decompiler . getCommentsForStatement ( s , commentMap ) . map ( emitComment ) . reduce ( ( p , c ) => p . concat ( c ) , [ ] ) ; return comments . concat ( out ) ; } function emitStmt ( s ) { if ( ts . isVariableStatement ( s ) ) { return emitVarStmt ( s ) ; } else if ( ts . isClassDeclaration ( s ) ) { return emitClassStmt ( s ) ; } else if ( ts . isEnumDeclaration ( s ) ) { return emitEnumStmt ( s ) ; } else if ( ts . isExpressionStatement ( s ) ) { return emitExpStmt ( s ) ; } else if ( ts . isFunctionDeclaration ( s ) ) { return emitFuncDecl ( s ) ; } else if ( ts . isIfStatement ( s ) ) { return emitIf ( s ) ; } else if ( ts . isForStatement ( s ) ) { return emitForStmt ( s ) ; } else if ( ts . isForOfStatement ( s ) ) { return emitForOfStmt ( s ) ; } else if ( ts . isWhileStatement ( s ) ) { return emitWhileStmt ( s ) ; } else if ( ts . isReturnStatement ( s ) ) { return emitReturnStmt ( s ) ; } else if ( ts . isBlock ( s ) ) { return emitBlock ( s ) ; } else if ( ts . isTypeAliasDeclaration ( s ) ) { return emitTypeAliasDecl ( s ) ; } else if ( ts . isEmptyStatement ( s ) ) { return [ ] ; } else if ( ts . isModuleDeclaration ( s ) ) { return emitModuleDeclaration ( s ) ; } else if ( ts . isBreakStatement ( s ) ) { return [ 'break' ] ; } else if ( ts . isContinueStatement ( s ) ) { return [ 'continue' ] ; } else { pxt . tickEvent ( "depython.todo.statement" , { statement : s . kind } ) ; return throwError ( s , 3002 , `Not supported in MakeCode Python: ${ ts . SyntaxKind [ s . kind ] } (${ s . kind })` ) ; } } function emitModuleDeclaration ( s ) { let name = getName ( s . name ) ; let stmts = s . body && s . body . getChildren ( ) . map ( emitNode ) . reduce ( ( p , c ) => p . concat ( c ) , [ ] ) . map ( n => indent1 ( n ) ) ; return [ `@namespace` , `class ${ name }:` ] . concat ( stmts || [ ] ) ; } function emitTypeAliasDecl ( s ) { let typeStr = pxtc . emitPyTypeFromTypeNode ( s . type ) ; let name = getName ( s . name ) ; return [ `${ name } = ${ typeStr }` ] ; } function emitReturnStmt ( s ) { if ( ! s . expression ) return [ 'return' ] ; let [ exp , expSup ] = emitExp ( s . expression ) ; let stmt = expWrap ( "return " , exp ) ; return expSup . concat ( stmt ) ; } function emitWhileStmt ( s ) { let [ cond , condSup ] = emitExp ( s . expression ) ; let body = emitBody ( s . statement ) ; let whileStmt = expWrap ( "while " , cond , ":" ) ; return condSup . concat ( whileStmt ) . concat ( body ) ; } type RangeItr = { name : string ; fromIncl : string ; toExcl : string ; } ; function isNormalInteger ( str ) { let asInt = Math . floor ( Number ( str ) ) ; return asInt !== Infinity && String ( asInt ) === str ; } function getSimpleForRange ( s ) { if ( ! s . initializer ) return null ; if ( s . initializer . kind !== ts . SyntaxKind . VariableDeclarationList ) return null ; let initDecls = s . initializer as ts . VariableDeclarationList ; if ( initDecls . declarations . length !== 1 ) { return null ; } let decl = initDecls . declarations [ 0 ] ; let result_name = getName ( decl . name ) ; if ( ! decl . initializer || ! isConstExp ( decl . initializer ) || ! isNumberType ( decl . initializer ) ) { return null ; } let [ fromNum , fromNumSup ] = emitExp ( decl . initializer ) ; if ( fromNumSup . length ) return null ; let result_fromIncl = expToStr ( fromNum ) ; if ( ! s . condition ) return null ; if ( ! ts . isBinaryExpression ( s . condition ) ) return null ; if ( ! ts . isIdentifier ( s . condition . left ) ) return null ; if ( getName ( s . condition . left ) != result_name ) return null ; if ( ! isNumberType ( s . condition . right ) ) { return null ; } let [ toNumExp , toNumSup ] = emitExp ( s . condition . right ) ; if ( toNumSup . length ) return null ; let toNum = expToStr ( toNumExp ) ; let result_toExcl = toNum ; if ( s . condition . operatorToken . kind === ts . SyntaxKind . LessThanEqualsToken && isNormalInteger ( toNum ) ) { result_toExcl = "" + ( Number ( toNum ) + 1 ) ; } else if ( s . condition . operatorToken . kind !== ts . SyntaxKind . LessThanToken ) return null ; if ( ! s . incrementor ) return null ; if ( ! ts . isPostfixUnaryExpression ( s . incrementor ) && ! ts . isPrefixUnaryExpression ( s . incrementor ) ) return null ; if ( s . incrementor . operator !== ts . SyntaxKind . PlusPlusToken ) return null ; if ( ! ( result_fromIncl < result_toExcl ) ) return null ; let result = { name : result_name , fromIncl : result_fromIncl , toExcl : result_toExcl } ; return result ; } function emitBody ( s ) { let body = emitStmt ( s ) . map ( indent1 ) ; if ( body . length < 1 ) body = [ indent1 ( "pass" ) ] ; return body ; } function emitForOfStmt ( s ) { if ( ! ts . isVariableDeclarationList ( s . initializer ) ) { pxt . tickEvent ( "depython.todo.forof.complexexp" ) ; return throwError ( s , 3003 , "Unsupported expression in for..of initializer: " + s . initializer . getText ( ) ) ; } let names = s . initializer . declarations . map ( d => getName ( d . name ) ) ; if ( names . length !== 1 ) { pxt . tickEvent ( "depython.todo.forof.multidecl" ) ; return throwError ( s , 3004 , "Unsupported multiple declerations in for..of: " + s . initializer . getText ( ) ) ; } let name = names [ 0 ] ; let [ exp , expSup ] = emitExp ( s . expression ) ; let out = expSup ; out = out . concat ( expWrap ( `for ${ name } in ` , exp , ":" ) ) ; let body = emitBody ( s . statement ) ; out = out . concat ( body ) ; return out ; } function emitForStmt ( s ) { let rangeItr = getSimpleForRange ( s ) ; if ( rangeItr ) { let { name , fromIncl , toExcl } = rangeItr ; let forStmt = fromIncl === "0" ? `for ${ name } in range(${ toExcl }):` : `for ${ name } in range(${ fromIncl }, ${ toExcl }):` ; let body = emitBody ( s . statement ) ; return [ forStmt ] . concat ( body ) ; } let out = [ ] ; if ( s . initializer ) { if ( ts . isVariableDeclarationList ( s . initializer ) ) { let decls = s . initializer . declarations . map ( emitVarDecl ) . reduce ( ( p , c ) => p . concat ( c ) , [ ] ) ; out = out . concat ( decls ) ; } else { let [ exp , expSup ] = emitExp ( s . initializer ) ; out = out . concat ( expSup ) . concat ( exp ) ; } } let cond ; if ( s . condition ) { let [ condStr , condSup ] = emitExp ( s . condition ) ; out = out . concat ( condSup ) ; cond = expToStr ( condStr ) ; } else { cond = "True" ; } let whileStmt = `while ${ cond }:` ; out . push ( whileStmt ) ; let body = emitStmt ( s . statement ) . map ( indent1 ) ; if ( body . length === 0 && ! s . incrementor ) body = [ indent1 ( "pass" ) ] ; out = out . concat ( body ) ; if ( s . incrementor ) { let unaryIncDec = tryEmitIncDecUnaryStmt ( s . incrementor ) ; if ( unaryIncDec ) { out = out . concat ( unaryIncDec . map ( indent1 ) ) ; } else { let [ inc , incSup ] = emitExp ( s . incrementor ) ; out = out . concat ( incSup ) . concat ( inc . map ( indent1 ) ) ; } } return out ; } function emitIf ( s ) { let { supportStmts , ifStmt , rest } = emitIfHelper ( s ) ; return supportStmts . concat ( [ ifStmt ] ) . concat ( rest ) ; } function emitIfHelper ( s ) { let sup = [ ] ; let [ cond , condSup ] = emitExp ( s . expression ) ; sup = sup . concat ( condSup ) ; let ifStmt = `if ${ expToStr ( cond ) }:` ; let ifRest = [ ] ; let th = emitBody ( s . thenStatement ) ; ifRest = ifRest . concat ( th ) ; if ( s . elseStatement ) { if ( ts . isIfStatement ( s . elseStatement ) ) { let { supportStmts , ifStmt , rest } = emitIfHelper ( s . elseStatement ) ; let elif = `el${ ifStmt }` ; sup = sup . concat ( supportStmts ) ; ifRest . push ( elif ) ; ifRest = ifRest . concat ( rest ) ; } else { ifRest . push ( "else:" ) ; let el = emitBody ( s . elseStatement ) ; ifRest = ifRest . concat ( el ) ; } } return { supportStmts : sup , ifStmt : ifStmt , rest : ifRest } ; } function emitVarStmt ( s ) { let decls = s . declarationList . declarations ; return decls . map ( emitVarDecl ) . reduce ( ( p , c ) => p . concat ( c ) , [ ] ) ; } function emitClassStmt ( s ) { if ( ! SUPPORT_CLASSES ) { pxt . tickEvent ( "depython.todo.classes" ) ; return throwError ( s , 3016 , "Unsupported: classes are not supported in Python right now." ) ; } let out = [ ] ; if ( ! s . name ) { pxt . tickEvent ( "depython.todo.anonymousclass" ) ; return throwError ( s , 3011 , "Unsupported: anonymous class" ) ; } let isEnum = s . members . every ( isEnumMem ) ; let name = getName ( s . name ) ; if ( isEnum ) out . push ( `class ${ name }(Enum):` ) ; else out . push ( `class ${ name }:` ) ; let mems = s . members . map ( emitClassMem ) . reduce ( ( p , c ) => p . concat ( c ) , [ ] ) . filter ( m => m ) ; if ( mems . length ) { out = out . concat ( mems . map ( indent1 ) ) ; } return out ; } function emitEnumStmt ( s ) { let out = [ ] ; out . push ( `class ${ getName ( s . name ) }(Enum):` ) ; let allInit = s . members . every ( m => ! ! m . initializer ) ; let noInit = ! s . members . every ( m => ! ! m . initializer ) ; if ( ! allInit && ! noInit ) { pxt . tickEvent ( "depython.todo.enummix" ) ; return throwError ( s , 3005 , "Unsupported enum decleration: has mixture of explicit and implicit initialization" ) ; } if ( allInit ) { pxt . tickEvent ( "depython.todo.enuminit" ) ; return throwError ( s , 3006 , "Unsupported: explicit enum initialization" ) ; } let val = 0 ; for ( let m of s . members ) { out . push ( indent1 ( `${ getName ( m . name ) } = ${ val ++ }` ) ) ; } return out ; } function isEnumMem ( s ) { if ( s . kind !== ts . SyntaxKind . PropertyDeclaration ) return false ; let prop = s as ts . PropertyDeclaration ; if ( ! prop . modifiers || prop . modifiers . length !== 1 ) return false ; for ( let mod of prop . modifiers ) if ( mod . kind !== ts . SyntaxKind . StaticKeyword ) return false ; if ( ! prop . initializer ) return false ; if ( prop . initializer . kind !== ts . SyntaxKind . NumericLiteral ) return false ; return true ; } function emitClassMem ( s ) { if ( ts . isPropertyDeclaration ( s ) ) return emitPropDecl ( s ) ; else if ( ts . isMethodDeclaration ( s ) ) return emitFuncDecl ( s ) ; else if ( ts . isConstructorDeclaration ( s ) ) { return emitConstructor ( s ) ; } return [ "# unknown ClassElement " + s . kind ] ; } function emitConstructor ( s ) { let res = [ ] ; const memberProps = s . parameters . filter ( ts . isParameterPropertyDeclaration ) ; memberProps . forEach ( p => { res = [ ... res , ... emitPropDecl ( p ) ] ; } ) ; res = [ ... res , ... emitFuncDecl ( s , "__init__" ) ] ; if ( memberProps . length && res . slice ( - 1 ) [ 0 ] . endsWith ( "pass" ) ) res . pop ( ) ; memberProps . forEach ( p => { const nm = getName ( p . name ) ; res . push ( indent1 ( `self.${ nm } = ${ nm }` ) ) ; } ) ; return res ; } function emitDefaultValue ( t ) { if ( hasTypeFlag ( t , ts . TypeFlags . NumberLike ) ) return "0" ; else if ( hasTypeFlag ( t , ts . TypeFlags . StringLike ) ) return `""` ; else if ( hasTypeFlag ( t , ts . TypeFlags . BooleanLike ) ) return "False" ; return undefined ; } function emitPropDecl ( s ) { let nm = getName ( s . name ) ; if ( s . initializer ) { let [ init , initSup ] = emitExp ( s . initializer ) ; return [ ... initSup , `${ nm } = ${ expToStr ( init ) }` ] ; } else if ( ts . isParameterPropertyDeclaration ( s ) && s . type ) { const t = tc . getTypeFromTypeNode ( s . type ) ; const defl = emitDefaultValue ( t ) ; if ( defl ) { return [ `${ nm } = ${ defl }` ] ; } } pxt . tickEvent ( "depython.todo.classwithoutinit" ) ; return throwError ( s , 3006 , "Unsupported: class properties without initializers" ) ; } function isUnaryPlusPlusOrMinusMinus ( e ) { if ( ! ts . isPrefixUnaryExpression ( e ) && ! ts . isPostfixUnaryExpression ( e ) ) return false ; if ( e . operator !== ts . SyntaxKind . MinusMinusToken && e . operator !== ts . SyntaxKind . PlusPlusToken ) return false ; return true ; } function tryEmitIncDecUnaryStmt ( e ) { if ( ! isUnaryPlusPlusOrMinusMinus ( e ) ) return null ; let [ operand , sup ] = emitExp ( e . operand ) ; let incDec = e . operator === ts . SyntaxKind . MinusMinusToken ? " -= 1" : " += 1" ; let out = sup ; out . push ( `${ expToStr ( operand ) }${ incDec }` ) ; return out ; } function emitExpStmt ( s ) { let unaryExp = tryEmitIncDecUnaryStmt ( s . expression ) ; if ( unaryExp ) return unaryExp ; const [ exp , expSup ] = emitExp ( s . expression ) ; if ( expSup . length ) return [ "" , ... expSup , ... exp , "" ] ; else return [ ... expSup , ... exp ] ; } function emitBlock ( s ) { const stmts = s . getChildren ( ) . map ( emitNode ) . reduce ( ( p , c ) => p . concat ( c ) , [ ] ) ; return stmts ; } function emitFuncDecl ( s , name ? , altParams ? , skipTypes ? ) { let paramList = [ ] ; if ( s . kind === ts . SyntaxKind . MethodDeclaration || s . kind === ts . SyntaxKind . Constructor ) { paramList . push ( "self" ) ; } let paramDeclDefs = altParams ? mergeParamDecls ( s . parameters , altParams ) : s . parameters ; let paramDecls = paramDeclDefs . map ( d => emitParamDecl ( d , ! skipTypes ) ) ; paramList = paramList . concat ( paramDecls ) ; let params = paramList . join ( ", " ) ; let out = [ ] ; let fnName ; if ( name ) fnName = name ; else if ( s . name ) fnName = getName ( s . name ) ; else { pxt . tickEvent ( "depython.todo.anonymousfunc" ) ; return throwError ( s , 3012 , "Unsupported: anonymous function decleration" ) ; } out . push ( `def ${ fnName }(${ params }):` ) ; if ( ! s . body ) { pxt . tickEvent ( "depython.todo.funcwithoutbody" ) ; return throwError ( s , 3013 , "Unsupported: function decleration without body" ) ; } let stmts = [ ] ; if ( ts . isBlock ( s . body ) ) stmts = emitBlock ( s . body ) ; else { let [ exp , sup ] = emitExp ( s . body ) ; stmts = stmts . concat ( sup ) ; stmts . concat ( exp ) ; } if ( stmts . length ) { let globals = scopeLookup . getExplicitGlobals ( s ) . map ( g => getName ( g ) ) ; if ( globals && globals . length ) stmts . unshift ( `global ${ globals . join ( ", " ) }` ) ; let nonlocals = scopeLookup . getExplicitNonlocals ( s ) . map ( n => getName ( n ) ) ; if ( nonlocals && nonlocals . length ) stmts . unshift ( `nonlocal ${ nonlocals . join ( ", " ) }` ) ; out = out . concat ( stmts . map ( indent1 ) ) ; } else { out . push ( indent1 ( "pass" ) ) ; } return out ; } function emitParamDecl ( s , inclTypesIfAvail = true ) { let nm = s . altName || getName ( s . name ) ; let typePart = "" ; if ( s . type && inclTypesIfAvail ) { let typ = pxtc . emitPyTypeFromTypeNode ( s . type ) ; if ( typ && typ . indexOf ( "(TODO" ) === - 1 ) { typePart = `: ${ typ }` ; } } let initPart = "" ; if ( s . initializer ) { let [ initExp , initSup ] = emitExp ( s . initializer ) ; if ( initSup . length ) { pxt . tickEvent ( "depython.todo.complexinit" ) ; return throwError ( s , 3007 , `Unsupported: complex expression in parameter default value not supported. Expression: ${ s . initializer . getText ( ) }` ) ; } initPart = ` = ${ expToStr ( initExp ) }` ; } return `${ nm }${ typePart }${ initPart }` ; } function emitVarDecl ( s ) { let out = [ ] ; let varNm = getName ( s . name ) ; if ( s . initializer ) { let [ exp , expSup ] = emitExp ( s . initializer ) ; out = out . concat ( expSup ) ; let declStmt ; if ( s . type ) { let translatedType = pxtc . emitPyTypeFromTypeNode ( s . type ) ; declStmt = `${ varNm }: ${ translatedType } = ${ expToStr ( exp ) }` ; } else { declStmt = `${ varNm } = ${ expToStr ( exp ) }` ; } out . push ( declStmt ) ; return out ; } else { } return out ; } type ExpRes = [ string [ ] , string [ ] ] ; function asExpRes ( str , sup ? ) { return [ [ str ] , sup || [ ] ] ; } function expToStr ( exps , char = '\n' ) { return exps . join ( char ) ; } function expWrap ( pre = "" , exps , suff = "" ) { exps [ 0 ] = pre + exps [ 0 ] ; exps [ exps . length - 1 ] = exps [ exps . length - 1 ] + suff ; return exps ; } function emitOp ( s , node ) { switch ( s ) { case ts . SyntaxKind . BarBarToken : return "or" ; case ts . SyntaxKind . AmpersandAmpersandToken : return "and" ; case ts . SyntaxKind . ExclamationToken : return "not" ; case ts . SyntaxKind . LessThanToken : return "<" ; case ts . SyntaxKind . LessThanEqualsToken : return "<=" ; case ts . SyntaxKind . GreaterThanToken : return ">" ; case ts . SyntaxKind . GreaterThanEqualsToken : return ">=" ; case ts . SyntaxKind . EqualsEqualsEqualsToken : case ts . SyntaxKind . EqualsEqualsToken : return "==" ; case ts . SyntaxKind . ExclamationEqualsEqualsToken : case ts . SyntaxKind . ExclamationEqualsToken : return "!=" ; case ts . SyntaxKind . EqualsToken : return "=" ; case ts . SyntaxKind . PlusToken : return "+" ; case ts . SyntaxKind . MinusToken : return "-" ; case ts . SyntaxKind . AsteriskToken : return "*" ; case ts . SyntaxKind . PlusEqualsToken : return "+=" ; case ts . SyntaxKind . MinusEqualsToken : return "-=" ; case ts . SyntaxKind . PercentToken : return "%" ; case ts . SyntaxKind . SlashToken : return "/" ; case ts . SyntaxKind . PlusPlusToken : case ts . SyntaxKind . MinusMinusToken : pxt . tickEvent ( "depython.todo.unsupportedop" , { op : s } ) ; return throwError ( node , 3008 , "Unsupported ++ and -- in an expression (not a statement or for loop)" ) ; case ts . SyntaxKind . AmpersandToken : return "&" ; case ts . SyntaxKind . CaretToken : return "^" ; case ts . SyntaxKind . LessThanLessThanToken : return "<<" ; case ts . SyntaxKind . GreaterThanGreaterThanToken : return ">>" ; case ts . SyntaxKind . GreaterThanGreaterThanGreaterThanToken : pxt . tickEvent ( "depython.todo.unsupportedop" , { op : s } ) ; return throwError ( node , 3009 , "Unsupported operator: >>>" ) ; case ts . SyntaxKind . AsteriskAsteriskToken : return "**" ; case ts . SyntaxKind . AsteriskAsteriskEqualsToken : return "**=" ; case ts . SyntaxKind . PercentEqualsToken : return "%=" ; case ts . SyntaxKind . AsteriskEqualsToken : return "*=" ; case ts . SyntaxKind . SlashEqualsToken : return "/=" ; default : pxt . tickEvent ( "depython.todo.unsupportedop" , { op : s } ) ; return throwError ( node , 3008 , `Unsupported Python operator (code: ${ s })` ) ; } } function emitBinExp ( s ) { let isLStr = isStringType ( s . left ) ; let isRStr = isStringType ( s . right ) ; let isStrConcat = s . operatorToken . kind === ts . SyntaxKind . PlusToken && ( isLStr || isRStr ) ; let [ left , leftSup ] = emitExp ( s . left ) ; if ( isStrConcat && ! isLStr ) left = expWrap ( "str(" , left , ")" ) ; let op = emitOp ( s . operatorToken . kind , s ) ; let [ right , rightSup ] = emitExp ( s . right ) ; if ( isStrConcat && ! isRStr ) right = expWrap ( "str(" , right , ")" ) ; let sup = leftSup . concat ( rightSup ) ; return [ expWrap ( expToStr ( left ) + " " + op + " " , right ) , sup ] ; } function emitDotExp ( s ) { let pyName = tryGetPyName ( s ) ; if ( pyName ) return asExpRes ( pyName ) ; let [ left , leftSup ] = emitExp ( s . expression ) ; let right = getName ( s . name ) ; if ( right === "length" ) { return asExpRes ( `len(${ expToStr ( left ) })` , leftSup ) ; } return asExpRes ( `${ expToStr ( left ) }.${ right }` , leftSup ) ; } function getSimpleExpNameParts ( s ) { if ( ts . isPropertyAccessExpression ( s ) ) { let nmPart = getName ( s . name ) ; let nmRight = nmPart . substr ( nmPart . lastIndexOf ( "." ) + 1 ) ; if ( ts . isIdentifier ( s . expression ) ) { return [ nmRight ] ; } return getSimpleExpNameParts ( s . expression ) . concat ( [ nmRight ] ) ; } else if ( ts . isIdentifier ( s ) ) { return [ getName ( s ) ] ; } else return [ ] ; } function getNameHint ( param ? , calleeExp ? , allParams ? , allArgs ? ) { let calleePart = "" ; if ( calleeExp ) calleePart = getSimpleExpNameParts ( calleeExp ) . map ( U . snakify ) . join ( "_" ) ; let enumParamParts = [ ] ; if ( allParams && allParams . length > 1 && allArgs && allArgs . length > 1 ) { for ( let i = 0 ; i < allParams . length && i < allArgs . length ; i ++ ) { let arg = allArgs [ i ] ; let argType = tc . getTypeAtLocation ( arg ) ; if ( hasTypeFlag ( argType , ts . TypeFlags . EnumLike ) ) { let argParts = getSimpleExpNameParts ( arg ) . map ( U . snakify ) ; enumParamParts = enumParamParts . concat ( argParts ) ; } } } let otherParamsPart = enumParamParts . join ( "_" ) ; let paramPart = "" ; if ( ! calleePart && ! otherParamsPart && param ) paramPart = getName ( param . name ) ; let hint = [ calleePart , otherParamsPart , paramPart ] . filter ( s => s ) . map ( U . snakify ) . map ( s => s . toLowerCase ( ) ) . join ( "_" ) || "my_callback" ; let allWords = hint . split ( "_" ) ; if ( allWords . length > 4 ) { allWords = dedupWords ( allWords ) ; } let lessUsefulWords = pxt . U . toDictionary ( [ "any" , "on" , "event" ] , s => s ) ; while ( allWords . length > 2 ) { let newWords = removeOne ( allWords , lessUsefulWords ) ; if ( newWords . length == allWords . length ) break ; allWords = newWords ; } allWords = [ "on" , ... allWords ] ; return allWords . join ( "_" ) ; function dedupWords ( words ) { let usedWords = { } ; let out = [ ] ; for ( let w of words ) { if ( w in usedWords ) continue ; usedWords [ w ] = true ; out . push ( w ) ; } return out ; } function removeOne ( words , exclude ) { let out = [ ] ; let oneExcluded = false ; for ( let w of words ) { if ( w in exclude && ! oneExcluded ) { oneExcluded = true ; continue ; } out . push ( w ) ; } return out ; } } function getCommaSep ( exps ) { let res = exps . join ( ", " ) ; if ( res . length > 60 && exps . length > 1 ) { return exps . map ( ( el , i ) => { let sep = el . charAt ( el . length - 1 ) == "," ? "" : "," ; if ( i == 0 ) { let lines = el . split ( '\n' ) ; if ( lines . length > 1 ) { let first = lines . shift ( ) ; el = first + '\n' + indent1 ( lines . join ( '\n' ) ) ; } return el + sep ; } else if ( i == exps . length - 1 ) { return indent1 ( el ) ; } else { return indent1 ( el + sep ) ; } } ) ; } return [ res ] ; } function emitArgExp ( s , param ? , calleeExp ? , allParams ? , allArgs ? ) { if ( ( ts . isFunctionExpression ( s ) || ts . isArrowFunction ( s ) ) && param ) { if ( param . type && ts . isFunctionTypeNode ( param . type ) ) { let altParams = undefined ; let fnNameHint = getNameHint ( param , calleeExp , allParams , allArgs ) ; return emitFnExp ( s , fnNameHint , altParams , true ) ; } } return emitExp ( s ) ; } function emitCallExp ( s ) { let calleeType = tc . getTypeAtLocation ( s . expression ) ; let calleeTypeNode = tc . typeToTypeNode ( calleeType ) ; let calleeParameters = ts . createNodeArray ( [ ] ) ; if ( ts . isFunctionTypeNode ( calleeTypeNode ) ) { calleeParameters = calleeTypeNode . parameters ; if ( s . arguments && calleeParameters . length < s . arguments . length ) { pxt . tickEvent ( "depython.todo.argparammismatch" , { kind : s . kind } ) ; return throwError ( s , 3010 , "TODO: Unsupported call site where caller the arguments outnumber the callee parameters: " + s . getText ( ) ) ; } } const sym = tryGetSymbol ( s . expression ) ; if ( s . arguments && sym && sym . attributes . shim == "TD_ID" ) { return emitExp ( s . arguments [ 0 ] ) ; } if ( ts . isPropertyAccessExpression ( s . expression ) ) { if ( s . expression . name . getText ( ) === "toString" ) { const [ inner , innerSup ] = emitExp ( s . expression . expression ) ; return [ expWrap ( `str(` , inner , `)` ) , innerSup ] ; } } let [ fnExp , fnSup ] = emitExp ( s . expression ) ; let fn = expToStr ( fnExp ) ; let sargs = s . arguments || ts . createNodeArray ( ) ; let argExps = sargs . map ( ( a , i , allArgs ) => emitArgExp ( a , calleeParameters [ i ] , s . expression , calleeParameters , allArgs ) ) ; let sup = argExps . map ( ( [ _ , aSup ] ) => aSup ) . reduce ( ( p , c ) => p . concat ( c ) , fnSup ) ; if ( fn . indexOf ( "_py.py_" ) === 0 ) { if ( argExps . length <= 0 ) { pxt . tickEvent ( "depython.todo._pynoargs" ) ; return throwError ( s , 3014 , "Unsupported: call expression has no arguments for _py.py_ fn" ) ; } fn = fn . substr ( 7 ) . split ( "_" ) . filter ( ( _ , i ) => i !== 0 ) . join ( "_" ) ; const recv = argExps . shift ( ) ! [ 0 ] ; const args = getCommaSep ( argExps . map ( ( [ a , _ ] ) => a ) . reduce ( ( p , c ) => p . concat ( c ) , [ ] ) ) ; return [ expWrap ( `${ recv }.${ fn }(` , args , ")" ) , sup ] ; } let args = getCommaSep ( argExps . map ( ( [ a , _ ] ) => a ) . reduce ( ( p , c ) => p . concat ( c ) , [ ] ) ) ; return [ expWrap ( `${ fn }(` , args , ")" ) , sup ] ; } type ParameterDeclarationExtended = ts . ParameterDeclaration & { altName ? : string ; } ; function mergeParamDecls ( primary , alt ) { let decls = [ ] ; let paramNames = { } ; for ( let i = 0 ; i < Math . max ( primary . length , alt . length ) ; i ++ ) { let p ; if ( primary [ i ] ) { p = primary [ i ] ; paramNames [ getName ( p . name ) ] = true ; } else { p = alt [ i ] ; let name = getName ( p . name ) ; if ( paramNames [ name ] ) { name = pxtc . decompiler . getNewName ( name , paramNames ) ; p = Object . assign ( { altName : name } , alt [ i ] ) ; } } decls . push ( p ) ; } return ts . createNodeArray ( decls , false ) ; } function emitFnExp ( s , nameHint ? , altParams ? , skipType ? ) { if ( SUPPORT_LAMBDAS && ! ts . isBlock ( s . body ) ) { let [ fnBody , fnSup ] = emitExp ( s . body as ts . Expression ) ; if ( fnSup . length === 0 ) { let paramDefs = altParams ? mergeParamDecls ( s . parameters , altParams ) : s . parameters ; let paramList = paramDefs . map ( p => emitParamDecl ( p , false ) ) . join ( ", " ) ; let stmt = paramList . length ? `lambda ${ paramList }: ${ expToStr ( fnBody ) }` : `lambda: ${ expToStr ( fnBody ) }` ; return asExpRes ( stmt ) ; } } let fnName = s . name ? getName ( s . name ) : getNewGlobalName ( nameHint || "my_function" ) ; let fnDef = emitFuncDecl ( s , fnName , altParams , skipType ) ; return asExpRes ( fnName , fnDef ) ; } function getUnaryOpSpacing ( s ) { switch ( s ) { case ts . SyntaxKind . ExclamationToken : return " " ; case ts . SyntaxKind . PlusToken : case ts . SyntaxKind . MinusToken : return "" ; default : return " " ; } } function emitPreUnaryExp ( s ) { let op = emitOp ( s . operator , s ) ; let [ exp , expSup ] = emitExp ( s . operand ) ; let space = getUnaryOpSpacing ( s . operator ) ; let res = `${ op }${ space }${ expToStr ( exp ) }` ; return asExpRes ( res , expSup ) ; } function emitPostUnaryExp ( s ) { let op = emitOp ( s . operator , s ) ; let [ exp , expSup ] = emitExp ( s . operand ) ; let space = getUnaryOpSpacing ( s . operator ) ; let res = `${ expToStr ( exp ) }${ space }${ op }` ; return asExpRes ( res , expSup ) ; } function emitArrayLitExp ( s ) { let els = s . elements . map ( emitExp ) ; let sup = els . map ( ( [ _ , sup ] ) => sup ) . reduce ( ( p , c ) => p . concat ( c ) , [ ] ) ; let inner = getCommaSep ( els . map ( ( [ e , _ ] ) => e ) . reduce ( ( p , c ) => p . concat ( c ) , [ ] ) ) ; return [ expWrap ( "[" , inner , "]" ) , sup ] ; } function emitElAccessExp ( s ) { if ( ! s . argumentExpression ) { pxt . tickEvent ( "depython.todo.accesswithoutexp" ) ; return throwError ( s , 3015 , "Unsupported: element access expression without an argument expression" ) ; } let [ left , leftSup ] = emitExp ( s . expression ) ; let [ arg , argSup ] = emitExp ( s . argumentExpression ) ; let sup = leftSup . concat ( argSup ) ; let exp = `${ expToStr ( left ) }[${ expToStr ( arg ) }]` ; return asExpRes ( exp , sup ) ; } function emitParenthesisExp ( s ) { let [ inner , innerSup ] = emitExp ( s . expression ) ; return asExpRes ( `(${ expToStr ( inner ) })` , innerSup ) ; } function emitMultiLnStrLitExp ( s ) { if ( ts . isNoSubstitutionTemplateLiteral ( s ) ) { return asExpRes ( `"""\n${ indent1 ( s . text . trim ( ) ) }\n"""` ) ; } let [ tag , tagSup ] = emitExp ( s . tag ) ; let [ temp , tempSup ] = emitExp ( s . template ) ; let sup = tagSup . concat ( tempSup ) ; let exp = `${ expToStr ( tag ) }(${ expToStr ( temp ) })` ; return asExpRes ( exp , sup ) ; } function emitIdentifierExp ( s ) { if ( s . text == "undefined" ) return asExpRes ( "None" ) ; let name = getName ( s ) ; return asExpRes ( name ) ; } function visitExp ( s , fn ) { let visitRecur = ( s ) => visitExp ( s , fn ) ; if ( ts . isBinaryExpression ( s ) ) { return visitRecur ( s . left ) && visitRecur ( s . right ) ; } else if ( ts . isPropertyAccessExpression ( s ) ) { return visitRecur ( s . expression ) ; } else if ( ts . isPrefixUnaryExpression ( s ) || ts . isPostfixUnaryExpression ( s ) ) { return s . operator !== ts . SyntaxKind . PlusPlusToken && s . operator !== ts . SyntaxKind . MinusMinusToken && visitRecur ( s . operand ) ; } else if ( ts . isParenthesizedExpression ( s ) ) { return visitRecur ( s . expression ) ; } else if ( ts . isArrayLiteralExpression ( s ) ) { return s . elements . map ( visitRecur ) . reduce ( ( p , c ) => p && c , true ) ; } else if ( ts . isElementAccessExpression ( s ) ) { return visitRecur ( s . expression ) && ( ! s . argumentExpression || visitRecur ( s . argumentExpression ) ) ; } return fn ( s ) ; } function isConstExp ( s ) { let isConst = ( s ) : boolean => { switch ( s . kind ) { case ts . SyntaxKind . PropertyAccessExpression : case ts . SyntaxKind . BinaryExpression : case ts . SyntaxKind . ParenthesizedExpression : case ts . SyntaxKind . ArrayLiteralExpression : case ts . SyntaxKind . ElementAccessExpression : case ts . SyntaxKind . TrueKeyword : case ts . SyntaxKind . FalseKeyword : case ts . SyntaxKind . NullKeyword : case ts . SyntaxKind . UndefinedKeyword : case ts . SyntaxKind . NumericLiteral : case ts . SyntaxKind . StringLiteral : case ts . SyntaxKind . NoSubstitutionTemplateLiteral : return true ; case ts . SyntaxKind . CallExpression : case ts . SyntaxKind . NewExpression : case ts . SyntaxKind . FunctionExpression : case ts . SyntaxKind . ArrowFunction : case ts . SyntaxKind . Identifier : case ts . SyntaxKind . ThisKeyword : return false ; case ts . SyntaxKind . PrefixUnaryExpression : case ts . SyntaxKind . PostfixUnaryExpression : let e = s as ts . PrefixUnaryExpression | ts . PostfixUnaryExpression ; return e . operator !== ts . SyntaxKind . PlusPlusToken && e . operator !== ts . SyntaxKind . MinusMinusToken ; } return false ; } ; return visitExp ( s , isConst ) ; } function emitCondExp ( s ) { let [ cond , condSup ] = emitExp ( s . condition ) ; let [ tru , truSup ] = emitExp ( s . whenTrue ) ; let [ fls , flsSup ] = emitExp ( s . whenFalse ) ; let sup = condSup . concat ( truSup ) . concat ( flsSup ) ; let exp = `${ tru } if ${ expToStr ( cond ) } else ${ expToStr ( fls ) }` ; return asExpRes ( exp , sup ) ; } function emitExp ( s ) { if ( ts . isBinaryExpression ( s ) ) return emitBinExp ( s ) ; if ( ts . isPropertyAccessExpression ( s ) ) return emitDotExp ( s ) ; if ( ts . isCallExpression ( s ) ) return emitCallExp ( s ) ; if ( ts . isNewExpression ( s ) ) return emitCallExp ( s ) ; if ( ts . isFunctionExpression ( s ) || ts . isArrowFunction ( s ) ) return emitFnExp ( s ) ; if ( ts . isPrefixUnaryExpression ( s ) ) return emitPreUnaryExp ( s ) ; if ( ts . isPostfixUnaryExpression ( s ) ) return emitPostUnaryExp ( s ) ; if ( ts . isParenthesizedExpression ( s ) ) return emitParenthesisExp ( s ) ; if ( ts . isArrayLiteralExpression ( s ) ) return emitArrayLitExp ( s ) ; if ( ts . isElementAccessExpression ( s ) ) return emitElAccessExp ( s ) ; if ( ts . isNoSubstitutionTemplateLiteral ( s ) || ts . isTaggedTemplateExpression ( s ) ) return emitMultiLnStrLitExp ( s as ts . NoSubstitutionTemplateLiteral | ts . TaggedTemplateExpression ) ; switch ( s . kind ) { case ts . SyntaxKind . TrueKeyword : return asExpRes ( "True" ) ; case ts . SyntaxKind . FalseKeyword : return asExpRes ( "False" ) ; case ts . SyntaxKind . ThisKeyword : return asExpRes ( "self" ) ; case ts . SyntaxKind . NullKeyword : case ts . SyntaxKind . UndefinedKeyword : return asExpRes ( "None" ) ; } if ( ts . isIdentifier ( s ) ) return emitIdentifierExp ( s ) ; if ( ts . isNumericLiteral ( s ) || ts . isStringLiteral ( s ) ) return asExpRes ( s . getText ( ) ) ; if ( ts . isConditionalExpression ( s ) ) return emitCondExp ( s ) ; pxt . tickEvent ( "depython.todo.expression" , { kind : s . kind } ) ; return throwError ( s , 3017 , "Unsupported expression kind: " + s . kind ) ; } } }