namespace pxt . blocks { export enum NT { Prefix , Postfix , Infix , Block , NewLine } export interface JsNode { type : NT ; children : JsNode [ ] ; op : string ; id ? : string ; glueToBlock ? : GlueMode ; canIndentInside ? : boolean ; noFinalNewline ? : boolean ; } export enum GlueMode { None = 0 , WithSpace = 1 , NoSpace = 2 } const reservedWords = [ "break" , "case" , "catch" , "class" , "const" , "continue" , "debugger" , "default" , "delete" , "do" , "else" , "enum" , "export" , "extends" , "false" , "finally" , "for" , "function" , "if" , "import" , "in" , "instanceof" , "new" , "null" , "return" , "super" , "switch" , "this" , "throw" , "true" , "try" , "typeof" , "var" , "void" , "while" , "with" ] ; let placeholders = { } ; export function backtickLit ( s ) { return "`" + s . replace ( /[\\`${}]/g , f => "\\" + f ) + "`" ; } export function stringLit ( s ) { if ( s . length > 20 && /\n/ . test ( s ) ) return backtickLit ( s ) ; else return JSON . stringify ( s ) ; } export function mkNode ( tp , pref , children ) { return { type : tp , op : pref , children : children } ; } export function mkNewLine ( ) { return mkNode ( NT . NewLine , "" , [ ] ) ; } export function mkPrefix ( pref , children ) { return mkNode ( NT . Prefix , pref , children ) ; } export function mkPostfix ( children , post ) { return mkNode ( NT . Postfix , post , children ) ; } export function mkInfix ( child0 , op , child1 ) { return mkNode ( NT . Infix , op , child0 == null ? [ child1 ] : [ child0 , child1 ] ) ; } export function mkText ( s ) { return mkPrefix ( s , [ ] ) ; } export function mkBlock ( nodes ) { return mkNode ( NT . Block , "" , nodes ) ; } export function mkGroup ( nodes ) { return mkPrefix ( "" , nodes ) ; } export function mkStmt ( ... nodes ) { let last = nodes [ nodes . length - 1 ] ; if ( last && last . type == NT . Block ) { } else { nodes . push ( mkNewLine ( ) ) ; } return mkGroup ( nodes ) ; } export function mkCommaSep ( nodes , withNewlines = false ) { const r = [ ] ; for ( const n of nodes ) { if ( withNewlines ) { if ( r . length > 0 ) r . push ( mkText ( "," ) ) ; r . push ( mkNewLine ( ) ) ; } else if ( r . length > 0 ) { r . push ( mkText ( ", " ) ) ; } r . push ( n ) ; } if ( withNewlines ) r . push ( mkNewLine ( ) ) ; return mkGroup ( r ) ; } export namespace Helpers { export function mkArrayLiteral ( args , withNewlines ? ) { return mkGroup ( [ mkText ( "[" ) , mkCommaSep ( args , withNewlines ) , mkText ( "]" ) ] ) ; } export function mkNumberLiteral ( x ) { return mkText ( x . toString ( ) ) ; } export function mkBooleanLiteral ( x ) { return mkText ( x ? "true" : "false" ) ; } export function mkStringLiteral ( x ) { return mkText ( stringLit ( x ) ) ; } export function mkPropertyAccess ( name , thisArg ) { return mkGroup ( [ mkInfix ( thisArg , "." , mkText ( name ) ) , ] ) ; } export function mkCall ( name , args , externalInputs = false , method = false ) { if ( method ) return mkGroup ( [ mkInfix ( args [ 0 ] , "." , mkText ( name ) ) , mkText ( "(" ) , mkCommaSep ( args . slice ( 1 ) , externalInputs ) , mkText ( ")" ) ] ) ; else return mkGroup ( [ mkText ( name ) , mkText ( "(" ) , mkCommaSep ( args , externalInputs ) , mkText ( ")" ) ] ) ; } export function stdCall ( name , args , externalInputs ) { return mkCall ( name , args , externalInputs ) ; } export function extensionCall ( name , args , externalInputs ) { return mkCall ( name , args , externalInputs , true ) ; } export function namespaceCall ( namespace , name , args , externalInputs ) { return mkCall ( namespace + "." + name , args , externalInputs ) ; } export function mathCall ( name , args ) { return namespaceCall ( "Math" , name , args , false ) ; } export function mkGlobalRef ( name ) { return mkText ( name ) ; } export function mkSimpleCall ( p , args ) { U . assert ( args . length == 2 ) ; return mkInfix ( args [ 0 ] , p , args [ 1 ] ) ; } export function mkWhile ( condition , body ) { return mkGroup ( [ mkText ( "while (" ) , condition , mkText ( ")" ) , mkBlock ( body ) ] ) ; } export function mkComment ( text ) { return mkText ( "// " + text ) ; } export function mkMultiComment ( text ) { let group = [ mkText ( "/**" ) , mkNewLine ( ) ] ; text . split ( "\n" ) . forEach ( ( c , i , arr ) => { if ( c ) { group . push ( mkText ( " * " + c ) ) ; group . push ( mkNewLine ( ) ) ; if ( i < arr . length - 1 ) { group . push ( mkText ( " * " ) ) ; group . push ( mkNewLine ( ) ) ; } } } ) ; return mkGroup ( group . concat ( [ mkText ( " */" ) , mkNewLine ( ) ] ) ) ; } export function mkAssign ( x , e ) { return mkSimpleCall ( "=" , [ x , e ] ) ; } export function mkParenthesizedExpression ( expression ) { return isParenthesized ( flattenNode ( [ expression ] ) . output ) ? expression : mkGroup ( [ mkText ( "(" ) , expression , mkText ( ")" ) ] ) ; } } export import H = Helpers ; const infixPriTable = { "=" : 3 , "+=" : 3 , "-=" : 3 , "?" : 4 , ":" : 4 , "||" : 5 , "&&" : 6 , "|" : 7 , "^" : 8 , "&" : 9 , "==" : 10 , "!=" : 10 , "===" : 10 , "!==" : 10 , "<" : 11 , ">" : 11 , "<=" : 11 , ">=" : 11 , ">>" : 12 , ">>>" : 12 , "<<" : 12 , "+" : 13 , "-" : 13 , "*" : 14 , "/" : 14 , "%" : 14 , "**" : 15 , "!" : 16 , "~" : 16 , "P-" : 16 , "P+" : 16 , "++" : 16 , "--" : 16 , "." : 18 , } ; export interface BlockSourceInterval { id : string ; startLine : number ; startPos : number ; endLine : number ; endPos : number ; } export function flattenNode ( app ) { let sourceMap = [ ] ; let sourceMapById = { } ; let output = "" ; let indent = "" ; let variables = [ { } ] ; let currentLine = 0 ; function append ( s ) { if ( typeof ( s ) === 'string' ) { currentLine += ( s . match ( /\n/g ) || [ ] ) . length ; } output += s ; } function flatten ( e0 ) { function rec ( e , outPrio ) { if ( e . type != NT . Infix ) { for ( let c of e . children ) rec ( c , - 1 ) ; return ; } let r = [ ] ; function pushOp ( c ) { if ( c [ 0 ] == "P" ) c = c . slice ( 1 ) ; r . push ( mkText ( c ) ) ; } let infixPri = U . lookup ( infixPriTable , e . op ) ; if ( infixPri == null ) U . oops ( "bad infix op: " + e . op ) ; if ( infixPri < outPrio ) pushOp ( "(" ) ; if ( e . children . length == 1 ) { pushOp ( e . op ) ; rec ( e . children [ 0 ] , infixPri ) ; r . push ( e . children [ 0 ] ) ; } else { let bindLeft = infixPri != 3 && e . op != "**" ; let letType = undefined ; rec ( e . children [ 0 ] , bindLeft ? infixPri : infixPri + 0.1 ) ; r . push ( e . children [ 0 ] ) ; if ( letType && letType != "number" ) { pushOp ( ": " ) ; pushOp ( letType ) ; } if ( e . op == "." ) pushOp ( "." ) ; else pushOp ( " " + e . op + " " ) ; rec ( e . children [ 1 ] , ! bindLeft ? infixPri : infixPri + 0.1 ) ; r . push ( e . children [ 1 ] ) ; } if ( infixPri < outPrio ) pushOp ( ")" ) ; e . type = NT . Prefix ; e . op = "" ; e . children = r ; } rec ( e0 , - 1 ) ; } let root = mkGroup ( app ) ; flatten ( root ) ; emit ( root ) ; if ( ! output ) append ( "\n" ) ; return { output , sourceMap } ; function emit ( n ) { if ( n . glueToBlock ) { removeLastIndent ( ) ; if ( n . glueToBlock == GlueMode . WithSpace ) { append ( " " ) ; } } let startLine = currentLine ; let startPos = output . length ; switch ( n . type ) { case NT . Infix : U . oops ( "no infix should be left" ) ; break ; case NT . NewLine : append ( "\n" + indent ) ; break ; case NT . Block : block ( n ) ; break ; case NT . Prefix : if ( n . canIndentInside ) append ( n . op . replace ( /\n/g , "\n" + indent + "    " ) ) ; else append ( n . op ) ; n . children . forEach ( emit ) ; break ; case NT . Postfix : n . children . forEach ( emit ) ; if ( n . canIndentInside ) append ( n . op . replace ( /\n/g , "\n" + indent + "    " ) ) ; else append ( n . op ) ; break ; default : break ; } let endLine = currentLine ; let endPos = Math . max ( output . length , 1 ) ; if ( n . id ) { if ( sourceMapById [ n . id ] ) { const node = sourceMapById [ n . id ] ; node . startLine = Math . min ( node . startLine , startLine ) ; node . endLine = Math . max ( node . endLine , endLine ) ; node . startPos = Math . min ( node . startPos , startPos ) ; node . endPos = Math . max ( node . endPos , endPos ) ; } else { const interval = { id : n . id , startLine : startLine , startPos , endLine : endLine , endPos } ; sourceMapById [ n . id ] = interval ; sourceMap . push ( interval ) ; } } } function write ( s ) { append ( s . replace ( /\n/g , "\n" + indent ) ) ; } function removeLastIndent ( ) { output = output . replace ( /\n *$/ , function ( ) { currentLine -- ; return "" ; } ) ; } function block ( n ) { let finalNl = n . noFinalNewline ? "" : "\n" ; if ( n . children . length == 0 ) { write ( " {\n\t\n}" + finalNl ) ; return ; } let vars = U . clone < Map < string > > ( variables [ variables . length - 1 ] || { } ) ; variables . push ( vars ) ; indent += "    " ; if ( output [ output . length - 1 ] != " " ) write ( " " ) ; write ( "{\n" ) ; for ( let nn of n . children ) emit ( nn ) ; indent = indent . slice ( 4 ) ; removeLastIndent ( ) ; write ( "\n}" + finalNl ) ; variables . pop ( ) ; } } export function isReservedWord ( str ) { return reservedWords . indexOf ( str ) !== - 1 ; } export function isParenthesized ( fnOutput ) { if ( fnOutput [ 0 ] !== "(" || fnOutput [ fnOutput . length - 1 ] !== ")" ) { return false ; } let unclosedParentheses = 1 ; for ( let i = 1 ; i < fnOutput . length ; i ++ ) { const c = fnOutput [ i ] ; if ( c === "(" ) { unclosedParentheses ++ ; } else if ( c === ")" ) { unclosedParentheses -- ; if ( unclosedParentheses === 0 ) { return i === fnOutput . length - 1 ; } } } return false ; } }